How to show total weekly count in one table - sql-server

I'm trying to achieve a report that will show all daily count as well weekly count in the same table. I've tried different techniques that I know but it seems that I wasn't able to get what I want.
I'm trying to show a similar table below.
+-----------+-----+-------+--------+--+--+--+
| August | | Count | | | | |
+-----------+-----+-------+--------+--+--+--+
| 8/1/2013 | Thu | 1,967 | | | | |
| 8/2/2013 | Fri | 1,871 | | | | |
| 8/3/2013 | Sat | 1,950 | | | | |
| 8/4/2013 | Sun | 2,013 | 7801 | | | |
| 8/5/2013 | Mon | 2,039 | | | | |
| 8/6/2013 | Tue | 1,871 | | | | |
| 8/7/2013 | Wed | 1,611 | | | | |
| 8/8/2013 | Thu | 1,680 | | | | |
| 8/9/2013 | Fri | 1,687 | | | | |
| 8/10/2013 | Sat | 1,649 | | | | |
| 8/11/2013 | Sun | 1,561 | 12,098 | | | |
+-----------+-----+-------+--------+--+--+--+
Please let me if there's an existing code or technique that I could to achieve something like this. Thanks.
Sherwin

Try something like this but make sure to check which WEEKDAY is Sunday on your server since this can be modified.
select T1.August, T1.[Count],
case DATEPART(WEEKDAY, O.Order_Date)
WHEN 1 THEN (SELECT CONVERT(varchar(10), SUM(T2.[Count]) FROM TableName T2 WHERE T2.August BETWEEN DATEADD(d,-7,T1.August) and T1.August))
ELSE ''
end as Weekly_Count
FROM TablleName T1
ORDER BY T.August

If you don't mind having those subtotal on a new row instead of on a new column, GROUP BY WITH ROLLUP could be the solution for you:
SET LANGUAGE GERMAN is used for setting monday as first day of the week and allowing us to sum up until sunday
SET LANGUAGE GERMAN;
WITH first AS
(
SELECT
date,
day,
DATEPART(dw, date) AS dayweek,
DATEPART(wk, date) AS week,
count
FROM example
)
SELECT
CASE WHEN (GROUPING(dayweek) = 1) THEN 'TOT' ELSE CAST(MAX(date) AS VARCHAR(20)) END AS date,
CASE WHEN (GROUPING(dayweek) = 1) THEN 'TOT' ELSE MAX(day) END AS day,
SUM(count) AS count
FROM first
GROUP BY week,dayweek WITH ROLLUP
see the complete example on sqlfiddle

If you use stored procedures, then make a temp table and loop it through with a cursor, and make the sums.
You could also do something like this:
SELECT CreatedDate, Amount, CASE WHEN DATENAME(dw , CreatedDate) = 'Sunday' THEN (SELECT SUM(Amount) FROM AmountTable at2 WHERE CreatedDate <= at1.CreatedDate AND CreatedDate > DATEADD(Day, -7, at1.CreatedDate)) ELSE 0 END AS 'WeekTotal'
from AmountTable at1

Would something along these lines work, the syntax may not be 100%
select
[Date],
DOW,
[Count],
Case When DOW = 'Mon' then 1 else 2 end as Partition_DOW,
SUM([Count]) OVER (PARTITION BY (Case When DOW = 'Mon' then 1 else 0 end) ORDER BY [Date]) AS 'Monthly_Total'
from My_table
where Month([Date]) = Month(Date()) AND Year([Date]) = Year(Date())

Related

SQL Server - Cumulate awake time for devices from event summary

