Eu tenho um método de trabalho baseado em Excel para criar uma tabela verdade de dois vetores exportados de um banco de dados PostgreSQL. O processo leva cerca de 4 horas para ser concluído devido a um grande número de operações VLOOKUP
e COUNTIFS
, portanto, estou procurando um método para implementar isso como uma visualização diretamente no banco de dados.
Os vetores de entrada são produzidos a partir de duas visualizações existentes em meu banco de dados, que não possuem chaves estrangeiras.
Para tornar essa pergunta e solução o mais genérica possível, criei um problema paralelo usando duas tabelas simples com dados de amostra para cobrir todos os casos possíveis:
CREATE TABLE group_membership
(
member character varying(6) NOT NULL,
group_name character varying(64) NOT NULL
);
INSERT INTO group_membership VALUES ('000001','A');
INSERT INTO group_membership VALUES ('000001','B');
INSERT INTO group_membership VALUES ('000001','B'); -- A value may occur more than once.
INSERT INTO group_membership VALUES ('000001','D'); -- A value may not necessarily have a corresponding row in the group table.
INSERT INTO group_membership VALUES ('000001','D');
INSERT INTO group_membership VALUES ('000002','B');
INSERT INTO group_membership VALUES ('000002','C');
INSERT INTO group_membership VALUES ('000002','E');
INSERT INTO group_membership VALUES ('000003','A');
INSERT INTO group_membership VALUES ('000003','C');
INSERT INTO group_membership VALUES ('000004','D');
INSERT INTO group_membership VALUES ('000004','E');
CREATE TABLE groups
(
name character varying(64) NOT NULL
);
INSERT INTO groups VALUES ('A');
INSERT INTO groups VALUES ('B');
INSERT INTO groups VALUES ('C');
INSERT INTO groups VALUES ('C'); -- A value may occur more than once.
INSERT INTO groups VALUES ('Z');
-- 'D' and 'E' not present in this table
Não há relações entre essas duas tabelas.
Estou tentando construir uma visão que criará uma tabela de verdade binária (matriz) assim:
member A B C Z
000001 t t f f
000002 f t t f
000003 t f t f
000004 f f f f
Onde a primeira coluna são os membros distintos da group_membership
tabela, e as colunas subsequentes mostram a presença ou ausência de member
apenas nos grupos definidos na group
tabela. A tabela resultante deve ser apenas booleana ( TRUE
se o membro aparecer em uma tupla com o grupo pelo menos uma vez, FALSE
caso contrário).
Por exemplo, algumas "células" específicas na tabela acima estariam em conformidade com o seguinte:
SELECT COUNT(*) > 0 AS value FROM group_membership WHERE group_name='A' AND member='000001';
value
-------
t
(1 row)
SELECT COUNT(*) > 0 AS value FROM group_membership WHERE group_name='Z' AND member='000001';
value
-------
f
(1 row)
E para criar a segunda coluna (a coluna 'A'):
SELECT COUNT(*) > 0 AS A FROM group_membership WHERE group_name='A' AND member='000001'
UNION ALL
SELECT COUNT(*) > 0 AS A FROM group_membership WHERE group_name='A' AND member='000002'
UNION ALL
SELECT COUNT(*) > 0 AS A FROM group_membership WHERE group_name='A' AND member='000003'
UNION ALL
SELECT COUNT(*) > 0 AS A FROM group_membership WHERE group_name='A' AND member='000004'
;
Ainda melhor seria algo assim ( 1
e 0
em vez de TRUE
e FALSE
):
member A B C Z
000001 1 1 0 0
000002 0 1 1 0
000003 1 0 1 0
000004 0 0 0 0
Onde a consulta para cada uma das "células" individuais pode ser da forma:
SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM group_membership WHERE group_name='A' AND member='000001';
Minha group_membership
tabela tem cerca de 50.000 linhas e minha group
tabela tem cerca de 200 linhas.
Nota: Se você fizer algo como o seguinte para ignorar grupos que não são comuns entre as duas tabelas, você acabará eliminando linhas como 000004
nos conjuntos de resultados de exemplo acima, que não é o que estou procurando (membro 000004
e grupo Z
devem ser presente no conjunto de resultados):
SELECT * FROM group_membership WHERE group_name IN (SELECT DISTINCT(name) FROM groups);
Como uma primeira tentativa de resolver esse problema, estou procurando criar um FUNCTION
que dependa de uma recursiva JOIN
sobre a group
tabela para construir a tabela de resultados.
Atualização: A FUNCTION
requer uma RETURNS TABLE
definição, que parece não ser uma solução viável, dado o número variável de colunas no conjunto de resultados. Algumas ideias adicionais que tenho são criar uma função que execute uma série de UNION
s em uma dimensão e, em seguida, seja agrupada com uma visualização que execute um UNION
sobre um crosstab()
dos resultados sobreSELECT DISTINCT(name) FROM groups ORDER BY name ASC;
Parece que você basicamente quer isso, sem escrever isso:
O Postgres não está estruturado para facilitar as tabelas dinâmicas.
aqui eu uso o SQL para formar a consulta acima como uma função temporária e, em seguida, puxo os fluxos de resultado disso
na função principal eu uso uma subseleção que me permite usar um CTE o que significa que posso ordenar os nomes das colunas.
Eu poderia ter criado uma visão temporária dentro da função principal, mas não pensei nisso até agora.
Estou supondo que os valores em group_name não são maiores que 64 octetos,
varchar(64)
não impõe isso - o tiponame
sim e provavelmente é mais adequado para essa tarefa.Aqui está uma função para gerar o valor de qualquer célula (estou usando
text
em vez deint
para evitar conflitos de tipo ao mesclar os cabeçalhos posteriormente):Com a função acima, podemos fazer:
E podemos converter a consulta acima para retornar um array ordenado:
No entanto, eu realmente não quero os colchetes lá, então usarei
string_agg
:Convertendo a consulta acima em uma função:
E, em seguida, invocando a função em uma consulta:
Os resultados acima são exatamente o que estou procurando, embora eu sinta que isso poderia ser melhorado das seguintes maneiras:
É claro que, em retrospecto, os nomes dos meus grupos não podem ser usados como nomes de colunas porque maiúsculas/minúsculas, caracteres especiais, espaços em branco etc.
Então, mantendo a abordagem baseada em array, tentarei obter a próxima melhor coisa.
Obtendo uma linha apenas dos nomes de grupos ordenados:
Então, posso criar uma visão que une os conjuntos de resultados para produzir a matriz completa:
Com esta solução, não há tabelas temporárias nem visualizações materializadas. A exibição produz resultados em um formulário que é fácil de importar para o Excel, então isso funciona para meus propósitos. Eu gostaria de resolver esse problema com menos funções (ou mesmo sem funções).
Exportando na linha de comando, posso soltar os cabeçalhos do conjunto de resultados:
Produz o seguinte arquivo:
Isso não é perfeito devido à citação, mas é próximo o suficiente para importar para o Excel após uma pesquisa/substituição mínima em um editor de texto.
Para chegar um pouco mais perto do arquivo de saída desejado:
E executando:
Produz o seguinte arquivo:
Incrementalmente mais perto, mas ainda não perfeito. No entanto, duas pesquisas/substituições de
""
to"
produzem:Que importa diretamente para o Excel. Essa abordagem pode causar problemas se houver aspas duplas no grupo ou nos nomes dos membros, portanto, se alguém tiver uma solução melhor para as aspas, gostaria de ouvi-la.