Conditional formatting on MAX value row - sql

Below is a table:
Paration by ID & capture the row of MAX value when Role = Red
ID Role HistID Date Style
1 Yellow 101 1/1/17 M
1 Red 101 1/2/17 F
1 Red (Null) 1/5/17 C
2 Blue 101 5/1/17 a
2 Yellow 201 4/1/17 b
2 Red 301 5/5/17 C
3 Yellow (Null)
Referece the below rows:
ID Role HistID Date Style
1 Red (Null) 1/5/17 c
2 Red 301 5/5/17 c
Now based off those rows apply a condition.
WHEN HistID IS NOT NULL and Style = C THEN 'Assigned'
ELSE'Unassigned'
END Status
Output:
ID Role HistID Date Style Status
1 Yellow 101 1/1/17 M Unassigned
1 Red 101 1/2/17 F Unassigned
1 Red (Null) 1/5/17 C Unassigned
2 Blue 101 5/1/17 a Assigned
2 Yellow 201 4/1/17 b Assigned
2 Red 301 5/5/17 C Assigned
3 Yellow (Null) Unassigned
Not so much the answer here, I would like understand and learn the syntax behind applying MAX , Case Expression and Keep clause.

Use window functions:
select t.*,
(case when matches_flag > 0 then 'Assigned' else 'Unassigned' end) as status
from (select t.*,
sum(case when role = 'Red' and histid is not null and style = 'C' then 1 else 0 end) over
(partition by id) as matches_flag
from t
) t;
EDIT:
The subquery is not actually needed. I just think it makes the logic easier to follow. You can do:
select t.*,
(case when sum(case when role = 'Red' and histid is not null and style = 'C' then 1 else 0 end) over (partition by id) > 0
then 'Assigned'
else 'Unassigned'
end) as status
from t;

Related

T-SQL LAG function for returning previous rows with different WHERE condition

I have data like:
table name: "Data"
ID Name Color Value
1 A Blue 1
2 B Red 2
3 A Blue 3
4 B Red 4
5 B Blue 3
6 A Red 4
Can I use a SQL LAG function to get for each Name that is Red, the previous value for for that name that was Blue (ordering by ID)?
Result set:
ID Name Color Value PreviousValue
2 B Red 2 NULL
4 B Red 4 NULL
6 A Red 4 3
select *
from
(
select *
,case when color = 'red' and color != lag(color) over(partition by name order by id) then lag(value) over(partition by name order by ID) end PreviousValue
from t
) t
where color = 'red'
order by id
ID
Name
Color
Value
PreviousValue
2
B
Red
2
null
4
B
Red
4
null
6
A
Red
4
3
Fiddle

Display output is columns based on filter criteria

I am trying to display data is columns/subcolumns based on certain filter criteria using case when statement but not getting required output.
data:
ID ID2 Country Type
1 001 US A
1 009 US A
2 002 AU B
3 003 CA A
3 005 CA A
4 007 US B
5 001 FR B
6 003 US B
7 002 US A
8 004 NZ A
based on my current case statement, here is how my output looks:
Type Country Count
B Other 2
B US 1
B Subtotal 3
A Other 4
A US 3
A Subtotal 7
Total 10
I want to display the following format, bonus if I can get the subtotal/totals:
Type-A Type-B
US Other US Other
3 4 1 2
I also need Subtotals, and Grandtotals, but these need to be calculated separately.
SubTotal: 7 SubTotal: 3
Grand Total: 10
You can like this
select
sum(case when Type = 'A' and Country = 'US' then 1 else 0 end) as US_TYPE_A,
sum(case when Type = 'A' and Country != 'US' then 1 else 0 end) as Other_TYPE_A,
sum(case when Type = 'B' and Country = 'US' then 1 else 0 end) as US_TYPE_B,
sum(case when Type = 'B' and Country != 'US' then 1 else 0 end) as Other_TYPE_B
from myTable

How to use conditional group by aggregations correctly

