2009-04-14 8 views
16

J'ai une table avec disons 20 lignes chacune avec un numéro pour l'ordre d'affichage (1-20). A partir d'une zone d'administration, vous pouvez faire glisser les lignes ou saisir manuellement un nouveau numéro pour chaque ligne.Mise à jour de l'ordre d'affichage de plusieurs lignes MySQL dans une ou très peu de requêtes

Certes, il n'est pas bon de faire une boucle sur une requête UPDATE pour chaque ligne, quelle est une alternative dans une ou très peu de requêtes appropriées pour mettre à jour une cellule dans 20 lignes ou plus, 50-200 +?


Edit: Beaucoup de bonnes réponses et des idées. Je pourrais développer les idées que j'ai considérées jusqu'ici:

Une chaîne de tableau: Je pourrais avoir l'ordre dans une chaîne énumérant les identifiants uniques de rangée dans l'ordre que je veux - par exemple les rangs 1,9,2, 6,23. Lorsque la commande est mise à jour, une mise à jour de champ masqué avec JavaScript et ajoute que la base de données ou un fichier texte lorsque vous avez terminé:

UPDATE `my_dispaly_order_table` SET `display_order`='1,9,2,6,23'; 

mise à jour chaque ligne individuellement: C'est ce que je voulais éviter, mais il serait ne changer que très rarement si 20-30 appels en un seul coup une fois par semaine ou par mois pourraient ne pas être un problème si simplement appeler UPDATE sur chaque ligne est ce que je fais habituellement:

UPDATE `mytable` SET `display_order`='1' WHERE `rowId` = 1; 
UPDATE `mytable` SET `display_order`='2' WHERE `rowId` = 9; 
UPDATE `mytable` SET `display_order`='3' WHERE `rowId` = 2; 
UPDATE `mytable` SET `display_order`='4' WHERE `rowId` = 6; 
UPDATE `mytable` SET `display_order`='5' WHERE `rowId` = 23; 
+0

c'est un problème de liste chaînée. peut-être implique un changement de schéma. – Randy

Répondre

0

Vous pouvez créer une table temporaire et remplir avec la clé primaire des lignes que vous souhaitez modifier et les nouvelles valeurs de display_order pour ces lignes, puis utilisez la version multi-table de UPDATE pour mettre à jour la table en une seule fois. Je ne supposerais pas que c'est plus rapide, cependant, pas sans tester les deux approches.

0

Ajoutez un ID (ou une autre clé) à la table et mettez à jour où ID (ou clé) = ID (ou clé) de la ligne modifiée. Bien sûr, vous devrez vous assurer qu'il n'y a pas de valeurs display_order en double, ou que vous êtes OK avec des liens dans display_order dans n'importe quel ordre, ou vous présenterez un second tie-breaker à l'ordre par liste.

1

Vous pourriez essayer de l'envelopper dans quelques déclarations, je ne pense pas que ce soit possible dans un seul. Par exemple, disons que vous allez mettre à jour la 10ème rangée. Vous voulez que tous les disques après 10 soient gonflés.

UPDATE table SET col=col+1 WHERE col > 10 
UPDATE table SET col=10 WHERE id = X 
... 

Mais c'est vraiment difficile de rouler dans toute la logique requise. Parce que certains enregistrements peuvent avoir besoin d'une diminution, etc. Vous voulez éviter les doublons, etc.

Pensez-y en termes de temps de développement par rapport au gain. Parce que même si quelqu'un trie cela une fois par jour, le surcoût est minime, comparé à la fixation dans une procédure stockée, ou pseudo-optimisation de cette fonctionnalité afin de ne pas exécuter 20 requêtes. Si cela ne fonctionne pas 100 fois par jour, 20 requêtes sont parfaitement bien.

0

Vous pouvez supprimer une réinsérer toutes les lignes - cela ferait toute l'opération dans seulement deux requêtes (ou trois si vous devez sélectionner toutes les données). Je ne compte pas sur elle pour être plus rapide, et vous auriez à le faire dans une transaction ou vous vous dirigerez vers vos sauvegardes avant trop longtemps. Cela pourrait aussi entraîner une fragmentation des tables.

Une meilleure option pourrait consister à enregistrer chaque changement comme une histoire puis faire quelque chose comme ceci:

exemple, la position 10 est déplacée vers le bas deux à 12

UPDATE table SET display_order = display_order -1 WHERE display_order BETWEEN 10 AND 12 
UPDATE table SET display_order = 12 WHERE row_id = [id of what was row 10] 
10

Vous devez tout d'abord faire en sorte que la colonne n'a pas d'index UNIQUE, sinon mysql vous dira que la contrainte est cassée lors de la requête. Après cela, vous pouvez faire des choses comme:

