How to SQL PIVOT this data by day? - sql

With the following schema and table, showing example output of the table, I'm trying to "PIVOT" this data to have rows consisting of the SERIAL and AREA, with each column being the COMPLIANCE for each "NIGHTOF" record. Ideal output shown below as well. I can not wrap my head around the PIVOT syntax no matter how many times I read examples...
Schema and table:
SELECT [CONTID], [AREA], [NIGHTOF], [COMPLIANCE] FROM ComplianceScores
Regular Output:
CONTID NIGHTOF AREA COMPLIANCE
001 2014-01-01 Room 2 28
001 2014-01-01 Room 2 18
001 2014-01-01 Room 2 20
003 2014-01-02 Room 1 18
003 2014-01-02 Room 1 70
003 2014-01-02 Room 1 80
008 2014-01-03 Room 1 0
009 2014-01-04 Room 1 35
Ideal output:
CONTID AREA 2014-01-01 2014-01-02 2014-01-03
001 Room 2 28 18 20
003 Room 1 18 70 80

I agree with #Sparky that your data and output don't match.
I think your data was meant to be like:
create table ComplianceScores (
CONTID char(3),
NIGHTOF date,
AREA varchar(10),
COMPLIANCE int
)
insert ComplianceScores (CONTID, NIGHTOF, AREA, COMPLIANCE)
values ('001', '2014-01-01', 'Room 2', 28)
, ('001', '2014-01-02', 'Room 2', 18)
, ('001', '2014-01-03', 'Room 2', 20)
, ('003', '2014-01-01', 'Room 1', 18)
, ('003', '2014-01-02', 'Room 1', 70)
, ('003', '2014-01-03', 'Room 1', 80)
And I think you're looking for this SQL:
select ContId, Area, [2014-01-01], [2014-01-02], [2014-01-03]
from (select Area, ContId, NightOf, Compliance
from ComplianceScores) SourceTable
pivot
(
sum(Compliance)
for NightOf in ([2014-01-01], [2014-01-02], [2014-01-03])
) pt;

Related

How to pivot specific columns while grouping other columns (SQL)

I am working on a SQL query in Azure Databricks Environment, where considering the following dataset:
CREATE OR REPLACE TABLE tb_user_info
(
clientid INT,
visitid STRING,
channel STRING,
conversion INT,
index INT,
value STRING
);
INSERT INTO tb_user_info VALUES
(123, 'abc123', 'google', 1, 11, '1250'),
(123, 'abc123', 'google', 1, 22, '25000'),
(123, 'abc123', 'google', 1, 33, '1K and 3K'),
(456, 'def456', 'facebook', 3, 11, '2860'),
(456, 'def456', 'facebook', 3, 22, '78000'),
(456, 'def456', 'facebook', 3, 33, '3K and 5K');
SELECT * FROM tb_user_info ORDER BY clientid, index
clientid
visitid
channel
conversion
index
value
123
abc123
google
1
11
1250
123
abc123
google
1
22
25000
123
abc123
google
1
33
1K and 3k
456
def456
facebook
3
11
2860
456
def456
facebook
3
22
78000
456
def456
facebook
3
33
3K and 5k
I want to get the following output:
clientid
visitid
channel
conversion
salary (index=11)
savings (index=22)
salary range (index=33)
123
abc123
google
1
1250
25000
1K and 3k
456
def456
facebook
1
2860
78000
3K and 5k
where the columns clientid, visitid, channel and conversion are grouped and the columns index and value are the columns that are pivoted.
I've tried using the Pivot function and I read this Documentation but I haven't been successful.
Could you help me with how can I solve this task?
I am not sure what actual problem you have encounted, I write one query, it seems work normally,
SELECT * FROM (
SELECT clientid, visitid , channel , conversion , ind , value
FROM tb_user_info
) ss
PIVOT (
max(value)
FOR ind in (
[11] ,[22] ,[33]
)
) as a

Calculate the difference in minutes between 'First-row' and 'Second-row' with 2 different columns in SQL

