create a cte of manually entered values - postgresql

I'd like to create a lookup table within my query using a cte e.g.
food, category
apple, fruit
carrot, vegetable
grape, fruit
How can I just type values directly in this way? Tried:
with
lookup_table as (
select
'apple', 'carrot', 'grape' as fruits,
'fruit', 'vegetable', 'fruit' as category
)
select * from lookup_table;
Gives:
"?column?" "?column?" fruits "?column?" "?column?" category
apple carrot grape fruit vegetable fruit
How can I create a manual lookup directly in this way with 2 fields, fruit and category, as well as the 3 corresponding values for each?

One elegant way in Postgres is to use a VALUES clause.
WITH
lookup
AS
(
SELECT *
FROM (VALUES ('apple',
'fruit'),
...
('grape',
'fruit')) AS v
(fruit,
category)
)
SELECT *
FROM lookup;

Related

PostgreSQL: recursively join a second table

I'm struggling with recursion in PostgreSQL. I need to join a first table with a second one, and then recursively join within the second table. I looked at quite a number of examples, but most are about finding the parent records within a single table, and this has left me utterly confused.
Here's a minimal example with tables thing and category. Records in thing may or may not have a category:
id
name
category
1
a5
3
2
passat
2
3
apple
NULL
Records in category may have one or more parents in the same table:
id
name
parent_category
1
vehicle
NULL
2
car
1
3
coupe
2
The result I'm looking for is the combination of all things with their categories, as well as the category level (1 for the direct parent, 2 for the level above).
thing_name
category_name
level
a5
coupe
1
a5
car
2
a5
vehicle
3
passat
car
1
passat
vehicle
2
apple
NULL
NULL
I have a DB Fiddle here: https://www.db-fiddle.com/f/b7V8ddragZZ9x2RsMkdFYn/5
CREATE TABLE category (
id INT,
name TEXT,
parent_category INT
);
INSERT INTO category VALUES (1, 'vehicle', null);
INSERT INTO category VALUES (2, 'car', 1);
INSERT INTO category VALUES (3, 'coupe', 2);
CREATE TABLE thing (
id INT,
name TEXT,
category INT
);
INSERT INTO thing VALUES (1, 'a5', 3);
INSERT INTO thing VALUES (2, 'passat', 2);
INSERT INTO thing VALUES (3, 'apple', null);
Use a CTE to join the tables, giving you a tree-like view of combined thing_categories, which you can then use with a normal recursive CTE.
with recursive join_thing_category as (
select thing.id as thing_id,
thing.name as thing_name,
thing.category as thing_category,
category.id as category_id,
category.name as category_name,
category.parent_category as parent_category
from thing left join category on thing.category=category.id
),
recursive_part(n) as (
select thing_id, thing_name, thing_category, category_id, category_name, parent_category, (0*parent_category) + 1 as level from join_thing_category
union all
select 1, thing_name, thing_category, cat.id category_id, cat.name, cat.parent_category as parent, level+1 as level from recursive_part rp cross join category cat
where cat.id=rp.parent_category
)
select thing_name, category_name, level from recursive_part order by 1, 2, 3 limit 1024;
thing_name
category_name
level
a5
car
2
a5
coupe
1
a5
vehicle
3
apple
passat
car
1
passat
vehicle
2
View on DB Fiddle
The (0*parent_category) + 1 as level bit is so that things with no category get NULL as their level instead of 1.

SQL: IF/CASE statement within WHERE clause

