SQL Way To Duplicate Rows Based on a Column Value - tsql

I need to basically duplicate each row with every other dept value, so looking at the first row where the dept is Other, I need a second row duplicated with the dept name being Math. There could be multiple depts, so I need a new row for each potential dept. Is there a way to do this through SQL?
Select 743818 teacherPersonID, 'STUTZ, DOUGLAS' teacherDisplay, 'FT' personnelStatus, 1.0 ftePosition, 'Other' dept, 'INTRO TO COMPUTER SCIENCE, STUDENT ASST' course_name, 28 ttlStudents, 4 termSeq, 'course1' courseName, 'students1' courseStudents
Into #teacherCourses
UNION
Select 743818, 'STUTZ, DOUGLAS', 'FT', 1.0, 'Math', 'ALGEBRA 3, MATH 115 KSU COLLEGE ALGEBRA', 30, 4, 'course2', 'students2'
UNION
Select 743818, 'STUTZ, DOUGLAS', 'FT', 1.0, 'Math', 'STUDENT ASST, ADVANCED GEOMETRY', 24, 4, 'course3', 'students3'
UNION
Select 743818, 'STUTZ, DOUGLAS', 'FT', 1.0, 'Other', 'STUDENT ASST, ADVANCED GEOMETRY', 24, 4, 'course3', 'students3'
UNION
Select 743818, 'STUTZ, DOUGLAS', 'FT', 1.0, 'Math', 'ADVANCED GEOMETRY', 25, 4, 'course4', 'students4'
UNION
Select 743818, 'STUTZ, DOUGLAS', 'FT', 1.0, 'Math', 'MATH 115 KSU COLLEGE ALGEBRA, ALGEBRA 3', 21, 4, 'course6', 'students6'
UNION
Select 743818, 'STUTZ, DOUGLAS', 'FT', 1.0, 'Math', 'ALGEBRA 3, MATH 115 KSU COLLEGE ALGEBRA', 19, 4, 'course7', 'students7'

Related

Sort Date Names Accurately by Partition

I have the following data:
with cte (id,[months]) as (
select 1, 'July 2019' union all
select 2, 'July 2019' union all
select 3, 'July 2019' union all
select 4, 'July 2019' union all
select 5, 'August 2019' union all
select 6, 'August 2019' union all
select 7, 'September 2019' union all
select 8, 'October 2019' union all
select 9, 'November 2019' union all
select 10, 'December 2019' union all
select 11, 'January 2020' union all
select 12, 'January 2020' union all
select 13, 'January 2020' union all
select 14, 'January 2020' union all
select 15, 'February 2020' union all
select 16, 'March 2020' union all
select 17, 'March 2020' union all
select 18, 'April 2020' union all
select 19, 'May 2020' union all
select 20, 'June 2020'
)
I require to create a Sort Column that ranks all the same months with the same number.
The problem I experienced with the following code is, it does not sort correctly and also does not provide me with the expected results:
select
*
, dense_rank() over (partition by months order by id) Sort
from cte
Current Results:
My expected results:
How should I change my script to achieve this?
After a lot of struggle, I managed to resolve this with this script:
select
*
, convert(date,'01 '+Months) MonthsConvertedToDate
, dense_rank() over (order by convert(date,'01 '+Months)) Sort
from cte
order by sort,id
Results:
See demo here
I am however open to better suggestions :-)

Finding consecutive records

