PostgreSQL peut-il indexer les colonnes de tableaux?

144

Je ne trouve pas de réponse définitive à cette question dans la documentation. Si une colonne est de type tableau, toutes les valeurs entrées seront-elles indexées individuellement?

J'ai créé une table simple avec une int[]colonne et y ai mis un index unique. J'ai remarqué que je ne pouvais pas ajouter le même tableau d'entiers, ce qui me porte à croire que l'index est un composite des éléments du tableau, pas un index de chaque élément.

INSERT INTO "Test"."Test" VALUES ('{10, 15, 20}');
INSERT INTO "Test"."Test" VALUES ('{10, 20, 30}');

SELECT * FROM "Test"."Test" WHERE 20 = ANY ("Column1");

L'index aide-t-il cette requête?

IamIC
la source
Est-il possible d'utiliser le type de données jsonbet d'utiliser les index? postgresql.org/docs/9.5/static/functions-json.html et postgresql.org/docs/9.5/static/datatype-json.html#JSON-INDEXING
user3791372

Réponses:

181

Oui, vous pouvez indexer un tableau, mais vous devez utiliser les opérateurs de tableau et le type d'index GIN .

Exemple:

    CREATE TABLE "Test"("Column1" int[]);
    INSERT INTO "Test" VALUES ('{10, 15, 20}');
    INSERT INTO "Test" VALUES ('{10, 20, 30}');

    CREATE INDEX idx_test on "Test" USING GIN ("Column1");

    -- To enforce index usage because we have only 2 records for this test... 
    SET enable_seqscan TO off;

    EXPLAIN ANALYZE
    SELECT * FROM "Test" WHERE "Column1" @> ARRAY[20];

Résultat:

Bitmap Heap Scan on "Test"  (cost=4.26..8.27 rows=1 width=32) (actual time=0.014..0.015 rows=2 loops=1)
  Recheck Cond: ("Column1" @> '{20}'::integer[])
  ->  Bitmap Index Scan on idx_test  (cost=0.00..4.26 rows=1 width=0) (actual time=0.009..0.009 rows=2 loops=1)
        Index Cond: ("Column1" @> '{20}'::integer[])
Total runtime: 0.062 ms
Remarque

il semble que dans de nombreux cas, l' option gin__int_ops soit requise

create index <index_name> on <table_name> using GIN (<column> gin__int_ops)

Je n'ai pas encore vu de cas où cela fonctionnerait avec l'opérateur && et @> sans les options gin__int_ops

