PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Използване на една и съща колона няколко пъти в клаузата WHERE

Това е случай на релационно разделение. Добавих етикета.

Индекси

Приемане на ограничение 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 не се използва, когато е създаден в таблица със съществуващи данни

Повече обяснения в ръководството:

  • Статистика, използвана от Планировчика


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Присъединете се към заявка за броене на generate_series() и извлечете нулеви стойности като '0'

  2. Мигриране на съществуващи данни за auth.User към нов потребителски модел на Django 1.5?

  3. Postgresql заявка между периоди от време

  4. psycopg2:вмъкване на няколко реда с една заявка

  5. Създаване на частичен уникален индекс с sqlalchemy на Postgres