Pergunta

Fiquei me perguntando qual é a melhor maneira de implementar um sistema de tags, como o usado no SO.Eu estava pensando nisso, mas não consigo encontrar uma boa solução escalonável.

Eu estava pensando em ter uma solução básica de 3 tabelas:tendo uma tags mesa, um articles mesas e um tag_to_articles mesa.

Esta é a melhor solução para este problema ou existem alternativas?Usando esse método, a tabela ficaria extremamente grande com o tempo e, para pesquisar, isso não é muito eficiente, presumo.Por outro lado, não é tão importante que a consulta seja executada rapidamente.

Foi útil?

Solução

Eu acredito que você achará interessante esta postagem no blog: Tags: esquemas de banco de dados

O problema: você deseja ter um esquema de banco de dados onde possa marcar um marcador (ou uma postagem no blog ou o que for) com o quantos tags quiser. Mais tarde, você deseja executar consultas para restringir os favoritos a um sindicato ou cruzamento de tags. Você também deseja excluir (digamos: menos) algumas tags do resultado da pesquisa.

Solução "mysqlicious"

Nesta solução, o esquema tem apenas uma tabela, é desnormalizada. Esse tipo é chamado de “solução mySqlicious” porque o mysqlicious importa Del.icio.us dados em uma tabela com essa estrutura.

enter image description hereenter image description here

Interseção (e) Consulta para "Search+Web Service+SemWeb":

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

União (ou) Consulta para “Pesquisa | Serviço da Web | SemWeb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

Consulta menos para "Search+Web Service-semweb"

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

Solução "Scuttle"

Scuttle organiza seus dados em duas tabelas. Essa tabela “Sccategories” é a "tag" -Table e tem uma chave estrangeira para a tabela de "marcadores".

enter image description here

Interseção (e) Consulta para "Bookmark+Web Service+SemWeb":

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

Primeiro, todas as combinações de marcadores de marcadores são pesquisadas, onde a tag é "Bookmark", "Web Service" ou "semweb" (categoria em ('marcador', 'service da web', 'semweb')), então apenas os favoritos que As três tags procuradas são levadas em consideração (tendo contagem (b.bid) = 3).

União (ou) Consulta para “Bookmark | Web Service | SemWeb”:Basta deixar de fora a cláusula de ter e você terá Union:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

Consulta de menos (exclusão) para “Bookmark+Web Service-SEMWEB”, ou seja: Bookmark e Web Service e não SemWeb.

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

Deixar de fora a contagem de ter leva à consulta para "Bookmark | Web Service-semweb".


Solução "toxi"

Toxi criou uma estrutura de três tabela. Através da tabela "Tagmap", os favoritos e as tags estão relacionados a N para M. Cada tag pode ser usada em conjunto com diferentes favoritos e vice -versa. Este db-schema também é usado pelo WordPress. As consultas são as mesmas da solução "Scuttle".

enter image description here

Interseção (e) Consulta para “Bookmark+Web Service+SemWeb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

União (ou) Consulta para “Bookmark | Web Service | SemWeb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

Consulta de menos (exclusão) para “Bookmark+Web Service-SEMWEB”, ou seja: Bookmark e Web Service e não SemWeb.

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

Deixar de fora a contagem de ter leva à consulta para "Bookmark | Web Service-semweb".

Outras dicas

Nada de errado com sua solução de três tabela.

Outra opção é limitar o número de tags que podem ser aplicadas a um artigo (como 5 em SO) e adicioná -las diretamente à sua tabela de artigos.

A normalização do banco de dados tem seus benefícios e desvantagens, assim como as coisas de uma tira em uma tabela tem benefícios e desvantagens.

Nada diz que você não pode fazer as duas coisas. Isso vai contra os paradigmas relacionais do banco de dados para repetir informações, mas se o objetivo é o desempenho, você pode ter que quebrar os paradigmas.

A implementação proposta de três tabela funcionará para marcação.

O Stack Overflow usa, no entanto, implementação diferente. Eles armazenam tags na coluna VARCHAR na tabela de postagens em texto simples e usam indexação de texto completo para buscar postagens que correspondam às tags. Por exemplo posts.tags = "algorithm system tagging best-practices". Tenho certeza de que Jeff mencionou isso em algum lugar, mas esqueço onde.

A solução proposta é a melhor-se não é a única maneira que posso pensar para abordar o relacionamento de muitos para muitos entre tags e artigos. Então, meu voto é para 'Sim, ainda é o melhor'. Eu estaria interessado em qualquer alternativa.

Se o seu banco de dados suportar matrizes indexáveis ​​(como PostgreSQL, por exemplo), eu recomendaria uma solução totalmente desnormalizada - armazene tags como uma matriz de strings na mesma tabela.Caso contrário, uma tabela secundária mapeando objetos para tags é a melhor solução.Se precisar armazenar informações extras em tags, você pode usar uma tabela de tags separada, mas não faz sentido introduzir uma segunda junção para cada pesquisa de tag.

Gostaria de sugerir o MySqlicious otimizado para melhor desempenho. Antes disso, as desvantagens da solução de toxi (3 tabela) são

Se você tiver milhões de perguntas e possui 5 tags em cada uma, haverá 5 milhões de entradas na tabela de tagmap. Então, primeiro temos que filtrar 10 mil entradas de tagmap com base na pesquisa de tags e depois filtrar as perguntas correspondentes dessas 10 mil. Portanto, enquanto filtrando se o ID do ARTIC é simples numérico, está tudo bem, mas se for um meio UUID (32 Varchar), filtrar precisa de uma comparação maior, embora seja indexada.

Minha solução:

Sempre que a nova tag é criada, tenha contador ++ (base 10) e converta esse contador em base64. Agora, cada nome de tag terá ID base64. e passe este ID para a interface do usuário junto com o nome. Dessa forma, você terá no máximo dois ID de char até termos 4095 tags criadas em nosso sistema. Agora concatenar essas várias tags em cada coluna de tags de tabela de perguntas. Adicione o delimitador também e faça com que seja classificado.

Então a mesa se parece com isso

enter image description here

Durante a consulta, consulte o ID em vez do nome de tag real. Já que é Classificado, and A condição da tag será mais eficiente (LIKE '%|a|%|c|%|f|%).

Observe que o delimitador de espaço único não é suficiente e precisamos de delimitador duplo para diferenciar tags como sql e mysql Porque LIKE "%sql%" retornará mysql resultados também. Deveria estar LIKE "%|sql|%"

Sei que a pesquisa não é indexada, mas você pode ter indexado em outras colunas relacionadas a artigos como o autor/datetime, o caso levará à varredura completa da tabela.

Finalmente, com esta solução, nenhuma junção interna é necessária, onde milhões de registros devem ser comparados com 5 milhões de registros na condição de junção.

CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

Notas:

  • Isso é melhor do que Toxi, pois não passa por muitas: muitas tabela que dificulta a otimização.
  • Claro, minha abordagem pode ser um pouco mais volumosa (do que toxi) devido às tags redundantes, mas essa é uma pequena porcentagem do todo O banco de dados e as melhorias de desempenho podem ser significativas.
  • É altamente escalável.
  • Não tem (porque não precisa) um substituto AUTO_INCREMENT Pk. Portanto, é melhor que Scuttle.
  • MySqlicious é uma merda porque não pode usar um índice (LIKE com conduzindo curinga; False hits em substringas)
  • Para o MySQL, use o mecanismo = innodb para obter efeitos de 'agrupamento'.

Discussões relacionadas (para MySQL):
Muitos: muitos mapeamentos de otimização da tabela
listas ordenadas

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top