Jak w przypadku każdego zapytania, najbardziej wydajną metodą jest „to zależy”. W grę wchodzi wiele zmiennych - liczba wierszy w tabelach, długość wierszy, istnienie indeksów, pamięć RAM na serwerze itp.
Najlepszym sposobem, w jaki mogę sobie wyobrazić rozwiązanie tego rodzaju problemu (myśląc o łatwości utrzymania i dobrym podejściu do wydajności), jest użycie CTE, które pozwala na utworzenie tymczasowego wyniku i ponowne wykorzystanie go w całym zapytaniu. CTE używają słowa kluczowego WITH i zasadniczo aliasują wynik jako tabelę, dzięki czemu można wielokrotnie JOIN przeciwko niemu:
WITH user_memberships AS (
SELECT *
FROM memberships
WHERE user_id = ${id}
), user_apps AS (
SELECT *
FROM apps
INNER JOIN user_memberships
ON user_memberships.team_id = apps.team_id
), user_collections AS (
SELECT *
FROM collections
INNER JOIN user_memberships
ON user_memberships.team_id = collections.team_id
), user_webhooks AS (
SELECT *
FROM webhooks
LEFT OUTER JOIN user_collections ON user_collections.id = webhooks.collection_id
INNER JOIN user_memberships
ON user_memberships.team_id = webhooks.team_id
OR user_memberships.team_id = user_collections.team_id
)
SELECT events.*
FROM events
WHERE app_id IN (SELECT id FROM user_apps)
OR collection_id IN (SELECT id FROM user_collections)
OR membership_id IN (SELECT id FROM user_memberships)
OR team_id IN (SELECT team_id FROM user_memberships)
OR user_id = ${id}
OR webhook_id IN (SELECT id FROM user_webhooks)
;
Korzyści płynące z robienia tego w ten sposób to:
- Każde CTE może skorzystać z indeksu na odpowiednich predykatach JOIN i szybciej zwrócić wyniki tylko dla tego podzbioru, zamiast zmuszać planistę wykonania do próby rozwiązania serii złożonych predykatów
- CTE mogą być utrzymywane indywidualnie, co ułatwia rozwiązywanie problemów z podzbiorami
- Nie naruszasz zasady DRY
- Jeśli CTE ma wartość poza zapytaniem, możesz przenieść ją do procedury składowanej i odwołać się do niej