Ich möchte Zeilen basierend darauf auswählen, ob eine Spalte in einer großen Liste von Werten enthalten ist, die ich als ganzzahliges Array übergebe.
Hier ist die Abfrage, die ich derzeit verwende:
SELECT item_id, other_stuff, ...
FROM (
SELECT
-- Partitioned row number as we only want N rows per id
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
item_id, other_stuff, ...
FROM mytable
WHERE
item_id = ANY ($1) -- Integer array
AND end_date > $2
ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12
Die Tabelle ist wie folgt aufgebaut:
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+---------
item_id | integer | | not null |
allowed | boolean | | not null |
start_date | timestamp without time zone | | not null |
end_date | timestamp without time zone | | not null |
...
Indexes:
"idx_dtr_query" btree (item_id, start_date, allowed, end_date)
...
Ich habe diesen Index erstellt, nachdem ich verschiedene ausprobiert und EXPLAIN
die Abfrage ausgeführt habe. Dieser war sowohl für das Abfragen als auch für das Sortieren am effizientesten. Hier ist die EXPLAIN-Analyse der Abfrage:
Subquery Scan on x (cost=0.56..368945.41 rows=302230 width=73) (actual time=0.021..276.476 rows=168395 loops=1)
Filter: (x.r <= 12)
Rows Removed by Filter: 90275
-> WindowAgg (cost=0.56..357611.80 rows=906689 width=73) (actual time=0.019..248.267 rows=258670 loops=1)
-> Index Scan using idx_dtr_query on mytable (cost=0.56..339478.02 rows=906689 width=73) (actual time=0.013..130.362 rows=258670 loops=1)
Index Cond: ((item_id = ANY ('{/* 15,000 integers */}'::integer[])) AND (end_date > '2018-03-30 12:08:00'::timestamp without time zone))
Planning time: 30.349 ms
Execution time: 284.619 ms
Das Problem ist, dass das int-Array bis zu 15.000 Elemente enthalten kann und die Abfrage in diesem Fall ziemlich langsam wird (ca. 800 ms auf meinem Laptop, einem aktuellen Dell XPS).
Ich dachte, das Übergeben des int-Arrays als Parameter könnte langsam sein, und da die Liste der IDs vorher in der Datenbank gespeichert werden kann, habe ich dies versucht. Ich habe sie in einem Array in einer anderen Tabelle gespeichert und verwendet item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, was langsamer war als mein aktueller Ansatz. Ich habe auch versucht, sie Zeile für Zeile zu speichern und zu verwenden item_id IN (SELECT item_id FROM ...)
, was sogar mit den für meinen Testfall relevanten Zeilen in der Tabelle noch langsamer war.
Gibt es einen besseren Weg, dies zu tun?
Update: Nach Evans Kommentaren habe ich einen anderen Ansatz versucht: Jedes Element ist Teil mehrerer Gruppen. Anstatt die Element-IDs der Gruppe zu übergeben, habe ich versucht, die Gruppen-IDs in mytable hinzuzufügen:
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+---------
item_id | integer | | not null |
allowed | boolean | | not null |
start_date | timestamp without time zone | | not null |
end_date | timestamp without time zone | | not null |
group_ids | integer[] | | not null |
...
Indexes:
"idx_dtr_query" btree (item_id, start_date, allowed, end_date)
"idx_dtr_group_ids" gin (group_ids)
...
Neue Abfrage ($ 1 ist die Zielgruppen-ID):
SELECT item_id, other_stuff, ...
FROM (
SELECT
-- Partitioned row number as we only want N rows per id
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
item_id, other_stuff, ...
FROM mytable
WHERE
$1 = ANY (group_ids)
AND end_date > $2
ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12
Erklären Sie analysieren:
Subquery Scan on x (cost=123356.60..137112.58 rows=131009 width=74) (actual time=811.337..1087.880 rows=172023 loops=1)
Filter: (x.r <= 12)
Rows Removed by Filter: 219726
-> WindowAgg (cost=123356.60..132199.73 rows=393028 width=74) (actual time=811.330..1040.121 rows=391749 loops=1)
-> Sort (cost=123356.60..124339.17 rows=393028 width=74) (actual time=811.311..868.127 rows=391749 loops=1)
Sort Key: item_id, start_date, allowed
Sort Method: external sort Disk: 29176kB
-> Seq Scan on mytable (cost=0.00..69370.90 rows=393028 width=74) (actual time=0.105..464.126 rows=391749 loops=1)
Filter: ((end_date > '2018-04-06 12:00:00'::timestamp without time zone) AND (2928 = ANY (group_ids)))
Rows Removed by Filter: 1482567
Planning time: 0.756 ms
Execution time: 1098.348 ms
Bei Indizes gibt es möglicherweise Verbesserungspotenzial, aber es fällt mir schwer zu verstehen, wie Postgres sie verwendet. Daher bin ich mir nicht sicher, was ich ändern soll.
quelle
mytable
, mit ungefähr 500.000 verschiedenenitem_id
. Für diese Tabelle gibt es keinen natürlichen natürlichen eindeutigen Schlüssel. Die Daten werden automatisch für sich wiederholende Ereignisse generiert. Ich denke, dasitem_id
+start_date
+name
(Feld hier nicht gezeigt) könnte eine Art Schlüssel darstellen.Antworten:
Ja, verwenden Sie eine temporäre Tabelle. Es ist nichts Falsches daran, eine indizierte temporäre Tabelle zu erstellen, wenn Ihre Abfrage so verrückt ist.
Aber noch besser als das ...
Sie wählen 3% Ihrer Datenbank einzeln aus. Ich muss mich fragen, ob es nicht besser ist, Gruppen / Tags usw. im Schema selbst zu erstellen. Ich persönlich musste noch nie 15.000 verschiedene IDs in eine Abfrage senden.
quelle