onlyを別の列でソートしながら、どのようにして1つの列を_GROUP BY
_できますか?.
私は次のことをしようとしています:
_SELECT dbId,retreivalTime
FROM FileItems
WHERE sourceSite='something'
GROUP BY seriesName
ORDER BY retreivalTime DESC
LIMIT 100
OFFSET 0;
_
last/ n /アイテムをFileItemsから降順で選択し、DISTINCT
のseriesName
の値で行をフィルタリングします。上記のクエリは_ERROR: column "fileitems.dbid" must appear in the GROUP BY clause or be used in an aggregate function
_でエラーになります。このクエリの出力を取得するためにdbid
の値が必要であり、ソーステーブルでJOIN
の値を取得して、残りの列を取得しました。
これは基本的に以下の質問のゲシュタルトであり、明確にするために多くの無関係な詳細が削除されていることに注意してください。
私はsqlite3からPostgreSQLに移行しているシステムを持っています。
_ SELECT
d.dbId,
d.dlState,
d.sourceSite,
[snip a bunch of rows]
d.note
FROM FileItems AS d
JOIN
( SELECT dbId
FROM FileItems
WHERE sourceSite='{something}'
GROUP BY seriesName
ORDER BY MAX(retreivalTime) DESC
LIMIT 100
OFFSET 0
) AS di
ON di.dbId = d.dbId
ORDER BY d.retreivalTime DESC;
_
基本的に、データベース内のlastn DISTINCT
アイテムを選択します。ここで、個別の制約が1つの列にあり、並べ替え順序が異なるカラム。
残念ながら、上記のクエリはsqliteでは正常に動作しますが、PostgreSQLではエラー_psycopg2.ProgrammingError: column "fileitems.dbid" must appear in the GROUP BY clause or be used in an aggregate function
_でエラーが発生します。
残念ながら、dbId
をGROUP BY句に追加すると問題が修正されますが(_GROUP BY seriesName,dbId
_など)、dbid
はデータベースの主キーであり、すべての値が異なるため、クエリ結果に対する個別のフィルタリングは機能しなくなります。
Postgresのドキュメント を読むとSELECT DISTINCT ON ({nnn})
がありますが、返される結果は_{nnn}
_でソートする必要があります。
したがって、_SELECT DISTINCT ON
_を使用してやりたいことを行うには、すべての_DISTINCT {nnn}
_とそのMAX(retreivalTime)
に対してクエリを実行し、再度ソートする必要があります_{nnn}
_ではなくretreivalTime
を使用して、最大の100を取得し、テーブルに対してそれらを使用してクエリを実行し、残りの行を取得します。 seriesName
列に〜175Kの行と〜14Kの個別の値があり、最新の100だけが必要です。このクエリはパフォーマンスがやや重要です(クエリ時間は1/2秒未満が必要です)。
ここでの単純な仮定は、基本的に、DBはretreivalTime
の降順で各行を反復処理し、LIMIT
項目が表示されたら停止する必要があるため、完全なテーブルクエリは理想的ではありませんが、実際にどのように理解するつもりはありませんデータベースシステムは内部的に最適化されますが、私はこれに完全に間違ったアプローチをしている可能性があります。
FWIW、Idoは、異なるOFFSET
値を使用することがありますが、offset>〜500が完全に許容される場合、クエリ時間が長くなります。基本的に、OFFSET
は、スクロールカーソルを各接続専用にする必要なしに逃げることができる、くだらないページングメカニズムです。おそらく、いつか再訪するでしょう。
参照- このクエリにつながる1か月前に尋ねた質問 。
OK、もっとメモ:
_ SELECT
d.dbId,
d.dlState,
d.sourceSite,
[snip a bunch of rows]
d.note
FROM FileItems AS d
JOIN
( SELECT seriesName, MAX(retreivalTime) AS max_retreivalTime
FROM FileItems
WHERE sourceSite='{something}'
GROUP BY seriesName
ORDER BY max_retreivalTime DESC
LIMIT %s
OFFSET %s
) AS di
ON di.seriesName = d.seriesName AND di.max_retreivalTime = d.retreivalTime
ORDER BY d.retreivalTime DESC;
_
説明されているようにクエリに対して正しく機能しますが、_GROUP BY
_句をremoveすると、失敗します(アプリケーションではオプションです)。
_psycopg2.ProgrammingError: column "FileItems.seriesname" must appear in the GROUP BY clause or be used in an aggregate function
_
私は、PostgreSQLでサブクエリがどのように機能するかを根本的に理解していないと思います。どこがいけないの?サブクエリは基本的にインライン関数であり、結果がメインクエリにフィードされるだけであるという印象を受けました。
まだあなたのレーダーにはないように見える重要な質問:
同じseriesName
の各行セットから、one行の列、またはany複数の行の値(一緒に移動する場合としない場合があります)?
あなたの答えは後者です、あなたは最大のdbid
を最大のretreivaltime
と組み合わせます。これは別の行から来るかもしれません。
consistent行を取得するには、_DISTINCT ON
_を使用してサブクエリにラップし、結果を異なる順序で並べます。
_SELECT * FROM (
SELECT DISTINCT ON (seriesName)
dbid, seriesName, retreivaltime
FROM FileItems
WHERE sourceSite = 'mk'
ORDER BY seriesName, retreivaltime DESC NULLS LAST -- latest retreivaltime
) sub
ORDER BY retreivaltime DESC NULLS LAST
LIMIT 100;
_
_DISTINCT ON
_の詳細:
余談ですが、おそらくretrievalTime
である必要があります。さらに良いのは_retrieval_time
_です。引用符で囲まれていない大文字と小文字の識別子は、Postgresでの混乱の一般的な原因です。
ここでは大きなテーブルを扱っているため、インデックスを使用できるクエリが必要です。これは、上記のクエリには当てはまりません(_WHERE sourceSite = 'mk'
_を除く)。
よく調べてみると、問題はルーズインデックススキャンの特殊なケースのようです。 Postgresはネイティブではルーズインデックススキャンをサポートしていませんが、再帰CTEでエミュレートできます。 Postgres Wikiの単純なケースのコード例 があります。
SOに関する関連回答、より高度なソリューション、説明、フィドル:
しかし、あなたのケースはもっと複雑です。しかし、私はそれをあなたのために機能させるための変種を見つけたと思います。このインデックスに基づく(_WHERE sourceSite = 'mk'
_なし)
_CREATE INDEX mi_special_full_idx ON MangaItems
(retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST, dbid)
_
または(_WHERE sourceSite = 'mk'
_を使用)
_CREATE INDEX mi_special_granulated_idx ON MangaItems
(sourceSite, retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST, dbid)
_
最初のインデックスは両方のクエリに使用できますが、追加のWHERE条件では完全に効率的ではありません。 2番目のインデックスは、最初のクエリに対して非常に限定的に使用されます。クエリの両方のバリアントがあるので、bothインデックスを作成することを検討してください。
Index Onlyscans を許可するために、最後にdbid
を追加しました。
再帰CTEを使用したこのクエリは、インデックスを使用します。 Postgres 9.3でテストしましたが、動作します:順次スキャンなし、すべてindex-onlyスキャン:
_WITH RECURSIVE cte AS (
(
SELECT dbid, seriesName, retreivaltime, 1 AS rn, ARRAY[seriesName] AS arr
FROM MangaItems
WHERE sourceSite = 'mk'
ORDER BY retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT i.dbid, i.seriesName, i.retreivaltime, c.rn + 1, c.arr || i.seriesName
FROM cte c
, LATERAL (
SELECT dbid, seriesName, retreivaltime
FROM MangaItems
WHERE (retreivaltime, seriesName) < (c.retreivaltime, c.seriesName)
AND sourceSite = 'mk' -- repeat condition!
AND seriesName <> ALL(c.arr)
ORDER BY retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST
LIMIT 1
) i
WHERE c.rn < 101
)
SELECT dbid
FROM cte
ORDER BY rn;
_
seriesName
は一意ではないため、retreivaltime
を_ORDER BY
_に含めるには、needします。 「ほぼ」の一意性は、依然としてnot一意です。
非再帰クエリは、最新の行から始まります。
再帰クエリは、100行になるまで、リストにないseriesName
を含む次の最新の行を追加します。
重要な部分は、JOIN
条件_(b.retreivaltime, b.seriesName) < (c.retreivaltime, c.seriesName)
_および_ORDER BY
_句_ORDER BY retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST
_です。どちらもインデックスのソート順と一致しているため、マジックが発生します。
重複を除外するために配列でseriesName
を収集します。 b.seriesName <> ALL(c.foo_arr)
のコストは、行の数に応じて徐々に増加しますが、100行だけの場合でも、まだ安価です。
コメントで明記されているようにdbid
を返すだけです。
以前にも同様の問題を扱ってきました。以下は、部分インデックスとループ関数に基づいた高度に最適化された完全なソリューションです。
正しく行われた場合、おそらく最もマテリアライズドビューを除いて最も速い方法です。しかし、もっと複雑です。
コメントに記載されているように、書き込み操作は多くなく、パフォーマンスも重要ではないため(質問に含める必要があります)、saveマテリアライズドビューの上位n個の事前計算された行。基になるテーブルに関連する変更を加えた後、更新します。代わりに、パフォーマンス重視のクエリをマテリアライズドビューに基づいてください。
最新の1000 dbid
程度の「薄い」mvの場合もあります。クエリで、元のテーブルに結合します。たとえば、コンテンツが時々更新されても、上位n行は変更されないままである場合があります。
または、返す行全体を含む「太い」mv。まだ高速です。明らかに、より頻繁に更新する必要があります。
わかりました、私はドキュメントをもっと読みました、そして今、私は問題を少なくとも少しよく理解します。
基本的には、GROUP BY seriesName
集計の結果として、dbid
には複数の値が存在する可能性があります。 SQLiteとMySQLでは、どうやらDBエンジンが1つをランダムに選択するだけです(これは私のアプリケーションではまったく問題ありません)。
ただし、PostgreSQLははるかに保守的であるため、ランダムな値を選択するのではなく、エラーをスローします。
このクエリを機能させる簡単な方法は、関連する値に集計関数を適用することです。
SELECT MAX(dbid) AS mdbid, seriesName, MAX(retreivaltime) AS mrt
FROM MangaItems
WHERE sourceSite='mk'
GROUP BY seriesName
ORDER BY mrt DESC
LIMIT 100
OFFSET 0;
これにより、クエリ出力が完全に修飾され、クエリが機能するようになります。
ええと、私は実際に、データベースの外でいくつかの手続き型ロジックを使用して、やりたいことを達成するために仕上げました。
基本的に、99%の時間、lastが欲しい 100 200件クエリプランナーはこれを最適化していないようで、OFFSET
の値が大きい場合、手続き型フィルターの処理速度が大幅に低下します。
とにかく、名前付きカーソルを使用して手動でデータベースの行を反復処理し、数百のグループで行を取得しました。次に、アプリケーションコードでそれらを区別するためにそれらをフィルター処理し、必要な明確な結果の数を累積した直後にカーソルを閉じます。
mako
コード(基本的にはpython)。たくさんのデバッグステートメントが残っています。
<%def name="fetchMangaItems(flags='', limit=100, offset=0, distinct=False, tableKey=None, seriesName=None)">
<%
if distinct and seriesName:
raise ValueError("Cannot filter for distinct on a single series!")
if flags:
raise ValueError("TODO: Implement flag filtering!")
whereStr, queryAdditionalArgs = buildWhereQuery(tableKey, None, seriesName=seriesName)
params = Tuple(queryAdditionalArgs)
anonCur = sqlCon.cursor()
anonCur.execute("BEGIN;")
cur = sqlCon.cursor(name='test-cursor-1')
cur.arraysize = 250
query = '''
SELECT
dbId,
dlState,
sourceSite,
sourceUrl,
retreivalTime,
sourceId,
seriesName,
fileName,
originName,
downloadPath,
flags,
tags,
note
FROM MangaItems
{query}
ORDER BY retreivalTime DESC;'''.format(query=whereStr)
start = time.time()
print("time", start)
print("Query = ", query)
print("params = ", params)
print("tableKey = ", tableKey)
ret = cur.execute(query, params)
print("Cursor ret = ", ret)
# for item in cur:
# print("Row", item)
seenItems = []
rowsBuf = cur.fetchmany()
rowsRead = 0
while len(seenItems) < offset:
if not rowsBuf:
rowsBuf = cur.fetchmany()
row = rowsBuf.pop(0)
rowsRead += 1
if row[6] not in seenItems or not distinct:
seenItems.append(row[6])
retRows = []
while len(seenItems) < offset+limit:
if not rowsBuf:
rowsBuf = cur.fetchmany()
row = rowsBuf.pop(0)
rowsRead += 1
if row[6] not in seenItems or not distinct:
retRows.append(row)
seenItems.append(row[6])
cur.close()
anonCur.execute("COMMIT;")
print("duration", time.time()-start)
print("Rows used", rowsRead)
print("Query complete!")
return retRows
%>
</%def>
これは現在、最新の 100 200の異なるシリーズアイテム 115 〜80ミリ秒(より短い時間は、TCPソケット)ではなく、ローカル接続を使用している場合)、約1500行の処理中。
コメントを来てください:
buildWhereQuery
は私自身の動的クエリビルダーです。はい、これは恐ろしい考えです。はい、SQLalchemyなどについて知っています。 A.これは私が自宅のLANの外で使用することを期待していない個人的なプロジェクトであり、B。それはSQLを学ぶための素晴らしい方法であるため、独自に作成しました。autocommit
モードoffを使用する場合)。匿名カーソルをインスタンス化し、SQL(ここではBEGIN
のみ)を発行し、名前付きカーソルを作成し、それを使用して閉じ、最後に匿名カーソルでコミットする必要があります。