-- Move #10 down (i.e. swap #10 and #11) 
UPDATE mytable SET display_order = 
    CASE display_order 
    WHEN 10 THEN 11 
    WHEN 11 THEN 10 
    END CASE 
WHERE display_order BETWEEN 10 AND 11; 

-- Move #4 to #10 
UPDATE mytable SET display_order 
    CASE display_order 
    WHEN 4 THEN 10 
    ELSE display_order - 1 
    END CASE 
WHERE display_order BETWEEN 4 AND 10; 

Mais vous devriez vous assurer que vous faites les choses en une seule étape. échanger en deux étapes entraînera une numérotation brisée si vous n'utilisez pas d'identifiants. i.e. .:

-- Swap in two steps will not work as demostrated here: 

UPDATE mytable SET display_order = 10 WHERE display_order = 11; 
-- Now you have two entries with display_order = 10 

UPDATE mytable SET display_order = 11 WHERE display_order = 10; 
-- Now you have two entries with display_order = 11 (both have been changed) 

Et voici une référence à la CASE statement of mysql.

+0

Je me suis cogné la tête contre mon propre index 'UNIQUE' en essayant d'utiliser PHP pour résoudre ce problème de logique. Je devrais enlever cet index comme tu l'as dit! J'étais juste inquiet de l'introduction d'erreurs dans la base de données. – Xeoncross

0

Collectez la nouvelle commande dans une variable temporaire et placez un bouton «enregistrer cette commande» dans la zone d'administration. Puis enregistrer la commande pour les lignes avec un tour. Vous obtiendrez de meilleurs temps de réponse, des changements pouvant être annulés, moins de lignes modifiées (parce qu'au niveau bas dans les dbms, pratiquement aucune mise à jour n'était possible, mais enregistrez une nouvelle instance de la ligne entière et supprimez l'ancienne) . Après tout, il s'agirait d'une solution à moindre coût pour tout le réapprovisionnement et économiserait un peu de codage sur l'optimisation des mises à jour.

1

Je pense à ce problème, et la solution que je suis arrivé est d'avoir un nombre décimal comme ordre et changer le nombre de changement d'élément pour un numéro entre l'autre et l'élément précédent

Order Item 
----- ---- 
1  Original Item 1 
2  Original Item 2 
3  Original Item 3 
4  Original Item 4 
5  Original Item 5 

Si vous modifiez le point 4 à la 2ème position, vous obtenez:

Order Item 
----- ---- 
1  Original Item 1 
1.5  Original Item 4 
2  Original Item 2 
3  Original Item 3 
5  Original Item 5 

Si vous changez l'élément 3 à la 3ème position, vous obtenez:

Order Item 
----- ---- 
1  Original Item 1 
1.5  Original Item 4 
1.75  Original Item 3 
2  Original Item 2 
5  Original Item 5 

Théoriquement, il y a toujours une décimale entre deux décimales, mais vous pourriez rencontrer certaines limites de stockage.

De cette façon, il vous suffit de mettre à jour la ligne en cours de réorganisation.

+0

Je suppose que deux problèmes avec ceci est que vous devrez peut-être mettre à jour quelques champs mais au moins pas tous ... il pourrait devenir un peu poilu après le temps! J'ai eu un concept similaire mais j'ai commencé en unités de 100, donc il y avait au moins des entiers au lieu de décimales mais pas trop différent ... Je dirais que vous voudriez toujours mettre à jour la commande de temps en temps pour être des nombres normaux. Merci pour la contribution. –

+0

Cela peut ne pas fonctionner si bien pour un tri fréquent, donc vous continuez d'ajouter ou de déplacer des éléments vers le haut (news par exemple) pour que vous finissiez avec 0.1, 0.0001 - mais je suppose que ça peut aller dans les milliers ... Bien qu'il semble que ce soit une idée légèrement à gauche, je suis en fait penché vers cette solution ... –

+0

Je suis d'accord pour dire que ça peut devenir un peu poilu après un certain temps, mais je ne sais pas si l'utilisateur va le réordonner (I supposez que cela dépend du type de site, etc.) et vous pouvez exécuter une requête "normaliser" tous les mois pour garder les choses plus propres. –

1

Si vous devez faire glisser des lignes, c'est une bonne implémentation pour un linked list. Si vous avez rangé vos lignes avec linked list, cela signifie que vous mettrez à jour au plus 3 lignes à la fois, même si vous déplacez le bloc entier (à condition qu'il soit contigu).

Créer une table de vos lignes comme ceci:

CREATE TABLE t_list (
     id INT NOT NULL PRIMARY KEY, 
     parent INT NOT NULL, 
     value VARCHAR(50) NOT NULL, 
     /* Don't forget to create an index on PARENT */ 
     KEY ix_list_parent ON (parent) 
) 

id parent value 

