Encontre as capitalizações mais comuns do valor da coluna MySQL

0

Suponha que eu tenha duas tabelas MySQL representando itens de algum tipo e os rótulos de texto que são aplicados a esses itens. É um mapeamento de muitos para muitos de itens para rótulos, representado com relações de chave estrangeira:

> SELECT * FROM Labels;
+----+--------+
| id | label  |
+----+--------+
| 1  | foobar |
| 2  | FooBar |
| 3  | fooBar |
| 4  | Foobar |
| 5  | BLAH   |
| 6  | blah   |
| 7  | Stuff  |
+----+--------+

> SELECT * FROM ItemsToLabels;
+----+----------+
| id | label_id |
+----+----------+
| 1  | 1        |
| 1  | 6        |
| 2  | 1        |
| 3  | 1        |
| 3  | 7        |
| 4  | 2        |
| 5  | 4        |
| 6  | 6        |
| 7  | 5        |
| 7  | 6        |
+----+----------+

Eu quero construir uma consulta que me forneça a lista de capitalizações de rótulo mais usadas . Em outras palavras, quero o seguinte resultado:

> SELECT some magic query;
+----+--------+-----+
| id | label  | cnt |
+----+--------+-----+
| 1  | foobar | 3   |
| 6  | blah   | 3   |
| 7  | Stuff  | 1   |
+----+--------+-----+

Eu sei que posso usar COLLATE UTF8_GENERAL_CI para obter todas as linhas com a mesma ortografia, mas diferentes capitalizações, mas isso não me levou a uma solução. Alguma idéia?

    
por agable 30.03.2016 / 19:44

1 resposta

0

Eu acho que descobri. Muito obrigado a Alex Reece por ser uma caixa de ressonância e a esta resposta por fornecer uma estrutura para obter resultados estáveis. Eu coloquei minha resposta final e meu processo para chegar abaixo.

Resposta final

> SELECT lc1.id, lc1.label, mc.max_cnt
FROM (
  SELECT Labels.id, Labels.label, COUNT(*) AS cnt
  FROM Labels
  JOIN ItemsToLabels
  ON Labels.id = ItemsToLabels.label_id
  GROUP BY Labels.id
) lc1
INNER JOIN (
  SELECT id, label, max(cnt) as max_cnt
  FROM (
    SELECT Labels.id, Labels.label, COUNT(*) AS cnt
    FROM Labels
    JOIN ItemsToLabels
    ON Labels.id = ItemsToLabels.label_id
    GROUP BY Labels.id
  ) lc2
  GROUP BY label COLLATE UTF8_GENERAL_CI
) mc
ON lc1.cnt = mc.max_cnt
AND lc1.label LIKE mc.label COLLATE UTF8_GENERAL_CI;
+----+--------+-----+
| id | label  | cnt |
+----+--------+-----+
|  1 | foobar |   3 |
|  6 | blah   |   3 |
|  7 | Stuff  |   1 |
+----+--------+-----+

Você pode ver um SQLFiddle dele aqui: link

Processo para chegar a ele

Contagens de computação

O primeiro passo foi simplesmente calcular o número de vezes que cada etiqueta foi usada. Isso é bem direto:

> SELECT Labels.id, Labels.label, COUNT(*) AS cnt
  FROM Labels
  JOIN ItemsToLabels
  ON Labels.id = ItemsToLabels.label_id
  GROUP BY Labels.id;
+----+--------+-----+
| id | label  | cnt |
+----+--------+-----+
|  1 | foobar |   3 |
|  2 | FooBar |   1 |
|  3 | fooBar |   0 |
|  4 | Foobar |   1 |
|  5 | BLAH   |   1 |
|  6 | blah   |   3 |
|  7 | Stuff  |   1 |
+-------------+-----+

Obtendo contagens máximas

Em seguida, preciso classificar os rótulos por contagem decrescente e, em cada conjunto de rótulos que são escritos da mesma forma, mas possuem letras maiúsculas diferentes, escolha a parte superior:

