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

Casting del tipo NULL durante l'aggiornamento di più righe

Con un VALUES autonomo espressione PostgreSQL non ha idea di quali dovrebbero essere i tipi di dati. Con semplici letterali numerici il sistema è felice di assumere tipi corrispondenti. Ma con altri input (come NULL ) dovresti eseguire il cast in modo esplicito, come hai già scoperto.

Puoi interrogare pg_catalog (veloce, ma specifico per PostgreSQL) o lo information_schema (SQL lento, ma standard) per scoprire e preparare la tua istruzione con i tipi appropriati.

Oppure puoi usare uno di questi semplici "trucchi" (ho salvato il meglio per ultimo ):

0. Seleziona la riga con LIMIT 0 , aggiungi le righe con UNION ALL VALUES

UPDATE foo f
SET    x = t.x
     , y = t.y
FROM  (
  (SELECT pkid, x, y FROM foo LIMIT 0) -- parenthesis needed with LIMIT
   UNION ALL
   VALUES
      (1, 20, NULL)  -- no type casts here
    , (2, 50, NULL)
   ) t               -- column names and types are already defined
WHERE  f.pkid = t.pkid;

La prima sottoselezione della sottoquery:

(SELECT x, y, pkid  FROM foo LIMIT 0)

ottiene nomi e tipi per le colonne, ma LIMIT 0 impedisce di aggiungere una riga effettiva. Le righe successive vengono forzate al tipo di riga ora ben definito e vengono immediatamente verificate se corrispondono al tipo. Dovrebbe essere un sottile miglioramento aggiuntivo rispetto al tuo modulo originale.

Pur fornendo valori per tutti colonne della tabella questa breve sintassi può essere utilizzata per la prima riga:

(TABLE foo LIMIT 0)

Limitazione maggiore :Postgres esegue il cast dei valori letterali di input dei VALUES indipendenti immediatamente un'espressione a un tipo "best-effort". Quando in seguito tenta di eseguire il cast ai tipi specificati del primo SELECT , potrebbe essere già troppo tardi per alcuni tipi se non è presente alcun cast di assegnazione registrato tra il tipo presunto e il tipo di destinazione. Esempi:text -> timestamp o text -> json .

Professionista:

  • Spese generali minime.
  • Leggibile, semplice e veloce.
  • Devi solo conoscere i nomi delle colonne pertinenti della tabella.

Con:

  • La risoluzione del tipo può non riuscire per alcuni tipi.

1. Seleziona la riga con LIMIT 0 , aggiungi le righe con UNION ALL SELECT

UPDATE foo f
SET    x = t.x
     , y = t.y
FROM  (
  (SELECT pkid, x, y FROM foo LIMIT 0) -- parenthesis needed with LIMIT
   UNION ALL SELECT 1, 20, NULL
   UNION ALL SELECT 2, 50, NULL
   ) t               -- column names and types are already defined
WHERE  f.pkid = t.pkid;

Professionista:

  • Mi piace 0. , ma evita che la risoluzione dei tipi non vada a buon fine.

Con:

  • UNION ALL SELECT è più lento di VALUES espressione per lunghi elenchi di righe, come hai trovato nel test.
  • Sintassi dettagliata per riga.

2. VALUES espressione con tipo per colonna

...
FROM  (
   VALUES 
     ((SELECT pkid FROM foo LIMIT 0)
    , (SELECT x    FROM foo LIMIT 0)
    , (SELECT y    FROM foo LIMIT 0))  -- get type for each col individually
   , (1, 20, NULL)
   , (2, 50, NULL)
   ) t (pkid, x, y)  -- columns names not defined yet, only types.
...

Contrariamente a 0. questo evita una risoluzione prematura del tipo.

La prima riga in VALUES espressione è una riga di NULL valori che definisce il tipo per tutte le righe successive. Questa riga di disturbo iniziale viene filtrata da WHERE f.pkid = t.pkid più tardi, così non vede mai la luce del giorno. Per altri scopi puoi eliminare la prima riga aggiunta con OFFSET 1 in una sottoquery.

