Reputation: 399
I'm currently working with a postgres database, but my knowledge is limited to SQL. I am working with an orders database and need to select all customers who have purchased a selection of products. My table looks as follows:
order_id customer_name products_ordered
1 a {"apples":3, "pears":4}
2 b {"apples":4, "pears":4, "oranges":2}
3 c {"apples":2, "oranges":3}
4 a {"apples": 5}
5 c {"oranges": 4}
6 a {"oranges":1}
I need to check which customers have at some point ordered each product, apples, pears and oranges, so the output should be as follows:
customer_name
a
b
I have zero experience with accessing JSON objects in databases and I am wondering how I can retrieve a list of customers who have at some point placed an order for [list of keys].
Thanks in advance!
Upvotes: 0
Views: 220
Reputation:
You can do this with a simple query if you are willing to create a custom aggregate function that aggregates multiple JSONB values into a single one, merging the keys.
First create the aggregate function:
create aggregate jsonb_object_agg(jsonb)
(
sfunc = 'jsonb_concat',
stype = jsonb,
initcond = '{}'
);
Note that this is different then the built-in jsonb_object_agg()
which takes two parameters (key, value). It uses the jsonb_concat
function ||
to merge two JSONB values into one.
With that aggregate, your query is as simple as:
select customer_name
from orders
group by customer_name
having jsonb_object_agg(products_ordered) ?& array['pears','apples', 'oranges'];
The ?&
operator checks if all keys are present in the JSONB value.
Without the aggregate function you can do this:
select customer_name
from orders,
jsonb_each_text(products_ordered) as t(k,v)
group by customer_name
having jsonb_object_agg(t.k, t.v) ?& array['pears','apples', 'oranges'];
However this expands each JSON value into multiple rows for every order, just to aggregate it back again - this produces a bigger intermediate result than the "real" aggregate function, so it might be slower.
Online example: http://rextester.com/EAZVG14239
All of the above assumes that your products_ordered
is a JSONB
column. If it's a JSON
column, you need to cast it to JSONB
Upvotes: 0
Reputation: 3102
This isn't the prettiest query, but it'll work.
Extract the json keys into their own records, aggregate the records by customer name and then do an array-contains comparison. Somewhat abusing CTEs to do this though...
with
__customer_products as(
select
customer_name,
json_object_keys(products_ordered) as product_key
from
orders
),
__customer_products_merged as(
select
customer_name,
array_agg(product_key) as product_keys
from
__customer_products
group by
customer_name
)
select
customer_name
from
__customer_products_merged
where
product_keys @> array['apples', 'pears', 'oranges']
Upvotes: 1