web-dev-qa-db-fra.com

Distinct sur la colonne de données JSON Postgresql

Essayer de faire distinct sur un mode avec Rails.

2.1.1 :450 > u.profiles.select("profiles.*").distinct


Profile Load (0.9ms)  SELECT DISTINCT profiles.* FROM "profiles" INNER JOIN "integration_profiles" ON "profiles"."id" = "integration_profiles"."profile_id" INNER JOIN "integrations" ON "integration_profiles"."integration_id" = "integrations"."id" WHERE "integrations"."user_id" = $1  [["user_id", 2]]
PG::UndefinedFunction: ERROR:  could not identify an equality operator for type json
LINE 1: SELECT DISTINCT profiles.* FROM "profiles" INNER JOIN "integ...
                        ^
: SELECT DISTINCT profiles.* FROM "profiles" INNER JOIN "integration_profiles" ON "profiles"."id" = "integration_profiles"."profile_id" INNER JOIN "integrations" ON "integration_profiles"."integration_id" = "integrations"."id" WHERE "integrations"."user_id" = $1
ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR:  could not identify an equality operator for type json
LINE 1: SELECT DISTINCT profiles.* FROM "profiles" INNER JOIN "integ...
                        ^
: SELECT DISTINCT profiles.* FROM "profiles" INNER JOIN "integration_profiles" ON "profiles"."id" = "integration_profiles"."profile_id" INNER JOIN "integrations" ON "integration_profiles"."integration_id" = "integrations"."id" WHERE "integrations"."user_id" = $1
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/rack-mini-profiler-0.9.1/lib/patches/sql_patches.rb:109:in `prepare'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/rack-mini-profiler-0.9.1/lib/patches/sql_patches.rb:109:in `prepare'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:834:in `prepare_statement'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:795:in `exec_cache'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:139:in `block in exec_query'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/abstract_adapter.rb:442:in `block in log'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activesupport-4.0.4/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/abstract_adapter.rb:437:in `log'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:137:in `exec_query'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:908:in `select'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/abstract/database_statements.rb:32:in `select_all'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/connection_adapters/abstract/query_cache.rb:63:in `select_all'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/querying.rb:36:in `find_by_sql'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/relation.rb:585:in `exec_queries'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/association_relation.rb:15:in `exec_queries'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/relation.rb:471:in `load'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/relation.rb:220:in `to_a'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/activerecord-4.0.4/lib/active_record/relation.rb:573:in `inspect'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/railties-4.0.4/lib/Rails/commands/console.rb:90:in `start'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/railties-4.0.4/lib/Rails/commands/console.rb:9:in `start'
    from /Users/mmahalwy/.rvm/gems/Ruby-2.1.1/gems/railties-4.0.4/lib/Rails/commands.rb:62:in `<top (required)>'
    from bin/Rails:4:in `require'
    from bin/Rails:4:in `<main>'2.1.1 :451 > 

Obtenir une erreur PG::UndefinedFunction: ERROR: could not identify an equality operator for type json

La conversion en Hstore n'est pas une option pour moi dans ce cas. Des contournements?

38