I have the following table in which I have to calculate the difference in minutes between 'First-row' Emp_Out and 'Second-row' Emp_IN for each room and for each day starting from 12 am.
Table:
Date EMP_ID Room Emp_IN Emp_OUT Difference(In Min)
----- ------ ------ ------ ------- ------------------
9/1/22 001 Room 1 04:30 05:00 270 (First diff is calulated from 12am - Emp_IN)
9/1/22 002 Room 1 05:25 05:42 7
9/1/22 003 Room 1 05:48 06:13 6
9/1/22 001 Room 2 05:00 05:17 300 (First diff is calulated from 12am - Emp_IN)
9/1/22 002 Room 2 05:36 05:48 19
9/1/22 003 Room 2 05:51 06:05 3
Can LAG be used for it or I'm missing a logic which can be helped?
Use LAG to get the next row value, partition the rows using Room
The first entry of the day for the room will get Null and replace that with '00:00' which means 12 AM.
SELECT *,
DATEDIFF(MINUTE,ISNULL(LAG(Emp_out) OVER(PARTITION BY Date, Room ORDER BY Emp_In),'00:00'),Emp_In) [Difference in Min]
FROM Your_table
My sample scripts and the result
create table #temp(empdate date, empId int, room varchar(100), InTime time, outTime time)
insert into #temp Values ('2022-09-02','101','Room 1','04:30','05:00')
insert into #temp Values ('2022-09-02','102','Room 1','05:25','05:42')
insert into #temp Values ('2022-09-02','103','Room 1','05:48','07:00')
insert into #temp Values ('2022-09-02','101','Room 2','05:00','05:17')
insert into #temp Values ('2022-09-02','102','Room 2','05:36','05:48')
insert into #temp Values ('2022-09-02','103','Room 2','05:51','06:00')
SELECT *,
DATEDIFF(MINUTE,ISNULL(LAG(outTime) OVER(PARTITION BY Room ORDER BY InTime),'00:00'),Intime) [Difference in Min]
FROM #temp
DROP TABLE #temp
Output:
empdate empId room InTime outTime Difference in Min
---------- ----------- ---------- ---------------- ---------------- -----------------
2022-09-02 101 Room 1 04:30:00.0000000 05:00:00.0000000 270
2022-09-02 102 Room 1 05:25:00.0000000 05:42:00.0000000 25
2022-09-02 103 Room 1 05:48:00.0000000 07:00:00.0000000 6
2022-09-02 101 Room 2 05:00:00.0000000 05:17:00.0000000 300
2022-09-02 102 Room 2 05:36:00.0000000 05:48:00.0000000 19
2022-09-02 103 Room 2 05:51:00.0000000 06:00:00.0000000 3
Create the table
CREATE TABLE DEMOLAG (Date Datetime , EMP_ID INT , Room Varchar(50), EMP_IN TIME(0) , EMP_OUT TIME(0))
Insert the Records
INSERT INTO DEMOLAG VALUES ('9/1/22',001,'ROOM 1', '04:30','05:00')
INSERT INTO DEMOLAG VALUES ('9/1/22',002,'ROOM 1', '05:25','05:42')
INSERT INTO DEMOLAG VALUES ('9/1/22',003,'ROOM 1', '05:48','06:13')
INSERT INTO DEMOLAG VALUES ('9/1/22',001,'ROOM 2', '05:00','05:17')
INSERT INTO DEMOLAG VALUES ('9/1/22',002,'ROOM 2', '05:36','05:48')
INSERT INTO DEMOLAG VALUES ('9/1/22',003,'ROOM 2', '05:51','06:05')
USE LEG Function to achieve
SELECT *,
ISNULL((LAG(EMP_OUT,1) OVER(Partition by Room ORDER BY EMP_OUT,Room,Emp_ID ASC)),'00:00') AS Prvempout,
DATEDIFF(Minute,ISNULL((LAG(EMP_OUT,1) OVER(Partition by Room ORDER BY EMP_OUT,Room,Emp_ID ASC)),'00:00'),EMP_IN) AS Timediff
FROM DEMOLAG
For more detail explanation you can refer to Use LAG Function in SQL

Get star employee record from attendance according to each Month

