顧客のテーブルと購入のテーブルがあるとします。各購入は1人の顧客に属します。私は1つのSELECTステートメントで彼らの最後の購入とともにすべての顧客のリストを得たいです。ベストプラクティスは何ですか?索引作成に関するアドバイスはありますか?
あなたの答えにこれらのテーブル/カラム名を使ってください:
そしてもっと複雑な状況で、最後の購入をcustomerテーブルに入れることによってデータベースを非正規化することは(パフォーマンス的に)有益でしょうか?
(購入)IDが日付順にソートされていることが保証されている場合、LIMIT 1
のようなものを使用してステートメントを単純化できますか?
これはStackOverflowで定期的に発生しているgreatest-n-per-group
問題の例です。
これが私が通常それを解決することをお勧めする方法です:
SELECT c.*, p1.*
FROM customer c
JOIN purchase p1 ON (c.id = p1.customer_id)
LEFT OUTER JOIN purchase p2 ON (c.id = p2.customer_id AND
(p1.date < p2.date OR p1.date = p2.date AND p1.id < p2.id))
WHERE p2.id IS NULL;
説明:行p1
を指定すると、同じ顧客とより新しい日付を持つ行p2
はありません(関係がある場合はより新しいid
)。それが真実であるとわかったとき、p1
がその顧客の最新の購入です。
インデックスに関しては、列(customer_id
、purchase
、date
)の上のid
に複合インデックスを作成します。これにより、カバーインデックスを使用して外部結合を実行できます。最適化は実装に依存するため、必ずプラットフォームでテストしてください。最適化計画を分析するには、RDBMSの機能を使用してください。例えば。 MySQLではEXPLAIN
。
私が上で示した解決策の代わりにサブクエリを使う人もいますが、私の解決策は関係を解決するのをより簡単にすると思います。
サブセレクトを使ってこれを試すこともできます
SELECT c.*, p.*
FROM customer c INNER JOIN
(
SELECT customer_id,
MAX(date) MaxDate
FROM purchase
GROUP BY customer_id
) MaxDates ON c.id = MaxDates.customer_id INNER JOIN
purchase p ON MaxDates.customer_id = p.customer_id
AND MaxDates.MaxDate = p.date
Selectは、すべての顧客とその顧客のLast購入日に参加する必要があります。
データベースを指定していません。分析機能を可能にするものであれば、GROUP BYのものよりもこのアプローチを使用するほうが速い場合があります(Oracleでは間違いなく速く、SQL Serverの最近のエディションではおそらくもっと速いですが、他については知りません)。
SQL Serverの構文は次のようになります。
SELECT c.*, p.*
FROM customer c INNER JOIN
(SELECT RANK() OVER (PARTITION BY customer_id ORDER BY date DESC) r, *
FROM purchase) p
ON (c.id = p.customer_id)
WHERE p.r = 1
もう1つの方法は、結合条件にNOT EXISTS
条件を使用して後の購入をテストすることです。
SELECT *
FROM customer c
LEFT JOIN purchase p ON (
c.id = p.customer_id
AND NOT EXISTS (
SELECT 1 FROM purchase p1
WHERE p1.customer_id = c.id
AND p1.id > p.id
)
)
私は私の問題に対する解決策としてこのスレッドを見つけました。
しかし、私がそれらを試したとき、パフォーマンスは低かった。ベローは、より良いパフォーマンスを得るための私の提案です。
With MaxDates as (
SELECT customer_id,
MAX(date) MaxDate
FROM purchase
GROUP BY customer_id
)
SELECT c.*, M.*
FROM customer c INNER JOIN
MaxDates as M ON c.id = M.customer_id
これが役立つことを願っています。
これを試してください、それは役立ちます。
私は自分のプロジェクトでこれを使用しました。
SELECT
*
FROM
customer c
OUTER APPLY(SELECT top 1 * FROM purchase pi
WHERE pi.customer_id = c.Id order by pi.Id desc) AS [LastPurchasePrice]
SQLiteでテスト済み:
SELECT c.*, p.*, max(p.date)
FROM customer c
LEFT OUTER JOIN purchase p
ON c.id = p.customer_id
GROUP BY c.id
max()
集約関数は、各グループから最新の購入が選択されていることを確認します(ただし、日付列はmax()が最新のものになる形式であると想定します - 通常これが当てはまります)。あなたが同じ日付で購入を処理したいならば、あなたはmax(p.date, p.id)
を使うことができます。
インデックスに関しては、(customer_id、date、[あなたがあなたのselectに返したい他の購入コラム])で購入時のインデックスを使います。
LEFT OUTER JOIN
(INNER JOIN
とは対照的に)は、購入したことがない顧客も確実に含まれるようにします。
PostgreSQLを使用している場合は、グループの最初の行を見つけるためにDISTINCT ON
を使用できます。
SELECT customer.*, purchase.*
FROM customer
JOIN (
SELECT DISTINCT ON (customer_id) *
FROM purchase
ORDER BY customer_id, date DESC
) purchase ON purchase.customer_id = customer.id
DISTINCT ON
フィールド(ここではcustomer_id
)は、ORDER BY
句の左端のフィールドと一致する必要があります。
警告:これは非標準の句です。
これを試してください、
SELECT
c.Id,
c.name,
(SELECT pi.price FROM purchase pi WHERE pi.Id = MAX(p.Id)) AS [LastPurchasePrice]
FROM customer c INNER JOIN purchase p
ON c.Id = p.customerId
GROUP BY c.Id,c.name;