1 0  Value1 
2 3  Value2 
3 4  Value3 
4 1  Value4 

et utiliser cette requête MySQL pour sélectionner les lignes dans l'ordre:

SELECT @r := (
     SELECT id 
     FROM t_list 
     WHERE parent = @r 
     ) AS id 
FROM (
     SELECT @r := 0 
     ) vars, 
     t_list 

Cela parcourir votre liste, et retourner les articles commandés :

id parent value 

1 0  Value1 
4 1  Value4 
3 4  Value3 
2 3  Value2 

Pour déplacer une ligne, vous devez mettre à jour le parent de la ligne, le parent de son enfant actuel et le parent de la ligne que vous insérez avant.

Voir cette série d'articles sur mon blog sur la façon de le faire efficacement dans MySQL:

Il y a beaucoup d'articles parce qu'il ya des problèmes avec le verrouillage de ligne qui devrait être légèrement différents d'un pour chaque cas.

+0

Cela semble très compliqué ... ma pensée principale est d'ajouter plus de travail au lieu de mettre à jour l'ordre d'un article qu'il met à jour 3 champs pour chaque changement de commande ... –

+0

3 champs au lieu de 1 n'est pas un problème en 2009 :) Si vous avez plus de 1 000 lignes et des mises à jour fréquentes, la liste liée est la meilleure décision, car il y a toujours 3 champs quoi qu'il arrive. D'autres solutions nécessiteront la mise à jour de tous les champs ou un tri binaire susceptible de manquer de valeurs. – Quassnoi

11

La réponse de soulmerge m'a fait réfléchir et je pense que c'est une meilleure solution. Ce que vous devez faire est de sélectionner les lignes avec l'ID en utilisant IN() et ensuite utiliser CASE pour définir la valeur.

UPDATE mytable SET display_order = 
    CASE id 
    WHEN 10 THEN 1 
    WHEN 23 THEN 2 
    WHEN 4 THEN 3 
    END CASE 
WHERE id IN (10, 23, 4) 

J'ai un besoin pour cela dans mon application actuelle. En PHP, j'obtiens un ensemble d'identifiants sérialisés (et ordonnés) à partir de la fonction Sortable intégrée de jQuery UI. Ainsi, le tableau ressemble à ceci:

$new_order = array(4, 2, 99, 15, 32); // etc 

Pour générer la requête de mise à jour MySQL unique, je fais ceci:

$query = "UPDATE mytable SET display_order = (CASE id "; 
foreach($new_order as $sort => $id) { 
    $query .= " WHEN {$id} THEN {$sort}"; 
} 
$query .= " END CASE) WHERE id IN (" . implode(",", $new_order) . ")"; 

Le « imploser » à la fin me donne juste ma liste d'identification pour IN() . Cela fonctionne magnifiquement pour moi.

+1

C'est intelligent! Je lance une mise à jour même lorsque la liste est commandée ou qu'un élément est supprimé de la liste, une seule requête. – Kumar

+0

Je ne pense pas que la syntaxe soit correcte ici. Par exemple, il devrait simplement être "END" et non "END CASE". – Andrew

+0

Pour MySQL, vous devez utiliser 'END CASE'. Ref: http://dev.mysql.com/doc/refman/5.7/en/case.html –

1

Ceci est un excellent moyen de trier des éléments sur une base de données. Exactement ce que je cherchais;)

Voici une version améliorée de la version de Jamon Holmgren. Cette fonction est totalement dynamique:

function reOrderRows($tablename, $ordercol, $idsarray){ 

    $query = "UPDATE $tablename SET $ordercol = (CASE $ordercol "; 
    foreach($idsarray as $prev => $new) { 
     $query .= " WHEN $prev THEN $new\n"; 
    } 
    $query .= " END) WHERE $ordercol IN (" . implode(",", array_keys($idsarray)) . ")"; 

    mysql_query($query); 
} 

Cela suppose que vous avez une colonne $ ordercol sur votre table $ tablename juste aux fins de commande.

Le idsarray $ est un tableau dans le tableau de format ("current_order_num" => "updated_order_num"). Donc, si vous voulez juste échanger deux lignes dans votre tableau (imaginez que vous avez par exemple des icônes "monter" et "descendre" sur votre page web), vous pouvez utiliser cette fonction en plus de la précédente :

function swapRows($tablename, $ordercol, $firstid, $secondid){ 
    $swaparray = array("$firstid" => "$secondid", "$secondid" => "$firstid"); 
    reOrderRows($tablename, $ordercol, $swaparray); 
} 
4

un peu en retard, mais il peut être utile à quelqu'un d'autre:

UPDATE mytable SET display_order = FIND_IN_SET(rowId, '1,9,2,6,23') WHERE rowId in (1,9,2,6,23) 
Questions connexes