sql >> Database >  >> RDS >> PostgreSQL

NULL-type casten bij het bijwerken van meerdere rijen

Met een standalone VALUES expressie PostgreSQL heeft geen idee wat de datatypes zouden moeten zijn. Met eenvoudige numerieke letterlijke waarden neemt het systeem graag overeenkomende typen aan. Maar met andere invoer (zoals NULL ) zou je expliciet moeten casten - zoals je al hebt ontdekt.

U kunt pg_catalog . opvragen (snel, maar PostgreSQL-specifiek) of het information_schema (trage, maar standaard SQL) om uw verklaring te vinden en voor te bereiden met de juiste typen.

Of je kunt een van deze eenvoudige "trucs" gebruiken (ik heb het beste voor laatste bewaard ):

0. Selecteer rij met LIMIT 0 , voeg rijen toe met 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;

De eerste subselectie van de subquery:

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

krijgt namen en typen voor de kolommen, maar LIMIT 0 voorkomt dat het een echte rij toevoegt. Volgende rijen worden gedwongen tot het nu goed gedefinieerde rijtype - en er wordt onmiddellijk gecontroleerd of ze overeenkomen met het type. Zou een subtiele extra verbetering moeten zijn ten opzichte van je oorspronkelijke formulier.

Terwijl het verstrekken van waarden voor allen kolommen van de tabel deze korte syntaxis kan worden gebruikt voor de eerste rij:

(TABLE foo LIMIT 0)

Grote beperking :Postgres cast de letterlijke invoer van de vrijstaande VALUES uitdrukking onmiddellijk naar een "best-effort"-type. Wanneer het later probeert te casten naar de gegeven typen van de eerste SELECT , kan het voor sommige typen al te laat zijn als er geen geregistreerde toewijzing is gegoten tussen het veronderstelde type en het doeltype. Voorbeelden:text -> timestamp of text -> json .

Pro:

  • Minimale overhead.
  • Leesbaar, eenvoudig en snel.
  • U hoeft alleen de relevante kolomnamen van de tabel te kennen.

Min:

  • Typeresolutie kan voor sommige typen mislukken.

1. Selecteer rij met LIMIT 0 , voeg rijen toe met 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;

Pro:

  • Vind ik leuk 0. , maar vermijdt falende typeresolutie.

Min:

  • UNION ALL SELECT is langzamer dan VALUES uitdrukking voor lange lijsten met rijen, zoals je in je test hebt gevonden.
  • Uitgebreide syntaxis per rij.

2. VALUES expressie met per-kolomtype

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

In tegenstelling tot 0. dit voorkomt voortijdige typeresolutie.

De eerste rij in de VALUES expressie is een rij van NULL waarden die het type voor alle volgende rijen definiëren. Deze rij met voorloopruis wordt gefilterd door WHERE f.pkid = t.pkid later, zodat het nooit het daglicht ziet. Voor andere doeleinden kunt u de toegevoegde eerste rij verwijderen met OFFSET 1 in een subquery.

Pro:

  • Normaal sneller dan 1. (of zelfs 0. )
  • Korte syntaxis voor tabellen met veel kolommen en slechts enkele zijn relevant.
  • U hoeft alleen de relevante kolomnamen van de tabel te kennen.

Min:

  • Uitgebreide syntaxis voor slechts enkele rijen
  • Minder leesbaar (IMO).

3. VALUES uitdrukking met rijtype

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;

U kent duidelijk de naam van de tafel. Als je ook het aantal kolommen en hun volgorde weet, kun je hiermee aan de slag.

Voor elke tabel in PostgreSQL wordt automatisch een rijtype geregistreerd. Als u overeenkomt met het aantal kolommen in uw expressie, kunt u casten naar het rijtype van de tabel ('(1,50,)'::foo ) waardoor kolomtypes impliciet worden toegewezen. Zet niets achter een komma om een ​​NULL . in te voeren waarde. Voeg een komma toe voor elke irrelevante volgkolom.
In de volgende stap krijgt u toegang tot afzonderlijke kolommen met de gedemonstreerde syntaxis. Meer over Veldselectie in de handleiding.

Of u kunt toevoegen een rij met NULL-waarden en gebruik uniforme syntaxis voor actuele gegevens:

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

Pro:

  • Snelst (tenminste in mijn tests met weinig rijen en kolommen).
  • Kortste syntaxis voor enkele rijen of tabellen waarin u alle kolommen nodig heeft.
  • U hoeft de kolommen van de tabel niet te spellen - alle kolommen hebben automatisch de overeenkomende naam.

Min:

  • Niet zo bekende syntaxis voor veldselectie van record/rij/composiettype.
  • U moet het aantal en de positie van relevante kolommen in de standaardvolgorde weten.

4. VALUES uitdrukking met ontbonden rijtype

Vind je leuk 3. , maar met ontlede rijen in standaardsyntaxis:

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

Of, met weer een voorlooprij met NULL-waarden:

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

Voor- en nadelen zoals 3. , maar met een meer algemeen bekende syntaxis.
En je moet kolomnamen spellen (als je ze nodig hebt).

5. VALUES uitdrukking met typen opgehaald uit rijtype

Zoals Unril opmerkte, kunnen we de deugden van 2 combineren. en 4. om slechts een subset van kolommen op te geven:

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;

Voor- en nadelen zoals 4. , maar we kunnen met elke subset van kolommen werken en hoeven niet de volledige lijst te kennen.

Geeft ook een korte syntaxis weer voor de UPDATE zelf dat is handig voor gevallen met veel kolommen. Gerelateerd:

  • Bulkupdate van alle kolommen

4. en 5. zijn mijn favorieten.

db<>viool hier - alles demonstreren



  1. Hoe datums in datetime-velden in Postgresql te vergelijken?

  2. Een bestand opslaan in een database in plaats van in het bestandssysteem?

  3. Gebruik MySQL relationele databases op CentOS 5.

  4. SQL, hoe resultaten samen te voegen?