我正在寻找一些“推理规则”(类似于集合操作规则或逻辑规则),我可以使用它们来减少 SQL 查询的复杂性或大小。有没有这样的东西?任何文件,任何工具?您自己找到的任何等效项?它在某种程度上类似于查询优化,但在性能方面不同。
换一种说法:使用 JOIN、SUBSELECT、UNION 进行(复杂)查询是否可以(或不)通过使用一些转换规则将其简化为产生相同结果的更简单、等效的 SQL 语句?
因此,我正在寻找 SQL 语句的等效转换,例如大多数 SUBSELECT 可以重写为 JOIN。
我正在寻找一些“推理规则”(类似于集合操作规则或逻辑规则),我可以使用它们来减少 SQL 查询的复杂性或大小。有没有这样的东西?任何文件,任何工具?您自己找到的任何等效项?它在某种程度上类似于查询优化,但在性能方面不同。
换一种说法:使用 JOIN、SUBSELECT、UNION 进行(复杂)查询是否可以(或不)通过使用一些转换规则将其简化为产生相同结果的更简单、等效的 SQL 语句?
因此,我正在寻找 SQL 语句的等效转换,例如大多数 SUBSELECT 可以重写为 JOIN。
换一种说法:使用 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
可以使用, Lord help me, cursors 更有效地重写(你没听错: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
)
此查询可以大量重写以使用允许 aHASH JOIN而不是的相等条件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 提到的,优化器通常做得很好。帮助它的一种方法是确保索引和统计信息是最新的,并且存在适合您的查询工作负载的索引。
我喜欢用连接查询替换所有类型的子选择。
这个很明显:
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 在大请求中选择好的执行计划。
我喜欢团队中的每个人都遵循一套标准,使代码可读、可维护、可理解、可清洗等。:)
这里还有更多内容您最有用的数据库标准是什么?
鉴于 SQL 的性质,您绝对必须了解任何重构对性能的影响。 重构 SQL 应用程序是一个很好的重构资源,它非常强调性能(参见第 5 章)。
尽管简化可能不等于优化,但简化对于编写可读的 SQL 代码很重要,而这对于检查 SQL 代码的概念正确性(而不是语法正确性,您的开发环境应该为您检查)至关重要。在我看来,在理想的世界中,我们会编写最简单、最易读的 SQL 代码,然后优化器会将该 SQL 代码重写为任何形式(可能更冗长)将运行得最快。
我发现将 SQL 语句视为基于集合逻辑非常有用,尤其是当我需要组合 where 子句或找出 where 子句的复杂否定时。在这种情况下,我使用布尔代数定律。
简化 where 子句的最重要的可能是德摩根定律(注意“·”是“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 查询,但布尔逻辑可以帮助您了解连接类型以及您的查询应该返回什么。例如,对于表Aand B,an INNER JOINis like A AND B,a LEFT OUTER JOINis like(A AND NOT B) OR (A AND B)简化为A OR (A AND B),a FULL OUTER JOINisA OR (A AND B) OR B简化为A OR B。
我的方法是学习一般的关系理论,特别是关系代数。然后学习发现 SQL 中使用的构造,以从关系代数(例如通用量化又名除法)和微积分(例如存在量化)中实现运算符。问题是 SQL 具有关系模型中没有的特性,例如空值,无论如何最好将其重构掉。推荐阅读:SQL 和关系理论:如何通过 CJ 日期编写准确的 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;