sql >> Databasteknik >  >> RDS >> PostgreSQL

Castar NULL-typ vid uppdatering av flera rader

Med en fristående VALUES expression PostgreSQL har ingen aning om vilka datatyper som ska vara. Med enkla numeriska bokstaver antar systemet gärna matchningstyper. Men med annan input (som NULL ) du skulle behöva casta uttryckligen - som du redan har fått reda på.

Du kan fråga pg_catalog (snabb, men PostgreSQL-specifik) eller information_schema (långsam, men standard SQL) för att ta reda på och förbereda ditt uttalande med lämpliga typer.

Eller så kan du använda ett av dessa enkla "tricks" (jag sparade det bästa till sista ):

0. Välj rad med LIMIT 0 , lägg till rader med 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;

Det första undervalet av underfrågan:

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

får namn och typer för kolumnerna, men LIMIT 0 hindrar den från att lägga till en faktisk rad. Efterföljande rader tvingas till den nu väldefinierade radtypen - och kontrolleras omedelbart om de matchar typen. Bör vara en subtil ytterligare förbättring jämfört med din ursprungliga form.

Medan du tillhandahåller värden för alla kolumner i tabellen denna korta syntax kan användas för den första raden:

(TABLE foo LIMIT 0)

Stor begränsning :Postgres kastar ingångsliteralerna för de fristående VALUES uttryck till en "bästa ansträngning"-typ omedelbart. När den senare försöker casta till de givna typerna av den första SELECT , kan det redan vara för sent för vissa typer om det inte finns någon registrerad tilldelning mellan den antagna typen och måltypen. Exempel:text -> timestamp eller text -> json .

Proffs:

  • Minsta omkostnader.
  • Läsbart, enkelt och snabbt.
  • Du behöver bara känna till relevanta kolumnnamn för tabellen.

Kon:

  • Typupplösning kan misslyckas för vissa typer.

1. Välj rad med LIMIT 0 , lägg till rader med 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;

Proffs:

  • Gilla 0. , men undviker felaktig typupplösning.

Kon:

  • UNION ALL SELECT är långsammare än VALUES uttryck för långa listor med rader, som du hittade i ditt test.
  • Verbosed syntax per rad.

2. VALUES uttryck med typ per kolumn

...
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.
...

I motsats till 0. detta undviker för tidig typupplösning.

Den första raden i VALUES uttryck är en rad med NULL värden som definierar typen för alla efterföljande rader. Denna ledande brusrad filtreras av WHERE f.pkid = t.pkid senare, så den ser aldrig dagens ljus. För andra ändamål kan du eliminera den första raden som lagts till med OFFSET 1 i en underfråga.

Proffs:

  • Vanligtvis snabbare än 1. (eller till och med 0. )
  • Kort syntax för tabeller med många kolumner och endast få är relevanta.
  • Du behöver bara känna till relevanta kolumnnamn för tabellen.

Kon:

  • Verbosed syntax för endast några rader
  • Mindre läsbar (IMO).

3. VALUES uttryck med radtyp

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;

Du känner uppenbarligen till bordets namn. Om du också känner till antalet kolumner och deras ordning kan du arbeta med detta.

För varje tabell i PostgreSQL registreras en radtyp automatiskt. Om du matchar antalet kolumner i ditt uttryck kan du casta till tabellens radtyp ('(1,50,)'::foo ) och tilldelar därigenom kolumntyper implicit. Sätt ingenting bakom ett kommatecken för att ange en NULL värde. Lägg till ett kommatecken för varje irrelevant avslutande kolumn.
I nästa steg kan du komma åt enskilda kolumner med den visade syntaxen. Mer om Fältval i manualen.

Eller så kan du lägga till en rad med NULL-värden och använd enhetlig syntax för faktiska data:

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

Proffs:

  • Snabbast (åtminstone i mina tester med få rader och kolumner).
  • Kortaste syntax för några rader eller tabeller där du behöver alla kolumner.
  • Du behöver inte stava ut kolumner i tabellen - alla kolumner har automatiskt det matchande namnet.

Kon:

  • Inte så välkänd syntax för fältval från post / rad / sammansatt typ.
  • Du måste veta antalet och positionen för relevanta kolumner i standardordning.

4. VALUES uttryck med nedbrutet radtyp

Gilla 3. , men med nedbrutna rader i standardsyntax:

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

Eller, med en ledande rad med NULL-värden igen:

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

För- och nackdelar som 3. , men med mer allmänt känd syntax.
Och du måste stava kolumnnamn (om du behöver dem).

5. VALUES uttryck med typer hämtade från radtyp

Som Unril kommenterade kan vi kombinera fördelarna med 2. och 4. för att endast tillhandahålla en undergrupp av kolumner:

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;

För- och nackdelar som 4. , men vi kan arbeta med vilken delmängd som helst av kolumner och behöver inte känna till hela listan.

Visar också kort syntax för UPDATE i sig som är bekvämt för fall med många kolumner. Relaterat:

  • Massuppdatering av alla kolumner

4. och 5. är mina favoriter.

db<>spela här - demonstrera alla



  1. Programgranskning – Stellar Repair för MS SQL

  2. Hur får man ut antalet dagars skillnad mellan två datum på MySQL?

  3. PHP, ORM, MSSQL och Unicode, är det möjligt att få dessa att fungera tillsammans?

  4. Kan jag automatiskt skapa en tabell i PostgreSQL från en csv-fil med rubriker?