Calculating average with pivot - sql

I am trying to find the average with the help of pivot but not able to find the right solution.
The below is my query:
select branch, ISNULL([11:00], 0) as [11:00],ISNULL([11:15], 0) as
[11:15],ISNULL([11:30], 0) as [11:30], ISNULL([11:45], 0) as [11:45],
ISNULL([12:00], 0) as [12:00]
from
(
select b.branchname
,convert(varchar(5), intervals.interval_start_time, 108)
,sum(b.ordercount) ordercounts
from branch b cross apply dbo.getDate15MinInterval(CAST(b.TransactionDate
as date)) as intervals
where b.TransactionDate >= interval_start_time and b.TransactionDate <=
interval_end_time
and CAST(TransactionDate AS date) IN ('2017-07-01','2017-07-08')
group by DATEPART(WEEKDAY,TransactionDate),b.branchname,intervals.interval_start_time,intervals.interval_end_time
) t
pivot ( avg(ordercounts) for interval_start_time in ( [11:00], [11:15] ,
[11:30], [11:45], [12:00])) as p
My original table is:
Result from the above query is:
Expected result:
For 15minuteinterval query, please refer my original post:
Group data by interval of 15 minutes and use cross tab

SQL Server does integer arithmetic operations on integers. The problem is that this is an integer:
sum(b.ordercount) as ordercounts
(presumably).
So, just turn it into a floating/fixed point number. I usually just multiply by 1.0:
sum(b.ordercount)*1.0 as ordercounts
But you can be more specific about your types if you like.

Try casting to float -
AVG(CAST(ordercounts AS FLOAT))
SUM(CAST(b.ordercount AS FLOAT)) AS ordercounts

Can be done like this:
select branchname, [dayname], ISNULL([11:00], 0) as [11:00], AVG(CAST([11:00] as float)) over() [Avg_11:00]
from
(
select branchname, [dayname], ISNULL([11:00], 0) as [11:00], ISNULL([11:15], 0) as [13:15], ISNULL([11:30], 0) as [11:30], ISNULL([11:45], 0) as [11:45]
from
(
select intervals.[dayname]
, b.branchname
, convert(varchar(5), intervals.interval_start_time, 108) interval_start_time -- for hh:mm format
, sum(b.ordercount) ordercount
from branch b cross apply dbo.getDate15MinIntervals(CAST(b.TransactionDate as date)) as intervals
where b.transactiondate between interval_start_time and interval_end_time
group by intervals.[dayname], b.branchname, intervals.interval_start_time, intervals.interval_end_time
) t
pivot ( sum(ordercount) for interval_start_time in ( [11:00], [11:15] , [11:30], [11:45] )) as p
) t
group by branchname, [dayname], [11:00]
AVG OVER() is valid as of SQL Server 2008.
In this example I only used one interval, but you can extend it to all of the ones you need.
I tried with some sample data from yesterday's answer and it returns values as below:
Happy coding! :)

Related

How to spread month to day with amount value divided by total days per month

