PIVOT values from two columns to multiple columns - sql

Table: Sample
ID Day Status MS
----------------------------
1 1 0 10
1 2 0 20
1 3 1 15
2 3 1 3
2 30 0 5
2 31 0 6
Expected Result:
ID Day1 Day2 Day3....Day30 Day31 Status1 Status2 Status3...Status30 Status31
---------------------------------------------------------------------------------------
1 10 20 15 NULL NULL 0 0 1 NULL NULL
2 NULL NULL 3 5 6 NULL NULL 1 0 0
I want to get the MS and Status value for each day from 1 to 31 for each ID.
I have used PIVOT to get the below result.
Result:
ID Day1 Day2 Day3....Day30 Day31
-------------------------------------
1 10 20 15 NULL NULL
2 NULL NULL 3 5 6
Query:
SELECT
ID
,[1] AS Day1
,[2] AS Day2
,[3] AS Day3
.
.
.
,[30] AS Day30
,[31] AS Day31
FROM
(
SELECT
ID
,[Day]
,MS
FROM
Sample
) AS A
PIVOT
(
MIN(MS)
FOR [Day] IN([1],[2],[3],...[30],[31])
) AS pvtTable
How can I merge the Status column with the result?.

Try this. Use Another Pivot to transpose Status column. Then use aggregate (Max or Min) in select column list with group by Id to get the Result.
CREATE TABLE #est
(ID INT,[Day] INT,[Status] INT,MS INT)
INSERT #est
VALUES (1,1,0,10),(1,2,0,20),(1,3,1,15 ),
(2,3,1,3),(2,30,0,5),(2,31,0,6)
SELECT ID,
Max([Day1]) [Day1],
Max([Day2]) [Day2],
Max([Day3]) [Day3],
Max([Day30]) [Day30],
Max([Day31]) [Day31],
Max([status1]) [status1],
Max([status2]) [status2],
Max([status3]) [status3],
Max([status30])[status30],
Max([status31])[status31]
FROM (SELECT Id,
'status' + CONVERT(VARCHAR(30), Day) col_stat,
'Day' + CONVERT(VARCHAR(30), Day) Col_Day,
[status],
ms
FROM #est) a
PIVOT (Min([ms])
FOR Col_Day IN([Day1],[Day2],[Day3],[Day30],[Day31])) piv
PIVOT (Min([status]) FOR col_stat IN ([status1],[status2],[status3],[status30],[status31])) piv1
GROUP BY id

Related

SQL COUNT of zero values in multiple columns

I am calculating how many zeros appear in a series of columns based on a ID.
Example Table:
ID hour1 hour2 hour3
1 2 10 0
2 0 0 0
3 0 24 0
I think it would look something like this, but obviously it doesn't work
SELECT ID, COUNT(CASE WHEN(
FROM (VALUES (hour1) , (hour2) , (hour3))
AS VALUE (v)) AS ZERO_HOURS
Desired output:
ID ZERO_HOURS
1 1
2 3
3 2
One method is:
select t.id, h.num_zeros
from t cross apply
(select count(*) as num_zeros
from (values (hour1), (hour2), (hour3)) v(h)
where h = 0
) h;
Of course a case expression is not so hard either:
select t.id,
(case when hour1 = 0 then 1 else 0 end +
case when hour2 = 0 then 1 else 0 end
case when hour3 = 0 then 1 else 0 end
) as num_zeros
Or, if there are no negative or NULL values:
select t.id,
(1 - sign(hour1)) + (1 - sign(hour2)) + (1 - sign(hour3)) as num_zeros
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, hour1 INT, hour2 INT, hour3 INT);
INSERT INTO #tbl (hour1, hour2, hour3) VALUES
(2, 10, 0),
(0, 0 , 0),
(0, 24, 0);
-- DDL and sample data population, end
SELECT ID
, c.value('count(/root/*[./text()="0"])','INT') AS ZERO_HOURS
FROM #tbl
CROSS APPLY (
SELECT hour1, hour2, hour3
FOR XML PATH(''), TYPE, ROOT('root')) AS t(c);
Output
+----+------------+
| ID | ZERO_HOURS |
+----+------------+
| 1 | 1 |
| 2 | 3 |
| 3 | 2 |
+----+------------+

SQL Order By On two columns but same prority

I'm stuck on this simple select and don't know what to do.
I Have this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
5 | 200
6 | 200
7 | 100
8 | NULL
and want this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
7 | 100
5 | 200
6 | 200
8 | NULL
all group members keep together, but others order by ID.
I can not write this script because of that NULL records. NULL means that there is not any group for this record.
First you want to order your rows by the minimum ID of their group - or their own ID in case they belong to no group.Then you want to order by ID. That is:
order by min(id) over (partition by case when grp is null then id else grp end), id
If IDs and groups can overlap (i.e. the same number can be used for an ID and for a group, e.g. add a record for ID 9 / group 1 to your sample data) you should change the partition clause to something like
order by min(id) over (partition by case when grp is null
then 'ID' + cast(id as varchar)
else 'GRP' + cast(grp as varchar) end),
id;
Rextester demo: http://rextester.com/GPHBW5600
What about data after a null? In a comment you said don't sort the null.
declare #T table (ID int primary key, grp int);
insert into #T values
(1, NULL)
, (3, 100)
, (5, 200)
, (6, 200)
, (7, 100)
, (8, NULL)
, (9, 200)
, (10, 100)
, (11, NULL)
, (12, 150);
select ttt.*
from ( select tt.*
, sum(ff) over (order by tt.ID) as sGrp
from ( select t.*
, iif(grp is null or lag(grp) over (order by id) is null, 1, 0) as ff
from #T t
) tt
) ttt
order by ttt.sGrp, ttt.grp, ttt.id
ID grp ff sGrp
----------- ----------- ----------- -----------
1 NULL 1 1
3 100 1 2
7 100 0 2
5 200 0 2
6 200 0 2
8 NULL 1 3
10 100 0 4
9 200 1 4
11 NULL 1 5
12 150 1 6

SQL check if group containes certain values of given column (ORACLE)

I have table audit_log with these records:
log_id | request_id | status_id
1 | 2 | 5
2 | 2 | 10
3 | 2 | 20
4 | 3 | 10
5 | 3 | 20
I would like to know if there exists request_ids having status_id 5 and 10 at the same time. So this query should return request_id = 2 as its column status_id has values 5 and 10 (request_id 3 is omitted because status_id column has only value of 10 without 5).
How could I do this with SQL?
I think I should use group by request_id, but I don't know how to check if group has status_id with values 5 and 10?
Thanks,
mismas
This could be a way:
/* input data */
with yourTable(log_id , request_id , status_id) as (
select 1 , 2 , 5 from dual union all
select 2 , 2 , 10 from dual union all
select 3 , 2 , 20 from dual union all
select 4 , 3 , 10 from dual union all
select 5 , 3 , 20 from dual
)
/* query */
select request_id
from yourTable
group by request_id
having count( distinct case when status_id in (5,10) then status_id end) = 2
How it works:
select request_id,
case when status_id in (5,10) then status_id end as checkColumn
from yourTable
gives
REQUEST_ID CHECKCOLUMN
---------- -----------
2 5
2 10
2
3 10
3
So the condition count (distinct ...) = 2 does the work
SELECT request_id
FROM table_name
GROUP BY request_id
HAVING COUNT( CASE status_id WHEN 5 THEN 1 END ) > 0
AND COUNT( CASE status_id WHEN 10 THEN 1 END ) > 0
To check if both values exists (without regard to additional values) you can filter before aggregation:
select request_id
from yourTable
where status_id in (5,10)
group by request_id
having count(*) = 2 -- status_id is unique
-- or
having count(distinct status_id) = 2 -- status_id exists multiple times
This should do it:
select
log5.*, log10.status_id
from
audit_log log5
join audit_log log10 on log10.request_id = log5.request_id
where
log5.status_id = 5
and log10.status_id = 10
order by
log5.request_id
;
Here's the output:
+ ----------- + --------------- + -------------- + -------------- +
| log_id | request_id | status_id | status_id |
+ ----------- + --------------- + -------------- + -------------- +
| 1 | 2 | 5 | 10 |
+ ----------- + --------------- + -------------- + -------------- +
1 rows
And here's the sql to set up the example:
create table audit_log (
log_id int,
request_id int,
status_id int
);
insert into audit_log values (1,2,5);
insert into audit_log values (2,2,10);
insert into audit_log values (3,2,20);
insert into audit_log values (4,3,10);
insert into audit_log values (5,3,20);

inserting into table closing rows while keeping the date column

Following this question.
My table
id sum type date
1 3 -1 2017-02-02
1 6 -1 2017-02-04
1 -6 2 2017-02-01
1 -3 1 2017-02-09
1 3 -1 2017-02-17
1 6 -1 2017-02-05
This query finds people who pass the conditions and returns an occurrences number of rows of those users with some columns modified.
with t as(
select id
, -abs (sum) as sum
, sum (case when type = -1 then 1 else -1 end) as occurrences
--, collect_list(date) as time_col
from table
group by id, abs(sum)
having sum (case when type = -1 then 1 else -1 end) > 15
)
select t.id
, t.sum
, 2 as type
from t
lateral view explode (split (space (cast (occurrences as int) - 1),' ')) e
-- lateral view explode(time_col) time_table as time_key;
The problem is, I need every row to hold one date column from the list. I tried adding , collect_list(date) as time_col and then
lateral view explode(time_col) time_table as time_key;
but this just returned all possible combinations. I could probably use a join(would that work?), but I wondered if that's really necessary.
In the end these rows
1 3 -1 2017-02-17
1 6 -1 2017-02-05
would transform into
1 -3 2 2017-02-17
1 -6 2 2017-02-05
select val_id
,-val_sum as val_sum
,2 as val_type
,val_date
from (select val_id
,val_sum
,val_type
,val_date
,sum (case when val_type = -1 then 1 else -1 end) over
(
partition by val_id,-abs (val_sum)
) as occurrences
,row_number () over
(
partition by val_id,val_sum
order by val_date desc
) as rn
from mytable
) t
where val_type = -1
and rn <= occurrences
and occurrences > 15
;
Execution results (without and occurrences > 15)
+--------+---------+----------+------------+
| val_id | val_sum | val_type | val_date |
+--------+---------+----------+------------+
| 1 | -3 | 2 | 2017-02-17 |
+--------+---------+----------+------------+
| 1 | -6 | 2 | 2017-02-05 |
+--------+---------+----------+------------+

Counting values in a column separately

I have a table in my database with the following structure.
ID COMPANY_ID Status
-----------------------
1 10 1
2 10 2
3 12 2
4 12 2
5 12 1
6 13 3
7 14 3
8 14 3
9 10 1
10 10 2
I want to group my results on company ID and count each status and list them as separate columns.
i.e.
COMPANY_ID Status 1 Status 2 Status 3
-------------------------------------------
10 2 2 0
12 1 2 0
13 0 0 1
14 0 0 2
My question is how do I get the results above from my table? and probably join in with the company table.
Tried several possibilities, but didn't get the results.
select company_id
, count(case when status = 1 then 1 end) as [Status 1]
, count(case when status = 2 then 1 end) as [Status 2]
, count(case when status = 3 then 1 end) as [Status 3]
from YourTable
group by
company_id
This type of data transformation is known as a PIVOT. There are several ways that you are pivot the data.
You can use an aggregate function with a CASE expression:
select company_id,
sum(case when status = 1 then 1 else 0 end) status1,
sum(case when status = 2 then 1 else 0 end) status2,
sum(case when status = 3 then 1 else 0 end) status3
from yourtable
group by company_id;
See SQL Fiddle with Demo
Starting in SQL Server 2005+ you can use the PIVOT function:
select company_id,
[1] as Status1,
[2] as Status2,
[3] as Status3
from
(
select company_id, status
from yourtable
)src
pivot
(
count(status)
for status in ([1], [2], [3])
) piv
See SQL Fiddle with Demo.
The two versions above work well if you have a known number of values to transform into columns. But if it is unknown, then you can use dynamic SQL to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Status'+cast(status as varchar(10)))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT company_id,' + #cols + ' from
(
select company_id, ''Status''+cast(status as varchar(10)) Status
from yourtable
) x
pivot
(
count(Status)
for Status in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
All give the result:
| COMPANY_ID | STATUS1 | STATUS2 | STATUS3 |
--------------------------------------------
| 10 | 2 | 2 | 0 |
| 12 | 1 | 2 | 0 |
| 13 | 0 | 0 | 1 |
| 14 | 0 | 0 | 2 |