I have followed code which is supposed to give me consecutive records using PlanId and costcentreid. for example in the image the record number 7 has costcentreid of 14 and the costcentreid before that is 5, so then it will ignore all the records before that and return me StartDate of 2017-07-12. if the previous record was of the same cost centre, then it would keep going back till the cost centre is different and then return me the lowest date but it's not doing that. i have provided my sql. Could you please help?
Sample Data:
Scenario 1: Correct Answer should be '2017-07-12 11:56:52.560'
DECLARE #T TABLE (StartDate, PlanId, CostCentreId, PositionId, CostCentreFlavourID, CustomerPositionID)
INSERT #T(StartDate, PLanId, CostCentreID, PositionId, CostCentreFlavourID, CustomerPOsitionID)
VALUES('1998-10-23 00:00:00.000', 19130, 14, 129, 3, 1, 766 ),
('2010-06-22 00:00:00.000', 19130, 207, 25, 3, 1,16247),
('2012-05-01 16:27:04.460', 19130, 42, 14, 3, 1,23946),
('2013-04-30 18:57:57.617', 19130, 295, 14, 3, 1,29453),
('2015-03-03 09:31:28.133', 19130, 275, 5, 3,1, 39286),
('2015-06-26 15:48:35.637', 19130, 195, 5, 3,1,41985),
('2017-07-12 11:56:52.560', 19130, 1445, 14, 3, 1,57699)
Scenario 2: Correct Answer should be : '2015-06-26 15:48:35.637'
DECLARE #T TABLE (StartDate, PlanId, CostCentreId, PositionId, CostCentreFlavourID, CustomerPositionID)
INSERT #T(StartDate, PLanId, CostCentreID, PositionId, CostCentreFlavourID, CustomerPOsitionID)
VALUES('1998-10-23 00:00:00.000', 19130, 14, 129, 3,1,766 ),
('2010-06-22 00:00:00.000', 19130, 207, 25, 3, 1,16247),
('2012-05-01 16:27:04.460', 19130, 42, 14, 3, 1,23946),
('2013-04-30 18:57:57.617', 19130, 295, 14, 3, 1,29453),
('2015-03-03 09:31:28.133', 19130, 275, 5, 3, 1,39286),
('2015-06-26 15:48:35.637', 19130, 195, 14, 3,1, 41985),
('2017-07-12 11:56:52.560', 19130, 1445, 14, 3, 1,57699)
Scenario 3: Correct Answer should be: '2012-05-01 16:27:04.460'
DECLARE #T TABLE (StartDate, PlanId, CostCentreId, PositionId, CostCentreFlavourID, CustomerPositionID)
INSERT #T(StartDate, PLanId, CostCentreID, PositionId, CostCentreFlavourID, CustomerPOsitionID)
VALUES('1998-10-23 00:00:00.000', 19130, 14, 129, 3,1,766 ),
('2010-06-22 00:00:00.000', 19130, 207, 25, 3,1, 16247),
('2012-05-01 16:27:04.460', 19130, 42, 14, 3,1, 23946),
('2013-04-30 18:57:57.617', 19130, 295, 14, 3,1, 29453),
('2015-03-03 09:31:28.133', 19130, 275, 14, 3, 1,39286),
('2015-06-26 15:48:35.637', 19130, 195, 14, 3,1, 41985),
('2017-07-12 11:56:52.560', 19130, 1445, 14, 3,1, 57699)
WITH cte
AS (SELECT cp1.StartDate,
fp.PlanId,
p.CostCentreID,
p.PositionID,
fp.CostCentreFlavourId,
fp.CustomerWithNDISNumberOfPlans,
cp1.CustomerPositionID,
ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID ORDER BY cp1.StartDate) AS CustomerRow,
ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID, p.CostCentreID ORDER BY cp1.StartDate) AS CustomerCostCentreRow ,
ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID ORDER BY cp1.StartDate)
- ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID, p.CostCentreID ORDER BY cp1.StartDate) rn3
FROM #FlavouredPlans fp
INNER JOIN dbo.tblCustomerPositions cp1
ON cp1.CustomerID = fp.LADSCustomerID
INNER JOIN dbo.tblPositions p
ON p.PositionID = cp1.PositionID
--AND fp.CostCentreId = p.CostCentreID
WHERE fp.CostCentreFlavourId = 3
AND fp.OrderOfPlans = 1
),
ctePositionStartDate
AS (SELECT *,
MIN(cte.StartDate) OVER (PARTITION BY PlanId, CostCentreID, startdate, rn3) MinStartDate,
ROW_NUMBER() OVER (PARTITION BY PlanId ORDER BY cte.StartDate ASC) [Order]
FROM cte )
SELECT *
FROM ctePositionStartDate
WHERE ctePositionStartDate.planid = 19130
You can get your result by using window functions. Check this query
DECLARE #T TABLE (StartDate datetime, PlanId int, CostCentreId int, PositionId int, CostCentreFlavourID int, CustomerPositionID int)
INSERT #T(StartDate, PLanId, PositionId, CostCentreID, CostCentreFlavourID, CustomerPOsitionID)
VALUES
('19981023 00:00:00.000', 19130, 14, 129, 3,1),
('20100622 00:00:00.000', 19130, 207, 25, 3,1),
('20120501 16:27:04.460', 19130, 42, 14, 3,1),
('20130430 18:57:57.617', 19130, 295, 12, 3,1),
('20150303 09:31:28.133', 19130, 275, 14, 3, 1),
('20150626 15:48:35.637', 19130, 195, 14, 3,1),
('20170712 11:56:52.560', 19130, 1445, 14, 3,1)
select
*
from (
select
*, row_number() over (partition by PlanId, CostCentreID, rn1 - rn2 order by StartDate) r3
, max(StartDate) over (partition by PlanId, CostCentreID, rn1 - rn2) mx1
, max(StartDate) over (partition by PlanId) mx2
from (
select
*, row_number()over(partition by PlanId order by StartDate) rn1
, row_number()over(partition by PlanId, CostCentreID order by StartDate) rn2
from
#T
) t
) t
where
mx1 = mx2
and r3 = 1

