2009-04-14 10 views
16

Mam tabelę z powiedzeniem 20 wierszy, każdy z numerem dla kolejności wyświetlania (1-20).Aktualizowanie kolejności wyświetlania wielu wierszy MySQL w jednym lub kilku zapytaniach

SELECT * FROM `mytable` ORDER BY `display_order` DESC; 

W obszarze administracyjnym można przeciągnąć wiersze dookoła lub wpisać nowy numer ręcznie dla każdego wiersza.

Z pewnością nie jest dobrze przechodzić przez zapytanie UPDATE dla każdego wiersza, co jest alternatywą w jednym lub kilku zapytaniach odpowiednich do aktualizacji jednej komórki w 20 wierszach lub nawet więcej, 50-200 +?


Edit: wiele dobrych odpowiedzi i pomysłów. Mogę rozwinąć pomysły, które do tej pory rozważałem:

Jeden ciąg tablicowy: Mogę mieć kolejność w łańcuchu z unikalnymi identyfikatorami wierszy w żądanej kolejności - np. Wiersze 1,9,2, 6,23. Gdy zamówienie jest aktualizowana, ukryty aktualizacje polowe z JavaScript i dodaje, że w bazie danych lub pliku tekstowym po zakończeniu:

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

Aktualizacja każdy wiersz indywidualnie: To jest to, co starałem się uniknąć, ale to byłoby być zmieniane jedynie bardzo rzadko więc 20-30 połączenia w jedno trafienie raz na tydzień lub miesiąc nie może być problemem, więc po prostu wywołanie update na każdym rzędzie jest to, co zwykle zrobić:

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

jest to związane lista problem. może sugeruje zmianę schematu. – Randy

Odpowiedz

0

można by utworzyć tabelę tymczasową i zapełnij kluczem podstawowym wierszy, które chcesz zmienić, oraz nowymi wartościami display_order dla w tych wierszach, a następnie użyj wielostrefowej wersji UPDATE, aby zaktualizować tabelę za jednym razem. Nie zakładałbym jednak, że jest to szybsze, jednak nie bez testowania obu podejść.

0

Dodaj identyfikator (lub inny klucz) do tabeli i zaktualizuj gdzie id (lub klucz) = id (lub klucz) zmienionego wiersza.

Oczywiście musisz upewnić się, że albo nie ma duplikatów wartości display_order, albo że jesteś w porządku z powiązaniami w display_order wyświetlającymi w dowolnej kolejności, albo wprowadzisz drugi, tie-breaker do kolejność według listy.

1

Możesz spróbować zawrzeć to w kilku stwierdzeniach, nie sądzę, że jest to możliwe w jednym. Na przykład, powiedzmy, że zaktualizujesz 10. wiersz. Chcesz, aby każda płyta po 10 została podskoczona.

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

Ale naprawdę ciężko jest przetaczać się we wszystkich wymaganych logikach. Ponieważ niektóre rekordy mogą wymagać dekrementacji itp. Chcesz uniknąć duplikatów itp.

Pomyśl o tym w kategoriach czasu dewelopera a zysku.

Bo nawet jeśli ktoś sortuje to raz dziennie, narzut jest minimalny, w porównaniu do naprawienia go w procedurze przechowywanej, lub pseudo optymalizacji tej funkcji, aby nie uruchamiać 20 zapytań. Jeśli to nie działa 100 razy dziennie, 20 zapytań jest całkowicie w porządku.

0

Można usunąć ponownie wstawić wszystkie wiersze - to wykonałoby całą operację tylko w dwóch zapytaniach (lub trzech, jeśli trzeba wybrać wszystkie dane). Nie liczyłbym na to, że będzie szybszy, a będziesz musiał to zrobić w ramach transakcji, bo za długo będziesz kierował swoje kopie zapasowe. Może to również doprowadzić do fragmentacji tabeli.

Lepszym rozwiązaniem może być nagrać każdą zmianę w historii następnie zrobić coś takiego:

przykład, pozycja 10 jest przesuwana w dół od dwóch do 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

Najpierw należy upewnić się, że kolumny nie ma indeksu UNIQUE, w przeciwnym razie mysql powie ci, że ograniczenie zostało przerwane podczas zapytania. Następnie możesz wykonać następujące czynności:

-- 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; 

Ale powinieneś upewnić się, że robisz wszystko pojedynczo. zamiana w dwóch etapach spowoduje zerwanie numeracji, jeśli nie będzie używać identyfikatorów. tj .:

-- 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) 

Tutaj znajduje się odniesienie do CASE statement of mysql.

+0

Uderzyłem w głowę przeciwko własnemu indeksowi "UNIQUE" próbującemu użyć PHP do rozwiązania tego problemu logicznego. Powinienem usunąć ten indeks, tak jak powiedziałeś! Właśnie martwiłem się wprowadzaniem błędów do bazy danych. – Xeoncross

0

Zbierz nowe zamówienie w zmiennej tymczasowej i umieść przycisk "Zapisz to zamówienie" w obszarze administracyjnym Następnie zapisz kolejność dla wierszy z jedną rundą.

Otrzymasz lepsze czasy odpowiedzi, zmiany, które można cofnąć, mniej zmodyfikowanych wierszy (ponieważ na niskim poziomie w DBMS praktycznie nie było żadnych aktualizacji, ale zachowaj nowe wystąpienie całego wiersza i usuń stare) .

W końcu byłoby to rozwiązanie o niższym koszcie dla całej zmiany kolejności i zaoszczędziłoby trochę kodu na optymalizacji aktualizacji.

1