I'm working on a SQL server (used by BMC) to grab the uptime of some devices.
I've got a query that display me results like this:
| DeviceName | EventDate | EventType |
| ---------- | ----------------------- | ---------- |
| 1 | 2021-02-15 08:06:12.000 | getting up |
| 1 | 2021-02-12 15:07:26.000 | going down |
| 2 | 2021-02-16 08:12:54.000 | getting up |
| 2 | 2021-02-12 15:43:00.000 | going down |
| 3 | 2021-02-15 07:47:42.000 | getting up |
| 3 | 2021-02-12 15:38:41.000 | going down |
| 4 | 2021-02-15 08:10:07.000 | getting up |
| 5 | 2021-02-18 06:41:40.000 | getting up |
| ... | ... | ... |
I would like to get a result that looks like that:
| DeviceName | TotalUpTime (min) |
| ---------- | ----------------- |
| 1 | 16543 |
| 2 | 13639 |
| 3 | 13524 |
| 4 | 19235 |
| 5 | 12347 |
Here is my current query:
SELECT
DeviceName,
EventDate,
EventType
FROM **irrelevant complexe SELECT query**
ORDER BY DeviceName, EventDate DESC
Any help would be great!!
Many thx in advance!
SOLUTION:
Ok, here's what worked for me:
SELECT
DeviceName,
DATEDIFF(s, EventDate, EndTime)/60 AS [TotalUpTime (min)]
FROM (
SELECT *,
LEAD(CASE WHEN EventType = 32 THEN EventDate END, 1, GETDATE())
OVER (PARTITION BY DeviceName ORDER BY EventDate) AS EndTime
FROM (
**Irrelevant SELECT query**
) r
) s
WHERE EventType = 16 AND EndTime IS NOT NULL
Many thanks to #Charlieface whos response helped me a lot.
Hope this help someone someday, even if it's very specific.
SELECT
DeviceName,
SUM(DATEDIFF(ms, EventDate, EndTime) / 60000.0) AS [TotalUpTime (min)]
FROM (
SELECT *,
LEAD(CASE WHEN EventType = 'going down' THEN EventDate END, 1, GETDATE()) OVER
OVER (PARTITION BY DeviceName ORDER BY EventDate) AS EndTime
FROM table
)
WHERE EventType = 'getting up' AND EndTime IS NOT NULL
GROUP BY DeviceName

Date ranges into one row by ID with no gaps

