Nunca esquecerei uma das minhas primeiras tarefas SQL como jovem desenvolvedor júnior no Governo Federal. Tive de produzir um relatório para um dos nossos clientes para o ajudar a gerar algumas estatísticas para o gabinete do ministro. Quando os números do relatório variaram de acordo com as suas próprias estimativas por uma larga margem, convocaram uma reunião entre o meu chefe e eu. Não fazia ideia de onde ou que tinha errado, até o meu patrão ter dito “Robert, você criou um produto cartesiano”. Depois de ela ter terminado de me interrogar em frente dos nossos clientes, a reunião foi adiada e eu saí para reescrever a minha pergunta. Acabei por descobrir o que eles procuravam, mas o meu patrão nunca me deixou vivê-la, e depois disso, em todas as reuniões: “Robert, lembra-te da vez em que criaste um Produto Cartesiano…”. Acabei por me transferir para um departamento diferente cerca de seis meses mais tarde. Agora, é verdade que o meu patrão não era uma pessoa especialmente clemente, mas erros como esses podem realmente prejudicar a sua carreira. Hoje, gostaria de partilhar consigo o que aprendi sobre Produtos Cartesianos ao longo dos anos, para que os possa identificar e banir das suas consultas SELECT para sempre.
Como Gerar um Produto Cartesiano
A consulta seguinte extrai dados de duas tabelas sem qualquer tipo de filtragem. Omitir a cláusula WHERE pode ser útil em situações em que deseja ver todas as linhas de uma tabela mas deseja reordenar ou ocultar colunas não relevantes:
SELECT name, gender, CONCAT('$', FORMAT(salary, 2)) AS 'Monthly Salary' FROM employees, shops;
O problema aqui é que a consulta selecciona a partir de múltiplas tabelas. Sem qualquer adesão explícita a tabelas, acabamos com uma espécie de adesão por defeito chamada Join Cartesiana (ou Cross Join). O nome Cross Join refere-se ao facto de se juntar cada fila da primeira tabela a cada fila da segunda tabela. Por outras palavras, as Entradas Cartesianas representam a soma do número de colunas das tabelas de entrada mais o produto do número de linhas das tabelas de entrada.
É possível ver nos resultados que cada linha da primeira (empregados) tabela é devolvida para cada linha da segunda (lojas) tabela. Uma vez que existem três linhas na tabela de lojas, a consulta produz três de cada linha da tabela de empregados:
É um lote de linhas para duas pequenas tabelas. Os resultados crescem exponencialmente quando estão envolvidas mais filas e/ou mesas. Devido ao esforço que tal consulta coloca nos recursos do sistema e que o conjunto de dados resultante contém demasiada informação para o autor da consulta seleccionar o que é interessante, as Juntas Cartesianas são quase sempre realizadas por acidente. Como aprendemos no meu próprio conto de advertência, é bom saber como detectar uma antes que os seus clientes ou supervisor lhe voltem com perguntas sobre o porquê de haver tantas filas duplicadas. De facto, a presença de muitos duplicados, combinada com um conjunto de resultados invulgarmente grande, é um sinal indicador de que poderá ter um Produto Cartesiano nas suas mãos.
Como os critérios de filtragem podem mascarar um Produto Cartesiano
DeclaraçõesSELECT que contêm uma cláusula WHERE podem facilmente esconder um Produto Cartesiano porque nem todas as filas aparecerão em duplicado. Aqui está uma consulta de aspecto inocente o suficiente na qual alguém se esqueceu de incluir uma junta de tabela:
SELECT name, gender, CONCAT('$', FORMAT(salary, 2)) AS 'Monthly Salary' FROM shops, employeesWHERE shops.shop = 'Zurich';
Desde que a loja não apareça no conjunto de resultados, seria fácil aceitar a saída para ser precisa. Contudo, podemos facilmente verificar que apenas os dois primeiros empregados da lista trabalham em Zurique. Os outros três parecem ter escapado aos nossos critérios de filtragem!
O resultado de todas as colunas mostra mais claramente o que se está a passar. De facto, o filtro apenas devolveu a loja em Zurique. No entanto, sem uma tabela adequada juntar-se à consulta produz um registo para cada empregado, quer estejam ou não ligados à loja de Zurique. Isto faz sentido quando se considera que os empregados não estão associados a nenhuma loja sem uma adesão. Assim, o campo shop_id na tabela de empregados não tem nada a ver com o das lojas. O que a pergunta diz é “Traga-me todas as filas da tabela das lojas onde o nome corresponde a ‘Zurique’ e todas as filas da tabela dos empregados”:
shop_id |
shop |
id |
shop_id_1 |
gender |
p>nome |
salary |
Zurique |
m |
Jon Simpson |
||||
Zurique |
f >/td> |
Barbara Breitenmoser |
(NULL) |
|||
Zurique |
f |
Kirsten Ruegg |
||||
Zurique |
m |
Ralph Teller |
||||
Zurique |
m |
p>Peter Jonson |
Similiarmente, estreitando os resultados da tabela de empregados produz apenas linhas dessa tabela que correspondem aos critérios e todas as linhas da outra tabela. Aqui está uma consulta que filtra os empregados por salário:
SELECT name, gender, CONCAT('$', FORMAT(salary, 2)) AS 'Monthly Salary' FROM shops, employeesWHERE employees.salary > 5500;
Corresponde a uma linha da tabela de empregados, que é apresentada uma vez para cada linha da tabela de lojas:
nome |
género |
Salário Mensal |
Kirsten Ruegg |
f |
$5,600.00 |
Kirsten Ruegg |
f |
$5.600.00 |
Kirsten Ruegg |
f |
$5.600.00 |
Again, incluindo todas as colunas confirma esta suposição:
id >/td> |
p>shop_id |
gender |
nome /td> |
salary |
loja_id_1 |
shop /td> |
f |
Kirsten Ruegg |
Zurique |
||||
f |
Kirsten Ruegg |
Nova Iorque |
||||
f |
Kirsten Ruegg |
London |
Agora estes resultados não parecem imediatamente suspeitos porque, dependendo dos critérios e das colunas que são exibidas, os valores duplicados não são uma ocorrência invulgar:
A consulta continua a exibir um empregado para cada linha da tabela de lojas, mas desta vez exibe um empregado para cada linha da tabela de lojas, bem como três linhas para o empregado correspondente. Por outras palavras, todos os cinco empregados são exibidos para as lojas correspondentes (a de Zurique) e o registo do empregado cujo rendimento exceda $5500 (Kristen Ruegg) é repetido para cada linha da tabela de lojas.
Como vimos hoje, os Produtos Cartesianos não tendem a fornecer informações úteis. Portanto, a moral da história é a seguinte: evite as Juntas Cartesianas a todo o custo, a menos que tenha uma razão cristalina para o fazer.
Obtendo os Dados Corretos com Juntas SQL
” Ver Todos os Artigos do Colunista Rob Gravelle