I want to be able to count the total type of apples (organic only) from each continent, broken down by countries; including the total count if they're mixed.
For example, food item B1 is organic golden apples from the USA. Thus there should be a count of "1" golden_bag and "1" for organic. Now, A1 is also organic from Argentina - however, it has both granny and red delicious apples - thus it is counted as "1" mixed_bag and "1" for granny_bag and "1" for red_bag as well.
Finally, E1 and F1 are both fuji apples from laos, but one is organic the other isn't; so total count is 2 fuji_bag and it should have a total count of 1 for organic_fd.
Table X:
food_item | food_area | food_loc | food_exp
A1 lxgs argentina 1/1/20
B1 iyan usa 5/31/21
C1 lxgs peru 4/1/20
D1 wa8e norway 10/1/19
E1 894a laos 5/1/19
F1 894a laos 9/17/19
Table Y:
food_item | organic
A1 Y
B1 Y
C1 N
D1 N
E1 Y
F1 N
Table Z:
food_item | food_type
A1 189
A1 190
B1 191
C1 189
D1 192
E1 193
F1 193
SELECT continent, country,
SUM(organic) AS organic_fd, SUM(Granny) AS granny_bag,
SUM(Red_delc) AS red_bag, SUM(Golden) AS golden_bag,
SUM(Gala) AS gala_bag, SUM(Fuji) AS fuji_bag,
SUM(CASE WHEN Granny + Red_delc + Golden + Gala + Fuji > 1 THEN 1 ELSE 0 END) AS mixed_bag
FROM (SELECT (CASE SUBSTR (x.food_area, 4, 1)
WHEN 's' THEN 'SA' WHEN 'n' THEN 'NA'
WHEN 'e' THEN 'EU' WHEN 'a' THEN 'AS' ELSE NULL END) continent,
x.food_loc country, COUNT(y.organic) AS Organic
COUNT(CASE WHEN z.food_type = '189' THEN 1 END) AS Granny,
COUNT(CASE WHEN z.food_type = '190' THEN 1 END) AS Red_delc,
COUNT(CASE WHEN z.food_type = '191' THEN 1 END) AS Golden,
COUNT(CASE WHEN z.food_type = '192' THEN 1 END) AS Gala,
COUNT(CASE WHEN z.food_type = '193' THEN 1 END) AS Fuji
FROM x LEFT JOIN z ON x.food_item = z.food_item
LEFT JOIN y on x.food_item = y.food_item and y.organic = 'Y'
WHERE x.exp_date > sysdate
GROUP BY SUBSTR (x.food_area, 4, 1), x.food_loc, y.organic) h
GROUP BY h.continent, h.country, h.organic
I'm not getting the correct output, since for example, Laos will show TWICE to account for the organic count and non-organic count. So it will show 1 organic_fd and 0 organic_fd and 1 fuji_bag and the other line will be another 1 fuji_bag. I would like the TOTAL count. (Also, if I add more food items, my mixed_bag shows mostly "1" count for each record/lines).
Below is the desired output:
| continent | country |organic_fd | granny_bag| red_bag| golden_bag| gala_bag|fuji_bag | mixed_bag
| SA | argentina | 1 | 1 | 1 | 0 | 0 | 0 | 1
| SA | peru | 0 | 1 | 0 | 0 | 0 | 0 | 0
| NA | usa | 1 | 0 | 0 | 1 | 0 | 0 | 0
| EU | norway | 0 | 0 | 0 | 0 | 1 | 0 | 0
| AS | laos | 1 | 0 | 0 | 0 | 0 | 2 | 0
So, say I want to add another food item, G1 from Norway and it has 3 types of organic apples: fuji, red, granny... then Norway will now have a count of 1 for the following columns: mixed_bag, organic_fd, fuji_bag, red_bag ,granny_bag (in addition to the previous count of 1 gala_bag). If you add H1, which is exactly the same as G1, then it will now have a total count of 2 for the following: mixed_bag, organic_fd, fuji_bag,red_bag, granny_bag
The query:
WITH
t AS (
SELECT
CASE SUBSTR(X.food_area, LENGTH(X.food_area), 1)
WHEN 's' THEN 'SA'
WHEN 'n' THEN 'NA'
WHEN 'e' THEN 'EU'
WHEN 'a' THEN 'AS'
ELSE NULL
END AS continent,
x.food_loc AS country,
COUNT(DISTINCT CASE Y.organic WHEN 'Y' THEN X.food_item END) OVER (
PARTITION BY x.food_loc
) AS organic_fd,
CASE
WHEN MIN(Z.food_type) OVER (
PARTITION BY x.food_loc, X.food_item
) = Z.food_type AND
MAX(Z.food_type) OVER (
PARTITION BY x.food_loc, X.food_item
) > Z.food_type THEN 1 END AS mixed,
Z.food_type
FROM X
JOIN Y ON X.food_item = Y.food_item
JOIN Z ON Y.food_item = Z.food_item
)
SELECT
continent, country, organic_fd,
COUNT(CASE WHEN food_type = '189' THEN 1 END) AS Granny,
COUNT(CASE WHEN food_type = '190' THEN 1 END) AS Red_delc,
COUNT(CASE WHEN food_type = '191' THEN 1 END) AS Golden,
COUNT(CASE WHEN food_type = '192' THEN 1 END) AS Gala,
COUNT(CASE WHEN food_type = '193' THEN 1 END) AS Fuji,
COUNT(mixed) AS mixed_bag
FROM t
GROUP BY continent, country, organic_fd
You can try this query here: https://rextester.com/TSSH87409.
You have one to many relationship between x and z, and join may produce many rows for each row in x, like in case of A1. So you have to number rows in x at first, this is what my subquery t1 do, except of mapping values. Then group them taking max() for each counted column (granny, organic etc.), like in subquery t2. Finally sum values.
dbfiddle demo
with
t1 as (
select rn, food_item, food_area, food_loc country, food_exp, food_type,
decode(substr(food_area, 4, 1), 's', 'SA', 'n', 'NA', 'e', 'EU', 'a', 'AS') continent,
case organic when 'Y' then 1 else 0 end org,
case when food_type = '189' then 1 else 0 end gra,
case when food_type = '190' then 1 else 0 end red,
case when food_type = '191' then 1 else 0 end gol,
case when food_type = '192' then 1 else 0 end gal,
case when food_type = '193' then 1 else 0 end fuj
from (select rownum rn, x.* from x) x join y using (food_item) join z using (food_item)
where food_exp > sysdate),
t2 as (
select rn, country, continent, max(org) org, max(gra) gra,
max(red) red, max(gol) gol, max(gal) gal, max(fuj) fuj,
case when max(gra) + max(red) + max(gol) + max(gal) + max(fuj) > 1
then 1 else 0
end mix
from t1 group by rn, country, continent)
select continent, country, sum(org) organic_fd, sum(gra) granny, sum(red) red_delc,
sum(gol) golden_bag, sum(gal) gala_bag, sum(fuj) fuji_bag, sum(mix) mixed_bag
from t2
group by continent, country
Above query gave expected output, please test it and adjust if needed. I noticed you use left joins. If there is possibility that for some rows in X there is no data in Y or Z you may have to add nvl()s in calculations. Maybe you should also put mapped, hardcoded values into tables. Hardcoding them is not good practice. Hope this helps :)

