PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Casting de type NULL lors de la mise à jour de plusieurs lignes

Avec un VALUES autonome expression PostgreSQL n'a aucune idée de ce que devraient être les types de données. Avec des littéraux numériques simples, le système est heureux de supposer que les types correspondent. Mais avec une autre entrée (comme NULL ) vous auriez besoin de caster explicitement - comme vous l'avez déjà découvert.

Vous pouvez interroger pg_catalog (rapide, mais spécifique à PostgreSQL) ou le information_schema (SQL lent mais standard) pour trouver et préparer votre instruction avec les types appropriés.

Ou vous pouvez utiliser l'une de ces "astuces" simples (j'ai gardé le meilleur pour la fin ):

0. Sélectionnez la ligne avec LIMIT 0 , ajoutez des lignes avec 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 première sous-sélection de la sous-requête :

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

obtient les noms et les types des colonnes, mais LIMIT 0 l'empêche d'ajouter une ligne réelle. Les lignes suivantes sont contraintes au type de ligne désormais bien défini - et vérifiées immédiatement si elles correspondent au type. Devrait être une subtile amélioration supplémentaire par rapport à votre formulaire d'origine.

Tout en fournissant des valeurs pour tous colonnes du tableau, cette syntaxe courte peut être utilisée pour la première ligne :

(TABLE foo LIMIT 0)

Limitation majeure  :Postgres convertit les littéraux d'entrée des VALUES autonomes expression à un type "meilleur effort" immédiatement. Quand il essaie plus tard de caster les types donnés du premier SELECT , il peut déjà être trop tard pour certains types s'il n'y a pas de transtypage d'assignation enregistré entre le type supposé et le type cible. Exemples :text -> timestamp ou text -> json .

Pro :

  • Frais généraux minimum.
  • Lisible, simple et rapide.
  • Il vous suffit de connaître les noms de colonne pertinents du tableau.

Inconvénient :

  • La résolution de type peut échouer pour certains types.

1. Sélectionnez la ligne avec LIMIT 0 , ajouter des lignes avec 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 :

  • Comme 0. , mais évite l'échec de la résolution de type.

Inconvénient :

  • UNION ALL SELECT est plus lent que VALUES expression pour de longues listes de lignes, comme vous l'avez trouvé dans votre test.
  • Syntaxe détaillée par ligne.

2. VALUES expression avec le type par colonne

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

Contrairement à 0. cela évite une résolution de type prématurée.

La première ligne dans les VALUES expression est une ligne de NULL valeurs qui définissent le type pour toutes les lignes suivantes. Cette première ligne de bruit est filtrée par WHERE f.pkid = t.pkid plus tard, pour qu'il ne voie jamais la lumière du jour. À d'autres fins, vous pouvez éliminer la première ligne ajoutée avec OFFSET 1 dans une sous-requête.

Pro :

  • Généralement plus rapide que 1. (ou même 0. )
  • Syntaxe courte pour les tableaux avec de nombreuses colonnes et seulement quelques-unes sont pertinentes.
  • Il vous suffit de connaître les noms de colonne pertinents du tableau.

Inconvénient :

  • Syntaxe détaillée pour seulement quelques lignes
  • Moins lisible (IMO).

3. VALUES expression avec type de ligne

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;

Vous connaissez évidemment le nom de la table. Si vous connaissez également le nombre de colonnes et leur ordre, vous pouvez travailler avec cela.

Pour chaque table dans PostgreSQL, un type de ligne est enregistré automatiquement. Si vous faites correspondre le nombre de colonnes dans votre expression, vous pouvez convertir le type de ligne de la table ('(1,50,)'::foo ) attribuant ainsi implicitement des types de colonne. Ne mettez rien derrière une virgule pour entrer un NULL valeur. Ajoutez une virgule pour chaque colonne de fin non pertinente.
À l'étape suivante, vous pouvez accéder à des colonnes individuelles avec la syntaxe démontrée. En savoir plus sur la sélection de champs dans le manuel.

Ou vous pouvez ajouter une ligne de valeurs NULL et utilisez une syntaxe uniforme pour les données réelles :

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

Pro :

  • Le plus rapide (du moins dans mes tests avec peu de lignes et de colonnes).
  • Syntaxe la plus courte pour quelques lignes ou tableaux où vous avez besoin de toutes les colonnes.
  • Vous n'avez pas à épeler les colonnes du tableau :toutes les colonnes portent automatiquement le nom correspondant.

Inconvénient :

  • Syntaxe peu connue pour la sélection de champs à partir d'un type d'enregistrement/ligne/composite.
  • Vous devez connaître le nombre et la position des colonnes pertinentes dans l'ordre par défaut.

4. VALUES expression avec décomposé type de ligne

Comme 3. , mais avec des lignes décomposées en syntaxe 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

Ou encore, avec une première ligne de valeurs NULL :

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

Avantages et inconvénients comme 3. , mais avec une syntaxe plus connue.
Et vous devez épeler les noms de colonne (si vous en avez besoin).

5. VALUES expression avec les types extraits du type de ligne

Comme l'a commenté Unril, nous pouvons combiner les vertus de 2. et 4. pour ne fournir qu'un sous-ensemble de colonnes :

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;

Avantages et inconvénients comme 4. , mais nous pouvons travailler avec n'importe quel sous-ensemble de colonnes et nous n'avons pas besoin de connaître la liste complète.

Affichage également de la syntaxe courte pour le UPDATE lui-même qui est pratique pour les cas avec de nombreuses colonnes. Connexe :

  • Mise à jour groupée de toutes les colonnes

4. et 5. sont mes favoris.

db<>jouez ici - démontrant tout