Come eseguire operazioni di aggiornamento su colonne di tipo JSONB in ​​Postgres 9.4


132

Esaminando la documentazione per il tipo di dati JSONB di Postgres 9.4, per me non è immediatamente ovvio come eseguire gli aggiornamenti sulle colonne JSONB.

Documentazione per tipi e funzioni JSONB:

http://www.postgresql.org/docs/9.4/static/functions-json.html http://www.postgresql.org/docs/9.4/static/datatype-json.html

Ad esempio, ho questa struttura di tabella di base:

CREATE TABLE test(id serial, data jsonb);

L'inserimento è semplice, come in:

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Ora, come dovrei aggiornare la colonna "dati"? Questa è sintassi non valida:

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

È documentato da qualche parte ovvio che mi sono perso? Grazie.

Risposte:


32

Idealmente, non si utilizzano documenti JSON per dati strutturati e regolari che si desidera manipolare all'interno di un database relazionale. Utilizzare invece un design relazionale normalizzato .

JSON ha principalmente lo scopo di archiviare interi documenti che non devono essere manipolati all'interno di RDBMS. Relazionato:

L'aggiornamento di una riga in Postgres scrive sempre una nuova versione dell'intera riga. Questo è il principio base di modello MVCC Postgres . Dal punto di vista delle prestazioni, non importa se si modifica un singolo pezzo di dati all'interno di un oggetto JSON o tutti: è necessario scrivere una nuova versione della riga.

Quindi il consiglio nel manuale :

I dati JSON sono soggetti alle stesse considerazioni sul controllo della concorrenza di qualsiasi altro tipo di dati se archiviati in una tabella. Sebbene sia possibile archiviare documenti di grandi dimensioni, tenere presente che qualsiasi aggiornamento acquisisce un blocco a livello di riga sull'intera riga. Prendi in considerazione la possibilità di limitare i documenti JSON a dimensioni gestibili per ridurre la contesa tra i blocchi durante l'aggiornamento delle transazioni. Idealmente, i documenti JSON dovrebbero rappresentare ciascuno un dato atomico che le regole aziendali dettano non possono ragionevolmente essere ulteriormente suddivisi in dati più piccoli che potrebbero essere modificati in modo indipendente.

L'essenza di esso: per modificare qualsiasi cosa all'interno di un oggetto JSON, è necessario assegnare un oggetto modificato alla colonna. Postgres fornisce mezzi limitati per costruire e manipolare jsondati oltre alle sue capacità di archiviazione. L'arsenale di strumenti è cresciuto sostanzialmente con ogni nuova versione dalla versione 9.2. Ma il principale rimane: devi sempre assegnare un oggetto modificato completo alla colonna e Postgres scrive sempre una nuova versione di riga per qualsiasi aggiornamento.

Alcune tecniche su come lavorare con gli strumenti di Postgres 9.3 o versioni successive:

Questa risposta ha attirato più downvotes di tutte le altre mie risposte su SO insieme . Le persone non sembrano apprezzare l'idea: un design normalizzato è superiore per i dati non dinamici. Questo eccellente post sul blog di Craig Ringer spiega in modo più dettagliato:


6
Questa risposta riguarda solo il tipo JSON e ignora JSONB.
fiatjaf,

7
@fiatjaf: questa risposta è pienamente applicabile ai tipi di dati jsone jsonbsimili. Entrambi archiviano i dati JSON, jsonblo fanno in una forma binaria normalizzata che presenta alcuni vantaggi (e alcuni svantaggi). stackoverflow.com/a/10560761/939860 Nessuno dei due tipi di dati è utile per manipolare molto all'interno del database. Nessun tipo di documento è. Bene, va bene per documenti JSON piccoli e poco strutturati. Ma i documenti grandi e nidificati sarebbero una follia in quel modo.
Erwin Brandstetter,

7
"Istruzioni su come lavorare con gli strumenti di Postgres 9.3" dovrebbe davvero essere il primo nella tua risposta poiché risponde alla domanda posta .. a volte ha senso aggiornare json per modifiche di manutenzione / schema ecc. E le ragioni per non aggiornare json don Non mi interessa
Michael Wasser,

22
Questa risposta non è utile, scusa. @jvous, non vuoi accettare la risposta di Jimothy, poiché risponde davvero alla tua domanda?
Bastian Voigt,

10
Rispondi alla domanda prima di aggiungere il tuo commento / opinione / discussione.
Ppp

331

Se riesci ad aggiornare a Postgresql 9.5, il jsonb_setcomando è disponibile, come altri hanno già detto.

In ognuna delle seguenti istruzioni SQL, ho omesso il where clausola per brevità; ovviamente, vorresti aggiungerlo di nuovo.

Nome aggiornamento:

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');

Sostituisci i tag (invece di aggiungere o rimuovere tag):

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');

Sostituzione del secondo tag (indicizzato 0):

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');

Aggiungi un tag ( funzionerà fintanto che ci sono meno di 999 tag; la modifica dell'argomento da 999 a 1000 o superiore genera un errore . Questo non sembra più essere il caso in Postgres 9.5.3; un indice molto più grande può essere utilizzato) :

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);

Rimuovi l'ultimo tag:

UPDATE test SET data = data #- '{tags,-1}'

