sql >> Database >  >> RDS >> PostgreSQL

Meerdere keren dezelfde kolom gebruiken in de WHERE-component

Dit is een geval van relationele verdeling. Ik heb het label toegevoegd.

Indexen

Uitgaande van een PK- of UNIQUE-beperking op USER_PROPERTY_MAP(property_value_id, user_id) - kolommen in deze volgorde om mijn vragen snel te maken. Gerelateerd:

  • Is een samengestelde index ook goed voor zoekopdrachten op het eerste veld?

U moet ook een index hebben op PROPERTY_VALUE(value, property_name_id, id) . Nogmaals, kolommen in deze volgorde. Voeg de laatste kolom toe id alleen als u er alleen-index scans van krijgt.

Voor een bepaald aantal woningen

Er zijn veel manieren om het op te lossen. Dit zou een van de eenvoudigste en snelste moeten zijn voor precies twee eigenschappen:

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'

Geen bezoek aan tabel PROPERTY_NAME , aangezien u volgens uw voorbeeldquery al eigenschapnamen naar ID's lijkt te hebben omgezet. Anders zou je een join kunnen toevoegen aan PROPERTY_NAME in elke subquery.

We hebben een arsenaal aan technieken verzameld onder deze gerelateerde vraag:

  • SQL-resultaten filteren in een heeft-veel-door-relatie

Voor een onbekend aantal woningen

@Mike en @Valera hebben zeer nuttige vragen in hun respectievelijke antwoorden. Om dit nog dynamisch te maken :

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);

Alleen rijen toevoegen/verwijderen van de VALUES uitdrukking. Of verwijder de WITH clausule en de JOIN voor geen eigenschapsfilters helemaal niet.

Het probleem met deze klasse van zoekopdrachten (waarbij alle gedeeltelijke overeenkomsten worden geteld) is prestaties . Mijn eerste query is minder dynamisch, maar meestal aanzienlijk sneller. (Test gewoon met EXPLAIN ANALYZE .) Vooral voor grotere tafels en een groeiend aantal eigendommen.

Het beste van twee werelden?

Deze oplossing met een recursieve CTE zou een goed compromis moeten zijn:snel en dynamisch:

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 hier

De handleiding over recursieve CTE's.

De extra complexiteit loont niet voor kleine tafels waar de extra overhead opweegt tegen enig voordeel of het verschil te verwaarlozen is om mee te beginnen. Maar het schaalt veel beter en is in toenemende mate superieur aan 'teltechnieken' met groeiende tabellen en een groeiend aantal eigenschapsfilters.

Teltechnieken moeten alles bezoeken rijen in user_property_map voor alle gegeven eigenschapsfilters, terwijl deze zoekopdracht (evenals de eerste zoekopdracht) irrelevante gebruikers vroegtijdig kan elimineren.

Prestaties optimaliseren

Met huidige tabelstatistieken (redelijke instellingen, autovacuum actief), heeft Postgres kennis over "meest voorkomende waarden" in elke kolom en zal de joins opnieuw ordenen in de 1e zoekopdracht om eerst de meest selectieve eigenschapsfilters te evalueren (of in ieder geval niet de minst selectieve). Tot een bepaalde limiet:join_collapse_limit . Gerelateerd:

  • Postgresql join_collapse_limit en tijd voor het plannen van query's
  • Waarom vertraagt ​​een kleine verandering in de zoekterm de zoekopdracht zo veel?

Deze "deus-ex-machina"-interventie is niet mogelijk met de 3e vraag (recursieve CTE). Om de prestaties (misschien veel) te helpen, moet je eerst zelf selectievere filters plaatsen. Maar zelfs in het slechtste geval zal het nog steeds beter presteren dan het tellen van zoekopdrachten.

Gerelateerd:

  • Controleer statistische doelen in PostgreSQL

Veel meer bloederige details:

  • PostgreSQL gedeeltelijke index ongebruikt wanneer gemaakt op een tabel met bestaande gegevens

Meer uitleg in de handleiding:

  • Statistieken gebruikt door de planner


  1. Vind niet-numerieke waarden in een kolom in SQL Server

  2. Sorteren (volgorde op) gebruiken in Select-instructie in SQL Server - SQL Server / TSQL-zelfstudie, deel 109

  3. Meerdere PDF-bestanden samenvoegen/combineren tot één PDF in Oracle met behulp van PLPDF_TOOLKIT PL/SQL-pakket

  4. INSERT en UPDATE een record met behulp van cursors in orakel