Search code examples
postgresqlpostgresql-12

Compare a record value containing a list with column values in Postgres


I'm in need of writing a select statement that has to link two tables together using one column value containing a list of items separated by ',' and a column with each item listed individual, that can also filter the result using those same columns. Example:

tbl_requests
requestid | requesteditems
1234567   | laptop,monitor,mouse
1234568   | laptop,dock
1234569   | monitor,keyboard,mouse
1234570   | monitor,dock,keyboard

tbl_storage
storageid | item
123       | laptop
123       | monitor
123       | mouse
123       | keyboard
154       | laptop
154       | dock

I have no control of how the data is inserted into the request table. Regardless, I need a select statement that will return each requestid and a single storageid that contains all the requesteditems for that request. If no storagid is associated with all all requesteditems, that request is just left out. AKA:

Result Table
requestid | storageid
1234567   | 123
1234568   | 154
1234569   | 123

1234570 shouldn't be included as not storage id is associated with all requested items.

As far as I've gotten, I can link the two tables using unnest as follows:

select distinct on (r.requestid) r.request, s.storageid from tbl_requests r, unnest(string_to_array(r.requesteditems, ',')) r(items)
join tbl_storage s on s.item=r.items

This gets me close, but the result can vary as request 1234568 might return either 123 or 154 as storageid because the first item listed is laptop, which both storageid's have a record associated with laptop. Also, request 1234570 will be returned with storageid 123 because that storage id does have monitor associated with it.

Is it possible to add conditioning to the above query to ensure the only requests returned are ones with a storageid associate with every item listed in requesteditem; or am I going about this all wrong?


Solution

  • You can aggregate all storage items into an array, convert the requesteditems into a proper array and then join on those arrays:

    with storage as (
      select storageid, array_agg(item) items
      from tbl_storage
      group by storageid 
    ), requests as (
      select requestid, string_to_array(requesteditems,',') as items
      from tbl_requests
    )
    select r.requestid, s.storageid
    from requests r
      join storage s on s.items @> r.items 
    

    The expression s.items @> r.items is true if the array if the storage items (s.items) contains all elements in the array with the requested items.

    Online example