Get data from two date ranges within same table (MSSQL) - sql-server

I have this query:
SELECT
COUNT(DISTINCT ProdTr.OrdNo) AS Orders,
ProdTr.YrPr AS Period,
SUM(ProdTr.DAm) AS Total,
SUM(ProdTr.IncCst) AS Cost
FROM ProdTr
WHERE ProdTr.TrTp = 1 AND ProdTr.CustNo != 0
AND ProdTr.YrPr BETWEEN (201901) AND (201912)
GROUP BY ProdTr.YrPr
ORDER BY ProdTr.YrPr ASC
And it works well. It yields the expected result, sales data from the date period 2019-01 to 2019-12. Result:
I would like to add an extra column that shows the same data - but from last year. For period 2019-01 it should show sales data for 2018-01 (1 year back). I managed to do this with a subquery, but it is slow - and seems like a bad idea.
Are there any better ways to achieve this? Database version is MSSQL 2016.
Thank you very much for your time.

You can do it with conditional aggregation:
SELECT
COUNT(DISTINCT CASE WHEN LEFT(YrPr, 4) = '2019' THEN OrdNo END) AS Orders2019,
'2019' + RIGHT(YrPr, 2) AS Period2019,
SUM(CASE WHEN LEFT(YrPr, 4) = '2019' THEN DAm END) AS Total2019,
SUM(CASE WHEN LEFT(YrPr, 4) = '2019' THEN IncCst END) AS Cost2019,
SUM(CASE WHEN LEFT(YrPr, 4) = '2018' THEN DAm END) AS Total2018
FROM ProdTr
WHERE TrTp = 1 AND CustNo != 0
AND YrPr BETWEEN (201801) AND (201912)
GROUP BY RIGHT(YrPr, 2)
ORDER BY Period2019 ASC

You could do it like this:
WITH TwoYears AS (
SELECT COUNT(DISTINCT ProdTr.OrdNo) AS Orders
, ProdTr.YrPr AS Period
, SUM(ProdTr.DAm) AS Total
, SUM(ProdTr.IncCst) AS Cost
FROM ProdTr
WHERE ProdTr.TrTp = 1
AND ProdTr.CustNo != 0
AND ProdTr.YrPr BETWEEN 201801 AND 201912
GROUP BY ProdTr.YrPr
), CurrentYear AS (
SELECT Orders, Period, Total, Cost
FROM TwoYears
WHERE Period >= 201901
), PreviousYear AS (
SELECT Orders, Period, Total, Cost
FROM TwoYears
WHERE Period < 201901
)
SELECT c.Orders, c.Period, c.Total, c.Cost
, p.Orders AS PrevOrders, p.Period AS PrevPeriod, p.Total AS PrevTotal, p.Cost AS PrevCost
FROM CurrentYear c
FULL JOIN PreviousYear p ON p.Period = c.Period - 100
ORDER BY COALESCE(c.Period, p.Period + 100)

Related

SQL Server 2014 - Sum Working Hour group by Day/Night Time, Week/Weekend, Regular/Overtime

