Complex SQL Server Pivot Involving Null and blank vs. Filled values - sql-server

I need to separate the "dep" column into 2 different columns one being "recorded" the other being "unrecorded". the "unrecorded" column will contain the sum of time for entries where "dep" is blank or null for that day. The "recorded" column needs to display the sum of time for that day where the "dep" column is not null or blank.
So far this is what I have
SELECT Cast(Start_Time AS DATE) AS Date, dep,
sum(time) as "Total Time"
FROM A6K_Events
Group By Cast(Start_Time AS DATE), dep
and it yields this
+------------+--------------+------------+
| Date | Dep | Total Time |
+------------+--------------+------------+
| 2018-06-29 | Null | 3544 |
+------------+--------------+------------+
| 2018-06-29 | Other | 268 |
+------------+--------------+------------+
| 2018-06-29 | Training | 471 |
+------------+--------------+------------+
| 2018-06-29 | Change Point | 371 |
+------------+--------------+------------+
| 2018-06-28 | Null | 4519 |
+------------+--------------+------------+
| 2018-06-28 | Training | 1324 |
+------------+--------------+------------+
| 2018-06-28 | | 50 |
+------------+--------------+------------+
This is what I would like the end result to be
+------------+----------+------------+
| Date | Recorded | Unrecorded |
+------------+----------+------------+
| 2018-06-29 | 1110 | 3544 |
+------------+----------+------------+
| 2018-06-28 | 1324 | 4569 |
+------------+----------+------------+
Any suggestions or help would be appreciated. I cannot figure out how to pivot and filter out null and blank values into one column while the other filled values into another column.
Thank you.

Pivot is not required, we can fetch the data using CASE statement
SELECT start_time as Date, sum(case when dep is not NULL or dep <> '' then time end) as "Recorded", sum(case when dep is NULL or dep = '' then time end) as "Unrecorded"
FROM A6K_Events
Group By start_time order by 1 desc

Related

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

Selecting unique records based on date of effect, ending on date of discontinue

I have an interesting conundrum and I am using SQL Server 2012 or SQL Server 2016 (T-SQL obviously). I have a list of products, each with their own UPC code. These products have a discontinue date and the UPC code gets recycled to a new product after the discontinue date. So let's say I have the following in the Item_UPCs table:
Item Key | Item Desc | UPC | UPC Discontinue Date
123456 | Shovel | 0009595959 | 2018-04-01
123456 | Shovel | 0007878787 | NULL
234567 | Rake | 0009595959 | NULL
As you can see, I have a UPC that gets recycled to a new product. Unfortunately, I don't have an effective date for the item UPC table, but I do in an items table for when an item was added to the system. But let's ignore that.
Here's what I want to do:
For every inventory record up to the discontinue date, show the unique UPC associated with that date. An inventory record consists of the "Inventory Date", the "Purchase Cost", the "Purchase Quantity", the "Item Description", and the "Item UPC".
Once the discontinue date is over with (e.g.: it's the next day), start showing only the UPC that is in effect.
Make sure that no duplicate data exists and the UPCs are truly being "attached" to each row per whatever the date is in the query.
Here is an example of the inventory details table:
Inv_Key | Trans_Date | Item_Key | Purch_Qty | Purch_Cost
123 | 2018-05-12 | 123456 | 12.00 | 24.00
108 | 2018-03-22 | 123456 | 8.00 | 16.00
167 | 2018-07-03 | 234567 | 12.00 | 12.00
An example query:
SELECT DISTINCT
s.SiteID
,id.Item_Key
,iu.Item_Desc
,iu.Item_Department
,iu.Item_Category
,iu.Item_Subcategory
,iu.UPC
,iu.UPC_Discontinue_Date
,id.Trans_Date
,id.Purch_Cost
,id.Purch_Qty
FROM Inventory_Details id
INNER JOIN Item_UPCs iu ON iu.Item_Key = id.Item_Key
INNER JOIN Sites s ON s.Site_Key = id.Site_Key
The real query I have is far too long to post here. It has three CTEs and the resultant query. This is simply a mockup. Here is an example result set:
Site_ID | Item_Key | Item_Desc | Item_Department | Item_Category | UPC | UPC_Discontinue Date | Trans_Date | Purch_Cost | Purch_Qty
2457 | 123456 | Shovel | Digging Tools | Shovels | 0009595959 | 2018-04-01 | 2018-03-22 | 16.00 | 8.00
2457 | 123456 | Shovel | Digging Tools | Shovels | 0007878787 | NULL | 2018-03-22 | 16.00 | 8.00
2457 | 234567 | Rakes | Garden Tools | Rakes | 0009595959 | NULL | 2018-07-03 | 12.00 | 12.00
2457 | 123456 | Shovel | Digging Tools | Shovels | 0007878787 | NULL | 2018-05-12 | 24.00 | 12.00
Do any of you know how I can "assign" a UPC to a specific range of dates in my query and then "assign" an updated UPC to the item for every effective date thereafter?
Many thanks!
Given your current Item_UPC table, you can generate effective start dates from the Discontinue Date using the LAG analytic function:
With Effective_UPCs as (
select [Item_Key]
, [Item_Desc]
, [UPC]
, coalesce(lag([UPC_Discontinue_Date])
over (partition by [Item_Key]
order by coalesce( [UPC_Discontinue_Date]
, datefromparts(9999,12,31))
),
lag([UPC_Discontinue_Date])
over (partition by [UPC]
order by coalesce( [UPC_Discontinue_Date]
, datefromparts(9999,12,31))
)) [UPC_Start_Date]
, [UPC_Discontinue_Date]
from Item_UPCs i
)
select * from Effective_UPCs;
Which yields the following Results:
| Item_Key | Item_Desc | UPC | UPC_Start_Date | UPC_Discontinue_Date |
|----------|-----------|------------|----------------|----------------------|
| 123456 | Shovel | 0007878787 | 2018-04-01 | (null) |
| 123456 | Shovel | 0009595959 | (null) | 2018-04-01 |
| 234567 | Rake | 0009595959 | 2018-04-01 | (null) |
This function produces a fully open ended interval where both the start and discontinue dates could be null indicating that it's effective for all time. To use this in your query simply reference the Effective_UPCs CTE in place of the Item_UPCs table and add a couple additional predicates to take the effective dates into consideration:
SELECT DISTINCT
s.SiteID
,id.Item_Key
,iu.Item_Desc
,iu.Item_Department
,iu.Item_Category
,iu.Item_Subcategory
,iu.UPC
,iu.UPC_Discontinue_Date
,id.Trans_Date
,id.Purch_Cost
,id.Purch_Qty
FROM Inventory_Details id
INNER JOIN Effective_UPCs iu
ON iu.Item_Key = id.Item_Key
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date < id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date <= iu.UPC_Discontinue_Date)
INNER JOIN Sites s ON s.Site_Key = id.Site_Key
Note that the above query uses a partially open range (UPC_Start_Date < trans_date <= UPC_Discontinue_Date instead of <= for both inequalities) this prevents transactions occurring exactly on the discontinue date from matching both the prior and next Item_Key record. If transactions that occur exactly on the discontinue date should match the new record and not the old simply swap the two inequalities:
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date <= id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date < iu.UPC_Discontinue_Date)
instead of
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date < id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date <= iu.UPC_Discontinue_Date)

