web-dev-qa-db-ja.com

SQLステートメントを簡略化するための一般的な規則

SQLクエリの複雑さやサイズを削減するために使用できるいくつかの "推論ルール"(セット操作ルールやロジックルールに類似)を探しています。そのようなものはありますか?論文、ツールはありますか?自分で見つけた同等物はありますか?クエリの最適化にいくらか似ていますが、パフォーマンスの点では異なります。

別の言い方をすると、JOIN、SUBSELECT、UNIONを使用する(複雑な)クエリがあると、いくつかの変換規則を使用して、同じ結果を生成するより単純で同等のSQLステートメントに削減できます(またはできない)。

したがって、ほとんどのSUBSELECTをJOINとして書き換えることができるという事実のように、SQLステートメントの同等の変換を探しています。

65
MicSim

別の言い方をすると、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
        )

一部のRDBMSPostgreSQLなど)では、DISTINCTGROUP 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

主の助けを借りて、カーソルを使用してより効率的に書き換えることができます(あなたは私に正しく聞いた:cursorsmore 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に。

61
Quassnoi

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行のみ。

9
l0b0
  • 明らかなのは、SQLの「セット」ベースの操作で置き換えることができるカーソルを探すことです。
  • 私のリストの次は、非相関クエリとして書き直すことができる相関サブクエリを探します
  • 長いストアドプロシージャでは、個別のSQLステートメントを独自のストアドプロシージャに分割します。そうすることで、キャッシュされた独自のクエリプランを取得できます。
  • スコープを短くできるトランザクションを探します。私は定期的に、トランザクションの内部で安全に外部にあるステートメントを見つけます。
  • 多くの場合、副選択は単純な結合として書き直すことができます(現代のオプティマイザーは単純なものを見つけるのが得意です)。

@Quassnoiが述べたように、オプティマイザーはしばしば良い仕事をします。これを支援する1つの方法は、インデックスと統計が最新であること、およびクエリのワークロードに適切なインデックスが存在することを確認することです。

6
Mitch Wheat

チームの全員が一連の標準に従って、コードを読み取り可能、保守可能、理解可能、洗浄可能などにするのが好きです。:)

  • みんな同じエイリアスを使う
  • カーソルなし。ループなし
  • 存在できるときにINを考える理由
  • インデント
  • コーディングスタイルの一貫性

ここには他にもいくつかあります 最も有用なデータベース標準には何がありますか

5
Raj More

あらゆる種類の副選択を結合クエリに置き換えるのが好きです。

これは明らかです:

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が大きなリクエストで適切な実行プランを選択するのに役立ちます。

5
Cyril Gandon

SQLの性質を考えると、リファクタリングのパフォーマンスへの影響に注意する必要があります。 SQLアプリケーションのリファクタリング は、パフォーマンスに重点を置いたリファクタリングに関する優れたリソースです(第5章を参照)。

4
Jim Ferrans

単純化は最適化と同じではないかもしれませんが、可読SQLコードを書く際に単純化が重要になる可能性があります。これは、SQL開発者がSQLコードの概念の正しさをチェックできるようにするために重要です(構文の正しさではなく、開発環境で確認する必要があります)。理想的な世界では、最もシンプルで読みやすいSQLコードを記述し、オプティマイザがそのSQLコードを(おそらくより冗長な)形式で最も速く実行されるように書き換えます。

SQLステートメントをセットロジックに基づくものとして考えることは、特に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の部分が多い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クエリを結合を使用するものに変換する方法を示していませんが、ブールロジックは、結合タイプとクエリが返すものを理解するのに役立ちます。たとえば、テーブルABの場合、_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_に簡略化されます。

3
Nick Seigal

私のアプローチは、関係理論一般、特に関係代数を学ぶことです。次に、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;
0
onedaywhen