I have a list of tasks,
for each I have a TaskID, startTime and StopTime as milliseconds from 1-1-1970 and a list of users (#Tasks).
I need to calculate the time spent by each user on the task splitted by day/night time, week or weekend, regular/overtime considering nighttime hours from 10:00 PM to 06:00 AM.
Surely there's a better solution but so far I got this:
IF OBJECT_ID('tempdb..#Tasks') IS NULL
BEGIN
create table #Tasks
(
TaskID nvarchar(50),
DateStart bigint,
DateStop bigint,
Staff nvarchar(100)
)
insert into #Tasks values
('C001',1554181200000,1554190200000,'john,jack'),
('C002',1554202800000,1554212700000,'tom,john'),
('C003',1554228000000,1554246900000,'john,franck'),
('C004',1554613200000,1554626700000,'john')
END
GO
declare
#UserName nvarchar(50)='john',
#DateFrom datetime='2019-04-01',
#DateTo datetime='2019-04-30',
#nStart time='06:00:00',
#nStop time='22:00:00'
select
startday as [Day],
sum([WeekDay]) as [WeekDay],
sum([WeekNight]) as [WeekNight],
sum([WeekendDay]) as [WeekendDay],
sum([WeekendNight]) as [WeekendNight],
sum([TotalMinutes]) as [TotalMinutes],
0 WeekDayOverTime,
0 WeekNightOvertime,
0 WeekendDayOvertime,
0 WeekendNightOvertime,
[UserName]
,timeframe
from
(
select
iif(isWeekend=1,NightMinutes,0) WeekendNight,
iif(isWeekend=0,NightMinutes,0) WeekNight,
iif(isWeekend=1,DayMinutes,0) WeekendDay,
iif(isWeekend=0,DayMinutes,0) [WeekDay],
TotalMinutes,
username,
startday,
timeframe
from
(
select
iif(Before6>0,Before6,0)+ iif(After22>0,After22,0) NightMinutes,
TotalMinutes-iif(Before6>0,Before6,0)- iif(After22>0,After22,0) DayMinutes,
TotalMinutes,
startday,
isWeekend,
UserName,
timeframe
from
(
Select
(t.datestop-t.datestart)/60000 TotalMinutes,
datediff(n,convert(time,DATEADD(SECOND,t.DateStart/1000,'1970-01-01')),#nStart) Before6,
datediff(n,#nStop,convert(time,DATEADD(SECOND,t.DateStop/1000,'1970-01-01'))) After22,
iif((((DATEPART(DW, convert(datetime,DATEADD(SECOND,t.DateStart/1000,'1970-01-01'))) - 1 ) + ##DATEFIRST ) % 7) IN (0,6),1,0) isWeekend,
convert(varchar(10),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),126) startday,
STUFF(( SELECT distinct ' ' + convert(varchar(5),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),108)+'-'+convert(varchar(5),DATEADD(SECOND,t.DateStop/1000,'1970-01-01'),108) AS [text()]
FROM #Tasks tt
--WHERE tt.taskID=t.TaskID
FOR XML PATH('') ), 1, 1, '' ) AS [timeframe],
#UserName UserName
FROM #Tasks t
WHERE t.Staff like '%'+#UserName+'%'
and DATEADD(SECOND,t.DateStart/1000,'1970-01-01') between #DateFrom and #DateTo
) z
) zz
) zzz
group by startday,username,timeframe
order by startday
I need now to :
1) group result by day, summing up WeekDay,WeekNight,WeekendDay,WeekendNight and TotalMinutes, and concatenating timeframe so to have for example on 2nd April "05:00-07:30|11:00-13:45|18:00-23:00"
2) Not sum up time between 12:00 and 12:30 (if applicable) since it is lunch time
3) considering that after 8 hours daily it has to be calculated as overtime, I have to split the total minutes between in time and overtime, but depending if overtime is by daytime or nighttime or on the weekend
4) eventually using a holiday table
in other words we should have this:
Day TotalMinutes WeekDay WeekNight WeekendDay WeekendNight WeekDayOverTime WeekNightOvertime WeekendDayOvertime WeekendNightOvertime UserName timeframe
02/04/2019 630 420 60 0 0 45 75 0 0 john 05:00-07:30|11:00-13:45|18:00-23:00
07/04/2019 225 0 0 165 60 0 0 0 0 john 05:00-08:45
because (on 2nd April) we have:
First Task:
60 minutes of Regular NightTime
90 minutes of Regular DayTime
Second Task:
165 minutes of Regular DayTime, but have to count only 135 due to lunch time
Third Task:
240 DayTime
75 NightTime
but since with Task 1 and 2 we sum up 285 minutes, only the first 185 Minutes of Third task are Regular DayTime: the remaining 45 are Overtime DayTime, and the following 75 of NightTime are actually OvertimeNightTime
In this approach the first CTE (properDates) get the Start and Stop Datetimes, then you don't need to repeat that formula over the query.
The second CTE(splittedMinutes) is to get the same data you get in your current approach, except for the first CROSS APPLY, which is splitting the timeframes crossing with lunch time. The second CROSS APPLY gets the number of minutes and isWeekend value.
In the third CTE(qualifiedMinutes) I am using a window partition to get the accumulated minutes and generate the overtimes when applies.
At the end I used a selective SUM to separate weekdays and weekends in the aggregates
;with properDates AS (
SELECT TaskID, DATEADD(SECOND,t.DateStart/1000,'1970-01-01') as DateStart,DATEADD(SECOND,t.DateStop/1000,'1970-01-01') as DateStop, Staff
FROM #Tasks t
WHERE Staff LIKE '%' + #UserName + '%'
), splittedMinutes AS (
select
CAST(p.DateStart AS DATE) as [Day],
TotalMinutes,
SUM(TotalMinutes) OVER (PARTITION BY CAST(p.DateStart AS DATE) ORDER BY b.start) AS cumulate,
TotalMinutes - EarlyMinutes - LateMinutes as DayTime,
EarlyMinutes + LateMinutes as NightTime,
isWeekend,
CONVERT(VARCHAR(5),b.Start,108) + '-' + CONVERT(VARCHAR(5),b.Stop,108) as [timeframe]
from properdates p
cross apply (
select CAST(p.DateStart As TIME) AS Start, #bStart as Stop WHERE CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart
union
select #bStop as Start, CAST(DateStop AS TIME) AS Stop WHERE CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop
union
select CAST(p.DateStart AS TIME) AS Start, CAST(p.DateStop AS TIME) AS Stop WHERE NOT (CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart) AND NOT (CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop)
) b
cross apply (
select
DATEDIFF(Minute, b.Start, b.Stop) as TotalMinutes,
(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart) + ABS(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart))) / 2 as EarlyMinutes,
(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)) + ABS(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)))) / 2 as LateMinutes,
CASE WHEN DATEPART(DW, p.DateStart) IN (1,7) THEN 1 ELSE 0 END AS isWeekend
) c
), qualifiedMinutes As (
SELECT Day, TotalMinutes, RegularDay, RegularNight, OvertimeDay, OvertimeNight, isWeekend, timeframe
FROM splittedMinutes
OUTER APPLY (
SELECT RegularDay = CASE WHEN cumulate <= #maxTime THEN DayTime WHEN DayTime - (cumulate - TotalMinutes - #maxTime) > 0 THEN ABS(cumulate - TotalMinutes - #maxTime) ELSE 0 END
) RD
OUTER APPLY (
SELECT OvertimeDay = DayTime - RegularDay
) OWD
OUTER APPLY (
SELECT RegularNight = CASE WHEN cumulate <= #maxTime THEN NightTime WHEN (cumulate - TotalMinutes - #maxTime + RegularDay) < 0 THEN NightTime + (cumulate - TotalMinutes - #maxTime + RegularDay) ELSE 0 END
) RWN
OUTER APPLY (
SELECT OvertimeNight = NightTime - RegularNight
) OWN
)
SELECT
Day,
#UserName and UserName,
SUM(TotalMinutes) AS TotalMinutes,
SUM(CASE WHEN isWeekend = 0 THEN RegularDay ELSE 0 END) AS WeekDay,
SUM(CASE WHEN isWeekend = 0 THEN RegularNight ELSE 0 END) AS WeekNight,
SUM(CASE WHEN isWeekend = 1 THEN RegularDay ELSE 0 END) AS WeekendDay,
SUM(CASE WHEN isWeekend = 1 THEN RegularNight ELSE 0 END) AS WeekendNight,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeDay ELSE 0 END) AS WeekDayOverTime,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeNight ELSE 0 END) AS WeekNightOvertime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeDay ELSE 0 END) AS WeekendDayOverTime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeNight ELSE 0 END) AS WeekendNightOvertime,
STUFF((SELECT '|' + timeframe FROM qualifiedMinutes tt WHERE tt.Day = q.Day ORDER BY timeframe FOR XML PATH('') ), 1, 1, '' ) AS [timeframe]
FROM qualifiedMinutes q
GROUP BY Day

