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

Como passar um conjunto de linhas de uma função para outra?

Funções de tabela


Eu executo migrações de banco de dados complexas e de alta velocidade para viver, usando SQL como linguagem cliente e servidor (nenhuma outra linguagem é usada), todas rodando no lado do servidor, onde o código raramente aparece no mecanismo de banco de dados. As funções da tabela desempenham um papel ENORME no meu trabalho . Eu não uso "cursores", pois eles são muito lentos para atender aos meus requisitos de desempenho e tudo o que faço é orientado a resultados. As funções de tabela têm sido uma grande ajuda para mim eliminando completamente o uso de cursores, alcançando uma velocidade muito alta e contribuindo dramaticamente para reduzir o volume de código e melhorar a simplicidade.

Resumindo, você usa uma consulta que referencia duas (ou mais) funções de tabela para passar os dados de uma função de tabela para a próxima. O conjunto de resultados de consulta de seleção que chama as funções de tabela serve como o canal para passar os dados de uma função de tabela para a próxima. Na plataforma/versão DB2 em que trabalho, e parece com base em uma rápida olhada no manual do Postgres 9.1 que o mesmo é verdade lá, você só pode passar uma única linha de valores de coluna como entrada para qualquer uma das chamadas de função de tabela, como você descobriu. No entanto, como a chamada da função de tabela ocorre no meio do processamento do conjunto de resultados de uma consulta, você obtém o mesmo efeito de passar um conjunto de resultados inteiro para cada chamada de função de tabela, embora, no encanamento do mecanismo de banco de dados, os dados sejam passados apenas uma linha por vez para cada função de tabela.

As funções de tabela aceitam uma linha de colunas de entrada e retornam um único conjunto de resultados de volta à consulta de chamada (ou seja, select) que chamou a função. As colunas do conjunto de resultados retornadas de uma função de tabela tornam-se parte do conjunto de resultados da consulta de chamada e, portanto, estão disponíveis como entrada para a próxima função de tabela , referenciado posteriormente na mesma consulta, normalmente como uma junção subsequente. As colunas de resultados da primeira função de tabela são alimentadas como entrada (uma linha por vez) para a segunda função de tabela, que retorna suas colunas de conjunto de resultados no conjunto de resultados da consulta de chamada. As colunas do conjunto de resultados da primeira e da segunda função de tabela agora fazem parte do conjunto de resultados da consulta de chamada e agora estão disponíveis como entrada (uma linha por vez) para uma terceira função de tabela. Cada chamada de função de tabela amplia o conjunto de resultados da consulta de chamada por meio das colunas que ela retorna. Isso pode continuar até que você comece a atingir os limites na largura de um conjunto de resultados, o que provavelmente varia de um mecanismo de banco de dados para outro.

Considere este exemplo (que pode não corresponder aos requisitos ou recursos de sintaxe do Postgres enquanto trabalho no DB2). Este é um dos muitos padrões de design em que eu uso funções de tabela, é um dos mais simples, que eu acho muito ilustrativo, e um que eu prevejo que teria amplo apelo se funções de tabela estavam em uso mainstream pesado (que eu saiba, não são, mas acho que merecem mais atenção do que estão recebendo).

Neste exemplo, as funções de tabela em uso são:VALIDATE_TODAYS_ORDER_BATCH, POST_TODAYS_ORDER_BATCH e DATA_WAREHOUSE_TODAYS_ORDER_BATCH. Na versão do DB2 em que trabalho, você envolve a função de tabela dentro de "TABLE( place table function call and parameters here )", mas com base em uma rápida olhada em um manual do Postgres, parece que você omitiu o wrapper "TABLE ( )".
create table TODAYS_ORDER_PROCESSING_EXCEPTIONS as (

select      TODAYS_ORDER_BATCH.*
           ,VALIDATION_RESULT.ROW_VALID
           ,POST_RESULT.ROW_POSTED
           ,WAREHOUSE_RESULT.ROW_WAREHOUSED

from        TODAYS_ORDER_BATCH

cross join  VALIDATE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function]  ) 
              as VALIDATION_RESULT ( ROW_VALID )  --example: 1/0 true/false Boolean returned

left join   POST_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
              as POST_RESULT ( ROW_POSTED )  --example: 1/0 true/false Boolean returned
      on    ROW_VALIDATED = '1'

left join   DATA_WAREHOUSE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
              as WAREHOUSE_RESULT ( ROW_WAREHOUSED )  --example: 1/0 true/false Boolean returned
      on    ROW_POSTED = '1'

