PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Casting tipo NULL ao atualizar várias linhas


Com um VALUES autônomo expressão O PostgreSQL não tem idéia de quais devem ser os tipos de dados. Com literais numéricos simples, o sistema fica feliz em assumir tipos correspondentes. Mas com outra entrada (como NULL ) você precisaria converter explicitamente - como você já descobriu.

Você pode consultar pg_catalog (rápido, mas específico do PostgreSQL) ou o information_schema (lento, mas SQL padrão) para descobrir e preparar sua instrução com os tipos apropriados.

Ou você pode usar um desses "truques" simples (guardei o melhor para o último ):

0. Selecione a linha com LIMIT 0 , anexe linhas com 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;

A primeira sub-seleção da subconsulta:
(SELECT x, y, pkid  FROM foo LIMIT 0)

obtém nomes e tipos para as colunas, mas LIMIT 0 impede que ele adicione uma linha real. As linhas subsequentes são forçadas para o tipo de linha agora bem definido - e verificadas imediatamente se correspondem ao tipo. Deve ser uma sutil melhoria adicional em relação à sua forma original.

Ao fornecer valores para todos colunas da tabela esta sintaxe curta pode ser usada para a primeira linha:
(TABLE foo LIMIT 0)

limitação principal :Postgres converte os literais de entrada dos VALUES independentes expressão para um tipo de "melhor esforço" imediatamente. Quando mais tarde tentar converter para os tipos fornecidos do primeiro SELECT , pode ser tarde demais para alguns tipos se não houver uma conversão de atribuição registrada entre o tipo assumido e o tipo de destino. Exemplos:text -> timestamp ou text -> json .

Pro:
  • Mínimo overhead.
  • Legível, simples e rápido.
  • Você só precisa saber os nomes das colunas relevantes da tabela.

Contra:
  • A resolução de tipo pode falhar para alguns tipos.

1. Selecione a linha com LIMIT 0 , anexe linhas com 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:
  • Curta 0. , mas evita falhas na resolução de tipo.

Contra:
  • UNION ALL SELECT é mais lento que VALUES expressão para longas listas de linhas, como você encontrou em seu teste.
  • Sintaxe detalhada por linha.

2. VALUES expressão com tipo por coluna

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

Ao contrário de 0. isso evita a resolução prematura do tipo.

A primeira linha em VALUES expressão é uma linha de NULL valores que definem o tipo para todas as linhas subsequentes. Esta linha de ruído inicial é filtrada por WHERE f.pkid = t.pkid depois, para que nunca veja a luz do dia. Para outros fins, você pode eliminar a primeira linha adicionada com OFFSET 1 em uma subconsulta.

Pro:
  • Geralmente mais rápido que 1. (ou mesmo 0. )
  • Sintaxe curta para tabelas com muitas colunas e apenas algumas são relevantes.
  • Você só precisa saber os nomes das colunas relevantes da tabela.

Contra:
  • Sintaxe detalhada para apenas algumas linhas
  • Menos legível (IMO).

3. VALUES expressão com tipo de linha

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;

Você obviamente sabe o nome da tabela. Se você também souber o número de colunas e sua ordem, poderá trabalhar com isso.

Para cada tabela no PostgreSQL, um tipo de linha é registrado automaticamente. Se você corresponder ao número de colunas em sua expressão, poderá converter para o tipo de linha da tabela ('(1,50,)'::foo ) atribuindo assim tipos de coluna implicitamente. Não coloque nada atrás de uma vírgula para inserir um NULL valor. Adicione uma vírgula para cada coluna final irrelevante.
Na próxima etapa, você pode acessar colunas individuais com a sintaxe demonstrada. Mais sobre Seleção de campo no manual.

Ou você pode adicionar uma linha de valores NULL e use sintaxe uniforme para dados reais:
...
  VALUES
      ((NULL::foo))  -- row of NULL values
    , ('(1,20,)')    -- uniform ROW value syntax for all
    , ('(2,50,)')
...

Pro:
  • Mais rápido (pelo menos nos meus testes com poucas linhas e colunas).
  • Sintaxe mais curta para algumas linhas ou tabelas em que você precisa de todas as colunas.
  • Você não precisa soletrar as colunas da tabela - todas as colunas têm automaticamente o nome correspondente.

Contra:
  • Sintaxe não tão conhecida para seleção de campo de registro/linha/tipo composto.
  • Você precisa saber o número e a posição das colunas relevantes na ordem padrão.

4. VALUES expressão com decomposto tipo de linha


Curtir 3. , mas com linhas decompostas na sintaxe padrão:
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, com uma linha inicial de valores NULL novamente:
...
   VALUES
      ((NULL::foo).*)  -- row of NULL values
    , (1, 20, NULL)    -- uniform syntax for all
    , (2, 50, NULL)
...

Prós e contras como 3. , mas com a sintaxe mais conhecida.
E você precisa soletrar os nomes das colunas (se precisar deles).

5. VALUES expressão com tipos obtidos do tipo de linha


Como Unril comentou, podemos combinar as virtudes de 2. e 4. para fornecer apenas um subconjunto de colunas:
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;

Prós e contras como 4. , mas podemos trabalhar com qualquer subconjunto de colunas e não precisamos conhecer a lista completa.

Também exibindo a sintaxe curta para o UPDATE em si que é conveniente para casos com muitas colunas. Relacionado:
  • Atualização em massa de todas as colunas

4. e 5. são meus favoritos.

db<>mexa aqui - demonstrando todos