用于简化SQL语句的一般规则
我正在寻找一些“推理规则”(类似于设置操作规则或逻辑规则),我可以用它来减less复杂性或大小的SQL查询。 有没有这样的东西? 任何文件,任何工具? 您自己find的任何等价物? 它在某种程度上类似于查询优化,但不是在性能方面。
说明它的不同:使用JOINs,SUBSELECTs,UNIONs进行(复杂)查询有可能(或不是)通过使用一些转换规则将其简化为更简单的等效SQL语句,该语句产生相同的结果?
所以,我正在寻找SQL语句的等效转换,就像大多数SUBSELECT可以重写为JOIN一样。
说明它的不同:使用JOINs,SUBSELECTs,UNIONs进行(复杂)查询有可能(或不是)通过使用一些转换规则将其简化为更简单的等效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
,看起来丑陋,但可以产生更好的执行计划。
最常见的事情之一就是replace这个查询:
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
使用不同的执行计划,所以有时最好用另一个replace:
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
sorting和GROUP BY
哈希。
MySQL
缺lessFULL 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
更高效地执行此操作:
- 在MySQL中模拟
FULL OUTER JOIN
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
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帮助我,游标(你听到我的权利: cursors
, more efficiently
和SQL Server
在一个句子)重写。
在我的博客中看到这篇文章如何做到这一点:
- 压扁时间跨度:SQL Server
在金融应用程序中通常会遇到某种types的查询,这些查询会search货币的有效汇率,例如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
而不是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
尽pipe体积很大,但后者的查询速度要快6
倍。
这里的主要思想是用<=
replace<=
,这需要构build内存日历表。 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
子句中,以仅提取一行而不是全部。
- 我想最明显的是寻找任何游标,可以用一个基于SQL“Set”的操作replace。
- 接下来在我的列表中,查找可以重写为不相关查询的任何相关子查询
- 在长存储过程中,将单独的SQL语句分解为它们自己的存储过程。 这样他们将得到那里自己的caching查询计划。
- 寻找可以缩短范围的交易。 我经常在一个可以安全地在外面的交易中find陈述。
- 子select通常可以被重写为直接连接(现代优化器擅长于发现简单的连接)
正如@Quassnoi提到的那样,Optimiser经常做得很好。 帮助它的一种方法是确保索引和统计信息是最新的,并且为您的查询工作负载提供适当的索引。
我喜欢通过连接查询来replace所有types的子查询。
这一点很明显:
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在一个大的请求中select好的执行计划。
我喜欢团队中的每个人遵循一套标准,使代码易读,可维护,易于理解,可清洗等。
- 每个人都使用相同的别名
- 没有游标。 没有循环
- 为什么甚至想到IN的时候可以EXISTS
- INDENT
- 一致的编码风格
这里有更多的东西什么是你最有用的数据库标准?
鉴于SQL的本质,您绝对必须意识到任何重构的性能影响。 重构SQL应用程序是一个很好的重构资源,重点是性能(见第5章)。
尽pipe简化可能并不等于优化,但在编写可读的SQL代码时,简化是非常重要的,这对于能够检查SQL代码是否符合概念正确性(而不是语法正确性,开发环境应该为您检查)至关重要。 在我看来,在一个理想的世界里,我们会编写最简单,可读的SQL代码,然后优化器将重写SQL代码以任何forms(也许更详细)运行速度最快。
我发现把SQL语句看作基于set逻辑的思想是非常有用的,特别是当我需要将where子句或者where子句的复杂否定结合起来的时候。 在这种情况下,我使用布尔代数的定律 。
对于简化where子句最重要的可能是DeMorgan法则(注意“·”是“AND”,“+”是“OR”):
- NOT(x·y)= NOT x + NOT y
- NOT(x + y)= NOT x·NOT y
这在SQL中转换为:
NOT (expr1 AND expr2) -> NOT expr1 OR NOT expr2 NOT (expr1 OR expr2) -> NOT expr1 AND NOT expr2
这些规则可以在简化含有大量嵌套的AND
和OR
部分的子句中非常有用。
记住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查询,但布尔逻辑可以帮助您了解连接types以及查询应该返回的内容。 例如,对于表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
我的方法是学习一般的关系理论和特别的关系代数。 然后学会发现SQL中使用的构造来实现关系代数(例如通用量化aka division)和微积分(例如存在量化)的运算符。 问题在于SQL具有在关系模型中找不到的特征,例如空值,这些特征可能是最好的重构。 推荐阅读: SQL和关系理论:如何编写准确的SQL代码By CJ Date 。
在这方面,我不相信“大多数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;
join更详细!
或者,承认构造正在实施c
的投影上的抗连接,例如伪阿尔戈布拉
T1 { c } antijoin T2 { c }
简化使用关系运算符:
SELECT c FROM T1 EXCEPT SELECT c FROM T2;