PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Verificare se la chiave esiste in un JSON con PL/pgSQL?

Hai già scoperto che puoi testare l'espressione user_info->>'username' per NULLA. Ma la tua funzione è ancora molto inefficiente . E ci sono ancora ambiguità .

Soluzione migliore in Postgres 9.3

È costoso aggiornare ripetutamente una riga per più colonne. Postgres scrive una nuova versione di riga per ogni aggiornamento. Usa un singolo UPDATE se possibile:

CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info json)
  RETURNS json AS
$func$
BEGIN
   UPDATE users u
   SET    firstname = COALESCE(_user_info->>'firstname', u.firstname)
        , lastname  = COALESCE(_user_info->>'lastname' , u.lastname)
   WHERE  id = sp_update_user._user_id
   AND   ((_user_info->>'firstname') IS NOT NULL OR
          (_user_info->>'lastname')  IS NOT NULL);

   IF FOUND THEN
      RETURN '{"success":true}'::json;
   ELSE
      RETURN '{"success":false}'::json;
   END IF;
END
$func$  LANGUAGE plpgsql;

Chiama:

SELECT sp_update_user(123, '{"firstname": "jon", "lastname": "doe"}')
  • Questo è sostanzialmente più veloce per più colonne, poiché solo un singolo UPDATE (al massimo) viene eseguito. Se il WHERE la clausola non restituisce true , non viene eseguito alcun aggiornamento e ottieni '{"success":false}' come risultato.

  • Se a volte i valori nella tabella sono già quelli in cui vengono modificati, è possibile un'altra ottimizzazione. Considera l'ultimo paragrafo di questa risposta correlata:

  • La variabile/parametro user_id manca nell'originale.

  • C'è ancora un caso d'angolo ambiguità . Se l'elemento esiste ed è impostato su JSON null , ottieni anche un SQL NULL come risultato. Considera:

    SELECT ('{"b": null}'::json->>'b') IS NULL AS b_is_null
         , ('{"c": 2}'::json->>'b')    IS NULL AS b_missing;
    
  • Non sono sicuro del motivo per cui utilizzi il tipo di dati json come tipo di ritorno, l'ho appena tenuto. Ma se la funzione non si aggiorna, non puoi essere sicuro del motivo per cui ricevi false . Potrebbe non esserci alcuna riga con l'id specificato , la chiave nomina 'firstname' e 'lastname' potrebbe essere mancante o essere null ...


Soluzione superiore in Postgres 9.4

C'è un pulito e una soluzione semplice in Postgres 9.4 con jsonb con ? operatore di "esistenza" - che può anche utilizzare un indice per tabelle più grandi (non rilevante nella tua funzione):

SELECT ('{"b": null}'::jsonb ? 'b') AS b_is_null
     , ('{"c": 2}'::jsonb ? 'b')    AS b_missing;

E il ?| e ?& varianti per verificare la presenza di più chiavi contemporaneamente.
Quindi possiamo implementare:

CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info jsonb)
  RETURNS jsonb AS
$func$
BEGIN
   UPDATE users u
   SET    firstname = CASE WHEN _user_info ? 'firstname' THEN _user_info->>'firstname' ELSE u.firstname END
        , lastname  = CASE WHEN _user_info ? 'lastname'  THEN _user_info->>'lastname'  ELSE u.lastname  END
   WHERE  id = sp_update_user._user_id
   AND    _user_info ?| '{firstname,lastname}';

   IF FOUND THEN
      RETURN '{"success":true}'::jsonb;
   ELSE
      RETURN '{"success":false}'::jsonb;
   END IF;
END
$func$  LANGUAGE plpgsql;

Queste chiamate funzionano come previsto ora:

SELECT sp_update_user(123, '{"firstname": null, "lastname": "doe1"}'::jsonb);
SELECT sp_update_user(123, '{"firstname": "doris"}'::jsonb);