Total column in a pivot example

check here for background if needed:
Pivoting a table with parametrization
We have 3 tables.
tid_color - parametrization table
--------------------------
ID ColorDescription
--------------------------
1 Green
2 Yellow
3 Red
-------------------------
tid_car - parametrization table
--------------------------
ID CARDescription
-------------------------
1 Car X
2 Car Y
3 Car Z
--------------------------
table_owners_cars
------------------------------------------------
ID CarID ColorID Owner
------------------------------------------------
1 1 1 John
2 1 2 Mary
3 1 3 Mary
4 1 3 Giovanni
5 2 2 Mary
6 3 1 Carl
7 1 1 Hawking
8 1 1 Fanny
------------------------------------------------
CarID is FOREIGN KEY to tid_car
ColorId is FOREIGN KEY to tid_color
If we code:
SELECT tcar.CarDescription, tco.ColorDescription, Count(*) as Total
FROM table_owners_cars tocar
LEFT JOIN tid_color tco ON tco.Id = tocar.ColorId
LEFT JOIN tid_Car tcar ON tcar.Id = tocar.CarId
GROUP BY CarDescription, ColorDescription
it results as:
Id CarDescription ColorDescription Total
1 CarX Green 3
2 CarX Yellow 1
3 CarX Red 1
4 CarY Yellow 1
5 CarZ Green 1
I want to pivot exactly as follows:
---------------------------------------------
Id Car Green Yellow Red Total
---------------------------------------------
1 CarX 3 1 1 5
2 CarY 0 1 0 1
3 CarZ 1 0 0 1
---------------------------------------------
Now:
we want to count the total for each row in a particular column of the table_owners_cars and this value is close to total like we see in the last column (between parenthesis). There are CarX WITH a NULL for the colorID (same can happen with the other Car) and we want to know all the number of carX, carY, CarZ (with and without (=null or 0) assigned ColorId
---------------------------------------------------
Id Car Green Yellow Red Violet Total
---------------------------------------------------
1 CarX 3 1 1 0 5 (40)
2 CarY 0 1 0 0 1 (35)
3 CarZ 1 0 0 0 1 (4)
---------------------------------------------------
DESIRED TABLE
One try with the code (very similar to one provided in the aforementioned hyperlink):
SELECT pvt.CarID, tc.Description AS Car, CONCAT (' [1] as 'Green', [2] as 'Yellow', [3] as 'Red', [1]+[2]+[3] as 'total'', '(', count(*), ')' )
FROM
(SELECT CarID, colorId
FROM table_owners_cars tocar
) p
PIVOT
(
COUNT (ColorId)
FOR ColorId IN ( [1], [2], [3])
) AS pvt
INNER JOIN tid_car tc ON pvt.CarId=tc.Id
group by p.Car
this does not work. single quotes are also a nightmare with concat. Thanks in advance.
I just find these queries easier to do with conditional aggregation:
SELECT CarId, Description,
SUM(CASE WHEN color = 'Green' THEN 1 ELSE 0 END) as Green,
SUM(CASE WHEN color = 'Yellow' THEN 1 ELSE 0 END) as Yellow,
SUM(CASE WHEN color = 'Red' THEN 1 ELSE 0 END) as Red,
SUM(CASE WHEN color IN ('Green', 'Yellow', 'Red') THEN 1 ELSE 0 END) as total_gyr,
COUNT(*) as total
FROM table_owners_cars tocar
GROUP BY CarId, Description;
I see no reason to combine the two totals into a single string column -- as opposed to having them in separate integer columns. But, you can combine them if you want.

Pivot Table with Redshift (PostgreSQL) with Count

I'm facing a challenge with Redshift:
I'm trying to dynamically move rows into columns and aggregate by count, however I noticed the pivot table feature is only available from PostgreSQL 9.
Any idea about how to do the following?
index fruit color
1 apple red
2 apple yellow
2 banana blue
2 banana blue
3 banana blue
3 banana green
3 pear green
3 pear red
to:
index red yellow blue green
1 1 0 0 0
2 0 1 2 0
3 1 0 1 2
Essentially, grouping and counting occurrences of color per id (fruit is not so important, although I'll use it as a filter later).
Note: I might also want to do a binary transformation later on (i.e 0 for 0 and 1 if > 0)
Edit: If the above is not possible, any way to do this instead ?
index color count
1 red 1
1 yellow 0
1 blue 0
1 green 0
2 red 0
2 yellow 1
2 blue 2
2 green 0
3 red 1
3 yellow 0
3 blue 1
3 green 2
(again blue,yellow,blue and green should be dynamic)
For the Edit, you could do
select x.index, x.color, sum(case when y.index is not null then 1 else 0 end) as count
from
((select index
from [table]
group by index
order by index) a
inner join
(select color
from [table]
group by color
order by color) b
on 1 = 1) x
left outer join
[table] y
on x.index = y.index
and x.color = y.color
group by x.index, x.color
order by x.index, x.color
If PIVOT is not available in Redshift, then you could always just use a standard pivot query:
SELECT
index,
SUM(CASE WHEN color = 'red' THEN 1 ELSE 0 END) AS red,
SUM(CASE WHEN color = 'yellow' THEN 1 ELSE 0 END) AS yellow,
SUM(CASE WHEN color = 'blue' THEN 1 ELSE 0 END) AS blue,
SUM(CASE WHEN color = 'green' THEN 1 ELSE 0 END) AS green
FROM yourTable
GROUP BY index