PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Utilisation de la même colonne plusieurs fois dans la clause WHERE

C'est un cas de division relationnelle. J'ai ajouté la balise.

Index

En supposant une contrainte PK ou UNIQUE sur USER_PROPERTY_MAP(property_value_id, user_id) - colonnes dans cet ordre pour accélérer mes requêtes. Connexe :

  • Un index composite est-il également adapté aux requêtes sur le premier champ ?

Vous devriez également avoir un index sur PROPERTY_VALUE(value, property_name_id, id) . Encore une fois, les colonnes dans cet ordre. Ajoutez la dernière colonne id uniquement si vous obtenez des analyses d'index uniquement.

Pour un nombre donné de propriétés

Il existe de nombreuses façons de le résoudre. Cela devrait être l'un des plus simples et des plus rapides pour exactement deux propriétés :

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'

Ne visite pas la table PROPERTY_NAME , puisque vous semblez avoir déjà résolu les noms de propriété en ID, selon votre exemple de requête. Sinon, vous pouvez ajouter une jointure à PROPERTY_NAME dans chaque sous-requête.

Nous avons rassemblé un arsenal de techniques sous cette question connexe :

  • Comment filtrer les résultats SQL dans une relation has-many-through

Pour un nombre inconnu de propriétés

@Mike et @Valera ont des questions très utiles dans leurs réponses respectives. Pour rendre cela encore plus dynamique :

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

Ajouter/supprimer uniquement des lignes des VALUES expression. Ou supprimer le WITH clause et le JOIN pour aucun filtre de propriété du tout.

Le problème avec cette classe de requêtes (en comptant toutes les correspondances partielles) est la performance . Ma première requête est moins dynamique, mais généralement beaucoup plus rapide. (Il suffit de tester avec EXPLAIN ANALYZE .) Surtout pour les grandes tables et un nombre croissant de propriétés.

Le meilleur des deux mondes ?

Cette solution avec un CTE récursif devrait être un bon compromis :rapide et dynamique :

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 ici

Le manuel sur les CTE récursifs.

La complexité supplémentaire ne paie pas pour les petites tables où les frais généraux supplémentaires l'emportent sur tout avantage ou la différence est négligeable pour commencer. Mais il évolue beaucoup mieux et est de plus en plus supérieur aux techniques de "comptage" avec des tables croissantes et un nombre croissant de filtres de propriétés.

Les techniques de comptage doivent visiter tous lignes dans user_property_map pour tous les filtres de propriété donnés, alors que cette requête (ainsi que la 1ère requête) peut éliminer les utilisateurs non pertinents plus tôt.

Optimisation des performances

Avec les statistiques actuelles de la table (paramètres raisonnables, autovacuum en cours d'exécution), Postgres connaît les "valeurs les plus courantes" dans chaque colonne et réorganisera les jointures dans la 1ère requête d'évaluer d'abord les filtres de propriété les plus sélectifs (ou du moins pas les moins sélectifs). Jusqu'à une certaine limite :join_collapse_limit . Connexe :

  • Postgresql join_collapse_limit et temps de planification des requêtes
  • Pourquoi une légère modification du terme de recherche ralentit-elle autant la requête ?

Cette intervention "deus-ex-machina" n'est pas possible avec la 3ème requête (CTE récursif). Pour améliorer les performances (peut-être beaucoup), vous devez d'abord placer vous-même des filtres plus sélectifs. Mais même avec le pire des cas, il surpassera toujours les requêtes de comptage.

Connexe :

  • Vérifier les cibles de statistiques dans PostgreSQL

Des détails bien plus sanglants :

  • Index partiel PostgreSQL inutilisé lorsqu'il est créé sur une table avec des données existantes

Plus d'explications dans le manuel :

  • Statistiques utilisées par le planificateur