2012-05-04 32 views
7

Mam zapytanie, w którym chcę wybrać wszystkich użytkowników, którzy lubią dany zbiór artystów. Istnieją również inne kryteria WHERE dotyczące kraju itp. Oto, jak wygląda schemat.Kwerenda PostgreSQL - Zamawianie według podzapytania

  users      favourite_artists    artists 

+----------+------------+ +-----------+------------+ +--------+--------+ 
| id | country | | user_id | artist_id | | id | name | 
+----------+------------+ +-----------+------------+ +--------+--------+ 
|  1 |  gb  | |  1  |  6  | | 1 | Muse | 
|  2 |  gb  | |  1  |  5  | | 2 | RATM | 
|  3 |  us  | |  1  |  3  | | 3 | ABBA | 
|  4 |  us  | |  2  |  3  | | 4 | U2 | 
+----------+------------+ +-----------+------------+ +--------+--------+ 

Chcę zamówić je według liczby artystów, których lubią. Chcę również uwzględnić użytkowników, którzy nie lubią żadnego z artystów, ale którzy pasują do kryteriów WHERE. Oczekiwany zestaw wyników powinien wyglądać.

+--------+---------------+----------------+ 
| id | country | match_count | 
+--------+---------------+----------------+ 
| 6 |  gb  |  4  | 
| 9 |  gb  |  4  | 
| 2 |  gb  |  3  | 
| 1 |  gb  |  2  | 
| 5 |  gb  |  0  | 
| 4 |  gb  |  0  | 
+--------+---------------+----------------+ 

Próbowałem to zrobić za pomocą podzapytania uzyskać match_count i zamawianie przez to ale to wykonując dość powoli, tak myślałem, że tam musi być lepszy sposób.

SELECT users.id, users.country 
    (SELECT COUNT(*) FROM favourite_artists 
    WHERE user_id = users.id AND artist_id IN (1,3,4,9)) AS match_count   
    FROM "users" 
    WHERE users.country = 'gb' 
    ORDER BY match_count DESC; 

Używam Postgresql 9.0.7. jakieś pomysły?

Odpowiedz

6

W zapytaniu jest wykonywane jedno podzapytanie dla każdego wiersza w users. Takie zapytania są nazywane "skorelowane podkwerendy", a ich działanie, całkiem zrozumiałe, jest do bani.

Zamiast chcesz dołączyć:

SELECT users.id, users.country, count(artist_id) as match_count 
FROM users 
LEFT JOIN favourite_artists ON user_id = users.id AND artist_id IN (1,3,4,9) 
WHERE users.country = 'gb' 
GROUP BY 1, 2 
ORDER BY 3 DESC; 

To zapytanie będzie znacznie bardziej skutecznie uzyskać łączenie wierszy, zakładając, że masz indeksu na favourite_artists(user_id) - albo jeszcze lepiej w multi-column indexfavourite_artists(user_id, artist_id).

+1

+1 za bardzo miłe wprowadzenie. :) –

+1

Opieranie się na pozycji w klauzuli ORDER BY narusza teorię relacyjną, która powoduje, że kolejność pól w zestawie wyników nie jest określona. Proponuję zamiast tego polecenie "ORDER BY match_count". –

+1

@JoelFinkel Nie "narusza" niczego. Kolejność kolumn jest zdefiniowana * w zapytaniu *, więc enkapsulacja jest nienaruszona. W porządku. Nie rozumiem, dlaczego niektórzy ludzie mają swoje majtki w garści na ten niewiarygodnie trywialny problem, który nie ma absolutnie żadnej różnicy w wyniku lub cokolwiek innego w tej sprawie. Są większe ryby do smażenia. W rzeczywistości są * ryby * do smażenia - to nawet nie jest ryba. To nic. – Bohemian