Ordre arbitraire des enregistrements dans une table


28

Un besoin courant lors de l'utilisation d'une base de données est d'accéder aux enregistrements dans l'ordre. Par exemple, si j'ai un blog, je veux pouvoir réorganiser mes articles de blog dans un ordre arbitraire. Ces entrées ont souvent beaucoup de relations, donc une base de données relationnelle semble logique.

La solution courante que j'ai vue est d'ajouter une colonne entière order:

CREATE TABLE AS your_table (id, title, sort_order)
AS VALUES
  (0, 'Lorem ipsum',   3),
  (1, 'Dolor sit',     2),
  (2, 'Amet, consect', 0),
  (3, 'Elit fusce',    1);

Ensuite, nous pouvons trier les lignes par orderpour les obtenir dans le bon ordre.

Cependant, cela semble maladroit:

  • Si je veux déplacer l'enregistrement 0 au début, je dois réorganiser chaque enregistrement
  • Si je veux insérer un nouvel enregistrement au milieu, je dois réorganiser chaque enregistrement après
  • Si je veux supprimer un enregistrement, je dois réorganiser chaque enregistrement après

Il est facile d'imaginer des situations comme:

  • Deux enregistrements ont le même order
  • Il existe des écarts orderentre les enregistrements

Cela pourrait se produire assez facilement pour un certain nombre de raisons.

Voici l'approche adoptée par des applications comme Joomla:

Exemple d'approche de Joomla pour la commande

Vous pourriez dire que l'interface ici est mauvaise, et qu'au lieu que les humains éditent directement les nombres, ils devraient utiliser des flèches ou glisser-déposer - et vous auriez probablement raison. Mais dans les coulisses, la même chose se produit.

Certaines personnes ont proposé d'utiliser une décimale pour stocker la commande, de sorte que vous pouvez utiliser "2,5" pour insérer un enregistrement entre les enregistrements de l'ordre 2 et 3. Et bien que cela aide un peu, c'est sans doute encore plus compliqué car vous pouvez vous retrouver avec décimales bizarres (où vous arrêtez-vous? 2,75? 2,875? 2,8125?)

Existe-t-il une meilleure façon de stocker la commande dans une table?


5
Juste pour que tu saches. . . "La raison pour laquelle de tels systèmes sont appelés" relationnels "est que le terme relation n'est fondamentalement qu'un terme mathématique table ..." - An Introduction to Database Systems , CJ Date, 7e éd. p 25
Mike Sherrill 'Cat Recall'


@ MikeSherrill'CatRecall 'que je n'ai pas attrapé, j'ai corrigé la question avec l'ancien orderset le ddl.
Evan Carroll

Réponses:


17

Si je veux déplacer l'enregistrement 0 au début, je dois réorganiser chaque enregistrement

Non, il existe un moyen plus simple.

update your_table
set order = -1 
where id = 0;

Si je veux insérer un nouvel enregistrement au milieu, je dois réorganiser chaque enregistrement après

C'est vrai, sauf si vous utilisez un type de données qui prend en charge les valeurs "entre". Les types flottants et numériques vous permettent de mettre à jour une valeur à, disons, 2,5. Mais varchar (n) fonctionne aussi. (Pensez 'a', 'b', 'c'; puis pensez 'ba', 'bb', 'bc'.)

Si je veux supprimer un enregistrement, je dois réorganiser chaque enregistrement après

Non, il existe un moyen plus simple. Supprimez simplement la ligne. Les lignes restantes seront toujours triées correctement.

Il est facile d'imaginer des situations comme:

Deux enregistrements ont le même ordre

Une contrainte unique peut empêcher cela.

Il y a des lacunes dans l'ordre entre les enregistrements

Les lacunes n'ont aucun effet sur la façon dont un dbms trie les valeurs dans une colonne.

Certaines personnes ont proposé d'utiliser une décimale pour stocker la commande, de sorte que vous pouvez utiliser "2,5" pour insérer un enregistrement entre les enregistrements de l'ordre 2 et 3. Et bien que cela aide un peu, c'est sans doute encore plus compliqué car vous pouvez vous retrouver avec décimales bizarres (où vous arrêtez-vous? 2,75? 2,875? 2,8125?)