I am reviewing reports that contain date ranges by a member ID and upload date. This looks like the following:
+--------------------+---------------------+---------------------+--------------------+
| UploadDate | MemberID | StartDate | EndDate |
| | | | |
+-------------------------------------------------------------------------------------+
| 08/01/2020 | 12345 | 04/01/2020 | 10/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 08/01/2020 | 12345 | 01/01/2020 | 03/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 06/01/2020 | 12345 | 01/01/2020 | 03/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 06/01/2020 | 98765 | 02/01/2020 | 03/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 06/01/2020 | 98765 | 05/01/2020 | 08/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 07/01/2020 | 34568 | 01/01/2020 | 12/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 07/01/2020 | 34568 | 03/31/2020 | 06/01/2020 |
| | | | |
+--------------------+---------------------+---------------------+--------------------+
I need to merge rows with the same UploadDate and the same MemberID where their are no gaps in the date range StartDate - EndDate. If there are gaps the rows will not be merged.
The expected output would be:
+--------------------+---------------------+---------------------+--------------------+
| UploadDate | MemberID | StartDate | EndDate |
| | | | |
+-------------------------------------------------------------------------------------+
| 08/01/2020 | 12345 | 01/01/2020 | 10/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 06/01/2020 | 12345 | 01/01/2020 | 03/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 06/01/2020 | 98765 | 02/01/2020 | 03/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 06/01/2020 | 98765 | 05/01/2020 | 08/31/2020 |
| | | | |
+-------------------------------------------------------------------------------------+
| 07/01/2020 | 34568 | 01/01/2020 | 12/31/2020 |
| | | | |
+--------------------+---------------------+---------------------+--------------------+
I had been trying the following without success:
SELECT
ROW_NUMBER() OVER(ORDER BY [MemberID],[StartDate],[EndDate]) AS RN,
[MemberID],
[StartDate],
[EndDate],
LAG([EndDate],1) OVER (ORDER BY [MemberID],[StartDate], [EndDate]) AS PreviousEndDate
FROM
[dbo].[RCNI]
SELECT
*,
CASE WHEN Groups.PreviousEndDate >= [StartDate] THEN 0 ELSE 1 END AS IslandStartInd,
SUM(CASE WHEN Groups.PreviousEndDate >= [StartDate] THEN 0 ELSE 1 END) OVER (ORDER BY Groups.RN) AS IslandId
FROM
(
SELECT
ROW_NUMBER() OVER(ORDER BY [UploadDate], [MemberID],[StartDate], [Benefit End Date]) AS RN,
[UploadDate],
[MemberID],
[StartDate],
[EndDate],
LAG([EndDate],1) OVER (ORDER BY [UploadDate],[MemberID],[StartDate], [EndDate]) AS PreviousEndDate
FROM
[dbo].[RCNI]
) Groups
The solution below appears to work for the given sample data. In words with would be something like:
Filter out rows that have a period that falls completely within another row for the same UploadDate and MemberId (the not exists clause in the common table expression cte).
Look at the remaining rows for each UploadDate and MemberId combination (over(partition by r.UploadDate, r.MemberId ...) and sort them by StartDate (... order by r.StartDate)).
If the start date of a row comes before, is equal to or comes one day after the end date of the previous row for the combination (lag(r.EndDate) over(partition by r.UploadDate, r.MemberId order by r.StartDate)), then they must be merged.
If rows must be merged, then the start dates becomes the smallest start date of the combination (min(r.StartDate) over(partition by r.UploadDate, r.MemberId)). All rows that must be merged now have the same start date (StartDateNew).
Determine the new end date by grouping on UploadDate, MemberId and StartDateNew and taking the maximum value for EndDate.
Sample data
create table rcni
(
UploadDate date,
MemberId int,
StartDate date,
EndDate date
);
insert into rcni (UploadDate, MemberId, StartDate, EndDate) values
('08/01/2020', 12345, '04/01/2020', '10/31/2020'),
('08/01/2020', 12345, '01/01/2020', '03/31/2020'),
('06/01/2020', 12345, '01/01/2020', '03/31/2020'),
('06/01/2020', 98765, '02/01/2020', '03/31/2020'),
('06/01/2020', 98765, '05/01/2020', '08/31/2020'),
('07/01/2020', 34568, '01/01/2020', '12/31/2020'),
('07/01/2020', 34568, '03/31/2020', '06/01/2020');
Solution
with cte as
(
select r.UploadDate,
r.MemberId,
case
when r.StartDate <= dateadd(dd, 1, lag(r.EndDate) over(partition by r.UploadDate, r.MemberId order by r.StartDate))
then min(r.StartDate) over(partition by r.UploadDate, r.MemberId)
else r.StartDate
end as StartDateNew,
r.EndDate
from rcni r
where not exists ( select 'x'
from rcni r2
where r2.UploadDate = r.UploadDate
and r2.MemberId = r.MemberId
and r2.StartDate < r.StartDate
and r2.EndDate > r.EndDate )
)
select c.UploadDate,
c.MemberId,
c.StartDateNew,
max(c.EndDate) as EndDateNew
from cte c
group by c.UploadDate,
c.MemberId,
c.StartDateNew;
Fiddle

SQL Server Stock age calculation - Alternative to nested cursors

