Total column in a pivot example - sql

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.

Related

SQL Query to get multiple resultant on single column

I have a table that looks something like this:
id name status
2 a 1
2 a 2
2 a 3
2 a 2
2 a 1
3 b 2
3 b 1
3 b 2
3 b 1
and the resultant i want is:
id name total count count(status3) count(status2) count(status1)
2 a 5 1 2 2
3 b 4 0 2 2
please help me get this result somehow, i can just get id, name or one of them at a time, don't know how to put a clause to get this table at once.
Here's a simple solution using group by and case when.
select id
,count(*) as 'total count'
,count(case status when 3 then 1 end) as 'count(status1)'
,count(case status when 2 then 1 end) as 'count(status3)'
,count(case status when 1 then 1 end) as 'count(status2)'
from t
group by id
id
total count
count(status3)
count(status2)
count(status1)
2
5
1
2
2
3
4
0
2
2
Fiddle
Here's a way to solve it using pivot.
select *
from (select status,id, count(*) over (partition by id) as "total count" from t) tmp
pivot (count(status) for status in ([1],[2],[3])) pvt
d
total count
1
2
3
3
4
2
2
0
2
5
2
2
1
Fiddle

Getting unique Ids but not loose any data using SQL

Given a table sale where id is not unique:
id name item quantity
1 Darsh shoes 5
2 Liyah oil 1
2 Eiliyah watch 1
3 Zakaria notebook 2
3 Elliot shirt 3
4 Reese bag 1
I need to select all unique ids for a row and not loose any data(like for id in (2,3) where both name,item and quantity should be displayed in same row).Also there are maximum of 2 same id in sale table.
I tried using row_number() to get some unique pattern(s).
From this query :
Select a.id,a.name,a.item,a.quantity,b.name as name2,b.item as item2,b.quantity as quantity2
,row_number() over(partition by a.id order by a.id) as f1
,row_number() over(partition by a.name order by a.id) as f2
from sale a inner join sale b on a.id = b.id
I got this
id name item quantity name2 item2 quantity2 f1 f2
1 Darsh shoes 5 Darsh shoes 5 1 1
2 Eiliyah watch 1 Liyah oil 1 2 1
2 Eiliyah watch 1 Eiliyah watch 1 4 2
3 Elliot shirt 3 Zakaria notebook 2 2 1
3 Elliot shirt 3 Elliot shirt 3 4 2
2 Liyah oil 1 Eiliyah watch 1 3 1
2 Liyah oil 1 Liyah oil 1 1 2
4 Reese bag 1 Reese bag 1 1 1
3 Zakaria notebook 2 Elliot shirt 3 3 1
3 Zakaria notebook 2 Zakaria notebook 2 1 2
Now here the problem,If I filter f1,f2 and use IIF for remove repetitive data using this query :
Select id,name,item,quantity
,iif(name = name2,NULL,name2) as name2
,iif(item = item2,NULL,item2) as item2
,iif(quantity = quantity2,NULL,quantity2) as quantity2
from (
Select a.id,a.name,a.item,a.quantity,b.name as name2,b.item as item2,b.quantity as quantity2
,row_number() over(partition by a.id order by a.id) as f1
,row_number() over(partition by a.name order by a.id) as f2
from sale a inner join sale b on a.id = b.id
)t
where (f1=1 and f2=1) or(f1=3 and f2=1)
order by id
then quantity2 is (null) in 2nd row as shown below.
id name item quantity name2 item2 quantity2
1 Darsh shoes 5 NULL NULL NULL
2 Liyah oil 1 Eiliyah watch NULL
3 Zakaria notebook 2 Elliot shirt 3
4 Reese bag 1 NULL NULL NULL
So, there can be same quantity for different item and name.
Expected result:
id name item quantity name2 item2 quantity2
1 Darsh shoes 5 NULL NULL NULL
2 Liyah oil 1 Eiliyah watch 1
3 Zakaria notebook 2 Elliot shirt 3
4 Reese bag 1 NULL NULL NULL
Please help me.
Thanks!
One method is conditional aggregation . . . if you know that there are at most two duplicates per id:
select id,
max(case when seqnum = 1 then name end) as name_1,
max(case when seqnum = 1 then item end) as item_1,
max(case when seqnum = 1 then quantity end) as quantity_1,
max(case when seqnum = 2 then name end) as name_2,
max(case when seqnum = 2 then item end) as item_2,
max(case when seqnum = 2 then quantity end) as quantity_2
from (select s.*,
row_number() over (partition by id order by id) as seqnum
from sale s
) s
group by id;
As per your expected result .You can create temp(or Intermediate table) and as there are maximum of same two id ,then this can be your answer:
select *,row_number() over (partition by id order by id) as u_id into #test from sale
select * from (select * from #test where u_id=1) a
left join (select * from #test where u_id=2)b
on a.id = b.id

Conditional formatting on MAX value row

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;

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

SQL Server: How to select the second to last entry given a group of items?

I have a table with five columns:
mykey (INT pk)
name(VARCHAR)
otherdata (VARCHAR)
groupID (INT)
date DATETIME
active (INT)
Sample data:
mykey name otherdata groupID date active
----------------------------------------------------------
1 123_abc_nt cat 1 6-6-16 0
2 123_abc_nt dog 1 6-7-16 0
3 123_abc_nt car 1 6-8-16 1
4 123_xyz_nt red 2 6-9-16 0
5 123_xyz_nt blue 2 6-10-16 1
All entries in this table are grouped by groupID and name. In other words, if a name is 123_abc_nt, the group will be group "#insertgroupidhere". Due to the nature of the process that uses this table (which I can not change in any way), any change to either the "name" or "otherdata" column will produce a new row.
So say a user changes row 5 column "otherdata" from blue to green from a separate application that uses this table. In this case, that separate application will produce a new row: 6, 123_xyz_nt, green and mark the old column as not active 0.
mykey name otherdata groupID date active
-----------------------------------------------------------
1 123_abc_nt cat 1 6-6-16 0
2 123_abc_nt dog 1 6-7-16 0
3 123_abc_nt car 1 6-8-16 1
4 123_xyz_nt red 2 6-9-16 0
5 123_xyz_nt blue 2 6-10-16 0 <--- old row deactivated
6 123_xyz_nt green 2 6-11-16 1 <--- new row inserted and activated
This will occur to changes for ANY column in the table. So if I change name from _nt to _rt, the table will look like this:
mykey name otherdata groupID date active
---------------------------------------------------------
1 123_abc_nt cat 1 6-6-16 0
2 123_abc_nt dog 1 6-7-16 0
3 123_abc_nt car 1 6-8-16 1
4 123_xyz_nt red 2 6-9-16 0
5 123_xyz_nt blue 2 6-10-16 0
6 123_xyz_nt green 2 6-11-16 0 <--- old row deactivated
7 123_xyz_rt green 2 6-12-16 1 <--- new row inserted and activated
I need to write a query that will specifically detect this name change from _nt to _rt (or vice versa from _rt to _nt) and return the active row that has inccured this change. So in the last situation my query would need to return 7, 123_xyz_rt, green, 2, 1. Also, it needs to ignore any other column changes and only detect the nt/rt name column changes. There can be situations where I have multiple rows change at once also, and my query needs to detect this.
Example:
mykey name otherdata groupID date active
----------------------------------------------------------
1 123_abc_nt cat 1 6-6-16 0
2 123_abc_nt dog 1 6-7-16 0
3 123_abc_nt car 1 6-8-16 0 <-- deactivated
4 123_xyz_nt red 2 6-9-16 0
5 123_xyz_nt blue 2 6-10-16 0
6 123_xyz_nt green 2 6-11-16 0
7 123_xyz_rt green 2 6-12-16 0 <-- deactivated
8 123_xyz_nt green 2 6-13-16 1 <-- activated
9 123_abc_rt car 1 6-13-16 1 <-- activated (note the groupID)
I've been trying to nail down a query that does this but it's been exceedingly difficult. I cannot use triggers on the table in this database either so this needs to be strictly query based. Here is what I have attempted:
I have a temp table that catches any newly inserted rows:
DECLARE #NEWROWS_TEMP TABLE(mykey, name, active)
INSERT INTO #NEWROWS_TEMP
--PROCESS TO CATCH NEW ROWS....
Then with that list, I am trying to find rows that have changed names in a while loop:
--Loop Variables:
DECLARE #TOTALCOUNT INT = (SELECT COUNT(*) FROM #TEMP)
DECLARE #NAME_INVERTED VARCHAR(MAX)
DECLARE #NAME VARCHAR(MAX)
DECLARE #LOOPNUM INTEGER = 1
DECLARE #CURRENT_KEY INT = 0
--Loop to find entries that changed from (nt->rt) OR (rt->nt):
WHILE ((SELECT COUNT(*) FROM #TEMP) > 0 AND (SELECT #LOOPNUM) <= #TOTALCOUNT)
BEGIN
SET #CURRENT_KEY = (SELECT TOP 1 mykey FROM #TEMP)
SET #NAME = (SELECT name
FROM #TEMP
WHERE CLASSIC_KEY = #CURRENT_KEY)
SET #NAME_INVERTED = (SELECT CASE WHEN (CHARINDEX('_rt_',#NAME) > 0)
THEN
REPLACE(#NAME,'_rt_', '_nt_')
ELSE
CASE WHEN (CHARINDEX('_nt_',#NAME) > 0)
THEN
REPLACE(#NAME,'_nt_', '_rt_')
ELSE
NULL
END
END)
IF(EXISTS(
SELECT TOP 1 mykey
FROM mytable
WHERE name = #NAME_INVERTED AND
active = 0)
)
BEGIN
INSERT INTO #RESULT_LIST
SELECT #CURRENT_KEY
END
DELETE FROM #TEMP WHERE mykey = #CURRENT_KEY
SET #LOOPNUM = #LOOPNUM +1
END
This works except for situations where a row's name never changed but otherdata did and a past nt-rt change exists. The loop just skips over the fact that the row's name did not change.
So for example, if I do this:
mykey name otherdata groupID date active
----------------------------------------------------------
1 123_abc_nt cat 1 6-6-16 0
2 123_abc_nt dog 1 6-7-16 0
3 123_abc_nt car 1 6-8-16 0
4 123_xyz_nt red 2 6-9-16 0
5 123_xyz_nt blue 2 6-10-16 0
6 123_xyz_nt green 2 6-11-16 0
7 123_xyz_rt green 2 6-12-16 0
8 123_xyz_nt green 2 6-13-16 1
9 123_abc_rt car 1 6-13-16 0
10 123_abc_rt bike 1 6-13-16 1 <-- Name did not actually change. Otherdata did, but current query picks this up anuway since there once was a change from "123_abc_nt" to "123_abc_rt"
Is there a much easier way to get a similar result without using a loop? My method is proving to be highly inaccurate and hard to maintain.
This should return the expected result, check the name of the previous row with active=0 if it's different:
select * from mytable as t1
where active = 1
and name <>
( select top 1 name
from mytable as t2
where t1.groupID = t2.groupID
and active = 0
order by t2.date desc
)