List Hours worked with multiple shifts and across dates

I have a few questions on how to handle a few employee timekeeping queries in SQL 2012… I had asked another quesiton last week about determining the time between shifts, which I as able to get a great response to.
We are fed the data from our Point Of Sale Software Provider, so we cannot change the format of the data.
Work days are based on DateOfBusiness which are from 5:00 am until 4:59am the next calendar day, so it crosses midnight. Locations End Of Day process is offset a little to control balance loads, some are 5:05am instead of 5:00am. When the End Of Day process runs, it clocks everyone out then clocks them back in when the process completes.
- Employees clock out for the 30 minute required meal breaks, so there are 2 or more records for one day for these employees. Not sure why, but several employees have more than 3 records in one day (EmployeeShiftNumber).
- Shifts occasionally cross DateOfBusiness. In at 11:00PM - Out at 7:00am
I need to report list of initial InTime and Final OutTime and the number of minutes worked. I have will have to compare these against the table which holds the employee schedules. This reporting is not for Payroll purposes, but for comparisons against scheduled shifts.
I have included some sample data grouped by employee and by day and the values I would expect to see commented to the right. I also need to see the number of minutes worked in the shift.
--drop table #Shift
CREATE TABLE #Shift(
FKEmployeeNumber int,
DateOfBusiness datetime,
FKStoreId int,
EmployeeShiftNumber int,
FKJobCodeId int,
InHour int,
InMinute int,
OutHour int,
OutMinute int)
insert into #Shift ( FKEmployeeNumber, DateOfBusiness, FKStoreId, EmployeeShiftNumber, FKJobCodeId, InHour, InMinute,OutHour,OutMinute)
values
(23761, '11/30/2017', 3013, 0, 1, 17, 39, 21, 30),
(23761, '11/30/2017', 3013, 1, 1, 21, 30, 2, 39), -- 5:39PM 2:39AM
(23770, '11/30/2017', 3013, 0, 200, 7, 19, 16, 25), -- 7:19AM 4:25PM
(23938, '11/30/2017', 3013, 0, 1, 16, 4, 1, 26), -- 4:04AM 1:26AM
(24006, '11/30/2017', 3013, 0, 1, 7, 30, 18, 36),
(24006, '11/30/2017', 3013, 1, 1, 18, 36, 18, 40), -- 7:30AM 6:40PM
(24018, '11/30/2017', 3013, 0, 2, 8, 52, 17, 0), -- 8:52M 4:00PM
(25176, '11/30/2017', 3013, 0, 200, 15, 59, 20, 1), -- 3:59PM 8:01PM
(25176, '11/30/2017', 3013, 1, 200, 20, 30, 0, 05), -- 8:30PM 12:05AM
(25180, '11/30/2017', 3013, 0, 1, 21, 0, 5, 0), -- 9:00PM 5:00AM
(25187, '11/30/2017', 3013, 0, 1, 10, 0, 16, 6), -- 10:00AM 4:06PM
(35189, '11/30/2017', 3013, 0, 1, 16, 58, 2, 4), -- 4:58PM 2:04AM
(25147, '12/04/2017', 3106, 0, 1, 6, 58, 15, 2),
(25147, '12/04/2017', 3106, 1, 1, 15, 3, 15, 3), -- 6:58AM 3:03PM
(26291, '12/01/2017', 3118, 1, 200, 23, 15, 5, 5),
(26291, '12/02/2017', 3118, 0, 200, 5, 6, 7, 22), -- 11:15PM 7:22AM
(26291, '12/03/2017', 3118, 0, 200, 7, 30, 15, 38), -- 7:30AM 3:38PM
(26291, '12/04/2017', 3118, 0, 200, 23, 15, 5, 5),
(26291, '12/05/2017', 3118, 0, 200, 5, 6, 7, 12), -- 11:15PM 7:12AM
(26291, '12/05/2017', 3118, 1, 200, 23, 15, 5, 5),
(26291, '12/06/2017', 3118, 0, 200, 15, 14, 7, 5) -- 11:15PM 7:05AM
--Select * from #Shift
SELECT fkstoreid, FKEmployeeNumber AS EmpID, DateOfBusiness AS Date,
RIGHT(CONVERT(varchar(30), DATEADD(MINUTE, InTime_Mins, 0), 100), 7) AS InTime,
RIGHT(CONVERT(varchar(30), DATEADD(MINUTE, OutTime_Mins, 0), 100), 7) AS OutTime,
MinsWorked
FROM (
select sh.FKStoreId, sh.FKEmployeeNumber, sh.DateOfBusiness,
MIN(sh.InHour*60+InMinute) AS InTime_Mins,
MAX(sh.OutHour*60+OutMinute) AS OutTime_Mins,
SUM(((sh.outhour+case when sh.OutHour < sh.InHour then 24 else 0 end)*60 + sh.outminute) -
(sh.inhour*60 + sh.inminute)) AS MinsWorked
from #shift sh
group by sh.FKStoreId,sh.FKEmployeeNumber, sh.DateOfBusiness
) AS derived
order by FKEmployeeNumber, DateOfBusiness
I've written a sample of how you could do it.
Briefly summarized I perform these steps:
Mark records that should be grouped with either the next or the previous record, because they are a part of the same shift.
Find the start and end of each shift
Join all records for an employee that fall within each shift
Group the resultset per employee per shift, and calculate the minutes worked by summing up the minutes between checkin and checkout for each record within a shift.
Sample query:
WITH ShiftParts AS (
SELECT
FKEmployeeNumber,
DATEADD(MINUTE, InMinute, DATEADD(HOUR, InHour, DateOfBusiness)) CheckIn, --Datetime of the checkin
DATEADD(DAY, CASE WHEN OutHour < InHour THEN 1 ELSE 0 END,
DATEADD(MINUTE, OutMinute, DATEADD(HOUR, OutHour, DateOfBusiness))) CheckOut --Datetime of the checkout (add one day if we crossed midnight).
FROM #Shift
),
GroupInfo AS (
SELECT
*,
CASE
WHEN DATEDIFF(MINUTE, LAG(CheckOut, 1, NULL) OVER (PARTITION BY FKEmployeeNumber ORDER BY CheckOut), CheckIn) <= 120
THEN 1 ELSE 0 END AS GroupWithPrevious, --Determine whether we want to group this record with the previous
CASE
WHEN DATEDIFF(MINUTE, CheckOut, LEAD(CheckIn, 1, NULL) OVER (PARTITION BY FKEmployeeNumber ORDER BY CheckOut)) <= 120
THEN 1 ELSE 0 END AS GroupWithNext --Determine whether we want to group this record with the next
FROM ShiftParts
), ShiftStartAndEnd AS (
SELECT
*,
CASE WHEN GroupWithNext = 1 THEN LEAD(CheckOut, 1, NULL) OVER (PARTITION BY FKEmployeeNumber ORDER BY CheckOut) ELSE CheckOut END AS FinalCheckOut
FROM GroupInfo
WHERE GroupWithPrevious = 0 OR GroupWithNext = 0 --Only pick beginning and end of a shift
)
SELECT
sse.FKEmployeeNumber,
sse.CheckIn,
sse.FinalCheckOut,
SUM(DATEDIFF(MINUTE,sp.CheckIn,sp.CheckOut)) AS MinutesWorked
FROM ShiftStartAndEnd sse
LEFT OUTER JOIN ShiftParts sp ON sp.FKEmployeeNumber = sse.FKEmployeeNumber AND sp.CheckIn >= sse.CheckIn AND sp.CheckOut <= sse.FinalCheckOut
WHERE sse.GroupWithPrevious = 0
GROUP BY sse.FKEmployeeNumber, sse.CheckIn, sse.FinalCheckOut
Note: I think the last two records in your sample data should not be grouped
(26291, '12/05/2017', 3118, 1, 200, 23, 15, 5, 5),
(26291, '12/06/2017', 3118, 0, 200, 15, 14, 7, 5) -- 11:15PM 7:05AM
should be:
(26291, '12/05/2017', 3118, 1, 200, 23, 15, 5, 5), -- 11:19PM 5:05AM
(26291, '12/06/2017', 3118, 0, 200, 15, 14, 7, 5) -- 3:14PM 7:05AM

