Populate a row in SQL Server based on a condition (query optimization) - sql

I am trying to find all the people who took a timeoff for previous months, I got most of it. But if nobody took a timeoff in the current month, I need to populate a row saying 0 to get my visuals right.
This is what my data looks like:
This is what I have for the visual:
select s.*, DATEPART(MONTH, startd) as timeoff_Mon,
DATEDIFF(day, startd, endd) as timeoff, getdate() as dat,
DATEPART(MONTH, GETDATE()) as Current_Mon from sample s;
You can see that Jack was the only employee who took time off in January for a day, and John in Feb for 4 days,
Now, in March, I have got the current month which is the last column in the image above from the current date.
Using this current month, is there a way to populate a row or hardcode a row to populate the time off column as 0??
Desired Output:
Here is the SQL Fiddle with the above-mentioned problem.
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=9e8fbb2ae8dcfec9cc5fdfa784782753

A simple option is to use union all to add the final record if there is no row that overlaps the current month in the table:
select
s.*,
month(startd) as timeoff_mon,
datediff(day, startd, endd) as timeoff,
getdate() as dat,
month(getdate()) as current_mon
from sample s
union all
select null, null, null, null, null, 0, getdate(), datepart(month, getdate())
where not exists (
select 1
from sample s
where
s.startd < dateadd(mm, datediff(mm, 0, getdate()) + 1, 0)
and s.endd >= dateadd(mm, datediff(mm, 0, getdate()), 0)
)
Demo on DB Fiddle:
Name | ID | STARTD | ENDD | timeoff_mon | timeoff | dat | current_mon
:--------- | ---: | :--------- | :--------- | ----------: | ------: | :---------------------- | ----------:
Jack | 5 | 2020-01-25 | 2020-01-26 | 1 | 1 | 2020-03-28 14:28:56.867 | 3
John | 6 | 2020-02-05 | 2020-02-09 | 2 | 4 | 2020-03-28 14:28:56.867 | 3
null | null | null | null | null | 0 | 2020-03-28 14:28:56.867 | 3

Related

Creating and populating row in a table for missing dates after each first valid entry