> SELECT id, label, max(cnt)
  FROM (subquery ORDER BY cnt) AS s
  GROUP BY label COLLATE UTF8_GENERAL_CI;

Isso resulta em uma consulta que se parece com isso:

> SELECT id, label, max(cnt)
  FROM (
      SELECT Labels.id, Labels.label, COUNT(*) AS cnt
      FROM Labels
      JOIN ItemsToLabels
      ON Labels.id = ItemsToLabels.label_id
      GROUP BY Labels.id
      ORDER BY cnt DESC;
  ) AS s
  GROUP BY label COLLATE UTF8_GENERAL_CI;
+----+--------+----------+
| id | label  | max(cnt) |
+----+--------+----------+
|  1 | foobar |        3 |
|  6 | blah   |        3 |
|  7 | Stuff  |        1 |
+----+--------+----------+

Isso parece certo! E é quase ...

Por que isso não funciona

Acontece que o MySQL não garante que o GROUP BY realize uma classificação estável. É apenas por detalhes de implementação interna / de mudança que fazer um ORDER BY na subconsulta, seguido pelo GROUP BY na consulta externa, faz com que a linha com classificação superior ainda esteja no topo. E a instrução SELECT id, label, max(cnt) não garante que o ID e o rótulo que ela pega serão da mesma linha que o max (cnt). Por exemplo, apenas alterar ORDER BY na subconsulta resulta nas mesmas contagens retornadas, mas nos rótulos incorretos:

> SELECT id, label, max(cnt)
  FROM (
      SELECT Labels.id, Labels.label, COUNT(*) AS cnt
      FROM Labels
      JOIN ItemsToLabels
      ON Labels.id = ItemsToLabels.label_id
      GROUP BY Labels.id
      ORDER BY cnt ASC;
  ) AS s
  GROUP BY label COLLATE UTF8_GENERAL_CI;
+----+--------+----------+
| id | label  | max(cnt) |
+----+--------+----------+
|  1 | fooBar |        3 |
|  6 | BLAH   |        3 |
|  7 | Stuff  |        1 |
+----+--------+----------+

Então, precisamos de uma maneira de evitar isso.

Uma solução mais estável

Por sorte, eu encontrei essa outra resposta , que explica como fazer algo parecido com o que eu quero. O problema é que, nesse problema, o valor que eles querem no máximo (timestamp) já está na tabela. Enquanto neste problema, eu tenho que calcular a contagem.

Então, minha primeira tentativa foi criar uma tabela contendo todas as informações de que preciso e, em seguida, fazer referência a ela como na outra resposta:

CREATE TABLE LabelCounts (
    'id' INT, 'label' VARCHAR(80), 'cnt' INT
);

INSERT INTO LabelCounts (
  SELECT Labels.id, Labels.label, COUNT(*) AS cnt
  FROM Labels
  JOIN ItemsToLabels
  ON Labels.id = ItemsToLabels.label_id
  GROUP BY Labels.id
);

SELECT *
FROM LabelCounts lc1
INNER JOIN (
  SELECT id, label, max(cnt) as max_cnt
  FROM LabelCounts
  GROUP BY label COLLATE UTF8_GENERAL_CI
) lc2
ON lc1.cnt = lc2.max_cnt
AND lc1.label LIKE lc2.label COLLATE UTF8_GENERAL_CI;

Isso faz o trabalho. Você pode ver que ele recalcula corretamente o rótulo correto, não importando o retorno da consulta interna, ao JOIN na contagem sendo igual à contagem máxima.

Infelizmente, eu não iria executar isso em um ambiente onde eu poderia criar tabelas adicionais. E se eu usei CREATE TEMPORARY TABLE , não posso referenciá-lo duas vezes como preciso nesta consulta. Então o passo final foi alinhar a criação da tabela LabelCounts. Você pode ver a consulta resultante no topo desta resposta.

    
por 30.03.2016 / 21:02

Tags