PostgreSQL, renumber and cumulative sum at once

I have temporary table (without primary key) created as a result of few operations which are UNION together and which can be simulated with following one:
DROP TABLE IF EXISTS temp1;
CREATE TEMP TABLE temp1(rownum int, sname text, input_qty decimal(8,3),
output_qty decimal(8,3), cumulativesum decimal(8,3));
INSERT INTO temp1 (rownum, sname, input_qty, output_qty, cumulativesum)
VALUES (0, 'name 1', 3.186, 0, 0),
(0, 'name 2', 0, 0.24, 0),
(0, 'name 3', 0, 1, 0),
(0, 'name 4', 0.18, 0.125, 0),
(0, 'name 5', 0, 1.14, 0);
During past processes columns 'rownum' and 'cumulativesum' was intentionally filled with zeroes.
As a last two steps (or one if possible) I would like to enumerate rownum by +1 from beginning and calculate and write cumulative sum in column 'cumulativesum' to get table ready to write to html document more or less directly.
Cumulative sum should be calculated like:
lastcumulstivesum + input_qty - output_qty.
After all this should be result of operation:
1, 'name 1' 3.186 0.000 3.186
2, 'name 2' 0.000 0.240 2.946
3, 'name 3' 0.000 1.000 1.946
4, 'name 4' 0.180 0.125 2.001
5, 'name 5' 0.000 1.140 0.861
Please if someone can write described query(es) with presented table.
PostgreSQL 9.1, Windows 7

