Reputation: 1293
I have a table that list of all fruits bought by a customer at a store by customer:
| cust_name | fruit1 | fruit2 | fruit3 |
|-----------|--------|---------|--------|
| cust_a | apples | oranges | pears |
| cust_b | pears | | |
| cust_c | | | apples |
I am trying to create an output where shows which fruit (fruit1 / fruit2, fruit3) was bought by a customer that is tagged as apples
from the above table. I know case
statement can only be applied on a single column so I am wondering if there is a way to get customers who have bought apples
.
Expected output :
cust_a,fruit1
cust_b,
cust_c,fruit3
Upvotes: 1
Views: 1105
Reputation: 8180
You can create a type that stores a fruit and it's number:
CREATE TYPE num_fruit AS (
num integer,
fruit text
);
Given this type, you can use unnest
to expand your columns into rows (I'm not sure that the right expression):
CREATE TABLE customer_fruits (cust_name text, fruit1 text, fruit2 text, fruit3 text);
INSERT INTO customer_fruits VALUES
('cust_a', 'apples', 'oranges', 'pears'),
('cust_b', 'pears', NULL, NULL),
('cust_c', NULL, NULL, 'apples'),
('cust_d', 'apples', NULL, 'apples');
SELECT cust_name, unnest(ARRAY[ROW(1, fruit1), ROW(2, fruit2), ROW(3, fruit3)]::num_fruit[]) as nf FROM customer_fruits;
Output:
cust_name | nf
-----------------------
cust_a | (1,apples)
cust_a | (2,oranges)
cust_a | (3,pears)
cust_b | (1,pears)
cust_b | (2,)
cust_b | (3,)
cust_c | (1,)
cust_c | (2,)
cust_c | (3,apples)
cust_d | (1,apples)
cust_d | (2,)
cust_d | (3,apples)
Now, you just to SELECT
rows on the fruit
and return the num
:
WITH t AS (
SELECT cust_name, unnest(ARRAY[ROW(1, fruit1), ROW(2, fruit2), ROW(3, fruit3)]::num_fruit[]) as nf FROM customer_fruits
) SELECT cust_name, 'fruit' || num(nf) as 'fruit' FROM t WHERE fruit(nf) = 'apples';
cust_name | fruit
------------------
cust_a | fruit1
cust_c | fruit3
cust_d | fruit1
cust_d | fruit3
Or:
WITH t AS (
SELECT cust_name, unnest(ARRAY[ROW(1, fruit1), ROW(2, fruit2), ROW(3, fruit3)]::num_fruit[]) as nf FROM customer_fruits
) SELECT cust_name, array_agg('fruit' || num(nf)) as 'fruits' FROM t WHERE fruit(nf) = 'apples' GROUP BY 1;
cust_name | fruits
------------------
cust_c | {fruit3}
cust_a | {fruit1}
cust_d | {fruit1, fruit3}
You can even normalize your table (PK = cust_name + num):
WITH t AS (
SELECT cust_name, unnest(ARRAY[ROW(1, fruit1), ROW(2, fruit2), ROW(3, fruit3)]::num_fruit[]) as nf FROM the_table
) SELECT cust_name, num(nf), fruit(nf) FROM t WHERE fruit(nf) IS NOT NULL;
Upvotes: 0
Reputation:
One way to do this without writing a complicated WHERE clause and and that easily extends to more columns) is to convert the row into a JSON and iterate over the keys of the resulting JSON value:
select t.cust_name, string_agg(r.field, ',')
from the_table t
left join lateral jsonb_each_text(to_jsonb(t) - 'cust_name') as r(field, fruit)
on r.fruit = 'apples'
group by t.cust_name;
to_jsonb(t) - 'cust_name'
create a JSON value with all columns from the row and removes the cust_name
. It's not strictly necessary to remove cust_name
from the JSON as it is unlikely it contains a fruit name, so it would never be returned anyway.
jsonb_each_text()
then "iterates" over all columns and leaves only those that contain the value apples
the result is then aggregated back into a comma separated list in case.
With the following sample data:
create table the_table (cust_name text, fruit1 text, fruit2 text, fruit3 text)
insert into the_table
values
('cust_a', 'apples', 'oranges', 'pears'),
('cust_b', 'pears', null, null),
('cust_c', null, null, 'apples'),
('cust_d', 'apples', null, 'apples');
The above query returns:
cust_name | string_agg
----------+--------------
cust_a | fruit1
cust_b |
cust_c | fruit3
cust_d | fruit1,fruit3
Properly normalizing the data model would be a much better solution though.
Upvotes: 3
Reputation: 13237
Using CASE
with multiple WHEN
you can get the expected result:
DECLARE FruitName VARCHAR(50) := 'apples';
SELECT cust_name,
CASE WHEN fruit1 = FruitName THEN 'fruit1'
WHEN fruit2 = FruitName THEN 'fruit2'
WHEN fruit3 = FruitName THEN 'fruit3'
ELSE '' END AS fruit
Upvotes: 0