我正在寻找一些“推理规则”(类似于设置操作规则或逻辑规则),可以用来减少SQL查询的复杂性或大小。是否存在类似的东西?有论文,有工具吗?您自己发现的任何对等物吗?它在某种程度上类似于查询优化,但在性能方面却不一样。
换种说法:使用JOIN,SUBSELECT和UNION进行(复杂)查询是否有可能(或不能)通过使用一些转换规则将其简化为更简单的等效SQL语句,从而产生相同的结果?
因此,我正在寻找SQL语句的等效转换,例如大多数SUBSELECT都可以重写为JOIN的事实。
我正在寻找一些“推理规则”(类似于设置操作规则或逻辑规则),可以用来减少SQL查询的复杂性或大小。是否存在类似的东西?有论文,有工具吗?您自己发现的任何对等物吗?它在某种程度上类似于查询优化,但在性能方面却不一样。
换种说法:使用JOIN,SUBSELECT和UNION进行(复杂)查询是否有可能(或不能)通过使用一些转换规则将其简化为更简单的等效SQL语句,从而产生相同的结果?
因此,我正在寻找SQL语句的等效转换,例如大多数SUBSELECT都可以重写为JOIN的事实。
Answers:
换种说法:使用JOIN,SUBSELECT和UNION进行(复杂)查询是否有可能(或不能)通过使用一些转换规则将其简化为更简单的等效SQL语句,从而产生相同的结果?
这正是优化程序谋生的手段(不是说他们总是做得很好)。
由于SQL
是一种基于集合的语言,通常有多种方法可以将一个查询转换为另一个查询。
喜欢这个查询:
SELECT *
FROM mytable
WHERE col1 > @value1 OR col2 < @value2
可以转化为:
SELECT *
FROM mytable
WHERE col1 > @value1
UNION
SELECT *
FROM mytable
WHERE col2 < @value2
或这个:
SELECT mo.*
FROM (
SELECT id
FROM mytable
WHERE col1 > @value1
UNION
SELECT id
FROM mytable
WHERE col2 < @value2
) mi
JOIN mytable mo
ON mo.id = mi.id
,看起来较难看,但可以产生更好的执行计划。
最常见的事情之一是替换此查询:
SELECT *
FROM mytable
WHERE col IN
(
SELECT othercol
FROM othertable
)
与此:
SELECT *
FROM mytable mo
WHERE EXISTS
(
SELECT NULL
FROM othertable o
WHERE o.othercol = mo.col
)
在某些RDBMS
(如PostgreSQL
)中,DISTINCT
并GROUP BY
使用不同的执行计划,因此有时最好将一个替换为另一个:
SELECT mo.grouper,
(
SELECT SUM(col)
FROM mytable mi
WHERE mi.grouper = mo.grouper
)
FROM (
SELECT DISTINCT grouper
FROM mytable
) mo
与
SELECT mo.grouper, SUM(col)
FROM mytable
GROUP BY
mo.grouper
在中PostgreSQL
,DISTINCT
排序和GROUP BY
散列。
MySQL
缺少FULL OUTER JOIN
,因此可以将其重写为:
SELECT t1.col1, t2.col2
FROM table1 t1
LEFT OUTER JOIN
table2 t2
ON t1.id = t2.id
与
SELECT t1.col1, t2.col2
FROM table1 t1
LEFT JOIN
table2 t2
ON t1.id = t2.id
UNION ALL
SELECT NULL, t2.col2
FROM table1 t1
RIGHT JOIN
table2 t2
ON t1.id = t2.id
WHERE t1.id IS NULL
,但请参阅我的博客中有关如何更有效地执行此操作的文章MySQL
:
此分层查询在Oracle
:
SELECT DISTINCT(animal_id) AS animal_id
FROM animal
START WITH
animal_id = :id
CONNECT BY
PRIOR animal_id IN (father, mother)
ORDER BY
animal_id
可以转换为:
SELECT DISTINCT(animal_id) AS animal_id
FROM (
SELECT 0 AS gender, animal_id, father AS parent
FROM animal
UNION ALL
SELECT 1, animal_id, mother
FROM animal
)
START WITH
animal_id = :id
CONNECT BY
parent = PRIOR animal_id
ORDER BY
animal_id
,后者表现更好。
有关执行计划的详细信息,请参阅我的博客中的这篇文章:
要查找与给定范围重叠的所有范围,可以使用以下查询:
SELECT *
FROM ranges
WHERE end_date >= @start
AND start_date <= @end
,但是在SQL Server
这种更复杂的查询中,更快地产生相同的结果:
SELECT *
FROM ranges
WHERE (start_date > @start AND start_date <= @end)
OR (@start BETWEEN start_date AND end_date)
,不管您信不信,我的博客上也有一篇文章:
SQL Server
还缺乏一种执行累积聚合的有效方法,因此此查询:
SELECT mi.id, SUM(mo.value) AS running_sum
FROM mytable mi
JOIN mytable mo
ON mo.id <= mi.id
GROUP BY
mi.id
可以使用,主帮助我更有效地重写,游标(你听我的权利:cursors
,more efficiently
并SQL Server
在一个句子)。
请参阅我的博客中有关此操作的文章:
在金融应用程序中通常会遇到一种查询,查询某种货币的有效汇率,例如 Oracle
:
SELECT TO_CHAR(SUM(xac_amount * rte_rate), 'FM999G999G999G999G999G999D999999')
FROM t_transaction x
JOIN t_rate r
ON (rte_currency, rte_date) IN
(
SELECT xac_currency, MAX(rte_date)
FROM t_rate
WHERE rte_currency = xac_currency
AND rte_date <= xac_date
)
可以大量重写此查询以使用等于条件,HASH JOIN
而该条件允许a而不是NESTED LOOPS
:
WITH v_rate AS
(
SELECT cur_id AS eff_currency, dte_date AS eff_date, rte_rate AS eff_rate
FROM (
SELECT cur_id, dte_date,
(
SELECT MAX(rte_date)
FROM t_rate ri
WHERE rte_currency = cur_id
AND rte_date <= dte_date
) AS rte_effdate
FROM (
SELECT (
SELECT MAX(rte_date)
FROM t_rate
) - level + 1 AS dte_date
FROM dual
CONNECT BY
level <=
(
SELECT MAX(rte_date) - MIN(rte_date)
FROM t_rate
)
) v_date,
(
SELECT 1 AS cur_id
FROM dual
UNION ALL
SELECT 2 AS cur_id
FROM dual
) v_currency
) v_eff
LEFT JOIN
t_rate
ON rte_currency = cur_id
AND rte_date = rte_effdate
)
SELECT TO_CHAR(SUM(xac_amount * eff_rate), 'FM999G999G999G999G999G999D999999')
FROM (
SELECT xac_currency, TRUNC(xac_date) AS xac_date, SUM(xac_amount) AS xac_amount, COUNT(*) AS cnt
FROM t_transaction x
GROUP BY
xac_currency, TRUNC(xac_date)
)
JOIN v_rate
ON eff_currency = xac_currency
AND eff_date = xac_date
尽管笨拙,但后面的查询是 6
速度要快几倍。
这里的主要思想是将替换<=
为=
,这需要构建一个内存日历表。要JOIN
与。
以下是使用Oracle 8和9的一些信息(当然,有时做相反的事情可能会使查询更简单或更快捷):
如果括号不用于覆盖运算符优先级,则可以将其删除。一个简单的示例是您的where
子句中的所有布尔运算符都相同时:where ((a or b) or c)
等效于where a or b or c
。
子查询可以经常(如果不是总是)与主查询合并以简化它。以我的经验,这通常会大大提高性能:
select foo.a,
bar.a
from foomatic foo,
bartastic bar
where foo.id = bar.id and
bar.id = (
select ban.id
from bantabulous ban
where ban.bandana = 42
)
;
相当于
select foo.a,
bar.a
from foomatic foo,
bartastic bar,
bantabulous ban
where foo.id = bar.id and
bar.id = ban.id and
ban.bandana = 42
;
使用ANSI连接可以将很多“代码猴子”逻辑与where子句中真正有趣的部分分开:上一个查询等效于
select foo.a,
bar.a
from foomatic foo
join bartastic bar on bar.id = foo.id
join bantabulous ban on ban.id = bar.id
where ban.bandana = 42
;
如果要检查是否存在某行,请不要使用count(*),而要使用其中之一,rownum = 1
或者将查询放在where exists
子句中以仅获取一行而不是全部。
正如@Quassnoi所提到的,Optimizer通常做得很好。一种帮助它的方法是确保索引和统计信息是最新的,并且存在适合您的查询工作负载的索引。
我喜欢用联接查询替换所有子查询。
这是显而易见的:
SELECT *
FROM mytable mo
WHERE EXISTS
(
SELECT *
FROM othertable o
WHERE o.othercol = mo.col
)
通过
SELECT mo.*
FROM mytable mo inner join othertable o on o.othercol = mo.col
这是估计的:
SELECT *
FROM mytable mo
WHERE NOT EXISTS
(
SELECT *
FROM othertable o
WHERE o.othercol = mo.col
)
通过
SELECT mo.*
FROM mytable mo left outer join othertable o on o.othercol = mo.col
WHERE o.othercol is null
它可以帮助DBMS在大需求中选择良好的执行计划。
EXISTS
并且NOT EXISTS
没有这个问题。(可以通过使用它来解决,DISTINCT
但会降低效率。)
我希望团队中的每个人都遵循一套标准,以使代码可读,可维护,可理解,可清洗等。:)
这里还有更多内容您最有用的数据库标准有哪些?
尽管简化可能不等于优化,但是简化在编写可读的SQL代码中很重要,而这对于能够检查SQL代码中的概念正确性(而不是语法正确性,开发环境应该为您检查)至关重要。在我看来,在理想的世界中,我们将编写最简单易读的SQL代码,然后优化程序将重写该SQL代码,使其以任何形式(可能更冗长)运行最快。
我发现将SQL语句视为基于集合逻辑非常有用,尤其是当我需要组合where子句或找出where子句的复杂否定时。我使用布尔代数定律在这种情况下,。
简化where子句的最重要的大概是DeMorgan的定律(请注意,“·”是“ AND”,“ +”是“ OR”):
这将SQL转换为:
NOT (expr1 AND expr2) -> NOT expr1 OR NOT expr2
NOT (expr1 OR expr2) -> NOT expr1 AND NOT expr2
这些规则在简化具有大量嵌套AND
和OR
部分的where子句时非常有用。
记住该语句field1 IN (value1, value2, ...)
与等效field1 = value1 OR field1 = value2 OR ...
。这使您可以否定以下IN ()
两种方法之一:
NOT field1 IN (value1, value2) -- for longer lists
NOT field1 = value1 AND NOT field1 = value2 -- for shorter lists
子查询也可以考虑这种方式。例如,此否定where子句:
NOT (table1.field1 = value1 AND EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2))
可以重写为:
NOT table1.field1 = value1 OR NOT EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2))
这些法律没有告诉您如何将使用子查询的SQL查询转换为使用联接的SQL查询,但是布尔逻辑可以帮助您了解联接类型以及查询应返回的内容。例如,对于表A
和B
,一个INNER JOIN
是像A AND B
,一个LEFT OUTER JOIN
是像(A AND NOT B) OR (A AND B)
这简化到A OR (A AND B)
,并且FULL OUTER JOIN
是A OR (A AND B) OR B
其简化到A OR B
。
( P => Q ) <=> ( NOT ( P ) OR Q )
我的方法是学习一般的关系理论,特别是关系代数。然后学习从关系代数(例如,通用量化又称为除法)和微积分(例如,存在性量化)中找出SQL中用于实现运算符的构造。棘手的是,SQL具有在关系模型中找不到的功能,例如null,无论如何最好将其重构。推荐阅读:SQL和关系理论:如何按CJ Date编写准确的SQL代码。
因此,我不认为“大多数SUBSELECT都可以重写为JOIN的事实”表示一种简化。
以以下查询为例:
SELECT c
FROM T1
WHERE c NOT IN ( SELECT c FROM T2 );
用JOIN重写
SELECT DISTINCT T1.c
FROM T1 NATURAL LEFT OUTER JOIN T2
WHERE T2.c IS NULL;
加入更加冗长!
或者,认识到该构造正在c
例如伪代数的投影上实现反连接
T1 { c } antijoin T2 { c }
使用关系运算符的简化:
SELECT c FROM T1 EXCEPT SELECT c FROM T2;