Това е случай на релационно разделение. Добавих етикета.
Индекси
Приемане на ограничение PK или UNIQUE на USER_PROPERTY_MAP(property_value_id, user_id)
- колони в този ред, за да направим заявките ми бързи. Свързано:
- Съставният индекс добър ли е и за заявки в първото поле?
Трябва също да имате индекс на PROPERTY_VALUE(value, property_name_id, id)
. Отново колони в този ред. Добавете последната колона id
само ако получавате от него сканирания само за индекс.
За даден брой имоти
Има много начини за решаването му. Това трябва да е едно от най-простите и бързи заточно две свойства:
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'
Не посещавате таблица PROPERTY_NAME
, тъй като изглежда вече сте разрешили имената на свойства към идентификатори, според вашата примерна заявка. В противен случай можете да добавите присъединяване към PROPERTY_NAME
във всяка подзаявка.
Събрахме арсенал от техники по този свързан въпрос:
- Как да филтрирате SQL резултати във връзка има-много-през
За неизвестен брой свойства
@Mike и @Valera имат много полезни запитвания в съответните си отговори. За да направите това още по-динамично :
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);
Добавяне/премахване на редове само от VALUES
изразяване. Или премахнете WITH
клауза и JOIN
за без филтри за свойства изобщо.
Пропроблемът с този клас заявки (с отчитане на всички частични съвпадения) е производителност . Първата ми заявка е по-малко динамична, но обикновено значително по-бърза. (Просто тествайте с EXPLAIN ANALYZE
.) Особено за по-големи маси и нарастващ брой имоти.
Най-доброто от двата свята?
Това решение с рекурсивна CTE трябва да бъде добър компромис:бързи и динамичен:
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
dbfiddle тук
Ръководството за рекурсивните CTEs.
Допълнителната сложност не плаща за малки маси, където допълнителните режийни разходи надвишават всяка полза или разликата в началото е незначителна. Но мащабира много по-добре и все повече превъзхожда техниките за „броене“ с нарастващи таблици и нарастващ брой филтри за свойства.
Техниките за броене трябва да посетят всички редове в user_property_map
за всички дадени филтри за свойства, докато тази заявка (както и първата заявка) може да елиминира неподходящите потребители рано.
Оптимизиране на производителността
С текущата статистика на таблицата (разумни настройки, autovacuum
работи), Postgres има познания за "най-често срещаните стойности" във всяка колона и ще пренаредите присъединяванията в 1-ва заявка за да оцени първо най-селективните филтри за свойства (или поне не най-малко селективните). До определен лимит:join_collapse_limit
. Свързано:
- Postgresql join_collapse_limit и време за планиране на заявки
- Защо лека промяна в думата за търсене забавя толкова много заявката?
Тази намеса на „deus-ex-machina“ не е възможна с 3-та заявка (рекурсивен CTE). За да подпомогнете производителността (вероятно много), първо трябва сами да поставите по-селективни филтри. Но дори и при най-лошия случай, той пак ще превъзхожда заявките за броене.
Свързано:
- Проверете статистическите цели в PostgreSQL
Много повече кървави подробности:
- Частичен индекс на PostgreSQL не се използва, когато е създаден в таблица със съществуващи данни
Повече обяснения в ръководството:
- Статистика, използвана от Планировчика