SSRS : How to show all month of a year as X axis of a graph

I'm working on a graph that shows data for a whole year, but as I'm a newbie, I can't figure out how to show all month on the X axis (including those without data)
Here is the SELECT part of my SQL request :
SELECT DISTINCT
AB.Id AS ID,
MONTH(AP.Date) AS Date
Resulting in the following data:
| ID | Date |
|--------|--------|
| 1 | 2 |
| 2 | 3 |
| 3 | 2 |
| 4 | 2 |
| 5 | 3 |
| 6 | 3 |
| 7 | 3 |
|--------|--------|
Currently I'm using =MonthName(Fields!Date.Value)) as category group, resulting in the following graph :
4| X
3| X
2|
1|
0|_________________
February March
What I want to have is something like this :
4| X
3| X
2|
1|
0|______________________________________
January February March April ...
I've found some workaround on the net using a custom table in the database, but I can't in this case, as it is a MS Dynamics project.
Thank you for your time.
You need to return the right data to your chart so that there is something to include on the axis. To do this, you need to generate every single x-axis value and then find the total that relates to it.
In the script below, I am creating a table of dates that represent the start of the month for each month of the current year. It then goes and finds all the relevant values in your table for that month and does a count. This can then be used as your chart dataset with minimal processing on the client side, which will help with report performance.
declare #t table(ID int,DateValue Date)
insert into #t values (1,'20170204'),(2,'20170307'),(3,'20170203'),(4,'20170207'),(5,'20170304'),(6,'20170302'),(7,'20170309');
with d(d) as(select cast(dateadd(mm,n,dateadd(yyyy,datediff(yyyy,0,getdate()),0)) as date) from(values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) n(n))
select d.d
,count(t.ID) as c
from d
left join #t t
on(d.d = dateadd(mm,datediff(mm,0,t.DateValue),0))
group by d.d
order by d.d;
Output:
+------------+---+
| d | c |
+------------+---+
| 2017-01-01 | 0 |
| 2017-02-01 | 3 |
| 2017-03-01 | 4 |
| 2017-04-01 | 0 |
| 2017-05-01 | 0 |
| 2017-06-01 | 0 |
| 2017-07-01 | 0 |
| 2017-08-01 | 0 |
| 2017-09-01 | 0 |
| 2017-10-01 | 0 |
| 2017-11-01 | 0 |
| 2017-12-01 | 0 |
+------------+---+
You can add a right join to your query with all the month and return its column to the output:
SELECT DISTINCT
AB.Id AS ID,
m.n AS Date
right join (select * from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) m(n) ) m
on MONTH(AP.Date)=m.n

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