Home > Net >  SQL: Find Items based on their attributes
SQL: Find Items based on their attributes

Time:06-13

On PostgreSQL, I have a table item, and a table item_attribute. So an item can have multiple attributes.

table: item

id name
1 A
2 B
3 C

table: item_attribute

id item_id name value
1 1 foo bar
2 1 size M
3 1 country DE
4 2 foo baz
5 2 size M
6 2 country DE
7 3 other test
8 3 size M
9 3 country EN

Now, I need to find items that match a given set of attribute name/value pairs

So, let's say I want to find items with attributes of country with value DE and size with value M the result set should be

id name
1 A
2 B

if I want to find items with attributes of country with value EN and size with value M the result set should be

id name
3 C

Any hints here?

CodePudding user response:

Using an aggregation approach we can try:

SELECT i.id, i.name
FROM item i
INNER JOIN item_attribute ia
    ON ia.item_id = i.id
GROUP BY i.id, i.name
HAVING COUNT(*) FILTER (WHERE ia.name = 'country' AND ia.value = 'DE') > 0 AND
       COUNT(*) FILTER (WHERE ia.name = 'size' AND ia.value = 'M') > 0;

The above uses conditional aggregation to find items from Germnay in medium size.

CodePudding user response:

You can find the intersection between all "item_id"s that satisfy your conditions inside the "item_attribute" table, then match the corresponding value inside the "item" table.

WITH filtered_ids AS (
    SELECT item_id FROM item_attribute WHERE name  = 'country' AND value = 'DE'
    INTERSECT
    SELECT item_id FROM item_attribute WHERE name  = 'size'    AND value = 'M'
)
SELECT * 
FROM item
WHERE id IN (SELECT * FROM filtered_ids)

You can stack as many conditions as you want inside the "filtered_ids" cte.

Check the demo here.

CodePudding user response:

One option is to aggregate the attributes into a JSON value and use the quite powerful JSON operators to find the items

select itm.*
from item itm
where exists (select null
              from item_attribute att
              where att.item_id = itm.id
              group by item_id
              having jsonb_object_agg(name, value) @> '{"country": "DE", "size": "M"}'
              )

I am not not sure if this is faster or slower than the traditional approach with counting (see Tim Biegeleisen's answer). It is a bit more flexible though.

Depending on the data distribution, doing the aggregation once for all items might be faster:

select itm.*
from item itm
  join (
    select item_id, jsonb_object_agg(name, value) as attributes
    from item_attribute att
    group by item_id
    having jsonb_object_agg(name, value) @> '{"country": "DE", "size": "M"}'
  ) atr on atr.item_id = itm.id

This also enables to display all attributes (as JSON) in a column of the result.

  • Related