I have Employee and Employee details table. In Employee table it has column like Id(Primary Key), Name, Gender, Joining Date, Department and so on.
In Employee Details Table it has column like Id(Primary Key), Employee Table Id(Foreign Key), Current Salary, Daily login Time, Daily logout time and so on.
What I am trying to achieve is, finding the STAR Employee of the month. The formula is,
First I need to find the attendance of each employee according to the month,
which I am able to do and below is the result.
Id
Name
Attendance
Month
1
Ryan
20
January
2
Joanna
19
January
3
Tom
21
January
4
Lucy
10
January
5
Frank
15
January
6
Jane
17
January
7
Robert
11
January
8
Ryan
18
February
9
Joanna
17
February
10
Tom
20
February
11
Lucy
16
February
12
Frank
15
February
13
Jane
17
February
14
Robert
11
February
15
Ryan
22
March
16
Joanna
19
March
17
Tom
11
March
18
Lucy
10
March
19
Frank
15
March
20
Jane
17
March
21
Robert
15
March
Then I need to find the Highest Attendance of the month and 15% less will be the lowest attendance, If any employee fall in this range then that employee will will be the STAR employee of the month.
In this second step I am facing all the problem. I am able to get Highest and Lowest attendance but not able to compare.
Here is my query for calculating this:
SELECT MAX(Attendance) AS [Highest Attendance],
MAX(Attendance) - ( MAX(Attendance) * .15 ) AS [Lowest Attendance], Month
FROM Employee
GROUP BY Month;
Can anyone please help me with this?
I am using SQL Server 2017.
Here is some dummy data to create the above scenario:
-- create a table
CREATE TABLE Employee (
Id int PRIMARY KEY,
Name varchar(100) NOT NULL,
Attendance int NOT NULL,
Month varchar(20) NOT NULL
);
-- insert some values
INSERT INTO Employee VALUES (1, 'Ryan', 20, 'January');
INSERT INTO Employee VALUES (2, 'Joanna', 19, 'January');
INSERT INTO Employee VALUES (3, 'Tom', 21, 'January');
INSERT INTO Employee VALUES (4, 'Lucy', 10, 'January');
INSERT INTO Employee VALUES (5, 'Frank', 15, 'January');
INSERT INTO Employee VALUES (6, 'Jane', 17, 'January');
INSERT INTO Employee VALUES (7, 'Robert', 11, 'January');
INSERT INTO Employee VALUES (8, 'Ryan', 18, 'February');
INSERT INTO Employee VALUES (9, 'Joanna', 17, 'February');
INSERT INTO Employee VALUES (10, 'Tom', 20, 'February');
INSERT INTO Employee VALUES (11, 'Lucy', 16, 'February');
INSERT INTO Employee VALUES (12, 'Frank', 15, 'February');
INSERT INTO Employee VALUES (13, 'Jane', 17, 'February');
INSERT INTO Employee VALUES (14, 'Robert', 11, 'February');
INSERT INTO Employee VALUES (15, 'Ryan', 22, 'March');
INSERT INTO Employee VALUES (16, 'Joanna', 19, 'March');
INSERT INTO Employee VALUES (17, 'Tom', 11, 'March');
INSERT INTO Employee VALUES (18, 'Lucy', 10, 'March');
INSERT INTO Employee VALUES (19, 'Frank', 15, 'March');
INSERT INTO Employee VALUES (20, 'Jane', 17, 'March');
INSERT INTO Employee VALUES (21, 'Robert', 15, 'March');
My Expected example output is like:
Name
Month
Employee Attendance
Highest Attendance
Lowest Attendance
Status
Ryan
January
20
21
17.85
STAR EMPLOYEE
You can achieve the above scenario in many ways but I would like to use CTE. Common table expressions (CTEs) allow you to structure and organize your SQL queries.
WITH calculated_value AS(
SELECT MAX(Attendance) AS [Highest Attendance],
(MAX(Attendance) - ( MAX(Attendance) * .15 )) AS [Lowest Attendance], Month
FROM Employee
group by Month
)
SELECT e.Name, e.Month, e.Attendance as [Employee Attendance], cv.[Highest Attendance],
cv.[Lowest Attendance],
'STAR EMPLOYEE' AS [Status]
FROM calculated_value AS cv
JOIN Employee AS e ON e.Month = cv.Month
WHERE e.Attendance <= cv.[Highest Attendance] AND e.Attendance >= cv.[Lowest Attendance]
N.B: Joining with months I won't prefer
I'm not totally sure what you are after, but if you want to apply a status based on the attendance being greater than a minimum you can do something along the following lines using window functions
with m as (
select *, Max(attendance) over(partition by [month]) HighestAttendance
from employee
)
select [Name], [Month], Attendance,
HighestAttendance, LowestAttendance,
case when Attendance > LowestAttendance then 'Star employee' else 'you suck' end Status
from m
cross apply(values(HighestAttendance-(HighestAttendance*0.15)))v(LowestAttendance)
Most calculations can be done in a sub-query.
SELECT
[Name]
, [Month]
, Attendance AS [Employee Attendance]
, AttendanceMax AS [Highest Attendance]
, AttendanceThreshold AS [Lowest Attendance]
, CASE
WHEN Attendance >= AttendanceThreshold
THEN 'STAR EMPLOYEE'
END AS Status
FROM
(
SELECT *
, [MonthNumber] = CHARINDEX(UPPER(LEFT([Month], 3)),'___JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC')
, [AttendanceMax] = MAX(attendance) OVER (PARTITION BY [month])
, [AttendanceThreshold] = ROUND((1-0.15)*MAX(attendance) OVER (PARTITION BY [month]), 2)
FROM employee
) q
WHERE Attendance >= AttendanceThreshold
ORDER BY [MonthNumber] ASC, Attendance DESC
Name | Month | Employee Attendance | Highest Attendance | Lowest Attendance | Status
:----- | :------- | ------------------: | -----------------: | ----------------: | :------------
Tom | January | 21 | 21 | 17.85 | STAR EMPLOYEE
Ryan | January | 20 | 21 | 17.85 | STAR EMPLOYEE
Joanna | January | 19 | 21 | 17.85 | STAR EMPLOYEE
Tom | February | 20 | 20 | 17.00 | STAR EMPLOYEE
Ryan | February | 18 | 20 | 17.00 | STAR EMPLOYEE
Joanna | February | 17 | 20 | 17.00 | STAR EMPLOYEE
Jane | February | 17 | 20 | 17.00 | STAR EMPLOYEE
Ryan | March | 22 | 22 | 18.70 | STAR EMPLOYEE
Joanna | March | 19 | 22 | 18.70 | STAR EMPLOYEE
Demo on db<>fiddle here

