別の言い方をすれば、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
対。
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
主が私を助けてくれるカーソル(あなたは私を正しく聞いた: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
)
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
に。