myślę o tym problemie, a roztwór wymyśliłem jest o liczbę dziesiętną jako celu i zmienić liczbę zmianą pozycji dla szeregu pomiędzy następnego i poprzedniego elementu

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

Jeżeli zmienisz pozycję 4 do 2. pozycji, można uzyskać:

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

jeśli zmienić punkt 3 na 3. pozycji, można uzyskać:

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

Teoretycznie zawsze jest dziesiętny między dwoma miejscami dziesiętnymi, ale możesz napotkać pewne ograniczenia przechowywania.

W ten sposób wystarczy zaktualizować ponownie zamówiony rząd.

+0

Sądzę, że dwa problemy z tym są nadal konieczne, aby zaktualizować kilka pól, ale przynajmniej nie wszystkie ... po pewnym czasie mogą się nieco owłosić! Miałem podobny pomysł, ale zacząłem w jednostkach po 100, więc przynajmniej miał ints zamiast dziesiętnych, ale nie za bardzo różne ... Powiedziałbym, że nadal chciałbyś zaktualizować zamówienie co jakiś czas, by być normalnie spójnymi liczbami. Dzięki za wkład. –

+0

Może to nie działać tak dobrze do częstego sortowania, więc wciąż dodajesz lub zmieniasz pozycje na górze (np. W wiadomościach), więc skończysz z 0.1, 0.0001 - ale wtedy domyślam się, że może trafić do tysięcy ... chociaż wydaje mi się, że jest to kwestia z lewej strony pola, w rzeczywistości skłaniam się ku temu rozwiązaniu ... –

+0

Zgadzam się, że po pewnym czasie może się trochę owłosić, ale nie jestem pewien, czy użytkownik ponownie zamówi tak dużo (I zgadnij, że zależy to od typu witryny itp.), a co miesiąc możesz uruchamiać kwerendę "normalizuj", aby wszystko było jeszcze bardziej czyste. –

1

Jeśli chcesz przeciągnąć wiersze, jest to dobra implementacja dla linked list.

Posiadanie wierszy zamówionych z linked list oznacza, że ​​będziesz aktualizował co najwyżej 3 wierszy naraz - nawet jeśli przesuniesz cały blok (o ile jest ciągły).

Utwórz tabelę swoich wierszach tak:

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 

i używać tej MySQL zapytanie do wyboru wierszy w kolejności:

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

To będzie przechodzić swoją połączonej listy i powrót zamówione pozycje :

id parent value 

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

Aby przenieść wiersz, musisz zaktualizować parent wiersza, parent bieżącego potomka i parent wiersza, który wstawiasz wcześniej.

Zobacz tę serię artykułów na moim blogu o tym, jak to zrobić skutecznie w MySQL:

Istnieje wiele artykułów, ponieważ istnieją pewne problemy z blokowaniem wiersz, który należy traktować nieco inaczej dla każdego przypadku.

+0

Wydaje się to bardzo skomplikowane ... moim głównym założeniem jest to, że dodaje więcej pracy zamiast aktualizować kolejność jednej pozycji aktualizuje 3 pola dla każdej zmiany zamówienia ... –

+0

3 pola zamiast 1 nie jest problemem w 2009 roku :) Jeśli będziesz mieć więcej niż 1000 wierszy i częste aktualizacje, połączona lista to najlepsza decyzja, ponieważ zawsze są to 3 pola bez względu na wszystko. Inne rozwiązania będą wymagały albo aktualizacji wszystkich pól, albo sortowania binarnego, na którym prawdopodobnie zabraknie wartości. – Quassnoi

11

Odpowiedź soulmerge sprawiła, że ​​pomyślałem i uważam, że jest to lepsze rozwiązanie. Trzeba tylko wybrać wiersze o identyfikatorze za pomocą IN(), a następnie użyć CASE, aby ustawić wartość.

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) 

Potrzebuję tego w mojej obecnej aplikacji. W PHP dostaję serializowany (i uporządkowany) zbiór identyfikatorów z wbudowanej funkcji sortable jQuery UI. Więc tablica wygląda następująco:

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

Aby wygenerować pojedynczy aktualizacji MySQL Query, mogę to zrobić:

$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) . ")"; 

W „implode” na końcu po prostu daje mi listę identyfikacyjną dla IN() . Działa to pięknie dla mnie.

+1

To jest sprytne! Wystrzeliwuję aktualizację, nawet gdy lista jest uporządkowana lub element jest usuwany z listy, tylko jedno zapytanie. – Kumar

+0

Nie sądzę, że składnia jest tutaj poprawna. Na przykład powinno być po prostu "END", a nie "END CASE". – Andrew

+0

Dla MySQL musisz użyć 'END CASE'. Ref: http://dev.mysql.com/doc/refman/5.7/en/case.html –

1

To doskonały sposób sortowania pozycji w bazie danych. Dokładnie to, czego szukałem;)

Oto ulepszona wersja wersji Jamon Holmgren. Funkcja ta jest całkowicie dynamiczna:

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); 
} 

ten zakłada, że ​​masz kolumnę $ ordercol na stole $ nazwatabeli tylko dla celów zamawiania.

The $ idsarray jest tablicą w tablicy formatu ("current_order_num" => "updated_order_num").

Jeśli chcesz tylko zamienić dwie linie w swoim stole (wyobraź sobie, że masz na przykład ikony "przesuń w górę" i "przesuń w dół" na stronie), możesz użyć tej funkcji na poprzedniej stronie :

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

trochę późno, ale może to być przydatne dla kogoś innego:

UPDATE mytable SET display_order = FIND_IN_SET(rowId, '1,9,2,6,23') WHERE rowId in (1,9,2,6,23) 
Powiązane problemy