PostgreSql JSONB SELECT gegen mehrere Werte

7

Ich habe eine sehr einfache JSON-Tabelle, die ich mit einigen Beispieldaten fülle:

CREATE TABLE jsonthings(d JSONB NOT NULL);

INSERT INTO jsonthings VALUES ('{"name":"First","tags":["foo"]}');
INSERT INTO jsonthings VALUES ('{"name":"Second","tags":["foo","bar"]}');
INSERT INTO jsonthings VALUES ('{"name":"Third","tags":["bar","baz"]}');
INSERT INTO jsonthings VALUES ('{"name":"Fourth","tags":["baz"]}');

CREATE INDEX ON jsonthings USING GIN(d);

Und ich versuche, den Index beim Ausführen von a zu verwenden SELECT. Ein einfaches SELECTAbrufen der Zeilen, in denen der Wert ein einzelnes Element ist, funktioniert einwandfrei:

SELECT d FROM jsonthings WHERE d @> '{"name":"First"}';

Wenn nameich jedoch versuche, eine Abfrage auszuführen, die mehr als einem Wert von entspricht, kann ich nicht herausfinden, wie der Index verwendet wird. Ich habe es versucht:

SELECT d FROM jsonthings WHERE d->>'name' = ANY(ARRAY['First', 'Second']);
SELECT d FROM jsonthings WHERE d->'name' ?| ARRAY['First', 'Second'];
SELECT d FROM jsonthings WHERE d#>'{name}' ?| ARRAY['First','Second'];

und alle zeigen einen sequentiellen Scan der Tabelle (ich verwende enable_seqscan=false, um die Indexverwendung zu erzwingen, wenn möglich). Gibt es eine Möglichkeit, die Abfrage so umzuschreiben, dass sie einen Index verwendet? Mir ist bewusst, dass ich Folgendes tun könnte:

SELECT * FROM jsonthings WHERE d @> '{"name":"First"}' OR d @> '{"name":"Second"}';

Aber dann habe ich eine Abfrage mit variabler Länge und gehe durch JDBC, sodass die Vorteile der Abfrage als PreparedStatement verloren gehen.

Ich bin auch daran interessiert, eine ähnliche Abfrage für eine Reihe von Elementen im tagsSchlüssel zu sehen, z.

SELECT d FROM jsonthings WHERE d @> '{"tags":["foo"]}' OR d @> '{"tags":["bar"]}';

aber unter Verwendung einer ARRAYstatt mehrerer Bedingungen und unter Verwendung eines Index.

Dies ist auf PostgreSql 9.4.

jgm
quelle
Sie haben keine hohe Selektivität. Sie benötigen ca. 2-5% Daten des Recordset, um Indizes zu aktivieren. Fügen Sie weitere Datensätze ein, und dann wählt Ihr Abfrageanalysator möglicherweise den Index gegenüber dem sequentiellen Scan aus.
Mladen Uzelac
Danke für den Kommentar. Ich habe enable_seqscan auf false gesetzt, um die Verwendung des Index zu erzwingen, damit der Mangel an Daten nicht das Problem ist. Obwohl ich während des Testens weitere zehn Millionen Zeilen
hinzugefügt habe
Bitte posten Sie Ihren Erklärungsplan auf EXPLAIN.DEPESZ.com
Mladen Uzelac

Antworten:

3

Versuchen Sie in Dokumenten ( http://www.postgresql.org/docs/9.4/static/datatype-json.html ), den Ausdrucksindex zu verwenden:

CREATE INDEX idx_jsonthings_names ON jsonthings USING gin ((d -> 'name'));
SELECT d FROM jsonthings WHERE d @> '{"name": ["First", "Second"]}';
Mladen Uzelac
quelle
2
Ja, es sieht so aus, als müsste ich einen separaten Index verwenden, was seltsam erscheint, da der Index offensichtlich bereits vorhanden ist und in der Einzelelementabfrage verwendet wird. Und die Abfrage, die ich verwenden muss, ist, SELECT d FROM jsonthings WHERE d->'name' ?| ARRAY['First', 'Second'];sonst wird der Index nicht verwendet. Vielen Dank.
JGM
@jgm Haben Sie einen allgemeineren Weg gefunden, um dies zu erreichen? Ein separater Index pro Feld ist unpraktisch (und unmöglich, wenn dynamische Felder benötigt werden).
Tuukka Mustonen
1

Dies ist eine Antwort auf die Antwort von Mladen. Ich habe nicht genug Ruf, um einen Kommentar zu hinterlassen, aber ich wollte antworten, weil es so aussieht, als ob die Abfrage falsch ist und mich verwirrt hat und andere Leute in Zukunft verwirren könnte.

Sie erwähnen die Verwendung von:

SELECT d FROM jsonthings WHERE d @> '{"name": ["First", "Second"]}';

Um Einträge abzurufen, die entweder Firstoder Secondals Namen haben, scheint dies für mich jedoch nicht zu funktionieren PostgreSQL 9.4.4:

SELECT d FROM jsonthings WHERE d @> '{"name": ["First", "Second"]}';
 d
---
(0 rows)

Anscheinend versucht die obige Abfrage, Einträge abzurufen, bei denen das nameAttribut das Array enthält ["First", "Second"].

Wenn ich einen solchen Eintrag erstelle:

INSERT INTO jsonthings VALUES ('{"name":["First", "Second"],"tags":["baz"]}');

Versuchen Sie die Abfrage erneut, und es wird ein Ergebnis zurückgegeben:

SELECT d FROM jsonthings WHERE d @> '{"name": ["First", "Second"]}';
d
------------------------------------------------
{"name": ["First", "Second"], "tags": ["baz"]}
(1 row)

Dies unterscheidet sich jedoch von der Frage des Originalplakats, wie ein Index verwendet werden soll, wenn Einträge abgefragt werden, bei denen das nameAttribut entweder First oder war Second :

SELECT * FROM jsonthings WHERE d @> '{"name":"First"}' OR d @> '{"name":"Second"}';

Ich wollte dies hier bereitstellen, damit andere Leute nicht glauben, dass es möglich ist, eine ODER- Abfrage mit JSON durch Bereitstellen durchzuführen "name": ["First", "Second"], da dies irreführend ist.

Adamc
quelle