I have a dataset that represents number of views on individual files per day.
I would like to import this data into some visualization tool and show how many views a file received each day, beginning with the first valid date with an entry, in the form of something like a bar graph.
For example, I have a table like this:
+-----------+-----------+----------------+----------------+--------------+------------------+-----------------+------------------+
| Metadata1 | Metadata2 | Unique_Item_ID | Item_ID | Unique Views | Total View Count | Start_Date | End_Date |
+-----------+-----------+----------------+----------------+--------------+------------------+-----------------+------------------+
| Folder1 | Subf1 | {000dda83} | Document.docx | 6 | 11 | 11/27/2019 0:00 | 11/27/2019 23:59 |
| Folder2 | Sub2f | {004120b6} | Reporting.mp4 | 3 | 10 | 11/8/2019 0:00 | 11/8/2019 23:59 |
| Folder2 | Sub2f | {004120b6} | Reporting.mp4 | 8 | 13 | 11/20/2019 0:00 | 11/20/2019 23:59 |
| Folder2 | Sub2f | {004120b6} | Reporting.mp4 | 12 | 27 | 11/29/2019 0:00 | 11/29/2019 23:59 |
| Folder3 | Sub3f | {004f9957} | Case Study.pdf | 1 | 1 | 10/8/2019 0:00 | 10/8/2019 23:59 |
+-----------+-----------+----------------+----------------+--------------+------------------+-----------------+------------------+
From a query like:
SELECT
TOP 5 [Metadata1],
[Metadata2],
[Unique_Item_ID],
[Item_ID],
[Unique Views],
[Total View Count],
[Start_Date],
[End_Date]
FROM
DailyViewStats
How can I create a view that will create and populate rows with 0 view count for each Unique_Item_ID that does not exist, but only after the first occurrence of a valid existing row for each distinct Unique_Item_ID?
I know that I can use a partition function to identify the first valid row for each Unique_Item_ID, but I'm not sure how to leverage this. I tried using a cross join on all distinct Start_Dates in the table, to match up with all the unique items and their metadata, but I was unable to determine a WHERE statement that effectively removed any entry before the first valid one per Unique_Item_ID.
Using
ROW_NUMBER() OVER (PARTITION BY Unique_Item_ID ORDER BY Start_Date ASC) as RowNum
I believe I can use this to identify the minimum dates I need when RowNum = 1. But how do I use this?
If today were 11/29, for Document.docx, I want to see something like this:
+-----------+-----------+----------------+---------------+--------------+------------------+-----------------+------------------+
| Metadata1 | Metadata2 | Unique_Item_ID | Item_ID | Unique Views | Total View Count | Start_Date | End_Date |
+-----------+-----------+----------------+---------------+--------------+------------------+-----------------+------------------+
| Folder1 | Subf1 | {000dda83} | Document.docx | 6 | 11 | 11/27/2019 0:00 | 11/27/2019 23:59 |
| Folder1 | Subf1 | {000dda83} | Document.docx | 0 | 0 | 11/28/2019 0:00 | 11/28/2019 23:59 |
| Folder1 | Subf1 | {000dda83} | Document.docx | 0 | 0 | 11/29/2019 0:00 | 11/29/2019 23:59 |
+-----------+-----------+----------------+---------------+--------------+------------------+-----------------+------------------+
For each file existing in the table.
One direct way to do this is to employ a calendar table. In the example below the calendar is provided by a recursive CTE with a ~37K range of days. Once that is set up you want to overlay each of the unique Id's with each day. This is done below in the form of a cross join CTE, only including the keys. From the derived cross join table, simply LEFT JOIN the bulk of your data and the values will appear aligned with each day of the calendar. I took the liberty of simplifying your model below.
DECLARE #T TABLE( Unique_Item_ID NVARCHAR(50), Total_View_Count INT, DateViewed DATETIME)
INSERT #T VALUES
('000dda83',11, '11/27/2019'),
('004120b6',10, '11/8/2019'),
('004120b6',13, '11/20/2019')
DECLARE #StartDate DATETIME = '10/01/2019'
DECLARE #EndDate DATETIME = '01/01/2020'
;WITH OrderedDays as
(
SELECT CalendarDate = #StartDate
UNION ALL
SELECT CalendarDate = DATEADD(DAY, 1, CalendarDate)
FROM OrderedDays WHERE DATEADD (DAY, 1, CalendarDate) <= #EndDate
),
Calendar AS
(
SELECT
DayIndex = ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY CalendarDate),
CalendarDate,
CalenderDayOfMonth = DATEPART(DAY, CalendarDate),
CalenderMonthOfYear = DATEPART(MONTH, CalendarDate),
CalendarYear = DATEPART(YEAR, CalendarDate),
CalenderWeekOfYear = DATEPART(WEEK, CalendarDate),
CalenderQuarterOfYear = DATEPART(QUARTER, CalendarDate),
CalenderDayOfYear = DATEPART(DAYOFYEAR, CalendarDate),
CalenderDayOfWeek = DATEPART(WEEKDAY, CalendarDate),
CalenderWeekday = DATENAME(WEEKDAY, CalendarDate)
FROM
OrderedDays
)
,CrossJoinData AS
(
SELECT Unique_Item_ID, CalendarDate
FROM
Calendar C
CROSS JOIN #T T
GROUP BY
Unique_Item_ID, CalendarDate
HAVING
MIN(T.DateViewed) <= C.CalendarDate
)
SELECT
CJ.Unique_Item_ID,
CJ.CalendarDate,
T.Total_View_Count
FROM
CrossJoinData CJ
LEFT OUTER JOIN #T T ON T.Unique_Item_ID = CJ.Unique_Item_ID AND T.DateViewed = CJ.CalendarDate
ORDER BY
CJ.Unique_Item_ID,
CJ.CalendarDate
OPTION (MAXRECURSION 0)

SQL: Sum timestamp intervals of the same day

