web-dev-qa-db-fra.com

Utilisation d'une colonne d'ordre de tri dans une table de base de données

Disons que j'ai une table Product dans la base de données d'un site d'achat pour conserver la description, le prix, etc. des produits du magasin. Quelle est la façon la plus efficace de permettre à mon client de commander à nouveau ces produits?

Je crée une colonne Order (entier) à utiliser pour trier les enregistrements mais cela me donne des maux de tête concernant les performances en raison des méthodes primitives que j'utilise pour changer l'ordre de chaque enregistrement après celui que j'ai réellement besoin de changer. Un exemple:

Id    Order
5     3
8     1
26    2
32    5
120   4

Maintenant, que puis-je faire pour changer l'ordre de l'enregistrement avec ID=26 à 3?

Ce que j'ai fait, c'est créer une procédure qui vérifie s'il y a un enregistrement dans l'ordre cible (3) et met à jour l'ordre de la ligne (ID = 26) sinon. S'il existe un enregistrement dans l'ordre cible, la procédure s'exécute en envoyant l'ID de cette ligne avec target order + 1 comme paramètres.

Cela provoque la mise à jour de chaque enregistrement après celui que je souhaite modifier pour faire de la place:

Id    Order
5     4
8     1
26    3
32    6
120   5

Alors, que ferait une personne plus intelligente?

  • J'utilise SQL Server 2008 R2.

Modifier:

J'ai besoin que la colonne de commande d'un article soit suffisante pour le tri sans clé secondaire impliquée. La colonne de commande seule doit spécifier un emplacement unique pour son enregistrement.

En plus de tout, je me demande si je peux implémenter quelque chose comme une liste chaînée: une colonne 'Next' au lieu d'une colonne 'Order' pour conserver l'ID des éléments suivants. Mais je ne sais pas comment écrire la requête qui récupère les enregistrements dans un ordre correct. Si quelqu'un a également une idée de cette approche, merci de la partager.

31
Şafak Gür
Update product set order = order+1 where order >= @value changed

Bien qu'avec le temps, vous obtiendrez des "espaces" de plus en plus grands dans votre commande, mais ils seront toujours "triés"

Cela ajoutera 1 à la valeur en cours de modification et à chaque valeur qui la suit dans une seule instruction, mais l'instruction ci-dessus est toujours vraie. des "espaces" de plus en plus grands se formeront dans votre commande, atteignant éventuellement le point de dépasser une valeur INT.

Solution alternative compte tenu du désir de ne pas d'espace:

Imaginez une procédure pour: UpdateSortOrder avec les paramètres de @NewOrderVal, @ IDToChange, @ OriginalOrderVal

Processus en deux étapes selon que l'ordre nouveau/ancien monte ou descend le tri.

If @NewOrderVal < @OriginalOrderVal --Moving down chain 

--Create space for the movement; no point in changing the original 
    Update product set order = order+1 
    where order BETWEEN @NewOrderVal and @OriginalOrderVal-1;

end if

If @NewOrderVal > @OriginalOrderVal --Moving up chain

--Create space  for the momvement; no point in changing the original  
  Update product set order = order-1 
  where order between @OriginalOrderVal+1 and @NewOrderVal
end if

--Finally update the one we moved to correct value

    update product set order = @newOrderVal where ID=@IDToChange;

Concernant les meilleures pratiques; la plupart des environnements dans lesquels je me trouve veulent généralement quelque chose de groupé par catégorie et trié par ordre alphabétique ou basé sur la "popularité en vente", supprimant ainsi la nécessité de fournir un tri défini par l'utilisateur.

28
xQbert

Utilisez l'ancienne astuce que les programmes BASIC (entre autres) utilisaient: sautez les nombres dans la colonne d'ordre de 10 ou d'un autre incrément commode. Vous pouvez ensuite insérer une seule ligne (en effet, jusqu'à 9 lignes, si vous êtes chanceux) entre deux numéros existants (qui sont 10 séparés). Ou vous pouvez déplacer la ligne 370 à 565 sans avoir à modifier l'une des lignes à partir de 570.

6
Jonathan Leffler

Une solution que j'ai utilisée dans le passé, avec un certain succès, consiste à utiliser un "poids" au lieu d '"ordre". Le poids étant évident, plus un objet est lourd (c'est-à-dire: plus le nombre est bas), plus le poids est bas (plus le nombre est élevé).

Dans le cas où j'ai plusieurs articles avec le même poids, je suppose qu'ils sont de la même importance et je les commande par ordre alphabétique.

Cela signifie que votre SQL ressemblera à ceci:

ORDER BY 'weight', 'itemName'

j'espère que cela pourra aider.

5
PseudoNinja

Voici une approche alternative utilisant une expression de table commune (CTE).

Cette approche respecte un index unique sur la colonne SortOrder et comblera toutes les lacunes dans la séquence d'ordre de tri qui pourraient avoir été laissées par les opérations DELETE antérieures.

/* For example, move Product with id = 26 into position 3 */
DECLARE @id int = 26
DECLARE @sortOrder int = 3


;WITH Sorted AS (
    SELECT  Id,
            ROW_NUMBER() OVER (ORDER BY SortOrder) AS RowNumber
    FROM    Product
    WHERE   Id <> @id
)

UPDATE  p
SET     p.SortOrder = 
        (CASE 
            WHEN p.Id = @id THEN @sortOrder
            WHEN s.RowNumber >= @sortOrder THEN s.RowNumber + 1
            ELSE s.RowNumber
        END)
FROM    Product p
        LEFT JOIN Sorted s ON p.Id = s.Id 
3
cwills

Je développe actuellement une base de données avec une structure arborescente qui doit être commandée. J'utilise une méthode de type liste de liens qui sera commandée sur le client (pas sur la base de données). La commande peut également être effectuée dans la base de données via une requête récursive, mais ce n'est pas nécessaire pour ce projet.

J'ai fait ce document qui décrit comment nous allons implémenter le stockage de l'ordre de tri, y compris un exemple dans postgresql. S'il vous plait, n'hésitez pas à commenter!

https://docs.google.com/document/d/14WuVyGk6ffYyrTzuypY38aIXZIs8H-HbA81st-syFFI/edit?usp=sharing

3
Elmer

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

Structure: vous devez avoir 2 colonnes:

  1. pk = 32bit int

  2. ordre = 64 bits bigint (BIGINT, PAS DOUBLE !!!)

Insérer/Mettre à jourL

  1. Lorsque vous insérez le premier nouvel enregistrement, vous devez définir order = round(max_bigint / 2).

  2. Si vous insérez au début du tableau, vous devez définir order = round("order of first record" / 2)

  3. Si vous insérez à la fin du tableau, vous devez définir order = round("max_bigint - order of last record" / 2)

  4. Si vous insérez au milieu, vous devez définir order = 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 de maximalité avec normalisation (avec cette structure) vous pouvez avoir un "trou de cardinalité" en 32 bits.

C'est très simple et rapide!

N'oubliez pas AUCUN DOUBLE !!! Seul INT - l'ordre est une valeur de précision!

1
user2382679