This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Język SQL kryje w sobie wiele różnych tajemnic, których znajomość może bardzo ułatwić życie programisty. Nawet podczas korzystania z jego zwykłych - “nieprogramowalnych” elementów, takich jak podstawowe zapytania CRUD [mówiąc “programowalne”, mam na myśli wszelkiego rodzaju procedury składowane i im podobne] możemy dowiedzieć się o wielu sztuczkach, które potrafią przeobrazić zwracane wyniki w gotowy zestaw danych gotowy do użycia. Mając w pamięci jeden z moich niedawnych wpisów o zwracaniu numeru rekordu, dzisiaj także chciałbym przedstawić Wam jedną z takich sztuczek.

Wstęp.

Niecałe dwa tygodnie temu stanąłem przed niemałą zagwozdką - pobierałem z tabel rekordy, których dane miały tworzyć listę w odpowiedniej kolejności. Uprzedzam pytania - w zbiorze wynikowym nie było żadnego pola “ordering”, według którego można by uszeregować te dane, a wprowadzenie go i oprogramowanie w panelu administracyjnym nie było aż tak istotne, żeby się tym zająć na poważnie. Potrzebne było rozwiązanie “na szybko”, które jednak nie powodowałoby istotnego wzrostu współczynnika WTF/min dla ewentualnych przyszłych programistów, którzy zajrzeliby do tego projektu:

Obrazek: http://www.osnews.com/story/19266/WTFs_m.

Nie mogłem zatem skorzystać ze standardowej klauzuli ORDER BY [żadne pole nie dawałoby dobrych wyników], więc musiałem znaleźć coś, co pozwoli na posortowanie rekordów według własnej, ustalonej kolejności. Tradycyjnie zajrzałem do Internetu w poszukiwaniu czegoś, co rozwiązałoby mój problem.

Rozwiązanie.

Po krótkich poszukiwaniach mogę powiedzieć jedno - po raz kolejny nie zawiodłem się na MySQLu. Mówcie sobie co chcecie - ten silnik bazodanowy jest po prostu dobry, a żeby było śmieszniej, takiego rozwiązania nie posiada np. konkurencyjny, a także darmowy PostgreSQL. W każdym razie nie chcę wywoływać kolejnej wojny religijnej, więc skupmy się na samym rozwiązaniu. ;]

Rozwiązanie jest bardzo proste - MySQL udostępnia nam w tym przypadku ciekawą funkcję FIELD(), która pozwala na zdefiniowanie własnej listy wartości branych pod uwagę podczas sortowania wierszy. Jego składnia brzmi następująco:

1
FIELD([field], [value1], [value2], [value3], [...])

gdzie [field] to nazwa pola, którego wartości będą porównywane, a [value1], [value2], [value3] i tak dalej, to kolejne wartości sortujące. Jeśli to nie byłoby jasne, pozwolę sobie pomóc przykładem: ;]

Weźmy pod uwagę tabelę:

1
2
3
4
5
CREATE TABLE `sn_field` (
 `id` int(11) NOT NULL auto_increment,
 `name` varchar(255) NOT NULL,
 PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

i przykładowe dane w tej tabeli:

1
2
3
4
5
INSERT INTO `sn_field` (`id`, `name`) VALUES
(1, 'Third'),
(2, 'Fourth'),
(3, 'Second'),
(4, 'First');

Wykonując zapytanie pobierające wszystkie rekordy:

1
2
SELECT *
FROM `sn_field`

dostajemy poniższą listę:

Załóżmy, że chcielibyśmy jednak posortować te pozycje według ich faktycznej kolejności - tak, jak wcześniej wspomniałem możemy oczywiście skorzystać z pola ordering, które wypełnimy odpowiednimi numerkami, jednak to rozwiązanie nie pasuje do tematyki tego wpisu. ;] Zamiast niego możemy po prostu wykonać:

1
2
3
SELECT *
FROM `sn_field`
ORDER BY FIELD(`name`, 'First', 'Second', 'Third', 'Fourth') ASC;

Lub też:

1
2
3
SELECT *
FROM `sn_field`
ORDER BY FIELD(`id`, 4, 3, 1, 2) ASC;

I zwrócony wynik jest już całkiem poprawny:

Ważne: w przypadku, kiedy w funkcji FIELD() wyspecyfikujemy tylko część wartości pojawiających się w wierszach tabeli [np. kolejny rekord 5 => “Fifth”], to wszystkie inne zostaną posortowane według domyślnych reguł silnika bazodanowego, czyli po prostu będą wyrzucone do wyniku w mniej lub bardziej losowy sposób. ;]

Jak można zauważyć, do funkcji FIELD() można także zastosować modyfikatory ASC / DESC, stąd wniosek, że funkcję tą można zastosować jako normalny element listy w klauzuli ORDER BY. Polecam wykorzystywanie takich sztuczek. ;]

Podsumowanie.

Cóż mogę więcej powiedzieć - mam nadzieję, że w ten weekend uda mi się ponownie otworzyć serię wpisów pod tytułem Linkdump, także do zobaczenia w niedzielę. Tradycyjnie zachęcam do komentowania / wskazywania błędów / lepszych rozwiązań / itp. / itd. Miłego "nad ranem"! ;]