I'm setting up a new SQL query to summarize records from a table of employee attendance. These records are downloaded from a fingerprint or RFID sensor and recorded on the same table. I want to get the amount of hours on the workplace.
Everything works fine if the employee comes in and leaves the workplace once at day. The device generates 2 records on the table and that's are not a problem. It's easy identify entrance and exit time.
But I don't realize how to solve if the person comes in, take a break (leaves workplace) and after that he comes in again until the exit time.
Assume that they are always even records (arrival and exit timestamps) on each interval. Also, an employee never checks in one day and leaves on the next day.
I have the following query. Remember: this only gets the minimum timestamp (arrival time) and the max timestamp (leave time).
SELECT Userid, Name, Date, Entrance, Exit, Hours FROM
(SELECT Userid AS user,
CONVERT(VARCHAR, CONVERT(TIME, min(Checktime))) AS Entrance,
CONVERT(VARCHAR, CONVERT(TIME, max(Checktime))) AS Exit,
CONVERT(VARCHAR, CONVERT(TIME, max(Checktime)-min(CheckTime))) AS Hours,
CONVERT(VARCHAR, CONVERT(DATE, CheckTime)) AS Fecha,
COUNT(*) AS Regs,
SUM(edited) AS edited FROM attendance
WHERE CONVERT(DATE, CheckTime) < CONVERT(DATE, GETDATE())
GROUP BY Userid, CONVERT(DATE, CheckTime)) AS Hs
INNER JOIN Userinfo
ON Userinfo.Userid = Hs.user
ORDER BY Date DESC, Name ASC;
For example, if the table has the following records:
id | Logid | Userid | CheckTime | edited
1 | 10 | 1 | 2019-06-18 8:00:00 | 0
2 | 11 | 1 | 2019-06-18 12:00:00 | 0
3 | 12 | 1 | 2019-06-18 15:00:00 | 0
4 | 13 | 1 | 2019-06-18 17:00:00 | 0
5 | 14 | 2 | 2019-06-18 8:00:00 | 0
6 | 15 | 2 | 2019-06-18 17:00:00 | 0
What I get:
Userid | Name | Date | Entrance | Exit | Hours | edited
1 | Gandalf | 2019-06-18 | 8:00:00 | 17:00:00 | 9:00:00 | 0
2 | Frodo | 2019-06-18 | 8:00:00 | 17:00:00 | 9:00:00 | 0
What I need:
Userid | Name | Date | Entrance | Exit | Hours | edited
1 | Gandalf | 2019-06-18 | 8:00:00 | 17:00:00 | 6:00:00 | 0
2 | Frodo | 2019-06-18 | 8:00:00 | 17:00:00 | 9:00:00 | 0
The total time was calculated from (12:00:00 - 8:00:00) + (17:00:00 - 15:00:00).
The columns "Entrance" and "Exit" on this case are not necessary at all.
Have you an idea how I can solve this? Thank you very much!
This assume you have pair enter/exit and handle multiple breaks.
SQL DEMO
with cte as (
SELECT *, ROW_NUMBER() OVER (PARTITION BY [Userid], cast ([CheckTime] as Date)
ORDER BY [CheckTime]) as rn
FROM Table1 t1
)
SELECT c1.[Userid],
cast (c1.[CheckTime] as Date) as the_day,
SUM (DATEDIFF (hh, c1.[CheckTime], c2.[CheckTime])) as total_hours
FROM cte c1
JOIN cte c2
ON c1.rn = c2.rn -1
AND c1.[Userid] = c2.[Userid]
AND c1.rn % 2 = 1
GROUP BY c1.[Userid],
cast (c1.[CheckTime] as Date) ;
OUTPUT
| Userid | the_day | total_hours |
|--------|------------|-------------|
| 1 | 2019-06-18 | 6 |
| 2 | 2019-06-18 | 9 |
NOTE:
General syntax for DATEDIFF:
DATEDIFF(datepart, start_date, end_date)
Just realize the function DATEDIFF is used to calculate the time interval between two date values and return it as an integer.
So if you have 08:00 and 09:30 using hh as datepart you still get 1h. Maybe is better use mi and divide by 60
Perfect! Juan Carlos's solution works great!
I'm posting this because I've edited some of his code to match the original post requirements.
The code is exactly the same. Only I've changed/added a few lines
with cte as (
SELECT *, ROW_NUMBER() OVER (PARTITION BY [Userid], cast ([CheckTime] as Date)
ORDER BY [CheckTime]) as rn
FROM Table1 t1
WHERE CAST(CheckTime AS DATE) = '2019-06-17' -- Filter by specific date
)
SELECT c1.[Userid],
cast (c1.[CheckTime] as Date) as the_day,
-- Return time as HH:MM
CONVERT(VARCHAR, SUM (DATEDIFF (SECOND , c1.[CheckTime], c2.[CheckTime]))/3600) + ':' + right('00' + CONVERT(VARCHAR, CONVERT(FLOAT, (SUM (DATEDIFF (SECOND , c1.[CheckTime], c2.[CheckTime]))/60) - ((SUM (DATEDIFF (SECOND , c1.[CheckTime], c2.[CheckTime]))/3600)*60))),2) as total_time
FROM cte c1
JOIN cte c2
ON c1.rn = c2.rn -1
AND c1.[Userid] = c2.[Userid]
AND c1.rn % 2 = 1
GROUP BY c1.[Userid],
cast (c1.[CheckTime] as Date);
This query returns:
| Userid | the_day | total_time |
|--------|------------|-------------|
| 1 | 2019-06-18 | 6:00 |
| 2 | 2019-06-18 | 9:00 |

MSSQL query to get weekno from date, groupby weekno and sum sales

Is this possible in sql alone?
I have a table which contains the following rows
StoreID | Date | SalesItem |
1 | 2016-08-16 | Book |
2 | 2016-08-16 | Pen |
1 | 2016-08-15 | Pen |
1 | 2016-08-15 | Book |
The results I want would be
Store | Week | Sales
1 | 11 | 30
2 | 11 | 15
I'm using sql server 2008, the data set is much larger than the above example but that's basically what i would want to achieve in sql without processing in PHP afterwards.
What I have so far is
select [DATE], [store], count(store) as total, DATEPART(ww,DATE)
AS weeknum from [contracts] where [DATE] >= DATEADD(month, -12, GetDate())
group by [DATE], [store] order by [DATE] asc
Try the below code
SELECT Store,DATEPART(WW,Date),SUM(Sales) FROM TABLE1
GROUP BY Store, DATEPART(WW,Date)
As per edit of Question
SELECT Store,DATEPART(WW,Date),count(Sales) FROM TABLE1
GROUP BY Store, DATEPART(WW,Date)

