69

SQLクエリの複雑さやサイズを減らすために使用できる「推論規則」(集合演算規則や論理規則に似ています)を探しています。そのようなものはありますか?どんな論文、どんな道具?あなたが自分で見つけた同等物はありますか?クエリの最適化に似ていますが、パフォーマンスの点では異なります。

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

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

4

8 に答える 8

63

別の言い方をすれば、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)、異なる実行プランDISTINCTGROUP 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

PostgreSQLDISTINCT並べ替えと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

主が私を助けてくれるカーソル(あなたは私を正しく聞いた:cursorsmore 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このクエリは、次の代わりに許可する等式条件を使用するように大幅に書き直すことができます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に。

于 2009-07-01T14:17:56.470 に答える
9

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句に入れて、すべてではなく1つの行のみをフェッチします。

于 2009-07-01T15:03:34.193 に答える
6
  • 明らかなのは、SQLの「設定」ベースの操作で置き換えることができるカーソルを探すことだと思います。
  • 次の私のリストは、相関のないクエリとして書き直すことができる相関のあるサブクエリを探すことです。
  • 長いストアドプロシージャでは、個別のSQLステートメントを独自のストアドプロシージャに分割します。そうすれば、彼らはそこに独自のキャッシュされたクエリプランを取得します。
  • スコープを短縮できるトランザクションを探します。私は定期的に、安全に外部にある可能性のあるトランザクション内のステートメントを見つけます。
  • 副選択は、単純な結合として書き直すことができます(最新のオプティマイザーは単純なものを見つけるのが得意です)

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

于 2009-07-01T14:19:45.357 に答える
5

チームの全員が、コードを読みやすく、保守しやすく、理解しやすく、洗えるようにするための一連の標準に従うのが好きです。:)

  • 全員が同じエイリアスを使用します
  • カーソルはありません。ループなし
  • 存在できるのになぜINを考えるのか
  • インデント
  • コーディングスタイルの一貫性

ここにはさらにいくつかのものがありますあなたの最も有用なデータベース標準のいくつかは何ですか?

于 2009-07-01T14:53:30.297 に答える
5

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

これは明らかです:

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

于 2009-07-01T14:33:44.300 に答える
4

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

于 2009-07-01T15:58:25.130 に答える
3

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

特にwhere句を組み合わせたり、where句の複雑な否定を理解したりする必要がある場合は、SQLステートメントをsetロジックに基づいて考えると非常に便利であることがわかりました。この場合、ブール代数の法則を使用します。

where句を単純化するための最も重要なものは、おそらくド・モルガンの法則です(「・」は「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これらの法則は、ネストされた部分がたくさんあるwhere句を単純化するのに非常に役立ちますOR

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クエリを結合を使用するSQLクエリに変換する方法を示していませんが、ブール論理は、結合タイプとクエリが返すものを理解するのに役立ちます。たとえば、テーブルAB、の場合、anINNER JOINはのようになりA AND B、aLEFT OUTER JOINはに(A AND NOT B) OR (A AND B)単純化されてA OR (A AND B)、aFULL OUTER JOINA OR (A AND B) OR Bに単純化されA OR Bます。

于 2012-03-13T15:52:07.753 に答える
0

私のアプローチは、一般的な関係理論、特に関係代数を学ぶことです。次に、SQLで使用される構造を見つけて、関係代数(たとえば、全称記号、別名除算)と微積分(たとえば、存在記号)から演算子を実装する方法を学びます。落とし穴は、SQLにはリレーショナルモデルにはない機能があります。たとえば、nullは、とにかくリファクタリングするのが最適です。推奨読書: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;
于 2012-03-13T16:33:42.380 に答える