Grouping together date periods based on two separate rankings/groupings

Hi all sorry about my poorly worded title I am unsure as to how to phrase exactly what I need. But i will try and explain it better below:
I have a dataset that looks like this:
DECLARE #TestDATA TABLE (PERSON_ID int, START_DATE date, END_DATE date,SERVICE_RANK int)
INSERT INTO #TestDATA
VALUES
(123, '2018-01-31', '2018-02-14', 7),
(123, '2018-03-28', '2018-04-11', 4),
(123, '2018-04-12', '2018-04-30', 4),
(123, '2018-05-25', '2018-06-08', 7),
(123, '2018-06-08', '2018-06-15', 7),
(123, '2018-06-19', '2018-06-26', 7),
(123, '2018-06-26', '2018-09-28', 4),
(123, '2018-10-10', '2018-11-07', 7),
(123, '2018-11-27', '2018-12-11', 7),
(123, '2018-12-11', '2018-12-24', 7)
Which shows a date range and "service rank" for each person (there is only one person in this example but there are 10's of thousands in the database)
Where for each person_id and each service_rank I would like to group the date periods to identify how many distinct periods they have had. So in the above example this is what I would be looking for:
PERSON ID, START_DATE, END_DATE, SERVICE_RANK, SERVICE_PERIOD
123 2018-01-31 2018-02-14 7 1
123 2018-03-28 2018-04-11 4 2
123 2018-04-12 2018-04-30 4 2
123 2018-05-25 2018-06-08 7 3
123 2018-06-08 2018-06-15 7 3
123 2018-06-19 2018-06-26 7 3
123 2018-06-26 2018-09-28 4 4
123 2018-10-10 2018-11-07 7 5
123 2018-11-27 2018-12-11 7 5
123 2018-12-11 2018-12-24 7 5
I have tried row_number, rank, dense_rank and even had a go at the dreaded CURSOR FOR but I cannot get anything work as the windowed functions see the service ranks as the same, so for the above example it would see two service ranks when there are actually 5 they just share the same numbering.
Also in the dataset not every person will jump from one service_rank to another and back. They may go from one to another (eg 4 -> 7) and stay there or they may only have one service_rank over multiple rows.
Any ideas??
This is a gaps-and-islands problem. For this purpose, one method is lag() and a cumulative sum:
select t.*,
sum(case when prev_service_rank = service_rank then 0 else 1 end) over (partition by person_id order by start_date) as service_period
from (select t.*,
lag(service_rank) over (partition by person_id order by start_date) as prev_service_rank
from t
) t;

Calculate in a table with different cells - SQL Server / T-SQL

I have in my database (SQL Server 2008 R2) a table like this:
ID......Team...........Name......Age
102 Barcelona Mike 15
103 Barcelona Peter 10
104 Barcelona Jacke 10
105 Barcelona Jonas 10
106 Real Madrid Michael 20
107 Real Madrid Terry 26
108 Chelsea James 26
109 Chelsea Arthur 23
110 Chelsea Spence 22
How can I loop the field 'Team' and know that, there are records like Barcelona, Real Madrid and Chelsea.
After that I want to calculate the sum of the team player of each team.
For Barcelona: -> 10 + 10 + 10 + 15 = 45
For Real Madrid: -> 20 + 26 = 46
For Chelsea: -> 26 + 23 + 22 = 71
Fill each result in a separate variable.
The whole calculation should be done in a stored procedure.
The second thing, if I have a table like this:
ID......Team...........Name......HeaderGoal......FreeKickGoal
104 Barcelona Mike 2 1
105 Barcelona Peter 1 0
106 Real Madrid Michael 0 1
107 Real Madrid Terry 0 1
108 Chelsea James 0 0
109 Chelsea Arthur 2 3
110 Chelsea Spence 4 0
How can I loop the field 'Team' and know that, there are records like Barcelona, Real Madrid and Chelsea.
After that I want to calculate the sum of all Goals of each team with the goal type HeaderGoal and FreeKickGoal.
Example for
-> Barcelona: 2+1+1 = 4
-> Real Madrid: 1+1 = 2
-> Chelsea: 2 + 3 + 4 = 9
Fill each result in a separate variable.
The whole calculation should be done in a stored procedure.
I hope you can help me!
BK_
If I understood your question correctly it looks like what you want are aggregates for each group, something that is easily accomplished with the GROUP BY clause.
For the first query you would use:
SELECT team, SUM(age) AS 'Sum of the team'
FROM table
GROUP BY team
This will give this result:
Team Sum of the team
-------------------- ---------------
Barcelona 45
Chelsea 71
Real Madrid 46
and for the second:
SELECT team, SUM(headergoal + freekickgoal) AS 'Sum of goals'
FROM table
GROUP BY team
which will give this result:
Team Sum of goals
-------------------- ------------
Barcelona 4
Chelsea 9
Real Madrid 2
In your example data you list the desired result for the first part for Chelsea as 45 but I guess that is just a typo as you omitted one of Chelseas rows in the calculation?
As for turning it into a stored procedure I can just tell you that it's easy and refer you to the documentation as I won't do all the work for you...
Edit: added merge intoas a response to a comment:
To insert the result of the second query into an existing table you can use either a simple INSERT statement like this:
INSERT table_with_team_and_goals
SELECT team, SUM(headergoal + freekickgoal)
FROM table
GROUP BY team
or MERGE INTO which might be better if you intend to run the query many times (the target table will then be updated if the team already exist in it):
MERGE INTO table_with_team_and_goals AS target
USING (SELECT Team, SUM(headergoal + freekickgoal) AS goals FROM table_with_goals GROUP BY team) AS source
ON target.team=source.team
WHEN MATCHED THEN
UPDATE SET goals = source.goals
WHEN NOT MATCHED THEN
INSERT (Team, Goals)
VALUES (source.team, source.goals);
SELECT TEAM , Name, COUNT(TEAM) As GoalsPerTeam, COUNT(NAME) As GoalPerPlayer
FROM TABLE
GROUP BY TEAM , Name
This query will give you tolal goals scored by per player and per team .
-- Sum age by team
SELECT Team, SUM(Age) SumAge
FROM
(
SELECT Id, Team, Name, Age FROM
(
VALUES
(102, 'Barcelona' , 'Mike' , 15),
(103, 'Barcelona' , 'Peter' , 10),
(104, 'Barcelona' , 'Jacke' , 10),
(105, 'Barcelona' , 'Jonas' , 10),
(106, 'Real Madrid', 'Michael', 20),
(107, 'Real Madrid', 'Terry' , 26),
(108, 'Chelsea' , 'James' , 26),
(109, 'Chelsea' , 'Arthur' , 23),
(110, 'Chelsea' , 'Spence' , 22)
) AS X(Id, Team, Name, Age)
) X
GROUP BY Team
-- Sum goals by team
SELECT Team, SUM(HeaderGoal + FreeKickGoal) Goals
FROM
(
SELECT Id, Team, Name, HeaderGoal, FreeKickGoal FROM
(
VALUES
(104, 'Barcelona' , 'Mike' , 2, 1),
(105, 'Barcelona' , 'Peter' , 1, 0),
(106, 'Real Madrid', 'Michael', 0, 1),
(107, 'Real Madrid', 'Terry' , 0, 1),
(108, 'Chelsea' , 'James' , 0, 0),
(109, 'Chelsea' , 'Arthur' , 2, 3),
(110, 'Chelsea' , 'Spence' , 4, 0)
) AS X(Id, Team, Name, HeaderGoal, FreeKickGoal)
) X
GROUP BY Team