SQL Days before end of the month

i have got table with transactions, looking like:
+----+--------------+----------------+------+
| ID | OrderDate | DeliveryDate | EUR |
+----+--------------+----------------+------+
| 1 | 2015-02-21 | 2015-02-25 | 100 |
| 2 | 2015-03-01 | 2015-03-14 | 110 |
| 3 | 2015-03-01 | 2015-03-17 | 90 |
| 4 | 2015-03-10 | 2015-03-20 | 250 |
| 5 | 2015-03-31 | 2015-03-31 | 350 |
+----+--------------+----------------+------+
ANd I need to get sum of revenue and number of orders (COUNT of IDs) based on Days before the end of the month when order gets delivered.
SELECT datediff(day, OrderDate, CAST(DATEADD(month, DATEDIFF(month,0,getdate()+1,0)-1) as Date) as DBEOM, SUM(EUR) as Rev, COUNT(ID) as NumberOfOrders
FROM transactions
WHERE MONTH(DeliveryDate) = 3 AND YEAR(DeliveryDate) = 2015
GROUP BY datediff(day, OrderDate, CAST(DATEADD(month, DATEDIFF(month,0,getdate()+1,0)-1) as Date) as DBEOM
ORDER BY 1
The result in this case would be like:
+-----+-----+----------------+
|DBEOM| Rev | NumberOfOrders |
+-----+-----+----------------+
| 0 | 350 | 1 |
| 21 | 250 | 1 |
| 30 | 200 | 2 |
+-----+-----+----------------+
This is done in SQL 2008, so I can't simply use EOMONTH. I have tried, what is above, but i am getting
ERROR -
[Microsoft][ODBC SQL Server Driver][SQL Server]The datediff function
requires 3 argument(s).
Many thanks in advance for advice!
The easiest way I've found get the last day of the month with more primitive functions is to get the first day of the next month and then subtract a day.
I'm not a TSQL guy so this syntax likely won't be correct but you need something more like
DATEADD(day, DATEFROMPARTS(DATEPART(year, DATEADD(month,1,getdate()), DATEPART(month, DATEADD(month,1,getdate()), 1), -1)
Try:
SELECT datediff(day,
OrderDate,
dateadd(DAY,
-1,
dateadd(MONTH,
1,
dateadd(DAY,
1-day(DeliveryDate),
DeliveryDate
)
)
)
) as DBEOM, SUM(EUR) as Rev, COUNT(ID) as NumberOfOrders
FROM t
WHERE MONTH(DeliveryDate) = 3 AND YEAR(DeliveryDate) = 2015
GROUP BY datediff(day,
OrderDate,
dateadd(DAY,
-1,
dateadd(MONTH,
1,
dateadd(DAY,
1-day(DeliveryDate),
DeliveryDate
)
)
)
)
ORDER BY 1
sqlfiddle.com

updates in month and day differences

I have a table as shown below:
Note: the MAX last_orderdate is 20131015 and the format is yyyymmdd.
I would like to show the final result looks like below:
Is there any query to help me in this as I have 200000 plus records.
Thank you very much for spending your time to look at my question.
For DATEDIFF() function
Try this:
UPDATE A
SET A.monthDiff = DATEDIFF(mm, CONVERT(DATE, A.orderDate, 112), B.lastOrderDate),
A.dayDiff = DATEDIFF(dd, CONVERT(DATE, A.orderDate, 112), B.lastOrderDate)
FROM tableA A, (SELECT MAX(CONVERT(DATE, orderDate, 112)) lastOrderDate FROM tableA) B
Check the SQL FIDDLE DEMO
OUTPUT
| ID | ORDERDATE | MONTHDIFF | DAYDIFF |
|----|-----------|-----------|---------|
| 1 | 20130105 | 9 | 283 |
| 2 | 20130205 | 8 | 252 |
| 3 | 20130305 | 7 | 224 |
| 4 | 20130909 | 1 | 36 |
| 5 | 20131001 | 0 | 14 |
| 6 | 20131015 | 0 | 0 |
try something like this:
declare #a date
set #a='20130105'
declare #b date
set #b='20131015'
select datediff(d,#a,#b) as date_diff,datediff(m,#a,#b) as month_diff
Try this.
select DATEDIFF(DAYOFYEAR,'20131015','20131125').
DAYOFYEAR represents count of days. Depeneds on your requirement, you can change to see month,day or year difference using DATEDIFF