PostgreSQL, mixing SUM horizontaly and vertically

I have a temporary table which is result of previously heavy combined data from which I have to create html document to show.
This table in short illustrates situation:
DROP TABLE IF EXISTS temp11;
CREATE TABLE temp11 (t_idx int PRIMARY KEY, mydate text, myclass int, mypercent double precision, valpercent double precision, valclass double precision);
INSERT INTO temp11
(t_idx, mydate, myclass, mypercent, valpercent, valclass) VALUES
(1, '01.01.2014', 1, 10, 10, 1),
(2, '01.01.2014', 2, 20, 20, 4),
(3, '01.01.2014', 2, 20, 50, 10),
(4, '01.01.2014', 1, 10, 17, 1.7),
(5, '02.01.2014', 2, 20, 40, 8),
(6, '02.01.2014', 1, 10, 18, 1.8),
(7, '02.01.2014', 2, 20, 50, 10),
(8, '03.01.2014', 1, 10, 10, 1),
(9, '03.01.2014', 2, 20, 40, 8),
(10, '03.01.2014', 1, 10, 20, 2),
(11, '03.01.2014', 2, 20, 30, 6);
Now I have a query for grouping and summing that into dates and valclasses:
SELECT mydate, myclass, mypercent,
SUM(valpercent) AS sumvalpercent,
SUM(valclass) AS sumvalclass,
SUM(valpercent+valclass) AS sum_row
FROM temp11
GROUP BY mydate, myclass, mypercent
ORDER BY mydate;
Result of this query is expectable:
"01.01.2014" 2 20 70 14.0 84.0
"01.01.2014" 1 10 27 2.7 29.7
"02.01.2014" 1 10 18 1.8 19.8
"02.01.2014" 2 20 90 18.0 108.0
"03.01.2014" 2 20 70 14.0 84.0
"03.01.2014" 1 10 30 3.0 33.0
But needs are a bit extended.
Is it possible to do with PostgreSQL that in same process after every date I get vertically SUM of data inside that date and after all, at the end, SUM of data from all dates so result will look like this:
"01.01.2014" 2 20 70 14.0 84.0
"01.01.2014" 1 10 27 2.7 29.7
97 16.7 113.7
"02.01.2014" 1 10 18 1.8 19.8
"02.01.2014" 2 20 90 18.0 108.0
108 19.8 127.8
"03.01.2014" 2 20 70 14.0 84.0
"03.01.2014" 1 10 30 3.0 33.0
100 17.0 117.0
305 53.5 358.5
If this is possible such (or similar), how that query should look like with showed data?
The simplest way I can think of is to use UNION ALL to get all the desired output at once.
If you leave out the fact that the dates are shown (needed for the order by clause) this query gives the requested output in the simplest way.
SELECT mydate, myclass, mypercent,
SUM(valpercent) AS sumvalpercent,
SUM(valclass) AS sumvalclass,
SUM(valpercent+valclass) AS sum_row
FROM temp11
GROUP BY mydate, myclass, mypercent
UNION ALL
SELECT mydate || ' total', null, null,
SUM(valpercent) AS sumvalpercent,
SUM(valclass) AS sumvalclass,
SUM(valpercent+valclass) AS sum_row
FROM temp11
GROUP BY mydate
UNION ALL
SELECT 'Total', null, null,
SUM(valpercent) AS sumvalpercent,
SUM(valclass) AS sumvalclass,
SUM(valpercent+valclass) AS sum_row
FROM temp11
ORDER BY mydate;
Here's a fiddle
Perhaps it can be rewritten more elegantly using WITH
EDIT:
This will be more efficient because it only traverses through temp11 table just once. Then it only uses the temporary table temp100 which has much fewer rows for the additional totals (no more than one row per day). The UNIONs still remain and the logic is still the same.
WITH temp100 (mydate,myclass,mypercent, sumvalpercent,sumvalclass,sum_row) as (
SELECT mydate, myclass, mypercent,
SUM(valpercent) AS sumvalpercent,
SUM(valclass) AS sumvalclass,
SUM(valpercent+valclass) AS sum_row
FROM temp11
GROUP BY mydate, myclass, mypercent
)
SELECT mydate,myclass,mypercent, sumvalpercent,sumvalclass,sum_row
FROM temp100
UNION ALL
SELECT mydate || ' total' as mydate, null, null, SUM(sumvalpercent), SUM(sumvalclass), SUM(sum_row)
FROM temp100
GROUP BY mydate
UNION ALL
SELECT 'Total' as mydate, null, null, SUM(sumvalpercent), SUM(sumvalclass), SUM(sum_row)
FROM temp100
ORDER BY mydate;
This is the fiddle