Aggiornamento complesso (elimina l'ultimo tag, inserisci un nuovo tag e modifica il nome):

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');

È importante notare che in ciascuno di questi esempi, in realtà non si sta aggiornando un singolo campo dei dati JSON. Invece, stai creando una versione temporanea e modificata dei dati e assegnando quella versione modificata alla colonna. In pratica, il risultato dovrebbe essere lo stesso, ma tenendo presente ciò dovrebbe rendere più comprensibili gli aggiornamenti complessi, come l'ultimo esempio.

Nell'esempio complesso, ci sono tre trasformazioni e tre versioni temporanee: in primo luogo, l'ultimo tag viene rimosso. Quindi, quella versione viene trasformata aggiungendo un nuovo tag. Successivamente, la seconda versione viene trasformata modificando il namecampo. Il valore nella datacolonna viene sostituito con la versione finale.


42
ottieni punti bonus per mostrare come aggiornare una colonna in una tabella come richiesto dall'OP
chadrik

1
@chadrik: ho aggiunto un esempio più complesso. Non fa esattamente quello che hai richiesto, ma dovrebbe darti un'idea. Si noti che l'input per la jsonb_setchiamata esterna è l'output della chiamata interna e che l'input per quella chiamata interna è il risultato di data #- '{tags,-1}'. Vale a dire, i dati originali con l'ultimo tag rimosso.
Jimothy,

1
@PranaySoni: A tale scopo, probabilmente utilizzerei una procedura memorizzata o, se l'overhead non è un problema, riporto quei dati, li manipolerei nella lingua dell'applicazione, quindi li riscrivo. Sembra pesante, ma tieni presente, in tutti gli esempi che ho dato, non stai ancora aggiornando un singolo campo in JSON (B): stai sovrascrivendo l'intera colonna in entrambi i casi. Quindi un proc memorizzato non è davvero diverso.
Jimothy,

1
@Alex: Sì, un po 'un trucco. Se dicessi {tags,0}, ciò significherebbe "il primo elemento dell'array tags", permettendomi di dare un nuovo valore a quell'elemento. Utilizzando un numero elevato anziché 0, invece di sostituire un elemento esistente nell'array, aggiunge un nuovo elemento all'array. Tuttavia, se l'array avesse effettivamente più di 999.999.999 elementi, questo sostituirà l'ultimo elemento invece di aggiungerne uno nuovo.
Jimothy,

1
che dire se il campo contiene null? sembra non funzionare. Ad esempio, il campo info jsonb è nullo: "UPDATE organizzatore SET info = jsonb_set (info, '{country}', '" FRA "') dove info - >> 'country' :: text IS NULL;" Ottengo UPDATE 105 record ma nessuna modifica su db
stackdave


18

Per coloro che riscontrano questo problema e desiderano una soluzione molto rapida (e sono bloccati su 9.4.5 o precedenti), ecco cosa ho fatto:

Creazione della tabella di prova

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Istruzione di aggiornamento per modificare il nome della proprietà jsonb

UPDATE test 
SET data = replace(data::TEXT,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

In definitiva, la risposta accettata è corretta in quanto non è possibile modificare un singolo pezzo di un oggetto jsonb (in 9.4.5 o precedente); tuttavia, è possibile eseguire il cast dell'oggetto jsonb in una stringa (:: TEXT) e quindi manipolare la stringa e tornare all'oggetto jsonb (:: jsonb).

Ci sono due avvertimenti importanti

  1. questo sostituirà tutte le proprietà chiamate "name" nel json (nel caso in cui tu abbia più proprietà con lo stesso nome)
  2. questo non è efficiente come sarebbe jsonb_set se si utilizza 9.5

Detto questo, mi sono imbattuto in una situazione in cui ho dovuto aggiornare lo schema per il contenuto negli oggetti jsonb e questo era il modo più semplice per realizzare esattamente ciò che il poster originale stava chiedendo.


1
Buon Dio, ho cercato come fare un aggiornamento a jsonb per circa due ore in modo da poter sostituire tutti \u0000i caratteri null, l'esempio ha mostrato l'immagine completa. Grazie per questo!
Joshua Robinson,

3
sembra buono! tra l'altro il secondo argomento da sostituire nel tuo esempio include i due punti e il terzo no. Sembra che la tua chiamata dovrebbe esserereplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus

Grazie @davidicus! Ci scusiamo per l'aggiornamento molto ritardato, ma apprezzo che tu abbia condiviso per gli altri!
Chad Capra,

12

Questa domanda è stata posta nel contesto di Postgres 9.4, tuttavia i nuovi spettatori che giungono a questa domanda dovrebbero essere consapevoli che in Postgres 9.5, le operazioni di creazione / aggiornamento / eliminazione di documenti secondari sui campi JSONB sono supportate nativamente dal database, senza necessità di estensione funzioni.

Vedi: JSONB che modifica operatori e funzioni


7

aggiorna l'attributo 'nome':

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;

e se volessi rimuovere ad esempio gli attributi 'name' e 'tags':

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;

5

Ho scritto una piccola funzione per me che funziona in modo ricorsivo in Postgres 9.4. Ho avuto lo stesso problema (bene hanno risolto un po 'di questo mal di testa in Postgres 9.5). Comunque qui è la funzione (spero che funzioni bene per te):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

Ecco un esempio di utilizzo:

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

Come puoi vedere, analizza in profondità e aggiorna / aggiungi valori dove necessario.


Questo non funziona in 9.4, perché è jsonb_build_objectstato introdotto in 9.5
Greg

@Greg Hai ragione, ho appena controllato e sto eseguendo PostgreSQL 9.5 ora - ecco perché funziona. Grazie per averlo sottolineato - la mia soluzione non funzionerà in 9.4.
J. Raczkiewicz,

4

Forse: UPDATE test SET data = '"my-other-name"' :: json WHERE id = 1;

Ha funzionato con il mio caso, in cui i dati sono di tipo json


1
Ha funzionato anche per me, su postgresql 9.4.5. L'intero record viene riscritto in modo da non poter aggiornare un singolo campo atm.
kometen,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.