SQLクエリの複雑さやサイズを削減するために使用できるいくつかの "推論ルール"(セット操作ルールやロジックルールに類似)を探しています。そのようなものはありますか?論文、ツールはありますか?自分で見つけた同等物はありますか?クエリの最適化にいくらか似ていますが、パフォーマンスの点では異なります。
別の言い方をすると、JOIN、SUBSELECT、UNIONを使用する(複雑な)クエリがあると、いくつかの変換規則を使用して、同じ結果を生成するより単純で同等のSQLステートメントに削減できます(またはできない)。
したがって、ほとんどのSUBSELECTをJOINとして書き換えることができるという事実のように、SQLステートメントの同等の変換を探しています。
別の言い方をすると、JOIN、SUBSELECT、UNIONを含む(複雑な)クエリがあると、いくつかの変換規則を使用して、同じ結果を生成するより単純で同等のSQLステートメントに削減できます(またはできない)。
それがまさにオプティマイザが生計を立てるために行うことです(私は彼らが常にこれをうまくやっていると言っているのではありません).
SQL
はセットベースの言語であるため、通常、1つのクエリを別のクエリに変換する方法は複数あります。
このクエリのように:
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
見た目は悪いですが、実行計画が改善されます。
最も一般的なことの1つは、このクエリを置き換えることです。
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
vs.
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
vs.
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
を1つの文で)。
その方法については、私のブログのこの記事を参照してください。
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
地獄のようにかさばるものですが、後者のクエリは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 joinsを使用すると、多くの「コードモンキー」ロジックが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
句に入れてフェッチしますすべてではなく1行のみ。
@Quassnoiが述べたように、オプティマイザーはしばしば良い仕事をします。これを支援する1つの方法は、インデックスと統計が最新であること、およびクエリのワークロードに適切なインデックスが存在することを確認することです。
チームの全員が一連の標準に従って、コードを読み取り可能、保守可能、理解可能、洗浄可能などにするのが好きです。:)
ここには他にもいくつかあります 最も有用なデータベース標準には何がありますか
あらゆる種類の副選択を結合クエリに置き換えるのが好きです。
これは明らかです:
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コードを(おそらくより冗長な)形式で最も速く実行されるように書き換えます。
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 ()
を次の2つの方法のいずれかで否定できます。
_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クエリを結合を使用するものに変換する方法を示していませんが、ブールロジックは、結合タイプとクエリが返すものを理解するのに役立ちます。たとえば、テーブル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で使用されている構造を見つけて、関係代数(例:ユニバーサル数量化a.k.a.除算)および計算(例:存在量数量化)から演算子を実装する方法を学びます。問題は、SQLにリレーショナルモデルにはない機能があることです。 nullは、とにかくおそらくリファクタリングするのが最善です。推奨される読み物: SQLおよびリレーショナル理論:C. J.日付による正確な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;