Arquivo da tag: index

Uma tabela de relacionamento de muitos para muitos – resolvendo o problema de relação de exclusão

Começarei definindo o relacionamento de Muitos para Muitos MySQL (Experts podem pular para o próximo parágrafo).

O que é um relacionamento de Muitos para Muitos MySQL

Um relacionamento de Muitos para Muitos MySQL é um relacionamento que é multi-valorado em ambas as direções.

Esse tipo de relacionamento é auxiliado pelo uso de uma tabela de ligação. Por exemplo, uma Pergunta (Question) pode ter mais de uma Categoria (Categorie), e uma Categoria pode ter mais de uma Pergunta.

CREATE TABLE link (
   Question_id int unsigned NOT NULL,
   Category varchar(20),
   PRIMARY KEY  (`Question_id `,` Category`),
   KEY `category_question` (`Category`,`Question_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

+-------------+---------------+
| Question_id | Category      |
+-------------+---------------+
|  1          | Animals       |
|  1          | Business      |
|  1          | Vehicles      |
|  2          | Animals       |
|  3          | Business      |
|  3          | Vehicles      |
|  4          | Animals       |
|  4          | Vehicles      |
|  5          | Business      |
+-------------+---------------+

Essa é uma tabela de ligação chamada link, que nos ajuda a juntar relacionamentos entre as duas tabelas, Perguntas e Categorias.

Como podemos ver, essa tabela está refletindo um relacionamento de muitos para muitos (a pergunta 1 tem as categorias Animais, Negócios e Veículos – ‘Animals, Business and Vehicles’. A categoria Animais tem as perguntas 1, 2 e 4).

O que é o problema de relação de exclusão?

Dada uma tabela de relacionamento de muitos para muitos, como você é capaz de selecionar linhas que não tenham um relacionamento específico? Em termos do exemplo pergunta-categoria, essa pergunta é traduzida no seguinte:

Como você consegue selecionar (digamos as primeiras mil) perguntas que não têm a categoria Negócios. No nosso exemplo, queremos somente que os Question_id 2 e 4 sejam retornados.

Uma solução ruim

Uma solução ruim seria:

SELECT
DISTINCT Question_id
FROM
link
WHERE
Category <> "Business"
ORDER BY
Question_id DESC
LIMIT
1000;

Essa consulta irá funcionar se uma pergunta somente for permitida uma categoria. No nosso caso, como uma pergunta tem pelo menos uma categoria, desde que uma pergunta esteja associada à outra categoria que não seja Business, ela retornará. Portanto, o resultado dessa consulta é Question_id: 1, 2, 3, e 4 , que não satisfaz a relação de exclusão.

Primeira solução

SELECT
DISTINCT Question_id
FROM
link
WHERE
Question_id NOT IN (SELECT Question_id FROM link WHERE Category="Business")
ORDER BY
Question_id DESC
LIMIT
1000;

A consulta dependente interna (SELECT Question_id FROM link WHERE Category<>”Business”) tem um problema de desempenho – ela será executada pela menos mil vezes e, se ela não for rápida, os atrasos são multiplicados por um número que é, no mínimo, 1.000. Quando o método de acesso (o type field no EXPLAIN statement output) é index_subquery, um index de pesquisa de otimização é usado e um monte de sobrecarga é evitada.

Quando é ampla, a sub query está sendo reexecutada, como é feita para cada linha. Você deve verificar seus . You must check your EXPLAIN details.

Segunda solução

CREATE TEMPORARY TABLE not_wanted_Question_id (
UNIQUE KEY(Question_id)
) ENGINE=MEMORY
SELECT
DISTINCT Question_id
FROM
link
WHERE
Category="Business";

SELECT
DISTINCT link.Question_id
FROM
link
LEFT JOIN
not_wanted_Question_id nw ON (link.Question_id = nw.Question_id)
WHERE
nw.Question_id is NULL
ORDER BY
link.Question_id DESC
LIMIT
1000;

DROP TABLE not_wanted_Question_id;

O problema de execução de consultas internas múltiplas é reduzido a uma pesquisa uma única chave dentro de uma tabela na memória, que é basicamente uma pesquisa hash e é muito rápida. No entanto, a construção de perguntas indesejadas pode sair caro para tabelas grandes. Note que criar a tabela, selecionar e então soltar devem ser todos feitos juntos, para toda vez que você precisar dos resultados.

Terceira solução (a melhor em desempenho)

Esta é minha melhor solução para esse problema e com melhor desempenho para tabelas pesadas:

SELECT
 Question_id ,
 SUM(IF (Category="Business", 1, 0)) AS exclude
 FROM
 link
 GROUP BY
 Question_id DESC
 HAVING
 exclude=0
 LIMIT
 10000;

Isso funciona muito bem no nosso caso, porque temos um index no Question_id e colunas de categorias. A função SUM está contando o número de ocorrências indesejadas de categoria para cada Question_id na ordem e o statement Having está filtrando-os.

O LIMIT termina os cálculos assim que chega no número limite.

Texto original em ingles de Hazan Ilan, disponível em http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/

Fonte: Hazan Ilan/IMasters