I am trying to get a list of persons status in SQL Server.
New records are added each day when a person clocks in, so rows do not exist for the current day, however there will be a record of that person clocking in at least one in the table history.
Example Data
| Name | Status | Date |
|------+--------+-------------------------|
| Joe | In | 2019-09-03 07:56:00.000 |
| Jack | In | 2019-09-03 06:52:00.000 |
| Joe | In | 2019-08-21 07:02:00.000 |
| Jack | In | 2019-08-14 06:09:00.000 |
| Jane | In | 2019-08-15 07:38:00.000 |
Example SQL
select name, status
from timeclock
where clockInTime >= dateAdd(day, 0, dateDiff(day, 0, current_timestamp))
union
select name, status
from timeclock
where not exists (?)
The first select will incorporate Joe and Jack since hey have clocked in in the last 24 hours.
Expected Output
| Name | Status |
|------+--------+
| Joe | In |
| Jack | In |
| Jane | Out |
How do I also select each member only once (distinct), but cast Jane's status as out?
This sounds like what you want more is something like this:
SELECT [Name],
CASE WHEN COUNT(CASE WHEN clockInTime >= dateAdd(day, 0, dateDiff(day, 0, current_timestamp)) THEN 1 END) > 0 THEN 'In' ELSE 'OUT' END AS [Status]
FROM timeclock
GROUP BY [Name];
This uses conditional aggregation, and only counts the rows after the time.
Related
I ve got a data set similar to
+----+------------+------------+------------+
| ID | Udate | last_code | Ddate |
+----+------------+------------+------------+
| 1 | 05/11/2018 | ACCEPTED | 13/10/2018 |
| 1 | 03/11/2018 | ATTEMPT | 13/10/2018 |
| 1 | 01/11/2018 | INFO | 13/10/2018 |
| 1 | 22/10/2018 | ARRIVED | 13/10/2018 |
| 1 | 15/10/2018 | SENT | 13/10/2018 |
+----+------------+------------+------------+
I m trying to get the date difference for each code on Udate, but for the first date I want to make datedifference between Udate and Ddate.
So I ve been trying:
DATEDIFF(DAY,LAG(Udate) OVER (PARTITION BY Shipment_Number ORDER BY Udate), Udate)
to get the difference between dates and it works so far, but I also need the first date difference between Udate and Ddate.
I was thinking about ISNULL()
Also, at the end I need an average of days between codes as well, usually they keep the same pattern. Sample output data:
+----+------------+------------+------------+------------+
| ID | Udate | last_code | Ddate | Difference |
+----+------------+------------+------------+------------+
| 1 | 05/11/2018 | ACCEPTED | 13/10/2018 | 2 |
| 1 | 03/11/2018 | ATTEMPT | 13/10/2018 | 2 |
| 1 | 01/11/2018 | INFO | 13/10/2018 | 10 |
| 1 | 22/10/2018 | ARRIVED | 13/10/2018 | 7 |
| 1 | 15/10/2018 | SENT | 13/10/2018 | 2 |
+----+------------+------------+------------+------------+
Notice that when there is no previous code, the date diff is between Udate and Ddate.
Would appreciate any idea.
Thank you.
Well, ISNULL is the way to go here.
Since you also want the average difference, you can use a common table expression to get the difference, and query it to get the average:
First, Create and populate sample data (Please save us this step in your future questions)
-- This would not be needed if you've used ISO8601 for date strings (yyyy-mm-dd | yyyymmdd)
SET DATEFORMAT DMY;
DECLARE #T AS TABLE
(
ID int,
UDate date,
last_code varchar(10),
Ddate date
) ;
INSERT INTO #T (ID, Udate, last_code, Ddate) VALUES
(1, '05/11/2018', 'ACCEPTED', '13/10/2018'),
(1, '03/11/2018', 'ATTEMPT' , '13/10/2018'),
(1, '01/11/2018', 'INFO' , '13/10/2018'),
(1, '22/10/2018', 'ARRIVED' , '13/10/2018'),
(1, '15/10/2018', 'SENT' , '13/10/2018');
The cte:
WITH CTE AS
(
SELECT ID,
Udate,
last_code,
Ddate,
DATEDIFF(
DAY,
ISNULL(
LAG(Udate) OVER(PARTITION BY ID ORDER BY Udate),
Ddate
),
UDate
) As Difference
FROM #T
)
The query:
SELECT *, AVG(Difference) OVER(PARTITION BY ID) As AverageDifference
FROM CTE;
Results:
ID Udate last_code Ddate Difference AverageDifference
1 15.10.2018 SENT 13.10.2018 2 4
1 22.10.2018 ARRIVED 13.10.2018 7 4
1 01.11.2018 INFO 13.10.2018 10 4
1 03.11.2018 ATTEMPT 13.10.2018 2 4
1 05.11.2018 ACCEPTED 13.10.2018 2 4
I am trying to get records from same column where the MARK 1 & GRED 1 select from SECOND LATEST RANGE PERIOD and the MARK 2 & GRED 2 is from LATEST RANGE PEROID.
Example..
Sam GRED 2 is from 01.01.2014 - 30.06.2014 period
Sam MARK 2 is from 01.07.2014 - 31.09.2014 period
Below is example table and desire output.
RESULT TABLE
| Name | Gred | Mark | SemPeriod |
|------|------|-------|-------------------------|
| Sam | C | 45.60 | 01.01.2013 - 30.06.2013 |
| Sam | B | 55.55 | 01.07.2013 - 31.12.2013 |
| Sam | A | 85.50 | 01.01.2014 - 30.06.2014 |
| Sam | C | 48.60 | 01.07.2014 - 31.09.2014 |
| Sean | C | 45.60 | 01.01.2014 - 30.06.2014 |
| Sean | B | 55.55 | 01.07.2014 - 31.12.2014 |
| Sean | A | 85.50 | 01.01.2015 - 30.06.2015 |
| Sean | C | 48.60 | 01.07.2015 - 31.12.2015 |
DESIRED OUTPUT
| Name | Gred 1| Mark 1 | Gred 2 | Mark 2 |
|------|-------|--------|--------|---------|
| Sam | A | 85.50 | C | 48.60 |
| Sean | A | 85.50 | C | 48.60 |
I just can't get my head wrapped around this and still stuck to display the output. Help would be greatly appreciated.
What is the data type for SemPeriod ?
; WITH CTE AS
(
SELECT *, RN = ROW_NUMBER() OVER (PARTITION BY Name ORDER BY SemPeriod_Start DESC)
FROM yourtable
)
SELECT Name,
Gred1 = MAX(CASE WHEN RN = 2 THEN Gred END),
Mark1 = MAX(CASE WHEN RN = 2 THEN Mark END),
Gred2 = MAX(CASE WHEN RN = 1 THEN Gred END),
Mark2 = MAX(CASE WHEN RN = 1 THEN Mark END)
FROM CTE
GROUP BY Name
if your SemPeriod is string, you should split into 2 different date column.
If not, the query will be ugly like
PARTITION BY Name
ORDER BY CONVERT(DATE, LEFT(SemPeriod , charindex('-', SemPeriod) - 1), 103) DESC
for large dataset, it will be slow. Also the query will break if the date range is formatted differently in the SemPeriod or there are bad data in there.
To ensure data quality, it is best to split into 2 separate date column.
I have a tough one here I think. I have the following tables:
[Assets]
AssetId | Name
1 | Acura NSX
2 | Dodge Ram
[Assignments]
AssignmentId | AssetId | StartMileage | EndMileage | StartDate | EndDate
1 | 1 | 8000 | 10000 | 4/1/2015 | 5/1/2015
2 | 1 | 10000 | 16000 | 9/15/2015 | 1/5/2016
3 | 2 | 51000 | NULL | 1/1/2016 | NULL
[Reminders]
ReminderId | AssetId | Name | Distance | Time | Active
1 | 1 | Oil Change | 3000 (miles)| 3 (months)| 1
2 | 1 | Tire Rotation | 5000 | 6 | 0
3 | 2 | Oil Change | 3000 | 3 | 1
4 | 2 | Air Filter | 50000 | 48 | 1
[Maintenance]
MaintenanceId | AssetId | ReminderId | Mileage | Date | Vendor
1 | 1 | 1 | 10000 | 5/1/2015 | Jiffy Lube
2 | 2 | 3 | 51000 | 6/1/2015 | Dealership
I need a query that will join these 4 tables and return something like the following.
Name | Name | Current Mileage | Last Mileage | Last Date
Acura NSX | Oil Change | 16000 | 10000 | 5/1/2015
Dodge RAM | Oil Change | 51000 | 51000 | 6/1/2015
Dodge RAM | Air Filter | 51000 | -- | --
I need to take the distance threshold from the Reminders table and add it to the mileage from the Maintenance table then compare it to the start and end mileage from the Assignments table. If the threshold is greater than the start or end mileage then select the asset name, the name of the reminder, the current mileage (start or end mileage from Assignments, whichever is greater), and mileage and date from the last maintenance for that reminder. I need to do the same for time threshold. Add it to the date from the Maintenance table then compare it to today's date. If it's greater then display the asset.
Can one of you SQL gurus help me with this please?
UPDATE:
SELECT
v.Name,
r.Name AS Reminder,
a.CurrentMileage,
i.MaintenanceMileage,
i.MaintenanceDate
FROM
Assets v
LEFT JOIN
(SELECT AssetId,
COALESCE(EndMileage, StartMileage) AS CurrentMileage,
ROW_NUMBER() OVER (PARTITION BY AssetId
ORDER BY AssignmentId DESC) AS window_id
FROM Assignments) a
ON v.AssetId = a.AssetId
AND a.window_id = 1
JOIN
Reminders r
ON v.AssetId = r.AssetId
AND r.ActiveFlag = 1
LEFT JOIN
(SELECT AssetId,
ReminderId,
MAX(Mileage) AS MaintenanceMileage,
MAX([Date]) AS MaintenanceDate
FROM Maintenances
GROUP BY AssetId, ReminderId) i
ON r.ReminderId = i.ReminderId
AND (a.CurrentMileage > (NULLIF(i.MaintenanceMileage, 0) + r.DistanceThreshold))
OR (GETDATE() > DATEADD(m, r.[TimeThreshold], i.MaintenanceDate))
Here is a starting point:
SELECT v.Name AS [Asset Name], r.Name AS Reminder, a.CurrentMileage,
m.Mileage + r.Distance AS [Last Mileage], m.[Date] AS [Last Date]
FROM Assets v
JOIN ( -- get the latest relevant row as window_id = 1
SELECT AssetId, COALESCE(EndMileage, StartMileage) AS CurrentMileage,
COALESCE(EndDate, StartDate) AS AssignDate,
ROW_NUMBER() OVER (partition by AssetId
order by COALESCE(EndDate, StartDate) DESC) AS window_id
FROM Assignments
) a
ON v.AssetId = a.AssetId
AND a.window_id = 1
JOIN Reminders r
ON v.AssetId = r.AssetId
AND r.Active = 1
LEFT JOIN Maintenance m
ON r.AssetId = m.AssetId
AND r.ReminderId = m.ReminderId
-- corrected
AND ((a.CurrentMileage > (NULLIF(m.Mileage, 0) + r.Distance))
-- slightly oversimplified
OR (GETDATE() > DATEADD(m, r.[Time], COALESCE(m.[Date], a.AssignDate))))
The date calculations are slightly oversimplified because they use the latest assignment dates. What you would really want is a column Assets.InServiceDate that would anchor the time before the first maintenance would be due. But this will get you started.
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())
I have a dataset (DATASET1) that lists all employees with their Dept IDs, the date they started and the date they were terminated.
I'd like my query to return a dataset in which every row represents a day for each employee stayed employed, with number of days worked (Start-to-Date).
How do I this query? Thanks for your help, in advance.
DATASET1
DeptID EmployeeID StartDate EndDate
--------------------------------------------
001 123 20100101 20120101
001 124 20100505 20130101
DATASET2
DeptID EmployeeID Date #ofDaysWorked
--------------------------------------------
001 123 20100101 1
001 123 20100102 2
001 123 20100103 3
001 123 20100104 4
.... .... ........ ...
EIDT: My goal is to build a fact table which would be used to derive measures in SSAS. The measure I am building is 'average length of employment'. The measure will be deployed in a dashboard and the users will have the ability to select a calendar period and drill-down into month, week and days. That's why I need to start with such a large dataset. Maybe I can accomplish this goal by using MDX queries but how?
You can use a recursive CTE to perform this:
;with data (deptid, employeeid, inc_date, enddate) as
(
select deptid, employeeid, startdate, enddate
from yourtable
union all
select deptid, employeeid,
dateadd(d, 1, inc_date),
enddate
from data
where dateadd(d, 1, inc_date) <= enddate
)
select deptid,
employeeid,
inc_date,
rn NoOfDaysWorked
from
(
select deptid, employeeid,
inc_date,
row_number() over(partition by deptid, employeeid
order by inc_date) rn
from data
) src
OPTION(MAXRECURSION 0)
See SQL Fiddle with Demo
The result is similar to this:
| DEPTID | EMPLOYEEID | DATE | NOOFDAYSWORKED |
-----------------------------------------------------
| 1 | 123 | 2010-01-01 | 1 |
| 1 | 123 | 2010-01-02 | 2 |
| 1 | 123 | 2010-01-03 | 3 |
| 1 | 123 | 2010-01-04 | 4 |
| 1 | 123 | 2010-01-05 | 5 |
| 1 | 123 | 2010-01-06 | 6 |
| 1 | 123 | 2010-01-07 | 7 |
| 1 | 123 | 2010-01-08 | 8 |
| 1 | 123 | 2010-01-09 | 9 |
| 1 | 123 | 2010-01-10 | 10 |
| 1 | 123 | 2010-01-11 | 11 |
| 1 | 123 | 2010-01-12 | 12 |
SELECT DeptID, EmployeeID, Date, DATEDIFF(DAY, StartDate, '3/1/2011') AS ofDaysWorked
FROM DATASET1
See if that worked!