La raison derrière cela est que dans PostgreSQL (jusqu'à 9.3) il n'y a pas d'opérateur d'égalité défini pour json (ie val1::json = val2::json Lèvera toujours cette exception) - dans 9.4 il y en aura un pour le type jsonb.

Une solution de contournement est que vous pouvez convertir votre champ json en text. Mais cela ne couvrira pas toutes les égalités json. f.ex. {"a":1,"b":2} Doit être égal à {"b":2,"a":1}, Mais ne sera pas égal s'il est casté en text.

Une autre solution de contournement est (si vous avez une clé primaire pour cette table - qui devrait être), vous pouvez utiliser la forme DISTINCT ON (<expressions>) :

u.profiles.select("DISTINCT ON (profiles.id) profiles.*")

Remarque : Une mise en garde connue pour DISTINCT ON:

Les expressions DISTINCT ON doivent correspondre aux expressions ORDER BY les plus à gauche. La clause ORDER BY contiendra normalement des expressions supplémentaires qui déterminent la priorité souhaitée des lignes dans chaque groupe DISTINCT ON.

54
pozs

Désolé, je suis en retard sur cette réponse, mais cela pourrait aider les autres.

Si je comprends bien votre requête, vous obtenez uniquement des doublons possibles sur profiles en raison de la jointure plusieurs-à-plusieurs vers integrations (que vous utilisez pour déterminer quelle profiles accéder).

Pour cette raison, vous pouvez utiliser un nouveau GROUP BY fonction à partir de la 9.1 :

Lorsque GROUP BY est présent, il n'est pas valide que les expressions de la liste SELECT se réfèrent aux colonnes non groupées sauf dans les fonctions d'agrégation ou si la colonne non groupée dépend fonctionnellement des colonnes groupées, car il y aurait autrement être plus d'une valeur possible à renvoyer pour une colonne non groupée. Une dépendance fonctionnelle existe si les colonnes groupées (ou un sous-ensemble de celles-ci) sont la clé primaire de la table contenant la colonne non groupée.

Donc, dans votre cas, vous pourriez obtenir Ruby pour créer la requête (désolé, je ne connais pas la Ruby syntaxe que vous utilisez) ...

SELECT profiles.* 
FROM "profiles" 
  INNER JOIN "integration_profiles" ON "profiles"."id" = "integration_profiles"."profile_id" 
  INNER JOIN "integrations" ON "integration_profiles"."integration_id" = "integrations"."id" 
WHERE "integrations"."user_id" = $1
GROUP BY "profiles"."id"

J'ai uniquement supprimé la DISTINCT de votre clause SELECT et ajouté la GROUP BY.

En se référant UNIQUEMENT au id dans le GROUP BY, vous profitez de cette nouvelle fonctionnalité car toutes les colonnes profiles restantes sont "fonctionnellement dépendantes" de cette clé primaire id.

D'une manière ou d'une autre, cela évite à merveille la nécessité pour Postgres de faire des vérifications d'égalité sur les colonnes dépendantes (c'est-à-dire votre colonne json dans ce cas).

Le DISTINCT ON la solution est également excellente, et clairement suffisante dans votre cas, mais vous ne pouvez pas utiliser des fonctions d'agrégation comme array_agg avec ça. Vous POUVEZ avec ce GROUP BY approche. Jours heureux! :)

6
poshest

Si vous utilisez PG 9.4, utiliser JSONB plutôt que JSON résout ce problème. Exemple:

-- JSON datatype test 

create table t1 (id int, val json);
insert into t1 (id,val) values (1,'{"name":"value"}');
insert into t1 (id,val) values (1,'{"name":"value"}');
insert into t1 (id,val) values (2,'{"key":"value"}');
select * from t1 order by id;
select distinct * from t1 order by id;

-- JSONB datatype test 

create table t2 (id int, val jsonb);
insert into t2 (id,val) values (1,'{"name":"value"}');
insert into t2 (id,val) values (1,'{"name":"value"}');
insert into t2 (id,val) values (2,'{"key":"value"}');

select * from t2 order by id;

select distinct * from t2 order by id;

Result of running the above script :

CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
1 | {"name":"value"}
1 | {"name":"value"}
2 | {"key":"value"}

ERROR:  could not identify an equality operator for type json
LINE 1: select distinct * from t1 order by id;
                    ^
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
1 | {"name": "value"}
1 | {"name": "value"}
2 | {"key": "value"}

1 | {"name": "value"}
2 | {"key": "value"}

Comme vous pouvez le voir, PG a réussi à impliquer DISTINCT sur une colonne JSONB alors qu'il échoue sur une colonne JSON!

Essayez également ce qui suit pour voir que les clés du JSONB sont triées:

insert into t2 values (3, '{"a":"1", "b":"2"}');
insert into t2 values (3, '{"b":"2", "a":"1"}');
select * from t2;

1 | {"name": "value"}
1 | {"name": "value"}
2 | {"key": "value"}
3 | {"a": "1", "b": "2"}
3 | {"a": "1", "b": "2"}

notez que '{"b": "2", "a": "1"}' a été inséré comme '{"a": "1", "b": "2"}' donc PG identifie cela comme étant le même record :

select distinct * from t2;
3 | {"a": "1", "b": "2"}
2 | {"key": "value"}
1 | {"name": "value"}
3
emesika

Oui, malheureusement, postgres json n'implémente pas l'égalité, mais jsonb le fait. Donc, migrez les colonnes json vers jsonb et cela devrait fonctionner correctement.

0
bright