Professionista:

  • In genere più veloce di 1. (o anche 0. )
  • Sintassi breve per tabelle con molte colonne e solo poche sono rilevanti.
  • Devi solo conoscere i nomi delle colonne pertinenti della tabella.

Con:

  • Sintassi dettagliata solo per poche righe
  • Meno leggibile (IMO).

3. VALUES espressione con tipo riga

UPDATE foo f
SET x = (t.r).x         -- parenthesis needed to make syntax unambiguous
  , y = (t.r).y
FROM (
   VALUES
      ('(1,20,)'::foo)  -- columns need to be in default order of table
     ,('(2,50,)')       -- nothing after the last comma for NULL
   ) t (r)              -- column name for row type
WHERE  f.pkid = (t.r).pkid;

Ovviamente conosci il nome del tavolo. Se conosci anche il numero di colonne e il loro ordine, puoi lavorare con questo.

Per ogni tabella in PostgreSQL viene registrato automaticamente un tipo di riga. Se abbini il numero di colonne nella tua espressione, puoi eseguire il cast al tipo di riga della tabella ('(1,50,)'::foo ) assegnando così i tipi di colonna in modo implicito. Non inserire nulla dietro una virgola per inserire un NULL valore. Aggiungi una virgola per ogni colonna finale irrilevante.
Nel passaggio successivo puoi accedere alle singole colonne con la sintassi dimostrata. Ulteriori informazioni sulla Selezione del campo nel manuale.

Oppure potresti aggiungere una riga di valori NULL e usa una sintassi uniforme per i dati effettivi:

...
  VALUES
      ((NULL::foo))  -- row of NULL values
    , ('(1,20,)')    -- uniform ROW value syntax for all
    , ('(2,50,)')
...

Professionista:

  • Il più veloce (almeno nei miei test con poche righe e colonne).
  • Sintassi più breve per poche righe o tabelle in cui sono necessarie tutte le colonne.
  • Non è necessario precisare le colonne della tabella:tutte le colonne hanno automaticamente il nome corrispondente.

Con:

  • Sintassi non molto nota per la selezione dei campi da record/riga/tipo composito.
  • Devi conoscere il numero e la posizione delle colonne pertinenti nell'ordine predefinito.

4. VALUES espressione con scomposto tipo di riga

Come 3. , ma con righe scomposte nella sintassi standard:

UPDATE foo f
SET    x = t.x
     , y = t.y
FROM (
   VALUES
      (('(1,20,)'::foo).*)  -- decomposed row of values
    , (2, 50, NULL)
   ) t(pkid, x, y)  -- arbitrary column names (I made them match)
WHERE  f.pkid = t.pkid;     -- eliminates 1st row with NULL values

Oppure, di nuovo con una riga iniziale di valori NULL:

...
   VALUES
      ((NULL::foo).*)  -- row of NULL values
    , (1, 20, NULL)    -- uniform syntax for all
    , (2, 50, NULL)
...

Pro e contro come 3. , ma con una sintassi più comunemente nota.
E devi scrivere i nomi delle colonne (se ne hai bisogno).

5. VALUES espressione con tipi recuperati dal tipo di riga

Come ha commentato Unril, possiamo combinare le virtù di 2. e 4. per fornire solo un sottoinsieme di colonne:

UPDATE foo f
SET   (  x,   y)
    = (t.x, t.y)  -- short notation, see below
FROM (
   VALUES
      ((NULL::foo).pkid, (NULL::foo).x, (NULL::foo).y)  -- subset of columns
    , (1, 20, NULL)
    , (2, 50, NULL)
   ) t(pkid, x, y)       -- arbitrary column names (I made them match)
WHERE  f.pkid = t.pkid;

Pro e contro come 4. , ma possiamo lavorare con qualsiasi sottoinsieme di colonne e non è necessario conoscere l'elenco completo.

Visualizza anche una breve sintassi per UPDATE stesso che è conveniente per i casi con molte colonne. Correlati:

  • Aggiornamento collettivo di tutte le colonne

4. e 5. sono i miei preferiti.

db<>gioca qui - dimostrando tutto