Como trabalhar com CTEs (expressões de tabela comuns)¶
- Consulte também:
Neste tópico:
O que é uma CTE?¶
Uma CTE (expressão de tabela comum) é uma subconsulta nomeada definida em uma cláusula WITH. Você pode pensar na CTE como uma exibição temporária para uso na instrução que define a CTE. A CTE define o nome da exibição temporária, uma lista opcional de nomes de colunas e uma expressão de consulta (ou seja, uma instrução SELECT). O resultado da expressão da consulta é efetivamente uma tabela. Cada coluna dessa tabela corresponde a uma coluna na lista (opcional) de nomes de colunas.
O seguinte código é um exemplo de uma consulta que utiliza uma CTE:
WITH
my_cte (cte_col_1, cte_col_2) AS (
SELECT col_1, col_2
FROM ...
)
SELECT ... FROM my_cte;
No exemplo acima, a CTE começa na linha que contém my_cte (cte_col_1, cte_col_2) AS (
e termina na linha que contém )
.
Evite escolher nomes de CTE que correspondam ao seguinte:
Tabelas, exibições ou exibições materializadas. Se uma consulta define uma CTE com um determinado nome, a CTE tem precedência sobre tabelas, etc.
A CTE pode ser recursiva ou não recursiva. Uma CTE recursiva é uma CTE que se refere a si mesma. Uma CTE recursiva pode unir uma tabela a si mesma tantas vezes quanto necessário para processar dados hierárquicos na tabela.
CTEs aumentam a modularidade e simplificam a manutenção.
Dados hierárquicos e CTEs recursivas¶
CTEs recursivas permitem processar dados hierárquicos, tais como um detalhamento de peças (componente, subcomponentes) ou uma hierarquia de gerenciamento (gerente, funcionários). Para obter mais informações sobre dados hierárquicos e outras formas de consulta de dados hierárquicos, consulte Consulta de dados hierárquicos.
Uma CTE recursiva permite que você entre em todos os níveis de uma hierarquia sem saber com antecedência quantos níveis existem.
Visão geral da sintaxe da CTE recursiva¶
Esta seção fornece uma visão geral da sintaxe e como a sintaxe se relaciona com a forma como a recursividade funciona:
WITH [ RECURSIVE ] <cte_name> AS
(
<anchor_clause> UNION ALL <recursive_clause>
)
SELECT ... FROM ...;
- Onde:
anchor_clause
seleciona uma linha inicial ou um conjunto de linhas que representam o topo da hierarquia. Por exemplo, se você estiver tentando exibir todos os funcionários de uma empresa, a cláusula de âncora selecionaria o presidente da empresa.
A cláusula de âncora é uma instrução SELECT e pode conter qualquer constructo SQL com suporte. A cláusula de âncora não pode fazer referência ao
cte_name
.recursive_clause
seleciona a próxima camada da hierarquia com base na camada anterior. Na primeira iteração, a camada anterior é o resultado estabelecido a partir da cláusula de âncora. Nas iterações subsequentes, a camada anterior é a mais recente iteração concluída.
A
recursive_clause
é uma instrução SELECT; no entanto, a instrução é restrita a projeções, junções e filtros. Além disso, o seguinte não é permitido na instrução:Funções agregadas ou de janela.
GROUP BY
,ORDER BY
,LIMIT
ouDISTINCT
.
A cláusula recursiva pode fazer referência ao
cte_name
como uma tabela ou exibição regular.
Para uma descrição mais detalhada da sintaxe, consulte WITH.
Logicamente, a CTE recursiva é avaliada da seguinte forma:
A
anchor_clause
é avaliada e seu resultado é gravado tanto para o conjunto de resultados finais quanto para uma tabela de trabalho. Octe_name
é efetivamente um alias para a tabela de trabalho; em outras palavras, uma consulta referenciando octe_name
lê a partir da tabela de trabalho.Enquanto a tabela de trabalho não estiver vazia:
A
recursive_clause
é avaliada, usando o conteúdo atual da tabela de trabalho onde quer quecte_name
seja referenciado.O resultado de
recursive_clause
é gravado tanto para o conjunto de resultados finais como para uma tabela temporária.A tabela de trabalho é substituída pelo conteúdo da tabela temporária.
Com efeito, a saída da iteração anterior é armazenada em uma tabela de trabalho chamada cte_name
, e essa tabela é então uma das entradas para a próxima iteração. A tabela de trabalho contém apenas o resultado da mais recente iteração. Os resultados acumulados de todas as iterações até o momento são armazenados em outro lugar.
Após a iteração final, os resultados acumulados estão disponíveis para a instrução principal SELECT, referenciando cte_name
.
Considerações sobre CTE recursiva¶
Potencial para loops infinitos¶
A construção de uma CTE recursiva de forma incorreta pode causar um loop infinito. Nesses casos, a consulta continua a ser executada até que tenha sucesso, até que o tempo limite ocorra (por exemplo, ultrapassa o número de segundos especificado pelo parâmetro STATEMENT_TIMEOUT_IN_SECONDS) ou até que você cancele a consulta.
Para obter mais informações sobre como podem ocorrer loops infinitos e para diretrizes sobre como evitar este problema, consulte Solução de problemas de uma CTE recursiva.
Hierarquias não contíguas¶
Este tópico descreveu hierarquias e como as relações pai-filho podem ser usadas por CTEs recursivas. Em todos os exemplos deste tópico, as hierarquias são contíguas.
Para obter mais informações sobre hierarquias não contíguas, consulte Consulta de dados hierárquicos.
Exemplos¶
Esta seção inclui tanto exemplos de CTEs não recursivas como recursivas para contrastar os dois tipos.
CTE não recursiva, com dois níveis e autojunção¶
Este exemplo utiliza uma tabela de funcionários e gerentes:
CREATE OR REPLACE TABLE employees (title VARCHAR, employee_ID INTEGER, manager_ID INTEGER);INSERT INTO employees (title, employee_ID, manager_ID) VALUES ('President', 1, NULL), -- The President has no manager. ('Vice President Engineering', 10, 1), ('Programmer', 100, 10), ('QA Engineer', 101, 10), ('Vice President HR', 20, 1), ('Health Insurance Analyst', 200, 20);
Uma autojunção de dois níveis desta tabela de funcionários parece com:
SELECT emps.title, emps.employee_ID, mgrs.employee_ID AS MANAGER_ID, mgrs.title AS "MANAGER TITLE" FROM employees AS emps LEFT OUTER JOIN employees AS mgrs ON emps.manager_ID = mgrs.employee_ID ORDER BY mgrs.employee_ID NULLS FIRST, emps.employee_ID; +----------------------------+-------------+------------+----------------------------+ | TITLE | EMPLOYEE_ID | MANAGER_ID | MANAGER TITLE | |----------------------------+-------------+------------+----------------------------| | President | 1 | NULL | NULL | | Vice President Engineering | 10 | 1 | President | | Vice President HR | 20 | 1 | President | | Programmer | 100 | 10 | Vice President Engineering | | QA Engineer | 101 | 10 | Vice President Engineering | | Health Insurance Analyst | 200 | 20 | Vice President HR | +----------------------------+-------------+------------+----------------------------+
A consulta acima mostra todos os funcionários. Os funcionários de cada gerente aparecem perto de seu gerente no relatório. Entretanto, o relatório não mostra visualmente a hierarquia. Sem examinar cuidadosamente os dados, você não sabe quantos níveis existem na organização, e precisa ler cada linha para ver quais funcionários estão associados a um gerente específico.
Uma CTE recursiva pode exibir estes dados hierárquicos como uma árvore lateral, como mostrado na próxima seção.
CTE recursiva com saída recuada¶
Abaixo estão dois exemplos de utilização de uma CTE recursiva:
O primeiro usa recuos para mostrar os diferentes níveis da hierarquia. Para simplificar este exemplo, o código não produz as linhas em uma ordem específica.
O segundo exemplo usa recuos e mostra os funcionários de cada gerente imediatamente abaixo de seu gerente.
Saída não ordenada¶
Aqui está o primeiro exemplo.
1) WITH RECURSIVE managers
2) (indent, employee_ID, manager_ID, employee_title)
3) AS
4) (
5)
6) SELECT '' AS indent, employee_ID, manager_ID, title AS employee_title
7) FROM employees
8) WHERE title = 'President'
9)
10) UNION ALL
11)
12) SELECT indent || '--- ',
13) employees.employee_ID, employees.manager_ID, employees.title
14) FROM employees JOIN managers
15) ON employees.manager_ID = managers.employee_ID
16) )
17)
18) SELECT indent || employee_title AS Title, employee_ID, manager_ID
19) FROM managers
20) ;
A consulta inclui as seguintes seções:
A linha 2 contém os nomes das colunas para a “exibição” (CTE).
As linhas 4 - 16 contêm a CTE.
As linhas 6 - 8 contêm a cláusula de âncora da CTE.
As linhas 12 - 15 contêm a cláusula recursiva da CTE.
As linhas 18 - 19 contêm o SELECT principal que usa a CTE como uma exibição. Esta SELECT faz referência a:
O nome da CTE (
managers
), definido na linha 1.As colunas da CTE (
indent
,employee_id
, etc.) definidas na linha 2.
A CTE contém duas instruções SELECT:
A instrução SELECT na cláusula de âncora é executada uma vez e fornece o conjunto de linhas a partir do primeiro nível (superior) da hierarquia.
A SELECT na cláusula recursiva pode fazer referência à CTE. Você pode pensar na consulta como iteração, com cada iteração expandindo os resultados da consulta das iterações anteriores.
No exemplo do gerente/funcionário, a cláusula de âncora emite a primeira linha, que descreve o presidente da empresa.
Na próxima iteração da cláusula recursiva, a cláusula recursiva encontra todas as linhas cujo gerente é o presidente da empresa (ou seja, encontra todos os vice-presidentes). A terceira iteração encontra todos os funcionários cujo gerente é um dos vice-presidentes. A iteração continua até que haja uma iteração na qual todas as linhas recuperadas são linhas de funcionários de nível folha que não gerenciam ninguém. A instrução faz mais uma iteração, procurando (mas não encontrando) quaisquer funcionários cujos gerentes sejam funcionários de nível folha. A iteração produz 0 linhas e para.
Ao longo destas iterações, a cláusula UNION ALL acumula os resultados. Os resultados de cada iteração são adicionados aos resultados das iterações anteriores. Após a conclusão da última iteração, as linhas acumuladas (como qualquer linha produzida em uma cláusula WITH) são disponibilizadas para a cláusula SELECT principal da consulta. A SELECT principal pode então consultar essas linhas.
Este exemplo particular de consulta usa recuos para mostrar a natureza hierárquica dos dados. Se você olhar para a saída, verá que quanto mais baixo o nível do funcionário, mais os dados desse funcionário são recuados.
O recuo é controlado pela coluna chamada indent
. O recuo começa com 0 caracteres (uma cadeia de caracteres vazia na cláusula de âncora), e aumenta em 4 caracteres (---
) para cada iteração (ou seja, para cada nível da hierarquia).
Não surpreendentemente, é muito importante construir as junções corretamente e selecionar as colunas corretas na cláusula recursiva. As colunas em SELECT da cláusula recursiva devem corresponder corretamente às colunas da cláusula de âncora. Lembre-se de que a consulta começa com o presidente, depois seleciona os vice-presidentes e depois seleciona as pessoas que se reportam diretamente aos vice-presidentes, etc. Cada iteração procura funcionários cujo campo manager_id
corresponda a um dos valores managers.employee_id
produzidos na iteração anterior.
Expressado de outra forma, a ID do funcionário na “exibição” de gerentes é a ID de gerente para o próximo nível de funcionários. As IDs dos funcionários devem progredir para baixo através da hierarquia (presidente, vice-presidente, gerente sênior, gerente júnior, etc.) durante cada iteração. Se as IDs de funcionário não progredirem, então a consulta pode fazer loop infinito (se o mesmo manager_ID
continuar aparecendo na coluna managers.employee_ID
em diferentes iterações), ou pular um nível, ou falhar de outras maneiras.
Saída ordenada¶
O exemplo anterior não tinha cláusula ORDER BY, portanto, mesmo que o registro de cada funcionário esteja recuado corretamente, cada funcionário não necessariamente aparecia diretamente abaixo de seu gerente. O exemplo abaixo gera saída com recuo correto, e com os funcionários de cada gerente diretamente abaixo de seu próprio gerente.
A cláusula ORDER BY da consulta usa uma coluna adicional, chamada sort_key
. A chave de ordenação se acumula à medida que a cláusula recursiva itera; você pode pensar na chave de ordenação como uma sequência que contém toda a cadeia de comando acima de você (seu gerente, o gerente de seu gerente, etc.). A pessoa mais graduada nessa cadeia de comando (o presidente) está no início da cadeia de caracteres da chave de ordenação. Embora você normalmente não mostre a chave de ordenação, a consulta abaixo inclui a chave de ordenação na saída, para que seja mais fácil entender a saída.
Cada iteração deve aumentar o comprimento da chave de ordenação na mesma quantidade (mesmo número de caracteres), assim a consulta usa uma UDF (função definida pelo usuário) chamada skey
, com a seguinte definição, para gerar segmentos de comprimento consistente da chave de ordenação:
CREATE OR REPLACE FUNCTION skey(ID VARCHAR) RETURNS VARCHAR AS $$ SUBSTRING('0000' || ID::VARCHAR, -4) || ' ' $$ ;Aqui está um exemplo de saída da função
SKEY
:SELECT skey(12); +----------+ | SKEY(12) | |----------| | 0012 | +----------+
Aqui está a versão final da consulta. Isto coloca os funcionários de cada gerente imediatamente abaixo desse gerente e insere recuos com base no “nível” do funcionário:
WITH RECURSIVE managers -- Column list of the "view" (indent, employee_ID, manager_ID, employee_title, sort_key) AS -- Common Table Expression ( -- Anchor Clause SELECT '' AS indent, employee_ID, manager_ID, title AS employee_title, skey(employee_ID) FROM employees WHERE title = 'President' UNION ALL -- Recursive Clause SELECT indent || '--- ', employees.employee_ID, employees.manager_ID, employees.title, sort_key || skey(employees.employee_ID) FROM employees JOIN managers ON employees.manager_ID = managers.employee_ID ) -- This is the "main select". SELECT indent || employee_title AS Title, employee_ID, manager_ID, sort_key FROM managers ORDER BY sort_key ; +----------------------------------+-------------+------------+-----------------+ | TITLE | EMPLOYEE_ID | MANAGER_ID | SORT_KEY | |----------------------------------+-------------+------------+-----------------| | President | 1 | NULL | 0001 | | --- Vice President Engineering | 10 | 1 | 0001 0010 | | --- --- Programmer | 100 | 10 | 0001 0010 0100 | | --- --- QA Engineer | 101 | 10 | 0001 0010 0101 | | --- Vice President HR | 20 | 1 | 0001 0020 | | --- --- Health Insurance Analyst | 200 | 20 | 0001 0020 0200 | +----------------------------------+-------------+------------+-----------------+
A consulta seguinte mostra como fazer referência a um campo do nível anterior (superior) na hierarquia; preste especial atenção à coluna mgr_title
:
WITH RECURSIVE managers -- Column names for the "view"/CTE (employee_ID, manager_ID, employee_title, mgr_title) AS -- Common Table Expression ( -- Anchor Clause SELECT employee_ID, manager_ID, title AS employee_title, NULL AS mgr_title FROM employees WHERE title = 'President' UNION ALL -- Recursive Clause SELECT employees.employee_ID, employees.manager_ID, employees.title, managers.employee_title AS mgr_title FROM employees JOIN managers ON employees.manager_ID = managers.employee_ID ) -- This is the "main select". SELECT employee_title AS Title, employee_ID, manager_ID, mgr_title FROM managers ORDER BY manager_id NULLS FIRST, employee_ID ; +----------------------------+-------------+------------+----------------------------+ | TITLE | EMPLOYEE_ID | MANAGER_ID | MGR_TITLE | |----------------------------+-------------+------------+----------------------------| | President | 1 | NULL | NULL | | Vice President Engineering | 10 | 1 | President | | Vice President HR | 20 | 1 | President | | Programmer | 100 | 10 | Vice President Engineering | | QA Engineer | 101 | 10 | Vice President Engineering | | Health Insurance Analyst | 200 | 20 | Vice President HR | +----------------------------+-------------+------------+----------------------------+
Detalhamento de peças¶
As hierarquias de gerentes/empregados não são o único tipo de hierarquias de profundidade variável que você pode armazenar em uma única tabela e processar com uma CTE recursiva. Outro exemplo comum de dados hierárquicos é um “detalhamento de peças”, no qual cada componente pode ser listado com seus subcomponentes, cada um dos quais pode ser listado com seus subcomponentes.
Por exemplo, suponha que sua tabela contenha dados hierárquicos, tais como os componentes de um carro. Seu carro provavelmente contém componentes tais como um motor, rodas, etc. Muitos desses componentes contêm subcomponentes (por exemplo, um motor pode conter uma bomba de combustível). A bomba de combustível pode conter um motor, tubulação, etc. Você poderia listar todos os componentes e seus subcomponentes usando um CTE recursiva.
Para um exemplo de uma consulta que produz um detalhamento de peças, consulte WITH.
Solução de problemas de uma CTE recursiva¶
Uma consulta CTE recursiva é executada até ter sucesso ou atingir o tempo limite¶
Este problema pode ser causado por dois cenários diferentes:
Sua hierarquia de dados pode ter um ciclo.
Você pode ter criado um loop infinito.
Causa 1: Hierarquia de dados cíclica¶
Se sua hierarquia de dados contém um ciclo (ou seja, não é uma árvore verdadeira), há múltiplas soluções possíveis:
- Solução 1.1:
Se os dados não devem conter um ciclo, corrija os dados.
- Solução 1.2:
Limite a consulta de alguma forma (por exemplo, limitar o número de linhas de saída). Por exemplo:
WITH RECURSIVE t(n) AS ( SELECT 1 UNION ALL SELECT N + 1 FROM t ) SELECT n FROM t LIMIT 10;
- Solução 1.3:
Não use uma consulta que contenha uma CTE recursiva, que espera dados hierárquicos.
Causa 2: Loop infinito¶
Um loop infinito pode acontecer se a cláusula de projeção na recursive_clause
produzir um valor do “pai” (a iteração anterior) ao invés do “filho” (a iteração atual) e então a próxima iteração usar esse valor em uma junção quando deveria usar o valor da iteração atual na junção.
O seguinte pseudocódigo mostra um exemplo aproximado disso:
CREATE TABLE employees (employee_ID INT, manager_ID INT, ...); INSERT INTO employees (employee_ID, manager_ID) VALUES (1, NULL), (2, 1); WITH cte_name (employee_ID, manager_ID, ...) AS ( -- Anchor Clause SELECT employee_ID, manager_ID FROM table1 UNION ALL SELECT manager_ID, employee_ID -- <<< WRONG FROM table1 JOIN cte_name ON table1.manager_ID = cte_name.employee_ID ) SELECT ...
Neste exemplo, a cláusula recursiva passa seu valor pai (manager_id
) na coluna que deve ter o valor atual/filho (employee_id
). O pai aparecerá como o valor “atual” na próxima iteração, e será passado novamente como o valor “atual” para a geração seguinte, de modo que a consulta nunca avança através dos níveis; ela continua processando o mesmo nível a cada vez.
- Etapa 1:
Suponha que a cláusula de âncora selecione os valores
employee_id = 1
emanager_id = NULL
.CTE:
employee_ID manager_ID ----------- --------- 1 NULL
- Etapa 2:
Durante a primeira iteração da cláusula recursiva,
employee_id = 2
emanager_id = 1
emtable1
.CTE:
employee_ID manager_ID ----------- ---------- 1 NULL
table1
:employee_ID manager_ID ----------- ---------- ... 2 1 ...
Resultado da junção na cláusula recursiva:
table1.employee_ID table1.manager_ID cte.employee_ID cte.manager_ID ----------------- ----------------- --------------- -------------- ... 2 1 1 NULL ...
Projeção:
employee_ID manager_ID ----------- ---------- ... 2 1 ...
Entretanto, como as colunas
employee_id
emanager_id
são invertidas na projeção, a saída real da consulta (e portanto o conteúdo da CTE no início da próxima iteração) é:employee_ID manager_ID ----------- ---------- ... 1 2 -- Because manager and employee IDs reversed ...
- Etapa 3:
Durante a segunda iteração da cláusula recursiva:
CTE:
employee_ID manager_ID ----------- ---------- 1 2
table1
:employee_ID manager_ID ----------- ---------- ... 2 1 ...
Resultado da junção em cláusula recursiva:
table1.employee_ID table1.manager_ID cte.employee_ID cte.manager_ID ----------------- ----------------- --------------- -------------- ... 2 1 1 2 ...
Projeção:
employee_ID manager_ID ----------- ---------- ... 2 1 ...
Resultado da consulta (conteúdo da CTE no início da próxima iteração):
employee_ID manager_ID ----------- ---------- ... 1 2 -- Because manager and employee IDs reversed ...
Como você pode ver, no final da segunda iteração, a linha na CTE é a mesma que era no início da iteração:
employee_id
é1
.manager_id
é2
.
Assim, o resultado da junção durante a próxima iteração será o mesmo que o resultado da junção durante a iteração atual, e a consulta faz um loop infinito.
Se você criou um loop infinito:
- Solução 2:
Certifique-se de que a cláusula recursiva passe as variáveis corretas na ordem correta.
Certifique-se também de que a condição JOIN na cláusula recursiva esteja correta. Em um caso típico, o pai da linha “atual” deve ser unido ao valor filho/atual da linha pai.