I'm trying to do some kind of stock age calculation based on two tables.
I have a current stock for each reference and I'd like to match it with the most recent warehouse entrances until I have no units left.
My Stock_Table looks like this
| Product_Ref | Stock |
| ----------- | ------- |
| Prod_A | 100 |
| Prod_B | 50 |
My Entrances_Table (ordered by most recent date) looks like this
| Product_Ref | Month | Units |
| ----------- | ------- | ------- |
| Prod_A | July | 50 |
| Prod_A | June | 30 |
| Prod_A | May | 35 |
| Prod_B | May | 10 |
| Prod_B | April | 55 |
What I need (as a previous step to do some other calcuations that I have already figured out) is to build this results table:
| Product_Ref | Month | Units |
| ----------- | ------- | ------- |
| Prod_A | July | 50 |
| Prod_A | June | 30 |
| Prod_A | May | 20 | <-- previous 50+30 so only 20 "left" to achive 100 units
| Prod_B | May | 10 |
| Prod_B | April | 40 | <-- previous 10 so only 40 "left" to achive 100 units
I know I could iterate throught both tables with a nested cursor, but I would like to know if there's a more elegant solution (maybe via running sums, using lead() , CTE, or something that I'm missing ... )
Any ideas are appreciated!
Thank you very much in advance!
You can get the desired output by using Sum() over(order by). This function returns running total of the column in SQL. So the query will be:
SELECT A.Product_Ref ,
A.Month ,
CASE WHEN A.RT <= A.Stock THEN A.Units ELSE A.Stock - (LAG(A.RT) OVER (ORDER BY A.id)) END AS Units
FROM ( SELECT Product_Ref, Units, Month, Stock, Id ,
SUM(E.Units) OVER ( PARTITION BY E.Product_Ref ORDER BY E.Id ) AS RT
FROM Entrances E
INNER JOIN Stock S ON S.Prod = E.Product_Ref
) A;
To view the output of the query click on DEMO
Try with this query,
You can use Lead() in sql-server for check next row value of particular column.
select et.Product_Ref, et.Month, et.Unit,
case when SUM(et.Unit) OVER (PARTITION BY Product_Ref ORDER BY id) > st.Unit1
OR
(lead(et.id)over( order by et.id)) is null
then
case when lead(et.Product_Ref) over(order by id) <> et.Product_Ref
AND
( et.unit - ((SUM(et.Unit) OVER (PARTITION BY Product_Ref ORDER BY id) ) - st.Unit1) ) <= 0
then -- (-)ve Stock
st.Unit1 - ((SUM(et.Unit) OVER (PARTITION BY Product_Ref ORDER BY id) ))
else -- Stock Adjustment As per Stock_Table
et.unit - ((SUM(et.Unit) OVER (PARTITION BY Product_Ref ORDER BY id) ) - st.Unit1)
end
else et.unit -- Normal Stock
end as outputvalue
from Entrances_Table et
left join Stock_Table st on st.Product_Ref1 = et.Product_Ref
DB Fiddle

T-SQL Pivot table