Is it possible to return records matching the first part of a where clause; however, if no results are found, then move to the second part of the where clause?
Example:
create table #Fruits (
Fruit varchar(20),
Color varchar(20),
Size varchar(20)
)
insert into #Fruits
values ('Apple', 'Red', 'Medium'),
('Pear', 'Green', 'Medium'),
('Banana', 'Yellow', 'Medium'),
('Grapes', 'Purple', 'Small')
select * from #Fruits
where Fruit in ('Apple', 'Grapes') or Color = 'Green'
This will obviously return Apple, Grapes and Pear. My goal is to only find Apple and Grapes if they exist, otherwise return the Fruits that are Green.
I've tried to refer to this similar question: SQL: IF clause within WHERE clause but am having trouble incorporating the where.
I've also tried using ##rowcount:
select * from #Fruits where Fruit in ('Apple', 'Grapes')
if ##rowcount = 0
select * from #Fruits where Color = 'Green'
But if the first select returns nothing, it still returns an empty table as a result.
Thanks.
We can express your logic using a union:
select * from #Fruits where Fruit in ('Apple', 'Grapes')
union all
select * from #Fruits where Color = 'Green' and
not exists (select 1 from #Fruits
where Fruit in ('Apple', 'Grapes'));
We might also be able to combine the logic into a single query:
select *
from #Fruits
where
Fruit in ('Apple', 'Grapes') or
(Color = 'Green' and
not exists (select 1 from #Fruits where Fruit in ('Apple', 'Grapes'));

Select non-equal value in SQL grouping

I have a dataset of food eaten:
create table test
(group_id integer,
food varchar,
item_type varchar);
insert into test values
(764, 'apple', 'new_food'),
(123, 'berry', 'new_food'),
(123, 'apple', 'others'),
(123, 'berry', 'others'),
(86, 'carrot', 'others'),
(86, 'carrot', 'new_food'),
(86, 'banana', 'others');
In each group, the new food eaten is of item_type new_food. The previous food that was being eaten is whatever else in the group doesn't equal the new_food's value.
The dataset I would like from this would be:
| group | previous_food | new_food |
------------------------------------
764 null apple
123 apple berry
86 banana carrot
However, I can't get the group selections correct. My attempt is currently:
select
group_id,
max(case when item_type != 'new_food' then food else null end) as previous_food,
max(case when item_type = 'new_food' then food else null end) as new_food
from test
group by group_id
However, we can't rely on the max() function to pick the correct previous food since they are not necessarily alphabetically ordered.
I just need whichever other food in the grouping != the new_food. How can I get this?
Can I avoid using a subquery or is that inevitable? The database says I can't nest aggregate functions and it is frustrating.
Here is my sqlfiddle so far: http://sqlfiddle.com/#!17/a2a46/1
EDIT: I've solved this with a subquery here: http://sqlfiddle.com/#!17/dd8b9/12 but can we do better? Surely there must be a way of doing this comparison easily within the grouping no?

How to get column names and concatenate the column names delimited by a comma that has value 'Y' for each row?

For DB2
How do I get the column names that contain a 'Y' for each row and concatenate them into a comma-delimited list?
For example, when the base table looks like this:
person | apple | orange | grapes
--------------------------------
1 Y Y
2 Y
3 Y
The query result needs to look like this:
person | fruits
---------------------------
1 apple,orange
2 orange
3 grapes
I tried COALESCE, but that didn't work, as it would coalesce into Y.
I tried CASE WHEN f.apple ='Y' THEN 'apple'
WHEN f.orange = 'Y' THEN 'orange'
WHEN f.grapes = 'Y' THEN 'grapes'
END AS fruits
but the above would only give return one of the WHEN statements.
I tried CASE WHEN f.apple ='Y' THEN concat('apple,')
WHEN f.orange = 'Y' THEN concat('orange,')
when f.grapes = 'Y' THEN concat('grapes,')
END AS fruits
but that doesn't work obviously because it's a syntax error (rather new to SQL) and still, only one of the WHENs would work.
The LISTAGG aggregate function will make quick work of this task if the values for each person can be represented as a multi-row expression:
WITH fruitCTE (person, fruitname) AS (
SELECT person, 'apple' FROM originalTable WHERE apple = 'Y'
UNION ALL
SELECT person, 'orange' FROM originalTable WHERE orange = 'Y'
UNION ALL
SELECT person, 'grapes' FROM originalTable WHERE grapes = 'Y'
)
SELECT person,
LISTAGG( fruitname, ',' )
WITHIN GROUP ( ORDER BY fruitname ASC )
AS fruits
FROM fruitCTE
GROUP BY person
;
If your query also needs to show people who have no Y flags at all, you can modify the final/outer SELECT to perform a LEFT OUTER JOIN from originalTable to fruitCTE on the person column.

T-SQL select process variable

In SQL Server 2008 I have a Table- Fruits
Items Orders
Bananas 6
Bananas 2
Bananas 1
Mangos 4
Mangos 3
Apples 7
Apples 1
Apples 3
Apples 3
Using variables, how I can get below output. I am requesting variables because I would like perform several mathematical operations not described in this example.
Items Number of Orders Total Order Quantity Average Order Quantity
Bananas 3 9 3
Mangos 2 7 3.5
Apples 4 14 3.5
'Total Order Quantity' shows sum of all orders for given item
'Average Order Quantity' = 'Total Order Quantity'/'Number of Orders'
Many thanks.
Create table Fruits (Items varchar(10), Orders int)
insert into Fruits values ('Bananas',6)
insert into Fruits values ('Bananas',2)
insert into Fruits values ('Bananas',1)
insert into Fruits values ('Mangos',4)
insert into Fruits values ('Mangos',3)
insert into Fruits values ('Apples',7)
insert into Fruits values ('Apples',1)
insert into Fruits values ('Apples',3)
insert into Fruits values ('Apples',3)
select Items, count(Orders) as NumberOfOrders, sum(Orders) as TotalOrderQuantity, avg(Orders + 0.0) as AverageOrderQuantity
from Fruits
group by Items
Yes, really should steer clear of cursors when possible. Something like this approach would probably be best, storing the results of the query in a temporary table, and then running update statements to get your calculations:
declare #Table table
(
#Item varchar(10)
#OrderCount int
#QuantityTotal int
#AvgQuantity numeric(9, 2)
#Calc1 numeric(9, 2)
#Calc2 numeric(9, 2)
)
insert into #Table (#Item, #OrderCount, #QuantityTotal, #AvgQuantity)
select Items, count(Orders) as NumberOfOrders, sum(Orders) as TotalOrderQuantity, avg(Orders + 0.0) as AverageOrderQuantity
from Fruits
group by Items
order by 1
update #Table set #Calc1 = #OrderCount / #AvgQuantity,
#Calc2 = ...
select * from #Table
or if you can get all your calculations in a single row or joins with another table, you can do it in a single statement, like:
select *, (OrderCount / AvgQuantity) as Calc1, (... as Calc2)
from
(
select Items, count(Orders) as OrderCount, sum(Orders) as TotalQuantity, avg(Orders + 0.0) as AvgQuantity
from Fruits
group by Items
) t
declare csrCursor cursor for
select Items, count(Orders) as NumberOfOrders, sum(Orders) as TotalOrderQuantity, avg(Orders + 0.0) as AverageOrderQuantity
from Fruits
group by Items
order by 1
declare #Item varchar(10)
declare #OrderCount int
declare #QuantityTotal int
declare #AvgQuantity numeric(9, 2)
open csrCursor
fetch next from csrCursor into #Item, #OrderCount, #QuantityTotal, #AvgQuantity
while (##fetch_status = 0)
-- Do stuff with variables #Item, #OrderCount, #QuantityTotal, #AvgQuantity
-- Insert results in Temp Table
fetch next from csrCursor into #Item, #OrderCount, #QuantityTotal, #AvgQuantity
end
close csrCursor
deallocate csrCursor