J'essaie de comprendre comment stocker correctement les informations commandées dans une base de données relationnelle.
Un exemple:
Disons que j'ai une liste de lecture, composée de morceaux. Dans ma base de données relationnelle, j'ai une table de Playlists
, contenant des métadonnées (nom, créateur, etc.). J'ai également une table appelée Songs
, contenant une information sur la playlist_id
chanson, ainsi que sur la chanson (nom, artiste, durée, etc.).
Par défaut, lorsqu'un nouveau morceau est ajouté à une liste de lecture, il est ajouté à la fin. Lors de la commande sur Song-ID (ascendant), l'ordre sera l'ordre d'addition. Mais que se passe-t-il si un utilisateur doit pouvoir réorganiser des chansons dans la liste de lecture?
J'ai proposé quelques idées, chacune avec ses avantages et ses inconvénients:
- Une colonne appelée
order
, qui est un entier . Lorsqu'un morceau est déplacé, l'ordre de tous les morceaux entre son ancienne et sa nouvelle position est modifié pour refléter le changement. L'inconvénient de cela est que de nombreuses requêtes doivent être effectuées chaque fois qu'un morceau est déplacé, et l'algorithme de déplacement n'est pas aussi trivial qu'avec les autres options. - Une colonne appelée
order
, qui est un décimal (NUMERIC
). Lorsqu'un morceau est déplacé, une valeur à virgule flottante lui est attribuée entre les deux nombres adjacents. Inconvénient: les champs décimaux prennent plus d'espace et il peut être possible de manquer de précision, à moins que l'on prenne soin de redistribuer la plage après quelques modifications. - Une autre façon serait d'avoir un
previous
et unnext
champ qui référencent d'autres chansons. (ou sont NULL dans le cas de la première ou de la dernière chanson de la liste de lecture en ce moment; Fondamentalement, vous créez une liste liée ). Inconvénient: les requêtes comme «trouver le Xème morceau dans la liste» ne sont plus à temps constant, mais à temps linéaire.
Laquelle de ces procédures est la plus utilisée dans la pratique? Laquelle de ces procédures est la plus rapide sur des bases de données moyennes à grandes? Existe-t-il d'autres moyens de l'archiver?
EDIT: Par souci de simplicité, dans l'exemple, un morceau n'appartient qu'à une liste de lecture (relation plusieurs-à-un). Bien sûr, on pourrait également utiliser une table de jonction, donc song⟷playlist est une relation plusieurs-à-plusieurs (et appliquer l'une des stratégies ci-dessus sur cette table).
update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;
- c'est deux mises à jour - pas trente. Trois si vous voulez mettre de l'ordre dans une contrainte unique.Queries like 'find the Xth Song in the list' are no longer constant-time
est également vrai pour l'option 2.Réponses:
Les bases de données sont optimisées pour certaines choses. La mise à jour rapide de nombreuses lignes en fait partie. Cela devient particulièrement vrai lorsque vous laissez la base de données faire son travail.
Considérer:
Et vous voulez passer
Beat It
à la fin, vous auriez deux requêtes:Et c'est tout. Cela évolue très bien avec de très grands nombres. Essayez de mettre quelques milliers de chansons dans une liste de lecture hypothétique dans votre base de données et voyez combien de temps il faut pour déplacer une chanson d'un endroit à un autre. Comme ceux-ci ont des formes très standardisées:
Vous disposez de deux instructions préparées que vous pouvez réutiliser très efficacement.
Cela offre des avantages importants - l'ordre de la table est quelque chose que vous pouvez raisonner. La troisième chanson a toujours un
order
de 3. La seule façon de garantir cela est d'utiliser des entiers consécutifs comme ordre. L'utilisation de listes pseudo-liées ou de nombres décimaux ou d'entiers avec des espaces ne vous permettra pas de garantir cette propriété; dans ces cas, la seule façon d'obtenir le nième morceau est de trier la table entière et d'obtenir le nième enregistrement.Et vraiment, c'est beaucoup plus facile que vous ne le pensez. Il est simple de comprendre ce que vous voulez faire, de générer les deux instructions de mise à jour et pour que d'autres personnes les regardent et réalisent ce qui est fait.
la source
order
puisqueorder by
c'est un mot clé?order
valeur lors de l'ajout d'une nouvelle chanson à une liste de lecture. Disons que c'est la 9ème chanson, y a-t-il une meilleure façon d'insérer 9order
que de faire unCOUNT
avant d'ajouter l'enregistrement?Tout d'abord, il n'est pas clair d'après votre description de ce que vous avez fait, mais vous avez besoin d'un
PlaylistSongs
tableau qui contient unPlaylistId
et unSongId
, décrivant quelles chansons appartiennent à quelles listes de lecture.C'est dans ce tableau que vous devez ajouter les informations de commande.
Mon mécanisme préféré est avec des nombres réels. Je l'ai implémenté récemment et cela a fonctionné comme un charme. Lorsque vous souhaitez déplacer un morceau vers une position spécifique, vous calculez sa nouvelle
Ordering
valeur comme la moyenne desOrdering
valeurs du morceau précédent et du morceau suivant. Si vous utilisez un nombre réel 64 bits, vous manquerez de précision à peu près au même moment où l'enfer gèlera, mais si vous écrivez vraiment votre logiciel pour la postérité, alors envisagez de réaffecter de bellesOrdering
valeurs entières arrondies à toutes les chansons de chaque playlist de temps en temps.En prime, voici le code que j'ai écrit qui implémente cela. Bien sûr, vous ne pouvez pas l'utiliser tel quel, et ce serait trop de travail pour moi en ce moment de le désinfecter pour vous, donc je ne le poste que pour que vous puissiez en tirer des idées.
La classe est
ParameterTemplate
(peu importe, ne demandez pas!) La méthode obtient la liste des modèles de paramètres auxquels ce modèle appartient de son parentActivityTemplate
. (Quoi qu'il en soit, ne demandez pas!) Le code contient une certaine protection contre le manque de précision. Le diviseur est utilisé pour les tests: le test unitaire utilise un grand diviseur afin de manquer de précision rapidement, et ainsi déclencher le code de garde de précision. La deuxième méthode est publique et "à usage interne uniquement; ne pas invoquer" afin que le code de test puisse l'invoquer. (Il ne peut pas être privé de package car mon code de test n'est pas dans le même package que le code qu'il teste.) Le champ qui contrôle la commande est appeléOrdering
, accessible viagetOrdering()
etsetOrdering()
. Vous ne voyez aucun SQL car j'utilise le mappage relationnel-objet via Hibernate.la source
Ce qui a fonctionné pour moi, pour une petite liste de l'ordre de 100 articles, c'est d'adopter une approche hybride:
Vous vous retrouvez donc avec un ordre entier sans espace, stocké dans une colonne décimale. C'est assez propre, je pense. Mais il peut ne pas évoluer extrêmement bien une fois que vous avez des centaines de milliers de lignes que vous devez mettre à jour en une seule fois. Mais si vous le faites, pourquoi utilisez-vous un tri défini par l'utilisateur en premier lieu? (Remarque: si vous avez une grande table avec des millions d'utilisateurs mais que chaque utilisateur n'a que quelques centaines d'éléments à trier, vous pouvez très bien utiliser l'approche ci-dessus car vous utiliserez de toute façon une clause where pour limiter les modifications à un seul utilisateur )
la source