I’ve a table MachineStatus which stores the status history of a machine.
The table looks like this:
| MachineStatusId | From | To | State | MachineId |
----------------------------------------------------------------------------------------------------------------------------------------
| B065FC43-DBE7-E611-9BDB-801F02F47041 | 2017-01-30 07:00:00 | 2017-01-30 08:00:00 | 1 | 92649C7B-E962-4EB1-B631-00086EECA98A |
| B165FC43-DBE7-E611-9BDB-801F02F47041 | 2017-01-30 08:00:00 | 2017-01-30 09:00:00 | 200 | 92649C7B-E962-4EB1-B631-00086EECA98A |
| B265FC43-DBE7-E611-9BDB-801F02F47041 | 2017-01-30 07:00:00 | 2017-01-30 08:00:00 | 1 | A2649C7B-E962-4EB1-B631-00086EECA98A |
| B365FC43-DBE7-E611-9BDB-801F02F47041 | 2017-01-30 08:00:00 | 2017-01-30 09:00:00 | 500 | A2649C7B-E962-4EB1-B631-00086EECA98A |
It stores for each machine, for each status change a record with the information [From] when [To] when a certain [State] was valid.
I like to calculate the time each machine spent in each state.
The result should look like this:
| MachineId | Alias | State1 | State200 | State500 |
-------------------------------------------------------------------------------------------------
| 92649C7B-E962-4EB1-B631-00086EECA98A | Somename | 60 | 60 | 0 |
| A2649C7B-E962-4EB1-B631-00086EECA98A | Some other name | 60 | 0 | 60 |
Each state should be represented as a column.
Here is wat I have tried so far:
SELECT
MAX(mState.MachineId),
MAX(m.Alias),
SUM(CASE mState.State WHEN 1 THEN mState.Diff ELSE 0 END) AS CritTime,
SUM(CASE mState.State WHEN 200 THEN mState.Diff ELSE 0 END) AS OpTime,
SUM(CASE mState.State WHEN 500 THEN mState.Diff ELSE 0 END) AS OtherTime
FROM
(
SELECT
DATEDIFF(MINUTE, ms.[From], ISNULL(ms.[To], GETDATE())) AS Diff,
ms.State AS State,
MachineId
FROM
MachineStatus ms
WHERE
ms.[From] >= #rangeFrom AND
(ms.[To] <= #rangeEnd OR ms.[To] IS NULL)
) as mState
INNER JOIN Machines m ON m.MachineId = mState.MachineId
GROUP BY
mState.MachineId,
m.Alias,
mState.State
Calculating the time and grouping the result by machines works but I cannot figure out how to reduce the result set only contain one row per machine but with a column per state.
I started in your subquery without apply any sum to your calculated data:
SELECT m.MachineId,
m.Alias,
Minutes,
s.State
FROM machines m
INNER JOIN states s ON m.MachineId = s.MachineId
Then you can pivot() for [State] and calculate the sum() of every state in this form:
WITH Calc AS
(
SELECT m.MachineId,
m.Alias,
Minutes,
s.State
FROM machines m
INNER JOIN states s ON m.MachineId = s.MachineId
)
SELECT MachineId, Alias, [State1], [State2], [State500]
FROM
(SELECT MachineId, Alias, State, Minutes FROM Calc) AS SourceTable
PIVOT
(
SUM(Minutes) FOR State IN ([State1],[State2],[State500])
) AS PivotTable;
This is the result:
+--------------------------------------+---------+--------+--------+----------+
| MachineId | Alias | State1 | State2 | State500 |
+--------------------------------------+---------+--------+--------+----------+
| 92649C7B-E962-4EB1-B631-00086EECA98A | Alias 1 | 100 | 100 | 100 |
+--------------------------------------+---------+--------+--------+----------+
| A2649C7B-E962-4EB1-B631-00086EECA98A | Alias 2 | 10 | 20 | 70 |
+--------------------------------------+---------+--------+--------+----------+
Notice that you must know how many states return your data.
Can check it here: http://rextester.com/DHDX77489

SQL Server : how to use variable values from CTE in WHERE clause?

First of all please correct me if my title are not specific/clear enough.
I have use the following code to generate the start dates and end dates :
DECLARE #start_date date, #end_date date;
SET #start_date = '2016-07-01';
with dates as
(
select
#start_date AS startDate,
DATEADD(DAY, 6, #start_date) AS endDate
union all
select
DATEADD(DAY, 7, startDate) AS startDate,
DATEADD(DAY, 7, endDate) AS endDate
from
dates
where
startDate < '2017-03-31'
)
select * from dates
Below is part of the output from above query :
+------------+------------+
| startDate | endDate |
+------------+------------+
| 2016-07-01 | 2016-07-07 |
| 2016-07-08 | 2016-07-14 |
| 2016-07-15 | 2016-07-21 |
| 2016-07-22 | 2016-07-28 |
| 2016-07-29 | 2016-08-04 |
+------------+------------+
Now I have another table named sales, which have 3 columns sales_id,sales_date and sales_amount as below :
+----------+------------+--------------+
| sales_ID | sales_date | sales_amount |
+----------+------------+--------------+
| 1 | 2016-07-04 | 10 |
| 2 | 2016-07-06 | 20 |
| 3 | 2016-07-13 | 30 |
| 4 | 2016-07-19 | 15 |
| 5 | 2016-07-21 | 20 |
| 6 | 2016-07-25 | 25 |
| 7 | 2016-07-26 | 40 |
| 8 | 2016-07-29 | 20 |
| 9 | 2016-08-01 | 30 |
| 10 | 2016-08-02 | 30 |
| 11 | 2016-08-03 | 40 |
+----------+------------+--------------+
How can I create the query to show the total sales amount of each week (which is between each startDate and endDate from the first table)? I suppose I will need to use a recursive query with WHERE clause to check if the dates are in between startDate and endDate but I cant find a working example.
Here are my expected result (the startDate and endDate are the records from the first table) :
+------------+------------+--------------+
| startDate | endDate | sales_amount |
+------------+------------+--------------+
| 2016-07-01 | 2016-07-07 | 30 |
| 2016-07-08 | 2016-07-14 | 30 |
| 2016-07-15 | 2016-07-21 | 35 |
| 2016-07-22 | 2016-07-28 | 65 |
| 2016-07-29 | 2016-08-04 | 120 |
+------------+------------+--------------+
Thank you!
Your final Select (after the cte) should be something like this
Select D.*
,Sales_Amount = sum(Sales)
From dates D
Join Sales S on (S.sales_date between D.startDate and D.endDate)
Group By D.startDate,D.endDate
Order By D.startDate
EDIT: You could use a Left Join if you want to see missing dates from
Sales

Resources