Vous ne vous arrêtez pas avant d' avoir . Le dbms n'a aucun problème à trier les valeurs qui ont 2, 7 ou 15 places après la virgule décimale.

Je pense que votre vrai problème est que vous souhaitez voir les valeurs dans l'ordre trié sous forme d'entiers. Vous pouvez le faire.

create table your_table (
  id int primary key, 
  title varchar(13), 
  sort_order float
);

insert into your_table values
(0, 'Lorem ipsum', 2.0),
(1, 'Dolor sit', 1.5),
(2, 'Amet, consect', 0.0),
(3, 'Elit fusce', 1.0);

-- This windowing function will "transform" the floats into sorted integers.
select id, title,
       row_number() over (order by sort_order)
from your_table

Par souci de propreté, vous pouvez terminer le travail avec quelque chose commewith cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
Manngo

Voici un indice supplémentaire: si vous voulez qu'il soit vraiment parfait, vous devriez vérifier si vous déplacez plus de lignes que vous souhaitez rester intact. Si c'est le cas, mettez à jour les moins nombreux - les "intacts" - D
Ruben Boeck

7

C'est très simple. Vous devez avoir une structure de "trou de cardinalité":

Vous devez avoir 2 colonnes:

  1. pk = 32bit integer
  2. ordre = 64 bits bigint( pas double )

Insérer / mettre à jour

  1. Lors de l'insertion du premier nouvel enregistrement, définissez order = round(max_bigint / 2) .
  2. Lors de l'insertion au début du tableau, définissez order = round("order of first record" / 2)
  3. Lors de l'insertion à la fin du tableau, réglez order = round("max_bigint - order of last record" / 2) 4) Lors de l'insertion au milieu, réglezorder = round("order of record before - order of record after" / 2)

Cette méthode a une très grande cardinalité. Si vous avez une erreur de contrainte ou si vous pensez que vous avez une petite cardinalité, vous pouvez reconstruire la colonne d'ordre (normaliser).

En situation maximale avec normalisation (avec cette structure) vous pouvez avoir un "trou de cardinalité" en 32 bits.

N'oubliez pas de ne pas utiliser de types à virgule flottante - l'ordre doit être une valeur précise!


4

Généralement, la commande est effectuée en fonction de certaines informations contenues dans les enregistrements, le titre, l'ID ou tout ce qui est approprié pour cette situation particulière.

Si vous avez besoin d'une commande spéciale, l'utilisation d'une colonne entière n'est pas aussi mauvaise que cela puisse paraître. Par exemple, pour faire de la place pour un disque pour aller en 5e place, vous pouvez faire quelque chose comme:

update table_1 set place = place + 1 where place > 5.

Si tout va bien vous pouvez déclarer la colonne pour être uniqueet peut-être avoir une procédure pour faire des réarrangements "atomiques". Les détails dépendent du système mais c'est l'idée générale.


4

… C'est sans doute encore plus compliqué parce que vous pouvez vous retrouver avec des décimales étranges (où vous arrêtez-vous? 2,75? 2,875? 2,8125?)

On s'en fout? Ces chiffres ne sont là que pour que l'ordinateur puisse les traiter, peu importe le nombre de chiffres fractionnaires dont ils disposent ou leur laideur.

L'utilisation de valeurs décimales signifie que pour déplacer l'élément F entre les éléments J et K, tout ce que vous avez à faire est de sélectionner les valeurs de commande pour J et K, puis de les faire la moyenne puis de mettre à jour F. Deux instructions SELECT et une instruction UPDATE (probablement effectuées en utilisant un isolement sérialisable pour éviter blocages).

Si vous souhaitez voir des entiers plutôt que des fractions dans la sortie, calculez les entiers dans l'application cliente ou utilisez les fonctions ROW_NUMBER () ou RANK () (si votre SGBDR les inclut).


1

Dans mon propre projet, je prévois d'essayer une solution similaire à la solution du nombre décimal, mais en utilisant des tableaux d'octets à la place:

def pad(x, x_len, length):
    if x_len >= length:
        return x
    else:
        for _ in range(length - x_len):
            x += b"\x00"
        return x

def order_index(_from, _to, count, length=None):
    assert _from != _to
    assert _from < _to

    if not length:
        from_len = len(_from)
        to_len = len(_to)
        length = max(from_len, to_len)

        _from = pad(_from, from_len, length)
        _to = pad(_to, to_len, length)

    from_int = int.from_bytes(_from, "big")
    to_int = int.from_bytes(_to, "big")
    inc = (to_int - from_int)//(count + 1)
    if not inc:
        length += 1
        _from += b"\x00"
        _to += b"\x00"
        return order_index(_from, _to, count, length)

    return (int.to_bytes(from_int + ((x+1)*inc), length, "big") for x in range(count))
>>> index = order_index(b"A", b"Z", 24)
>>> [x for x in index]
[b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y']
>>> 
>>> index = order_index(b"A", b"Z", 25)
>>> [x for x in index]
[b'A\xf6', b'B\xec', b'C\xe2', b'D\xd8', b'E\xce', b'F\xc4', b'G\xba', b'H\xb0', b'I\xa6', b'J\x9c', b'K\x92', b'L\x88', b'M~', b'Nt', b'Oj', b'P`', b'QV', b'RL', b'SB', b'T8', b'U.', b'V$', b'W\x1a', b'X\x10', b'Y\x06']

L'idée est que vous ne pouvez jamais manquer de valeurs intermédiaires possibles, car vous ajoutez simplement un b"\x00"aux enregistrements impliqués si vous avez besoin de plus de valeurs. ( intest illimité en Python 3, sinon vous devrez choisir une tranche d'octets à la fin pour comparer, l'hypothèse étant que, entre deux valeurs adjacentes, les différences seraient compressées vers la fin.)

Par exemple, supposons que vous ayez deux enregistrements, b"\x00"et b"\x01", et que vous souhaitiez qu'un enregistrement les sépare. Il n'y a pas de valeurs disponibles entre 0x00et 0x01, donc vous ajoutez b"\x00"aux deux, et maintenant vous avez un tas de valeurs entre elles que vous pouvez utiliser pour insérer de nouvelles valeurs.

>>> records = [b"\x00", b"\x01", b"\x02"]
>>> values = [x for x in order_index(records[0], records[1], 3)]
>>> records = records + values
>>> records.sort()
>>> records
[b'\x00', b'\x00@', b'\x00\x80', b'\x00\xc0', b'\x01', b'\x02']

La base de données peut facilement le trier car tout finit par ordre lexicographique. Si vous supprimez un enregistrement, il est toujours en règle. Dans mon projet, j'ai créé b"\x00"et b"\xff"as FIRSTet LASTrecords, cependant, afin de les utiliser comme valeurs virtuelles "de" et "à" pour ajouter / ajouter de nouveaux enregistrements:

>>> records = []
>>> value = next(order_index(FIRST, LAST, 1))
>>> value
b'\x7f'
>>> records.append(value)
>>> value = next(order_index(records[0], LAST, 1))
>>> value
b'\xbf'
>>> records.append(value)
>>> records.sort()
>>> records
[b'\x7f', b'\xbf']
>>> value = next(order_index(FIRST, records[0], 1))
>>> value
b'?'
>>> records.append(value)
>>> records.sort()
>>> records
[b'?', b'\x7f', b'\xbf']

0

J'ai trouvé cette réponse beaucoup mieux. Citant entièrement:

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:

order song
1     Happy Birthday
2     Beat It
3     Never Gonna Give You Up
4     Safety Dance
5     Imperial March

Et vous voulez passer Beat Ità la fin, vous auriez deux requêtes:

update table 
  set order = order - 1
  where order >= 2 and order <= 5;

update table
  set order = 5
  where song = 'Beat It'

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:

update table 
  set order = order - 1
  where order >= ? and order <= ?;

update table
  set order = ?
  where song = ?

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 unorder 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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.