sql >> Base de Datos >  >> RDS >> PostgreSQL

Emitir tipo NULL al actualizar varias filas

Con un VALUES independiente expresión PostgreSQL no tiene idea de cuáles deberían ser los tipos de datos. Con literales numéricos simples, el sistema se complace en asumir tipos coincidentes. Pero con otra entrada (como NULL ) tendrías que emitir de forma explícita, como ya has descubierto.

Puede consultar pg_catalog (rápido, pero específico de PostgreSQL) o el information_schema (SQL lento, pero estándar) para averiguar y preparar su declaración con los tipos apropiados.

O puedes usar uno de estos "trucos" simples (guardé los mejores para último ):

0. Seleccionar fila con LIMIT 0 , agregue filas 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 primera subselección de la subconsulta:

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

obtiene nombres y tipos para las columnas, pero LIMIT 0 evita que agregue una fila real. Las filas subsiguientes se fuerzan al tipo de fila ahora bien definido, y se verifican de inmediato si coinciden con el tipo. Debería ser una sutil mejora adicional sobre su forma original.

Al proporcionar valores para todos columnas de la tabla, esta breve sintaxis se puede utilizar para la primera fila:

(TABLE foo LIMIT 0)

Limitación importante :Postgres emite los literales de entrada de los VALUES independientes expresión a un tipo de "mejor esfuerzo" inmediatamente. Cuando más tarde intenta convertir a los tipos dados del primer SELECT , puede que ya sea demasiado tarde para algunos tipos si no hay una conversión de asignación registrada entre el tipo supuesto y el tipo de destino. Ejemplos:text -> timestamp o text -> json .

Pro:

  • Gastos generales mínimos.
  • Legible, simple y rápido.
  • Solo necesita conocer los nombres de las columnas relevantes de la tabla.

Desventaja:

  • La resolución de tipos puede fallar para algunos tipos.

1. Seleccionar fila con LIMIT 0 , agregue filas 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;

Pro:

  • Me gusta 0. , pero evita errores en la resolución de tipos.

Desventaja:

  • UNION ALL SELECT es más lento que VALUES expresión para largas listas de filas, como encontró en su prueba.
  • Sintaxis detallada por fila.

2. VALUES expresión con tipo por columna

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

Contrario a 0. esto evita la resolución prematura de tipos.

La primera fila en VALUES expresión es una fila de NULL valores que define el tipo para todas las filas subsiguientes. Esta fila de ruido principal se filtra por WHERE f.pkid = t.pkid más tarde, por lo que nunca ve la luz del día. Para otros fines, puede eliminar la primera fila agregada con OFFSET 1 en una subconsulta.

Pro:

  • Normalmente más rápido que 1. (o incluso 0. )
  • Sintaxis corta para tablas con muchas columnas y solo unas pocas son relevantes.
  • Solo necesita conocer los nombres de las columnas relevantes de la tabla.

Desventajas:

  • Sintaxis detallada solo para unas pocas filas
  • Menos legible (IMO).

3. VALUES expresión con tipo de fila

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;

Obviamente conoces el nombre de la tabla. Si además conoces el número de columnas y su orden puedes trabajar con esto.

Para cada tabla en PostgreSQL, se registra automáticamente un tipo de fila. Si coincide con el número de columnas en su expresión, puede convertir al tipo de fila de la tabla ('(1,50,)'::foo ) asignando así tipos de columna implícitamente. No coloque nada detrás de una coma para ingresar un NULL valor. Agregue una coma para cada columna final irrelevante.
En el siguiente paso, puede acceder a columnas individuales con la sintaxis demostrada. Más información sobre Selección de campo en el manual.

O podrías agregar una fila de valores NULL y use una sintaxis uniforme para los datos reales:

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

Pro:

  • Más rápido (al menos en mis pruebas con pocas filas y columnas).
  • Sintaxis más corta para pocas filas o tablas donde necesita todas las columnas.
  • No es necesario que deletree las columnas de la tabla:todas las columnas tienen automáticamente el nombre correspondiente.

Desventajas:

  • Sintaxis no tan conocida para la selección de campos de registro/fila/tipo compuesto.
  • Necesita saber el número y la posición de las columnas relevantes en el orden predeterminado.

4. VALUES expresión con descompuesto tipo de fila

Me gusta 3. , pero con filas descompuestas en sintaxis estándar:

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

O, con una fila inicial de valores NULL de nuevo:

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

Pros y contras como 3. , pero con una sintaxis más conocida.
Y debe deletrear los nombres de las columnas (si los necesita).

5. VALUES expresión con tipos obtenidos del tipo de fila

Como comentaba Unril, podemos combinar las virtudes de 2. y 4. para proporcionar solo un subconjunto de columnas:

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;

Pros y contras como 4. , pero podemos trabajar con cualquier subconjunto de columnas y no es necesario conocer la lista completa.

También muestra una sintaxis corta para UPDATE en sí mismo que es conveniente para casos con muchas columnas. Relacionado:

  • Actualización masiva de todas las columnas

4. y 5. son mis favoritos.

db<>violín aquí - demostrando todo