web-dev-qa-db-fra.com

Importer des données json postgres dans un fichier csv

J'essaie d'importer dans postgres un fichier csv contenant les données d'une table. L'une des colonnes du tableau est de type jsonb.

Une ligne de mon fichier csv contient quelque chose comme

1,{"a":"b"}

Supposons que la table ait un schéma

id              | smallint          | 
data            | jsonb             | 

Si j'essaye juste d'insérer les données, tout fonctionne bien

INSERT INTO table VALUES (1, '{"a":"b"}');

Essayer d'importer directement à partir du fichier avec

COPY table FROM '/path/to/file.csv' DELIMITER ',' csv;

me donne l'erreur suivante:

ERROR:  invalid input syntax for type json
DETAIL:  Token "a" is invalid.
CONTEXT:  JSON data, line 1: {a...
COPY availability, line 1, column services: "{a: b}"

J'ai essayé de citer les champs avec ', avec ", avec \" et \', mais rien ne fonctionne ..

Quelle est la syntaxe correcte pour le faire?

7
marcosh

La commande PostgreSQL COPY est rarement idéale, mais elle fonctionne souvent. Pour référence, il existe de meilleures méthodes pour comprendre cela que de deviner.

CREATE TEMP TABLE baz AS
  SELECT 1::int, '{"a":"b"}'::jsonb;

Ce sont vos données d'exemple exactes. Maintenant, nous pouvons tester différents paramètres ..

# COPY baz TO STDOUT;
1   {"a": "b"}

COPY baz TO STDOUT DELIMITER ',';
1,{"a": "b"}

Vous verrez que ce qui précède génère les données exactes de votre questionnement ...

COPY baz TO '/tmp/data.csv' DELIMITER ',';

Il n'y a pas de problème. Du moins pas avec PostgreSQL 9.5.

Mode CSV

Alors, où est votre problème, c'est avec le mode CSV. Observer,

# COPY baz TO STDOUT;
1   {"a": "b"}
# COPY baz TO STDOUT CSV;
1,"{""a"": ""b""}"

Vous pouvez voir que ces deux sont différents maintenant. Essayons de charger le fichier non CSV en mode CSV qui prend le format généré par le mode CSV ci-dessus.

TRUNCATE baz;
COPY baz FROM '/tmp/data.csv' DELIMITER ',' CSV;
ERROR:  invalid input syntax for type json
DETAIL:  Token "a" is invalid.
CONTEXT:  JSON data, line 1: {a...
COPY baz, line 1, column jsonb: "{a: b}"

Maintenant, nous faisons une erreur. La raison de cela vient de RFC 418

Chaque champ peut ou non être placé entre guillemets doubles (cependant certains programmes, tels que Microsoft Excel, n'utilisent pas du tout de guillemets doubles). Si les champs ne sont pas entourés de guillemets doubles, les guillemets doubles peuvent ne pas apparaître à l'intérieur des champs.

  1. Ainsi JSON RFC 4627 spécifie noms dans les paires nom/valeur doivent être des chaînes qui nécessitent des guillemets doubles.
  2. Et CSV RFC 418 spécifie que si des guillemets doubles se trouvent à l'intérieur du champ, alors le champ entier doit être cité.

À ce stade, vous avez deux options ..

  1. N'utilisez pas le mode CSV.
  2. Ou, échappez aux citations intérieures.

Il s'agirait donc d'entrées valides sous les mêmes options en mode CSV.

#COPY baz TO STDOUT DELIMITER ',' CSV ESCAPE E'\\';
1,"{\"a\": \"b\"}"

# COPY baz TO STDOUT DELIMITER ',' CSV;
1,"{""a"": ""b""}"
5
Evan Carroll

Trouvé la solution, postgres utilise " comme caractère d'échappement, donc le format correct doit être

{"""a""": """b"""}
3
marcosh

Comme indiqué dans d'autres réponses, le CSV et le JSON spécifications (et probablement postgresql spécifications) sont quelque peu incompatibles. Pour les amener à arrêter de se battre, au moins dans leurs formes simples, vous devez échapper aux choses jusqu'à ce qu'elles soient un gâchis illisible. N'utilise pas CSV le mode est encore pire que le COPIE mourra sur tout ce qui JSON a un problème avec: les nouvelles lignes, les barres obliques inversées ou les guillemets intégrés.

J'ai eu exactement le même problème qu'avec une entrée beaucoup plus complexe: un tas de ENUM types, entiers et complexes JSON des champs. En être témoin:

create table messages( a blab, b integer, c text, d json, e json);

où le typique JSONs serait {"default":"little","sms":"bigger"} et ["name","number"]. Essayez d'importer une pile de ceux avec un COPIE commande: si les virgules à l'intérieur du JSON ne vous obtenez pas les guillemets! Je passe des heures là-dessus jusqu'à ce que je trouve ce bel article de blog qui a souligné les causes du problème et les options dont vous avez besoin pour en sortir.

Fondamentalement, vous devez changer le délimiteur et les champs de devis en quelque chose que vous pouvez garantir ne sera pas dans votre JSON Les données. Dans mon cas, je peux garantir beaucoup pour pouvoir

COPY messages( a, b, c, d, e) from stdin  csv quote '^' delimiter '|';
malfunction|5|La la la|{"default":"little","sms":"bigger"}|["name","number"]

Agréable et lisible, facile d'accès avec une substitution de caractères mineurs dans votre gestionnaire de texte préféré, et sans échapper à rien! Si vous ne pouvez pas garantir que les caractères utilisés ci-dessus ne peuvent pas être dans votre JSON alors vous pouvez utiliser le _ plutôt farfelu e'\x01' et e'\x02' comme JSON spec les juge totalement illégaux. Pas tout à fait aussi lisible et ainsi de suite mais ponctuellement correct.

Notez que les nouvelles lignes intégrées, comme certains JSON générateurs ont tendance à émettre à des fins de lisibilité, sont toujours "non-non", vous devez donc les filtrer hors de votre JSON.

1
Nadreck