sql >> Base de Datos >  >> RDS >> PostgreSQL

Usar la misma columna varias veces en la cláusula WHERE

Este es un caso de división relacional. Agregué la etiqueta.

Índices

Asumiendo una restricción PK o ÚNICA en USER_PROPERTY_MAP(property_value_id, user_id) - columnas en este orden para agilizar mis consultas. Relacionado:

  • ¿Un índice compuesto también es bueno para consultas en el primer campo?

También debe tener un índice en PROPERTY_VALUE(value, property_name_id, id) . De nuevo, columnas en este orden. Agregue la última columna id solo si obtiene escaneos de solo índice.

Para un número dado de propiedades

Hay muchas formas de solucionarlo. Este debería ser uno de los más simples y rápidos para exactamente dos propiedades:

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'

No visitar la tabla PROPERTY_NAME , ya que parece que ya ha resuelto los nombres de propiedad en ID, de acuerdo con su consulta de ejemplo. De lo contrario, podría agregar una unión a PROPERTY_NAME en cada subconsulta.

Hemos reunido un arsenal de técnicas bajo esta pregunta relacionada:

  • Cómo filtrar los resultados de SQL en una relación de varios procesos

Para un número desconocido de propiedades

@Mike y @Valera tienen consultas muy útiles en sus respectivas respuestas. Para hacer esto aún más dinámico :

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

Solo agregue/elimine filas de los VALUES expresión. O elimina el WITH cláusula y JOIN para sin filtros de propiedad en absoluto.

El problema con esta clase de consultas (contando todas las coincidencias parciales) es rendimiento . Mi primera consulta es menos dinámica, pero generalmente considerablemente más rápida. (Solo prueba con EXPLAIN ANALYZE .) Especialmente para mesas más grandes y un número creciente de propiedades.

¿Lo mejor de ambos mundos?

Esta solución con un CTE recursivo debería ser un buen compromiso:rápido y dinámico:

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 aquí

El manual sobre CTE recursivos.

La complejidad añadida no compensa las mesas pequeñas en las que los gastos generales adicionales superan cualquier beneficio o la diferencia es insignificante para empezar. Pero escala mucho mejor y es cada vez más superior a las técnicas de "conteo" con tablas crecientes y un número creciente de filtros de propiedades.

Las técnicas de conteo tienen que visitar todas filas en user_property_map para todos los filtros de propiedad dados, mientras que esta consulta (así como la primera consulta) puede eliminar usuarios irrelevantes antes.

Optimización del rendimiento

Con las estadísticas de la tabla actual (configuraciones razonables, autovacuum ejecutándose), Postgres tiene conocimiento sobre "valores más comunes" en cada columna y reordenará las uniones en la primera consulta para evaluar primero los filtros de propiedad más selectivos (o al menos no los menos selectivos). Hasta cierto límite:join_collapse_limit . Relacionado:

  • Postgresql join_collapse_limit y tiempo para la planificación de consultas
  • ¿Por qué un ligero cambio en el término de búsqueda ralentiza tanto la consulta?

Esta intervención "deus-ex-machina" no es posible con la 3ra consulta (CTE recursivo). Para ayudar al rendimiento (posiblemente mucho), primero debe colocar filtros más selectivos. Pero incluso con el pedido en el peor de los casos, seguirá superando las consultas de conteo.

Relacionado:

  • Comprobar objetivos de estadísticas en PostgreSQL

Mucho más detalles sangrientos:

  • El índice parcial de PostgreSQL no se usa cuando se crea en una tabla con datos existentes

Más explicación en el manual:

  • Estadísticas utilizadas por el planificador