To jest przypadek podziału relacyjnego. Dodałem tag.
Indeksy
Zakładając ograniczenie PK lub UNIQUE na USER_PROPERTY_MAP(property_value_id, user_id)
- kolumny w tej kolejności, aby moje zapytania były szybkie. Powiązane:
- Czy indeks złożony jest również odpowiedni dla zapytań w pierwszym polu?
Powinieneś także mieć indeks PROPERTY_VALUE(value, property_name_id, id)
. Ponownie kolumny w tej kolejności. Dodaj ostatnią kolumnę id
tylko wtedy, gdy zdobędziesz tylko skany indeksu.
Dla określonej liczby nieruchomości
Istnieje wiele sposobów na rozwiązanie tego problemu. To powinno być jednym z najprostszych i najszybszych dla dokładnie dwóch właściwości:
SELECT u.*
FROM users u
JOIN user_property_map up1 ON up1.user_id = u.id
JOIN user_property_map up2 USING (user_id)
WHERE up1.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 1 AND value = '101')
AND up2.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 2 AND value = '102')
-- AND u.user_name = 'user1' -- more filters?
-- AND u.city = 'city1'
Nie odwiedzasz tabeli PROPERTY_NAME
, ponieważ wygląda na to, że masz już rozwiązane nazwy właściwości na identyfikatory, zgodnie z przykładowym zapytaniem. W przeciwnym razie możesz dodać dołączenie do PROPERTY_NAME
w każdym podzapytaniu.
W ramach tego pytania zebraliśmy cały arsenał technik:
- Jak filtrować wyniki SQL w relacji ma wiele przejść
Dla nieznanej liczby nieruchomości
@Mike i @Valera mają w swoich odpowiedziach bardzo przydatne zapytania. Aby uczynić to jeszcze bardziej dynamicznym :
WITH input(property_name_id, value) AS (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
)
SELECT *
FROM users u
JOIN (
SELECT up.user_id AS id
FROM input
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
GROUP BY 1
HAVING count(*) = (SELECT count(*) FROM input)
) sub USING (id);
Dodawaj/usuwaj tylko wiersze z VALUES
wyrażenie. Lub usuń WITH
klauzula i JOIN
dla braku filtrów właściwości w ogóle.
problem przy tej klasie zapytań (licząc wszystkie dopasowania częściowe) jest wydajność . Moje pierwsze zapytanie jest mniej dynamiczne, ale zazwyczaj znacznie szybsze. (Po prostu przetestuj za pomocą EXPLAIN ANALYZE
.) Specjalnie dla większych stołów i rosnącej liczby obiektów.
Najlepsze z obu światów?
To rozwiązanie z rekurencyjnym CTE powinno być dobrym kompromisem:szybkie i dynamiczny:
WITH RECURSIVE input AS (
SELECT count(*) OVER () AS ct
, row_number() OVER () AS rn
, *
FROM (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
) i (property_name_id, value)
)
, rcte AS (
SELECT i.ct, i.rn, up.user_id AS id
FROM input i
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
WHERE i.rn = 1
UNION ALL
SELECT i.ct, i.rn, up.user_id
FROM rcte r
JOIN input i ON i.rn = r.rn + 1
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
AND up.user_id = r.id
)
SELECT u.*
FROM rcte r
JOIN users u USING (id)
WHERE r.ct = r.rn; -- has all matches
tutaj dbfiddle
Podręcznik dotyczący rekurencyjnych CTE.
Dodatkowa złożoność nie opłaca się za małe stoły, gdzie dodatkowe koszty przeważają nad korzyściami lub różnica jest na początku znikoma. Ale skaluje się znacznie lepiej i jest coraz lepszy od technik „liczenia” z rosnącymi tabelami i rosnącą liczbą filtrów właściwości.
Techniki liczenia muszą odwiedzić wszystkie wiersze w user_property_map
dla wszystkich podanych filtrów właściwości, podczas gdy to zapytanie (podobnie jak pierwsze zapytanie) może wcześnie wyeliminować nieodpowiednich użytkowników.
Optymalizacja wydajności
Z aktualnymi statystykami tabeli (rozsądne ustawienia, autovacuum
działa), Postgres ma wiedzę na temat „najczęstszych wartości” w każdej kolumnie i zmieni kolejność złączeń w pierwszym zapytaniu aby najpierw ocenić najbardziej selektywne filtry właściwości (lub przynajmniej nie najmniej selektywne). Do pewnego limitu:join_collapse_limit
. Powiązane:
- Postgresql join_collapse_limit i czas na planowanie zapytań
- Dlaczego niewielka zmiana wyszukiwanego hasła tak bardzo spowalnia zapytanie?
Ta interwencja „deus-ex-machina” nie jest możliwa w przypadku trzeciego zapytania (rekurencyjne CTE). Aby poprawić wydajność (być może dużo), musisz najpierw samodzielnie umieścić bardziej selektywne filtry. Ale nawet przy najgorszym przypadku zamówienia nadal będzie przewyższać liczbę zapytań.
Powiązane:
- Sprawdź cele statystyk w PostgreSQL
Dużo więcej krwawych szczegółów:
- Częściowy indeks PostgreSQL nie jest używany podczas tworzenia w tabeli z istniejącymi danymi
Więcej wyjaśnień w instrukcji:
- Statystyki używane przez planistę