SQL Grouping GPS Trip Data

this is my Table
SQL Fiddle
I need to create this report
the Driving Flag is the status of the car
so i need the first point of the true (driving) as the trip start
the last point as the end with total period and total distance
the same with the stop
and the max and avg speed for the movment
The Table Should be ordered by id to keep the driving record in order
Thank You
Edit
SELECT
carid
, (SUM(CASE WHEN speed < 3 THEN 0 ELSE DATEDIFF(minute, b.trackold, b.TrackTime) END)) AS speeding
, (SUM(CASE WHEN speed >= 3 THEN 0 ELSE DATEDIFF(minute, b.trackold, b.TrackTime) END)) AS parked
, round(sum(Distance / 1000), 2) AS Distance
, TrackDay
FROM
(
SELECT
carid
, TrackDay
, TrackTime
, trackold
, speed
, TrackDayOld
, diff
, Distance
FROM
(
SELECT
carid
, TrackTime
, LAG(TrackTime, 1, NULL) OVER (PARTITION BY carid ORDER BY TrackTime) AS trackold
, CONVERT(date, TrackTime) AS TrackDay
, CONVERT(date, LAG(TrackTime, 1, NULL) OVER (PARTITION BY carid ORDER BY TrackTime)) AS TrackDayOld
, speed
, datediff(minute, LAG(TrackTime, 1, NULL) OVER (PARTITION BY carid ORDER BY carid), TrackTime) AS diff
, Distance
FROM T_Tracking
) a
WHERE a.TrackDay = a.TrackDayOld
) b GROUP BY carid
, TrackDay
this is my last try (Before creating Drive Flag Based on speed)`
but it group by the whole day(get total )

sql query : sum of all rows and display in column

Here is My query :
SELECT Name,
Quantity,
Price,
(Quantity*Price)[Total Price]
FROM Menu,
Items_per_Table,
[735294]
WHERE Menu.Item_id = Items_per_Table.Item_id
AND [735294].OrderNo = Items_per_Table.OrderNo
AND Bill_Generated = 'True'
It returns :
Name Quantity Price Total Price
Aerated Drinks 1 30.00 30.00
Fresh Lime Soda/Water 3 60.00 180.00
Here I want the sum of total prices like this : 30+180 = 210 in another column.
How can I perform that ?
It Should Return :
Name Quantity Price Total Price All Over Price
Aerated Drinks 1 30.00 30.00 30
Fresh Lime Soda/Water 3 60.00 180.00 210
For SQL Server 2012, this should do what you need;
SELECT Name,
Quantity,
Price,
(Quantity*Price) [Total Price],
SUM(Quantity*Price) OVER (ORDER BY menu.item_id) [All Over Price]
FROM Menu,
Items_per_Table,
[735294]
WHERE Menu.Item_id = Items_per_Table.Item_id
AND [735294].OrderNo = Items_per_Table.OrderNo
AND Bill_Generated = 'True'
ORDER BY menu.item_id
SUM(...) OVER (ORDER BY...) does the running sum in the order you give it. Since you have no order in your question, I arbitrarily chose menu.item_id to order by.
EDIT: Forgot to add the SQLfiddle to test with.
Try using Common Table Expressions,(If you are using SQL < SQl 2012)
;with cteabc
as (
SELECT Name,
Quantity,
Price,
(Quantity*Price)[Total Price]
FROM Menu,
Items_per_Table,
[735294]
WHERE Menu.Item_id = Items_per_Table.Item_id
AND [735294].OrderNo = Items_per_Table.OrderNo
AND Bill_Generated = 'True'
)
select
t1.Name,
t1.Quantity,
t1.Price,
rt.runningTotal
from cteabc t1
cross apply (select sum([Total Price]) as runningTotal
from cteabc t2
where t2.rn<= t1.rn
) as rt
order by t1.Name
SQL Fiddle

Grouping Sale Quantities and Sale Figures by Days of the week using sql

Database: SQL Server 2008
I need to produce a sales report which groups sales figures into days of the week, e.g.;
Product Mon Tues Wed Thurs Friday Sat Sunday Total
Product 1 5 2 0 4 3 2 1 17
Product 2 2 1 4 3 1 1 1 13
I have two joined tables tbl_orders (primary table holding order no, order status etc.), tbl_orderitems (table holding item information, qty, price, product etc).
The date field is dbo.tbl_orders.dte_order_stamp
Structure of joined tables;
SELECT
dbo.tbl_orders.uid_orders,
dbo.tbl_orders.dte_order_stamp,
dbo.tbl_orders.txt_order_ref,
dbo.tbl_orders.uid_order_custid,
dbo.tbl_orders.uid_order_webid,
dbo.tbl_orders.txt_order_status,
dbo.tbl_orders.uid_order_addid,
dbo.tbl_orders.mon_order_tax,
dbo.tbl_orders.mon_order_grandtotal,
dbo.tbl_orders.mon_order_discount,
dbo.tbl_orders.bit_order_preorder,
dbo.tbl_orders.int_order_deposit_percent,
dbo.tbl_orders.mon_order_delivery,
dbo.tbl_orders.txt_order_deltype,
dbo.tbl_orders.txt_order_process,
dbo.tbl_orders.txt_voucher_code,
dbo.tbl_orders.txt_order_terms,
dbo.tbl_orders.dte_order_paydate,
dbo.tbl_orders.int_order_taxrate,
dbo.tbl_orders.txt_order_googleid,
dbo.tbl_orders.bit_order_archive,
dbo.tbl_orderitems.uid_orderitems,
dbo.tbl_orderitems.uid_orditems_orderid,
dbo.tbl_orderitems.txt_orditems_pname,
dbo.tbl_orderitems.uid_orditems_pcatid,
dbo.tbl_orderitems.uid_orditems_psubcatid,
dbo.tbl_orderitems.mon_orditems_pprice,
dbo.tbl_orderitems.int_orderitems_qty,
dbo.tbl_orderitems.txt_orditems_stype,
dbo.tbl_orderitems.txt_orditems_pref,
dbo.tbl_orderitems.uid_orditems_prodid
FROM
dbo.tbl_orders
INNER JOIN dbo.tbl_orderitems ON (dbo.tbl_orders.uid_orders = dbo.tbl_orderitems.uid_orditems_orderid)
I am using the following statement to get overall sales figures, can i adapt this to group by days of the week? Not really sure where to start, i have done some reading on datepart but not quite sure how to implement it, or would i be better to wrap select statements?
SELECT
SUM(dbo.tbl_orderitems.mon_orditems_pprice) AS prodTotal,
AVG(dbo.tbl_orderitems.mon_orditems_pprice) AS avgPrice,
count(dbo.tbl_orderitems.uid_orditems_prodid) AS prodQty,
dbo.tbl_orderitems.txt_orditems_pname
FROM dbo.tbl_orderitems
INNER JOIN dbo.tbl_orders ON (dbo.tbl_orderitems.uid_orditems_orderid = dbo.tbl_orders.uid_orders)
WHERE dbo.tbl_orders.txt_order_status = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sale_status#">
GROUP BY
dbo.tbl_orderitems.txt_orditems_pname
ORDER BY dbo.tbl_orderitems.txt_orditems_pname ASC
Any help would be appreciated.
You could use a PIVOT. Like this:
SELECT
pvt.txt_orditems_pname AS Product,
pvt.[Monday],
pvt.[Tuesday],
pvt.[Wednesday],
pvt.[Thursday],
pvt.[Friday],
pvt.[Saturday],
pvt.[Sunday],
(
pvt.[Monday]+pvt.[Tuesday]+pvt.[Wednesday]+pvt.[Thursday]+pvt.[Friday]+
pvt.[Saturday]+pvt.[Sunday]
) AS Total
FROM
(
SELECT
DATENAME(WEEKDAY,dbo.tbl_orders.dte_order_stamp) AS WeekDayName,
dbo.tbl_orderitems.mon_orditems_pprice,
dbo.tbl_orderitems.txt_orditems_pname
FROM dbo.tbl_orderitems
INNER JOIN dbo.tbl_orders
ON (dbo.tbl_orderitems.uid_orditems_orderid = dbo.tbl_orders.uid_orders)
WHERE
dbo.tbl_orders.txt_order_status = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sale_status#">
) AS SourceTable
PIVOT
(
SUM(mon_orditems_pprice)
FOR WeekDayName IN([Monday],[Tuesday],[Wednesday],
[Thursday],[Friday],[Saturday],[Sunday])
)
AS pvt
Or if you do not want to use a PIVOT. You can do it like this:
SELECT
t.txt_orditems_pname AS Product,
t.[Monday],
t.[Tuesday],
t.[Wednesday],
t.[Thursday],
t.[Friday],
t.[Saturday],
t.[Sunday],
(
t.[Monday]+t.[Tuesday]+t.[Wednesday]+t.[Thursday]+t.[Friday]+
t.[Saturday]+t.[Sunday]
) AS Total
FROM
(
SELECT
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=7 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Sunday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=1 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Saturday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=2 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Monday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=3 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Tuesday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=4 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Wednesday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=5 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Thursday],
SUM(CASE WHEN datepart(dw,dbo.tbl_orders.dte_order_stamp)=6 THEN dbo.tbl_orderitems.mon_orditems_pprice ELSE NULL END) AS [Friday],
dbo.tbl_orderitems.txt_orditems_pname
FROM dbo.tbl_orderitems
INNER JOIN dbo.tbl_orders
ON (dbo.tbl_orderitems.uid_orditems_orderid = dbo.tbl_orders.uid_orders)
WHERE
dbo.tbl_orders.txt_order_status = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sale_status#">
GROUP BY
dbo.tbl_orderitems.txt_orditems_pname
) AS t

SQL Server: pivoting across multiple colums

I have a serious problem. Say my data is arranged in this format:
Name Businees_unit Forecast Upside
Jack.N India 100 50
Jack.N China 250 20
I have to pivot Forecast and Upside for Business_Unit so the table will look like this
Name Forecast_India Upside_India Forecast_China Upside_China
Jack 100 50 250 20
Can this be done in one query?
Its my first entry, so any help is very welcomed.
Thanks
A generic solution:
select name,
sum(case when Businees_unit = 'India' then Forecast else 0 end) Forecast_India,
sum(case when Businees_unit = 'India' then Upside else 0 end) Upside_India,
sum(case when Businees_unit = 'China' then Forecast else 0 end) Forecast_China,
sum(case when Businees_unit = 'China' then Upside else 0 end) Upside_China
from My_table
group by name
I would use self join:
SELECT DISTINCT SomeTable.Name, China.Forecast as Forecast_China, China.Upside as Upside_China
, India.Forecast as Forecast_India, India.Upside as Upside_India
FROM SomeTable
inner join SomeTable India on India.Name = SomeTable.Name AND India.Business_unit = 'India'
inner join SomeTable China on China.Name = SomeTable.Name AND China.Business_unit = 'China'

Resources