Estou tentando entender como armazenar corretamente as informações solicitadas em um banco de dados relacional.
Um exemplo:
Digamos que eu tenha uma lista de reprodução, composta por músicas. Dentro do meu banco de dados relacional, tenho uma tabela Playlists
contendo alguns metadados (nome, criador, etc.). Eu também tenho uma tabela chamada Songs
, contendo informações sobre a playlist_id
música e também sobre a música (nome, artista, duração etc.).
Por padrão, quando uma nova música é adicionada a uma lista de reprodução, ela é anexada ao final. Ao fazer o pedido no ID da música (crescente), a ordem será a ordem de adição. Mas e se um usuário conseguir reordenar músicas na lista de reprodução?
Eu vim com algumas idéias, cada uma com suas vantagens e desvantagens:
- Uma coluna chamada
order
, que é um número inteiro . Quando uma música é movida, a ordem de todas as músicas entre a antiga e a nova posição é alterada, para refletir a alteração. A desvantagem disso é que muitas consultas precisam ser feitas toda vez que uma música é movida, e o algoritmo de movimentação não é tão trivial quanto nas outras opções. - Uma coluna chamada
order
, que é um decimal (NUMERIC
). Quando uma música é movida, é atribuído o valor do ponto flutuante entre os dois números adjacentes. Desvantagem: os campos decimais ocupam mais espaço e pode ser possível ficar sem precisão, a menos que seja tomado o cuidado de redistribuir o intervalo após algumas alterações. - Outra maneira seria ter
previous
umnext
campo e que faça referência a outras músicas. (ou são NULL no caso da primeira e da última música da lista de reprodução no momento; basicamente, você cria uma lista vinculada ). Desvantagem: consultas como 'encontrar a décima música na lista' não são mais de tempo constante, mas sim de tempo linear.
Qual desses procedimentos é mais frequentemente usado na prática? Qual desses procedimentos é mais rápido em bancos de dados de médio a grande porte? Existem outras maneiras de arquivar isso?
EDIT: Por uma questão de simplicidade, no exemplo, uma música pertence apenas a uma lista de reprodução (um relacionamento muitos-para-um). Obviamente, também é possível usar uma Junction Table para que a lista de músicas seja uma relação de muitos para muitos (e aplique uma das estratégias acima nessa tabela).
update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;
- são duas atualizações - não trinta. Três, se você quiser colocar uma restrição exclusiva em ordem.Queries like 'find the Xth Song in the list' are no longer constant-time
também é válido para a opção 2.Respostas:
Os bancos de dados são otimizados para certas coisas. A atualização rápida de muitas linhas é uma delas. Isso se torna especialmente verdadeiro quando você deixa o banco de dados fazer seu trabalho.
Considerar:
E você quer ir
Beat It
para o final, você teria duas consultas:E é isso. Isso aumenta muito bem com números muito grandes. Tente colocar alguns milhares de músicas em uma lista de reprodução hipotética em seu banco de dados e veja quanto tempo leva para mover uma música de um local para outro. Como estes têm formas muito padronizadas:
Você tem duas instruções preparadas que podem ser reutilizadas com muita eficiência.
Isso fornece algumas vantagens significativas - a ordem da tabela é algo que você pode pensar. A terceira música tem um
order
de 3, sempre. A única maneira de garantir isso é usar números inteiros consecutivos como a ordem. O uso de listas pseudo-vinculadas ou números decimais ou números inteiros com lacunas não permitirá garantir essa propriedade; nesses casos, a única maneira de obter a enésima música é ordenar a tabela inteira e obter o enésimo registro.E realmente, isso é muito mais fácil do que você pensa. É simples descobrir o que você deseja fazer, gerar as duas instruções de atualização e outras pessoas olharem para essas duas instruções de atualização e perceberem o que está sendo feito.
fonte
order
uma vez queorder by
é uma palavra-chave?order
valor ao adicionar uma nova música a uma lista de reprodução. Digamos que seja a 9ª música, existe uma maneira melhor de inserir 9 doorder
que fazerCOUNT
antes de adicionar o disco?Primeiro, não está claro em sua descrição o que você fez, mas você precisa de uma
PlaylistSongs
tabela que contenha aPlaylistId
e aSongId
, descrevendo quais músicas pertencem a quais listas de reprodução.É nesta tabela que você precisa adicionar informações sobre pedidos.
Meu mecanismo favorito é com números reais. Eu o implementei recentemente e funcionou como um encanto. Quando você deseja mover uma música para uma posição específica, calcula seu novo
Ordering
valor como a média dosOrdering
valores da música anterior e da próxima música. Se você usar um número real de 64 bits, ficará sem precisão quase ao mesmo tempo em que o inferno congelará, mas se estiver realmente escrevendo seu software para a posteridade, considere reatribuir bonsOrdering
valores inteiros arredondados a todas as músicas em cada lista de reprodução de vez em quando.Como um bônus adicional, aqui está o código que eu escrevi que implementa isso. É claro que você não pode usá-lo como está, e agora seria muito trabalhoso higienizá-lo para você, por isso estou postando apenas para que você possa ter idéias.
A classe é
ParameterTemplate
(seja o que for, não pergunte!) O método obtém a lista de modelos de parâmetros aos quais esse modelo pertence do paiActivityTemplate
. (Seja como for, não pergunte!) O código contém alguma proteção contra a falta de precisão. O divisor é usado para teste: o teste de unidade usa um divisor grande para ficar sem precisão rapidamente e, assim, acionar o código de proteção de precisão. O segundo método é público e "apenas para uso interno; não invoque" para que o código de teste possa invocá-lo. (Não pode ser privado de pacote porque meu código de teste não está no mesmo pacote que o código que ele testa.) O campo que controla a ordem é chamadoOrdering
, acessado por meio degetOrdering()
esetOrdering()
. Você não vê nenhum SQL porque estou usando o Mapeamento Relacional a Objetos via Hibernate.fonte
O que funcionou para mim, para uma pequena lista da ordem de 100 itens, foi usar uma abordagem híbrida:
Então, você acaba com uma ordem inteira sem intervalos, armazenada em uma coluna decimal. É bastante limpo, eu sinto. Mas isso pode não aumentar muito bem quando você tiver centenas de milhares de linhas que precisam atualizar, tudo de uma vez. Mas se sim, por que você está usando uma classificação definida pelo usuário em primeiro lugar? (Nota: se você tiver uma tabela grande com milhões de usuários, mas cada usuário tiver apenas algumas centenas de itens para classificar, poderá usar a abordagem acima muito bem, pois você usará uma cláusula where para limitar as alterações a apenas um usuário )
fonte