I have data with an amount of 1 month and want to change it to 30 days.
if 1 month the amount is 20000 then per day is 666.67
The following are sample data and results:
Account
Project
Date
Segment
Amount
Acc1
1
September 2022
Actual
20000
Result :
I need a query using sql server
You may try a set-based approach using an appropriate number table and a calculation with windowed COUNT().
Data:
SELECT *
INTO Data
FROM (VALUES
('Acc1', 1, CONVERT(date, '20220901'), 'Actual', 20000.00)
) v (Account, Project, [Date], Segment, Amount)
Statement for all versions, starting from SQL Server 2016 (the number table is generated using JSON-based approach with OPENJSON()):
SELECT d.Account, d.Project, a.[Date], d.Segment, a.Amount
FROM Data d
CROSS APPLY (
SELECT
d.Amount / COUNT(*) OVER (ORDER BY (SELECT NULL)),
DATEADD(day, CONVERT(int, [key]), d.[Date])
FROM OPENJSON('[1' + REPLICATE(',1', DATEDIFF(day, d.[Date], EOMONTH(d.[Date]))) + ']')
) a (Amount, Date)
Statement for SQL Server 2022 (the number table is generated with GENERATE_SERIES()):
SELECT d.Account, d.Project, a.[Date], d.Segment, a.Amount
FROM Data d
CROSS APPLY (
SELECT
d.Amount / COUNT(*) OVER (ORDER BY (SELECT NULL)),
DATEADD(day, [value], d.[Date])
FROM GENERATE_SERIES(0, DATEDIFF(day, d.[Date], EOMONTH(d.[Date])))
) a (Amount, Date)
Notes:
Both approaches calculate the days for each month. If you always want 30 days per month, replace DATEDIFF(day, d.[Date], EOMONTH(d.[Date])) with 29.
There is a rounding issue with this calculation. You may need to implement an additional calculation for the last day of the month.
You can use a recursive CTE to generate each day of the month and then divide the amount by the number of days in the month to achive the required output
DECLARE #Amount NUMERIC(18,2) = 20000,
#MonthStart DATE = '2022-09-01'
;WITH CTE
AS
(
SELECT
CurrentDate = #MonthStart,
DayAmount = CAST(#Amount/DAY(EOMONTH(#MonthStart)) AS NUMERIC(18,2)),
RemainingAmount = CAST(#Amount - (#Amount/DAY(EOMONTH(#MonthStart))) AS NUMERIC(18,2))
UNION ALL
SELECT
CurrentDate = DATEADD(DAY,1,CurrentDate),
DayAmount = CASE WHEN DATEADD(DAY,1,CurrentDate) = EOMONTH(#MonthStart)
THEN RemainingAmount
ELSE DayAmount END,
RemainingAmount = CASE WHEN DATEADD(DAY,1,CurrentDate) = EOMONTH(#MonthStart)
THEN 0
ELSE CAST(RemainingAmount-DayAmount AS NUMERIC(18,2)) END
FROM CTE
WHERE CurrentDate < EOMONTH(#MonthStart)
)
SELECT
CurrentDate,
DayAmount
FROM CTE
In case you want an equal split without rounding errors and without loops you can use this calculation. It spreads the rounding error across all entries, so they are all as equal as possible.
DECLARE #Amount NUMERIC(18,2) = 20000,
#MonthStart DATE = '20220901'
SELECT DATEADD(DAY,Numbers.i - 1,#MonthStart)
, ShareSplit.Calculated_Share
, SUM(ShareSplit.Calculated_Share) OVER (ORDER BY (SELECT NULL)) AS Calculated_Total
FROM (SELECT DISTINCT number FROM master..spt_values WHERE number BETWEEN 1 AND DAY(EOMONTH(#MonthStart)))Numbers(i)
CROSS APPLY(SELECT CAST(ROUND(#Amount * 100 / DAY(EOMONTH(#MonthStart)),0) * 0.01
+ CASE
WHEN Numbers.i
<= ABS((#Amount - (ROUND(#Amount * 100 / DAY(EOMONTH(#MonthStart)),0) / 100.0 * DAY(EOMONTH(#MonthStart)))) * 100)
THEN 0.01 * SIGN(#Amount - (ROUND(#Amount * 100 / DAY(EOMONTH(#MonthStart)),0) / 100.0 * DAY(EOMONTH(#MonthStart))))
ELSE 0
END AS DEC(18,2)) AS Calculated_Share
)ShareSplit

Select max date with given condition

I am using SQL server 2012
create table t(dt1 date,dt2 date,dt3 date,dt4 date)
insert into t values('1970-01-01','2008-10-10',NULL,NULL),(NULL,'2008-10-10','2017-10-12',NULL),('1970-01-01','2008-10-10',NULL,'2018-10-09')
I need to get the minimum date from these columns, if the column value ='1970-01-01' then I need the second minimum date.
Below is what I tried which is not resulting correct result.
select *,case when (dt1='1970-01-01' or dt2='1970-01-01' or dt3='1970-01-01' or dt4='1970-01-01' )and dt1<=dt2 then dt1 else dt2
end as DDt
from t
Expected output result:
Edit - I need the second minimum date, added more cases here.
Use outer apply
select *
from t
outer apply (select ddt = min(v)
from (values (dt1), (dt2), (dt3), (dt4)) q(v)
where v > '19700101'
) q
The following query works for the scenario : -
SELECT *, MinValue
FROM t
CROSS APPLY (SELECT MIN(d) MinValue FROM (VALUES (dt1), (dt2), (dt3),(dt4)) AS
a(d) WHERE d <> '01-01-1970') A
You could use subquery with values constructors
select *, (select min(dates)
from (values (dt1), (dt2), (dt3), (dt4))a(dates)
where a.dates > '1970-01-01') as DDt
from t;
Try this solution :
SELECT * , (SELECT MIN(Dates) FROM (VALUES (dt1), (dt2), (dt3), (dt4)) AS Fields(Dates) WHERE Fields.Dates > '1970-01-01') AS DDT
FROM [dbo].[t]

How to compute cumulative product in SQL Server 2008?

I have below table with 2 columns, DATE & FACTOR. I would like to compute cumulative product, something like CUMFACTOR in SQL Server 2008.
Can someone please suggest me some alternative.
Unfortunately, there's not PROD() aggregate or window function in SQL Server (or in most other SQL databases). But you can emulate it as such:
SELECT Date, Factor, exp(sum(log(Factor)) OVER (ORDER BY Date)) CumFactor
FROM MyTable
You can do it by:
SELECT A.ROW
, A.DATE
, A.RATE
, A.RATE * B.RATE AS [CUM RATE]
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY DATE) as ROW, DATE, RATE
FROM TABLE
) A
LEFT JOIN (
SELECT ROW_NUMBER() OVER(ORDER BY DATE) as ROW, DATE, RATE
FROM TABLE
) B
ON A.ROW + 1 = B.ROW
To calculate the cumulative product, as displayed in the CumFactor column in the original post, the following code does the job:
--first, load the sample data to a temp table
select *
into #t
from
(
values
('2/3/2000', 10),
('2/4/2000', 20),
('2/5/2000', 30),
('2/6/2000', 40)
) d ([Date], [Rate]);
--next, calculate cumulative product
select *, CumFactor = cast(exp(sum(log([Rate])) over (order by [Date])) as int) from #t;
Here is the result:

Adding total column and row to sql server pivot

I have a pivot table that I believe to be working. I want to add a total column and a total row to this pivot. Here is the code for the pivot table...
SELECT Month,
[N] AS Expected,
[R] AS Requested,
[T] AS Tires,
[U] AS Unexpected,
[D] AS Damage
FROM (
SELECT CustomerNo,
DATEPART(mm,InvoiceDate) AS Month,
[Type],
SUM(Total) AS Cost
FROM tbl_PM_History
WHERE (InvoiceDate >= #Start)
AND (InvoiceDate <= #End)
AND (CustomerNo = #Cust)
GROUP BY CustomerNo,
DATEPART(mm,InvoiceDate),
TYPE
) p
PIVOT (SUM(Cost) FOR [Type] IN ([N],[R],[T],[U],[D]))AS pvt
ORDER BY Month
Here's a fairly quick way (in programming time) to achieve what you want:
;
WITH details -- Wrap your original query in a CTE so as to encapsulate all your calculations
AS (
SELECT Month,
[N] AS Expected,
[R] AS Requested,
[T] AS Tires,
[U] AS Unexpected,
[D] AS Damage,
ISNULL([N],0) + ISNULL([R],0) + ISNULL([T],0) + ISNULL([U],0) + ISNULL([D], 0) as Total
FROM (
SELECT CustomerNo,
DATEPART(mm,InvoiceDate) AS Month,
[Type],
SUM(Total) AS Cost
FROM tbl_PM_History
WHERE (InvoiceDate >= #Start)
AND (InvoiceDate <= #End)
AND (CustomerNo = #Cust)
GROUP BY CustomerNo,
DATEPART(mm,InvoiceDate),
TYPE
) p
PIVOT (SUM(Cost) FOR [Type] IN ([N],[R],[T],[U],[D])) AS pvt
)
, summary // Use another CTE to add a total line, using UNION ALL
AS (
SELECT Month
, Expected,
, Requested
, Tires
, Unexpected
, Damage
, Total
, 0 as RecordCode
FROM details
UNION ALL
SELECT Null
, SUM(Expected)
, SUM(Requested)
, SUM(Tires)
, SUM(Unexpected)
, SUM(Damage)
, SUM(Total)
, 1
FROM details
)
SELECT Month -- Do your actual sorting.
, Expected,
, Requested
, Tires
, Unexpected
, Damage
, Total
FROM summary
ORDER by RecordCode, Month

Select first and last record each day

I have a table with an engineerID, DateTimeCreated as DateTime, JobID and AuditTypeID
I need a query shows first (engineerID, JobID with AuditTypeID 1) and last (engineerID, JobID with AuditTypeID 2) on each row of the query.
SELECT TOP (100) PERCENT
dbo.AuditTrail.EngineerId,
dbo.AuditTrail.AuditTypeId,
dbo.Engineers.Name,
dbo.Engineers.EngineerTypeCode,
dbo.AuditTrail.JobId,
CAST(dbo.AuditTrail.DateTimeCreated AS Date) AS _Date
FROM
dbo.AuditTrail
INNER JOIN
dbo.Engineers
ON dbo.AuditTrail.EngineerId = dbo.Engineers.EngineerId
WHERE
(dbo.AuditTrail.AuditTypeId = 1) AND
(dbo.Engineers.EngineerTypeCode = 'p') AND
(dbo.Engineers.EngineerTypeCode = 'p') AND
(DATEPART(mm, dbo.AuditTrail.DateTimeCreated) = 6) AND
(DATEPART(YYYY, dbo.AuditTrail.DateTimeCreated) = 2014)
group by
AuditTrail.engineerID,
JobID,
AuditTypeId,
Engineers.name,
Engineers.EngineerTypeCode,
CAST(dbo.AuditTrail.DateTimeCreated AS Date)
ORDER BY
dbo.AuditTrail.EngineerID DESC
for the first part of my query. Unfortunatly I cannot see to select the first record for each day
Any help will be greatly appreciated
First just get the data you need, including the create date. Then grouping that data by date, select the min of each day. Finally, join the two sets, selecting only the minimum of each day -- that is, the first occurrence of each day.
with
AllMonth( EngineerId, AuditTypeId, Name, EngineerTypeCode, JobId, DateTimeCreated )as(
SELECT TOP (100) PERCENT
a.EngineerId,
a.AuditTypeId,
e.Name,
e.EngineerTypeCode,
a.JobId,
a.DateTimeCreated
FROM dbo.AuditTrail a
JOIN dbo.Engineers e
ON e.EngineerId = a.EngineerId
AND e.EngineerTypeCode = a.EngineerTypeCode
WHERE
a.AuditTypeId = 1
AND a.EngineerTypeCode = 'p'
AND a.DateTimeCreated >= DateAdd( mm, DateDiff( mm, 0, GetDate()), 0)
AND a.DateTimeCreated < DateAdd( mm, DateDiff( mm, 0, GetDate()) + 1, 0)
),
FirstByDay( MinDate )as(
select Min( DateTimeCreated )
from AllMonth
group by cast( DateTimeCreated AS Date )
)
select *
from AllMonth a
join FirstByDay f
on f.MinDate = a.DateTimeCreated
ORDER BY a.EngineerID DESC;
To get the last item of each day, just add a max to FirstByDay and add to the join. Work it into one long row if you really want to.
Btw, didn't I hear a few years back that the later versions of MSSQL ignored top (100) percent? I don't work with it much these days, and my memory is...well, just...somewhere around here...