Frank Heikens
la source
19
Comme l'OP le suppose, cela n'indexe pas réellement les valeurs de tableau individuelles, mais indexe à la place le tableau entier. Donc, bien que cela aidera la requête en question (voir le plan d'explication), cela signifie que vous ne pouvez pas créer de contraintes uniques (facilement) sur des valeurs de tableau individuelles. Cela dit, si vous utilisez des tableaux d'entiers, vous pouvez utiliser le module contrib "intarray" pour indexer les valeurs des tableaux individuels, ce qui peut être beaucoup plus rapide dans de nombreux cas. (IIRC, des travaux sont en cours à ce sujet pour les valeurs de texte, mais les contributeurs seraient probablement les bienvenus pour aider à le terminer).
xzilla
15
Veuillez ne pas utiliser de lettres majuscules dans les identifiants PostgreSQL dans les exemples de code, cela déroute simplement les personnes qui ne sont pas familières avec les règles de citation / pliage de casse, en particulier les personnes novices dans PostgreSQL.
intégré
6
Pour répéter mon commentaire ici: d'après mon expérience, ces index offrent peu ou pas d'accélération à moins qu'ils ne gin__int_ops soient utilisés pour les integer[]colonnes. Il m'a fallu des années de frustration et de recherche d'autres solutions avant de découvrir cette classe d'opérations. C'est un faiseur de miracles à la limite.
IamIC
1
@IamIC cela signifie-t-il que je ne devrais pas prendre la peine d'indexer un tableau de chaînes? Et je ne devrais indexer que des tableaux d'entiers?
ryan2johnson9
93

@Tregoreg a soulevé une question dans le commentaire sur sa prime offerte:

Je n'ai pas trouvé les réponses actuelles fonctionnelles. L'utilisation de l'index GIN sur une colonne de type tableau n'augmente pas les performances de l'opérateur ANY (). N'y a-t-il vraiment pas de solution?

@ La réponse acceptée de Frank vous indique d'utiliser des opérateurs de tableau , ce qui est toujours correct pour Postgres 11. Le manuel:

... la distribution standard de PostgreSQL inclut une classe d'opérateurs GIN pour les tableaux, qui prend en charge les requêtes indexées à l'aide de ces opérateurs:

<@
@>
=
&&

La liste complète des classes d'opérateurs intégrées pour les index GIN dans la distribution standard se trouve ici.

Dans Postgres, les index sont liés à des opérateurs (qui sont implémentés pour certains types), pas à des types de données seuls ou à des fonctions ou autre. C'est un héritage de la conception Berkeley originale de Postgres et il est très difficile de changer maintenant. Et cela fonctionne généralement très bien. Voici un fil de discussion sur pgsql-bugs avec Tom Lane commentant cela.

Certaines fonctions PostGis (comme ST_DWithin()) semblent enfreindre ce principe, mais ce n'est pas le cas. Ces fonctions sont réécrites en interne pour utiliser les opérateurs respectifs .

L'expression indexée doit être à gauche de l'opérateur. Pour la plupart des opérateurs ( y compris tout ce qui précède ), le planificateur de requêtes peut y parvenir en inversant les opérandes si vous placez l'expression indexée à droite - étant donné que a COMMUTATORa été défini. La ANYconstruction peut être utilisée en combinaison avec divers opérateurs et n'est pas un opérateur en soi. Lorsqu'ils sont utilisés comme constant = ANY (array_expression)seuls index prenant en charge l' =opérateur sur les éléments du tableau, ils sont qualifiés et nous aurions besoin d'un commutateur pour = ANY(). Les index GIN sont sortis.

Postgres n'est actuellement pas assez intelligent pour en dériver une expression indexable GIN. Pour commencer, constant = ANY (array_expression)n'est pas complètement équivalent à array_expression @> ARRAY[constant]. Les opérateurs de tableau renvoient une erreur si des éléments NULL sont impliqués, tandis que la ANYconstruction peut traiter NULL de chaque côté. Et il existe différents résultats pour les incohérences de types de données.

Réponses connexes:

À part

Lorsque vous travaillez avec des integertableaux ( int4, pas int2ou int8) sans NULLvaleurs (comme votre exemple l'indique), considérez le module supplémentaire intarray, qui fournit des opérateurs spécialisés et plus rapides et un support d'index. Voir:

Quant à la UNIQUEcontrainte dans votre question qui est restée sans réponse: elle est implémentée avec un index btree sur toute la valeur du tableau (comme vous le soupçonniez) et n'aide pas du tout à la recherche d' éléments . Détails:

Erwin Brandstetter
la source
1
Aaaaaaah, je me sens assez gêné en ce moment, mais il ne m'est pas venu à l'esprit que postgres n'utiliserait pas l'index même si cela était théoriquement possible. C'est peut-être aussi à cause de mon manque de compréhension des postgres, comme le fait que les indices sont liés aux opérateurs. Merci d'avoir pris le temps de répondre à ma question mal posée et de partager vos connaissances!
Tregoreg
6
@Tregoreg: Ne soyez pas trop gêné, ce n'est vraiment pas trop évident. Je me souviens avoir été confus par cela moi-même lorsque je l'ai rencontré pour la première fois. La question et la clarification ajoutées devraient être très utiles au grand public.
Erwin Brandstetter
1
D'après mon expérience, ces index n'offrent que peu ou pas d'accélération, sauf s'ils gin__int_ops sont utilisés pour les integer[]colonnes. Il m'a fallu des années de frustration et de recherche d'autres solutions avant de découvrir cette classe d'opérations. C'est un faiseur de miracles à la limite.
IamIC
2
@IamIC: J'ai ajouté des pointeurs vers intarray. Cela semble intéressant, comme vous l'avez souligné.
Erwin Brandstetter
Pour les ANY (array_expression) = constantexpressions, les index GIN fonctionnent bien?
user10375
37

Il est maintenant possible d'indexer les éléments individuels du tableau. Par exemple:

CREATE TABLE test (foo int[]);
INSERT INTO test VALUES ('{1,2,3}');
INSERT INTO test VALUES ('{4,5,6}');
CREATE INDEX test_index on test ((foo[1]));
SET enable_seqscan TO off;

EXPLAIN ANALYZE SELECT * from test WHERE foo[1]=1;
                                                QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Index Scan using test_index on test  (cost=0.00..8.27 rows=1 width=32) (actual   time=0.070..0.071 rows=1 loops=1)
   Index Cond: (foo[1] = 1)
 Total runtime: 0.112 ms
(3 rows)

Cela fonctionne au moins sur Postgres 9.2.1. Notez que vous devez créer un index distinct pour chaque index de tableau, dans mon exemple, je n'ai indexé que le premier élément.

Ed4
la source
28
Ne le perdez pas - cette approche est sans espoir pour les tableaux de longueur variable où vous souhaitez utiliser l'opérateur ANY ().
Καrτhικ
24
Ce n'est vraiment pas très utile. Si vous avez un nombre fixe d'éléments de tableau, vous préférez utiliser des colonnes individuelles pour chaque élément (et des indices btree simples) au lieu de créer un index d'expression plus coûteux pour chaque élément de tableau. Le stockage de colonnes individuelles est également beaucoup moins coûteux sans surcharge de la baie.
Erwin Brandstetter