where       coalesce( ROW_VALID,      '0' ) = '0'   --Capture only exceptions and unprocessed work.  
      or    coalesce( ROW_POSTED,     '0' ) = '0'   --Or, you can flip the logic to capture only successful rows.
      or    coalesce( ROW_WAREHOUSED, '0' ) = '0'

) with data
  1. Se a tabela TODAYS_ORDER_BATCH contiver 1.000.000 linhas, então VALIDATE_TODAYS_ORDER_BATCH será chamado 1.000.000 vezes, uma vez foreach linha.
  2. Se 900.000 linhas passarem na validação dentro de VALIDATE_TODAYS_ORDER_BATCH, POST_TODAYS_ORDER_BATCH será chamado 900.000 vezes.
  3. Se apenas 850.000 linhas forem postadas com sucesso, VALIDATE_TODAYS_ORDER_BATCH precisará de algumas brechas fechadas LOL, e DATA_WAREHOUSE_TODAYS_ORDER_BATCH será chamado 850.000 vezes.
  4. Se 850.000 linhas chegaram ao Data Warehouse (ou seja, nenhuma exceção adicional foi gerada), a tabela TODAYS_ORDER_PROCESSING_EXCEPTIONS será preenchida com 1.000.000 - 850.000 =150.000 linhas de exceção.

As chamadas de função de tabela neste exemplo estão retornando apenas uma única coluna, mas podem estar retornando muitas colunas. Por exemplo, a função de tabela que valida uma linha de pedido pode retornar o motivo pelo qual um pedido falhou na validação.

Nesse design, praticamente toda a conversa entre um HLL e o banco de dados é eliminada, pois o solicitante do HLL está solicitando ao banco de dados que processe todo o lote em UMA solicitação. Isso resulta em uma redução de milhões de solicitações SQL ao banco de dados, em uma ENORME remoção de milhões de chamadas de procedimento ou método HLL e, como resultado, fornece uma ENORME melhoria no tempo de execução. Por outro lado, o código legado, que geralmente processa uma única linha por vez, normalmente enviaria 1.000.000 solicitações SQL de busca, 1 para cada linha em TODAYS_ORDER_BATCH, mais pelo menos 1.000.000 solicitações HLL e/ou SQL para fins de validação, mais pelo menos 1.000.000 HLL e /ou solicitações SQL para fins de postagem, mais 1.000.000 solicitações HLL e/ou SQL para envio do pedido ao data warehouse. Concedido, usando este design de função de tabela, dentro das funções de tabela solicitações SQL estão sendo enviadas para o banco de dados, mas quando o banco de dados faz solicitações para si mesmo (ou seja, de dentro de uma função de tabela), as solicitações SQL são atendidas muito mais rapidamente (especialmente em comparação com um cenário legado em que o solicitante HLL está fazendo processamento de linha única de um sistema remoto, com o pior caso em uma WAN - OMG, por favor, não faça isso).

Você pode facilmente ter problemas de desempenho se usar uma função de tabela para "buscar um conjunto de resultados" e, em seguida, unir esse conjunto de resultados a outras tabelas. Nesse caso, o otimizador SQL não pode prever qual conjunto de linhas será retornado da função de tabela e, portanto, não pode otimizar a junção com as tabelas subsequentes. Por esse motivo, raramente os uso para buscar um conjunto de resultados, a menos que eu saiba que o conjunto de resultados será um número muito pequeno de linhas, não causando um problema de desempenho ou não preciso ingressar em tabelas subsequentes.

Na minha opinião, uma razão pela qual as funções de tabela são subutilizadas é que muitas vezes são percebidas apenas como uma ferramenta para buscar um conjunto de resultados, que geralmente tem um desempenho ruim, então são descartadas como uma ferramenta "ruim" de usar.

As funções de tabela são imensamente úteis para enviar mais funcionalidades ao servidor, para eliminar a maior parte da conversa entre o servidor de banco de dados e programas em sistemas remotos e até mesmo para eliminar a conversa entre o servidor de banco de dados e programas externos no mesmo servidor. Até mesmo conversas entre programas no mesmo servidor carregam mais sobrecarga do que muitas pessoas imaginam, e muitas delas são desnecessárias. O coração do poder das funções de tabela está em usá-las para executar ações dentro do processamento do conjunto de resultados.

Existem padrões de design mais avançados para usar funções de tabela que se baseiam no padrão acima, onde você pode maximizar ainda mais o processamento do conjunto de resultados, mas este post é muito para a maioria já absorver.