He estado utilizándolos con éxito para agregar referencias de árboles recursivos utilizando activadores.
Por ejemplo, supongamos que tiene un árbol de categorías y desea buscar productos en cualquiera de las categorías (1,2,3) o cualquiera de sus subcategorías.
Una forma de hacerlo es usar una fea declaración with recursive
. Al hacerlo, se generará un plan rellenado con uniones merge/hash en tablas completas y materializaciones ocasionales.
with recursive categories as (
select id
from categories
where id in (1,2,3)
union all
...
)
select products.*
from products
join product2category on...
join categories on ...
group by products.id, ...
order by ... limit 10;
Otra es comprobar la validez de agregar los datos necesarios:
categories (
id int,
parents int[] -- (array_agg(parent_id) from parents) || id
)
products (
id int,
categories int[] -- array_agg(category_id) from product2category
)
index on categories using gin (parents)
index on products using gin (categories)
select products.*
from products
where categories && array(
select id from categories where parents && array[1,2,3]
)
order by ... limit 10;
Un problema con el enfoque anterior es que las estimaciones de fila para el operador & & son correos no deseados. (La selectividad es una función secundaria que aún no se ha escrito, y da como resultado filas de 1/200, independientemente de los valores en sus agregados). Dicho de otra manera, es muy posible que termine con un escaneo de índice donde un escaneo seq sería correcto
Para solucionarlo, aumenté las estadísticas en la columna indexada a gin y periódicamente busco en pg_stats para extraer estadísticas más apropiadas. Cuando un vistazo superficial a esas estadísticas revelan que usando & & para los valores especificados devolverá un plan incorrecto, reescribo las ocurrencias aplicables de & & con arrayoverlap() (este último tiene una selectividad de stub de 1/3), p.:
select products.*
from products
where arrayoverlap(cat_id, array(
select id from categories where arrayoverlap(parents, array[1,2,3])
))
order by ... limit 10;
(Lo mismo ocurre con el operador @ < ...)
Muchas gracias. Ahora está más claro. :) –