私は、time_stamp、usr_id、transaction_id、lives_remainingの列を持つレコードを含むPostgresテーブル(「ライブ」と呼ばれる)を扱っています。各usr_idの最新のlives_remaining合計を提供するクエリが必要です
例:
time_stamp | lives_remaining | usr_id | trans_id -------------------------------------- --- 07:00 | 1 | 1 | 1 09:00 | 4 | 2 | 2 10:00 | 2 | 3 | 3 10:00 | 1 | 2 | 4 11:00 | 4 | 1 | 5 11:00 | 3 | 1 | 6 13:00 | 3 | 3 | 1
指定された各usr_idの最新データを使用して行の他の列にアクセスする必要があるため、次のような結果を提供するクエリが必要です。
time_stamp | lives_remaining | usr_id | trans_id -------------------------------------- --- 11:00 | 3 | 1 | 6 10:00 | 1 | 2 | 4 13:00 | 3 | 3 | 1
前述のように、各usr_idはライフを獲得または損失する可能性があり、これらのタイムスタンプ付きイベントは非常に近接して発生し、同じタイムスタンプを持つことがありますしたがって、このクエリは機能しません。
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp) AS max_timestamp
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp = b.time_stamp
代わりに、正しい行を識別するために、time_stamp(最初)とtrans_id(2番目)の両方を使用する必要があります。また、適切な行の他の列のデータを提供するメインクエリにサブクエリからその情報を渡す必要があります。これは私が仕事を始めたハッキングされたクエリです:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp || '*' || trans_id)
AS max_timestamp_transid
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id
ORDER BY b.usr_id
わかりましたので、これは動作しますが、私はそれが好きではありません。クエリ内のクエリ、自己結合が必要です。MAXが最大のタイムスタンプとtrans_idを持つことがわかった行を取得することで、はるかに簡単になると思われます。テーブルの「存続中」には、解析する数千万行があるため、このクエリを可能な限り高速かつ効率的にしたいと思います。特にRDBMとPostgresは初めてなので、適切なインデックスを効果的に使用する必要があることを知っています。最適化の方法が少しわかりません。
同様の議論を見つけました here 。 Oracle分析関数に相当するある種のPostgresを実行できますか?
集約関数(MAXなど)で使用される関連列情報へのアクセス、インデックスの作成、より良いクエリの作成に関するアドバイスは大歓迎です!
追伸以下を使用して、私の事例を作成できます。
create TABLE lives (time_stamp timestamp, lives_remaining integer,
usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);
158kの擬似ランダム行(usr_idが0から10kの間で均一に分布し、trans_id
が0から30の間で均一に分布)を持つテーブルでは、
以下のクエリコストでは、Postgresのコストベースのオプティマイザーのコスト推定値(Postgresのデフォルトのxxx_cost
値を使用)を参照しています。これは、必要なI/OおよびCPUリソースの重み付き関数推定です。これを取得するには、PgAdminIIIを起動し、「Query/Explainオプション」を「Analyze」に設定して「Query/Explain(F7)」をクエリで実行します
usr_id
、trans_id
、time_stamp
)に複合インデックスが与えられた場合)usr_id
、trans_id
)に複合インデックスが与えられた場合)usr_id
、trans_id
、time_stamp
)に複合インデックスが与えられた場合)usr_id
、EXTRACT(Epoch FROM time_stamp)
の複合関数インデックスが与えられた場合) 、trans_id
))usr_id
、time_stamp
、trans_id
)); lives
テーブルを1回だけスキャンするという利点があり、一時的に(必要な場合) work_mem を増やしてメモリ内のソートに対応すると、すべての中で最速になります。クエリ。上記のすべての時間には、1万行すべての結果セットの取得が含まれます。
目標は、最小コストの見積もりand最小コストの実行時間であり、推定コストに重点を置いています。クエリの実行は実行時の条件(たとえば、関連する行が既にメモリに完全にキャッシュされているかどうか)に大きく依存しますが、コストの見積もりはそうではありません。一方、コストの見積もりはまさにそれ、見積もりであることに留意してください。
最適なクエリ実行時間は、負荷のない専用データベースで実行する場合に得られます(開発PCでpgAdminIIIを使用するなど)。クエリ時間は、実際のマシンの負荷/データアクセスの広がりに基づいて運用環境で異なります。 1つのクエリが他のクエリよりもわずかに速い(<20%)が、muchのコストが高い場合、一般的に実行時間は長くてもコストが低いクエリを選択する方が賢明です。
クエリの実行時にプロダクションマシンのメモリに競合がないと予想される場合(たとえば、RDBMSキャッシュとファイルシステムキャッシュは、同時クエリおよび/またはファイルシステムアクティビティによってスラッシングされません)、取得したクエリ時間スタンドアロン(開発PC上のpgAdminIIIなど)モードが代表的です。本番システムで競合がある場合、低コストのクエリはキャッシュにそれほど依存しないため、クエリ時間は推定コスト比に比例して低下しますwhereas高コストのクエリは同じデータが何度も繰り返される(安定したキャッシュがない場合に追加のI/Oをトリガーする)。例:
cost | time (dedicated machine) | time (under load) |
-------------------+--------------------------+-----------------------+
some query A: 5k | (all data cached) 900ms | (less i/o) 1000ms |
some query B: 50k | (all data cached) 900ms | (lots of i/o) 10000ms |
必要なインデックスを作成した後、ANALYZE lives
を一度実行することを忘れないでください。
クエリ#1
-- incrementally narrow down the result set via inner joins
-- the CBO may elect to perform one full index scan combined
-- with cascading index lookups, or as hash aggregates terminated
-- by one nested index lookup into lives - on my machine
-- the latter query plan was selected given my memory settings and
-- histogram
SELECT
l1.*
FROM
lives AS l1
INNER JOIN (
SELECT
usr_id,
MAX(time_stamp) AS time_stamp_max
FROM
lives
GROUP BY
usr_id
) AS l2
ON
l1.usr_id = l2.usr_id AND
l1.time_stamp = l2.time_stamp_max
INNER JOIN (
SELECT
usr_id,
time_stamp,
MAX(trans_id) AS trans_max
FROM
lives
GROUP BY
usr_id, time_stamp
) AS l3
ON
l1.usr_id = l3.usr_id AND
l1.time_stamp = l3.time_stamp AND
l1.trans_id = l3.trans_max
クエリ#2
-- cheat to obtain a max of the (time_stamp, trans_id) Tuple in one pass
-- this results in a single table scan and one nested index lookup into lives,
-- by far the least I/O intensive operation even in case of great scarcity
-- of memory (least reliant on cache for the best performance)
SELECT
l1.*
FROM
lives AS l1
INNER JOIN (
SELECT
usr_id,
MAX(ARRAY[EXTRACT(Epoch FROM time_stamp),trans_id])
AS compound_time_stamp
FROM
lives
GROUP BY
usr_id
) AS l2
ON
l1.usr_id = l2.usr_id AND
EXTRACT(Epoch FROM l1.time_stamp) = l2.compound_time_stamp[1] AND
l1.trans_id = l2.compound_time_stamp[2]
2013/01/29 update
最後に、バージョン8.4の時点で、Postgresは Window Function をサポートしています。つまり、次のようにシンプルで効率的なものを作成できます。
クエリ#3
-- use Window Functions
-- performs a SINGLE scan of the table
SELECT DISTINCT ON (usr_id)
last_value(time_stamp) OVER wnd,
last_value(lives_remaining) OVER wnd,
usr_id,
last_value(trans_id) OVER wnd
FROM lives
WINDOW wnd AS (
PARTITION BY usr_id ORDER BY time_stamp, trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
);
DISTINCT ON
( docs を参照)に基づいてクリーンバージョンを提案します。
SELECT DISTINCT ON (usr_id)
time_stamp,
lives_remaining,
usr_id,
trans_id
FROM lives
ORDER BY usr_id, time_stamp DESC, trans_id DESC;
相関サブクエリやGROUP BYを使用しない別の方法があります。私はPostgreSQLのパフォーマンスチューニングの専門家ではないので、この方法と他の人から提供されたソリューションの両方を試して、どちらがより適切かを確認することをお勧めします。
SELECT l1.*
FROM lives l1 LEFT OUTER JOIN lives l2
ON (l1.usr_id = l2.usr_id AND (l1.time_stamp < l2.time_stamp
OR (l1.time_stamp = l2.time_stamp AND l1.trans_id < l2.trans_id)))
WHERE l2.usr_id IS NULL
ORDER BY l1.usr_id;
trans_id
は、少なくとも任意のtime_stamp
。
あなたが言及した他のページの Mike Woodhouseの答え のスタイルが好きです。最大化されているものが単一の列である場合は特に簡潔です。その場合、サブクエリはMAX(some_col)
と_GROUP BY
_を他の列のみ使用できますが、2つの部分があります数量を最大化する場合は、代わりに_ORDER BY
_と_LIMIT 1
_を使用して(Quassnoiで行われるように)最大化できます。
_SELECT *
FROM lives outer
WHERE (usr_id, time_stamp, trans_id) IN (
SELECT usr_id, time_stamp, trans_id
FROM lives sq
WHERE sq.usr_id = outer.usr_id
ORDER BY trans_id, time_stamp
LIMIT 1
)
_
行コンストラクター構文WHERE (a, b, c) IN (subquery)
Niceを使用すると、必要な冗長性の量を削減できるので便利です。
実際、この問題にはハッキーな解決策があります。地域内の各フォレストの最大の木を選択するとします。
SELECT (array_agg(tree.id ORDER BY tree_size.size)))[1]
FROM tree JOIN forest ON (tree.forest = forest.id)
GROUP BY forest.id
フォレストごとにツリーをグループ化すると、ソートされていないツリーのリストが表示され、最大のものを見つける必要があります。最初にすべきことは、行をサイズでソートし、リストの最初の行を選択することです。効率が悪いように思えるかもしれませんが、数百万行ある場合、JOIN
'sおよびWHERE
条件を含むソリューションよりもかなり高速になります。
ところで、ORDER_BY
ために array_agg
はPostgresql 9.0で導入されました
Postgressql 9.5にはDISTINCT ONと呼ばれる新しいオプションがあります
SELECT DISTINCT ON (location) location, time, report
FROM weather_reports
ORDER BY location, time DESC;
重複行を削除し、ORDER BY句で定義された最初の行のみを残します。
公式を参照してください ドキュメント
SELECT l.*
FROM (
SELECT DISTINCT usr_id
FROM lives
) lo, lives l
WHERE l.ctid = (
SELECT ctid
FROM lives li
WHERE li.usr_id = lo.usr_id
ORDER BY
time_stamp DESC, trans_id DESC
LIMIT 1
)
(usr_id, time_stamp, trans_id)
にインデックスを作成すると、このクエリが大幅に改善されます。
常に、何らかの種類のPRIMARY KEY
をテーブルに含める必要があります。
ここには大きな問題があると思います。特定の行が別の行よりも遅れて発生したことを保証する単調に増加する「カウンター」はありません。次の例をご覧ください。
timestamp lives_remaining user_id trans_id
10:00 4 3 5
10:00 5 3 6
10:00 3 3 1
10:00 2 3 2
最新のエントリであるこのデータから判断することはできません。それは2番目のものですか、それとも最後のものですか?正しい答えを提供するために、このデータのいずれにも適用できるsortまたはmax()関数はありません。
タイムスタンプの解像度を上げることは大きな助けになるでしょう。データベースエンジンは要求をシリアル化するので、十分な解像度で、2つのタイムスタンプが同じにならないことを保証できます。
または、非常に長い間ロールオーバーしないtrans_idを使用します。ロールオーバーするtrans_idがあると、複雑な計算をしない限り、trans_id 6がtrans_id 1よりも新しいかどうかを(同じタイムスタンプに対して)知ることができません。