given the following table:
create table #T
(
user_id int,
project_id int,
datum datetime,
status varchar(10),
KM int
)
insert into #T values
(1, 1, '20160301 10:25', 'START', 1000),
(1, 1, '20160301 10:28', 'PASS', 1008),
(2, 2, '20160301 10:29', 'START', 2000),
(1, 1, '20160301 11:08', 'STOP', 1045),
(3, 3, '20160301 10:25', 'START', 3000),
(2, 2, '20160301 10:56', 'STOP', 2020),
(1, 4, '20160301 15:00', 'START', 1045),
(4, 5, '20160301 15:10', 'START', 400),
(1, 4, '20160301 15:10', 'PASS', 1060),
(1, 4, '20160301 15:20', 'PASS', 1080),
(1, 4, '20160301 15:30', 'STOP', 1080),
(4, 5, '20160301 15:40', 'STOP', 450),
(3, 3, '20160301 16:25', 'STOP', 3200)
I have to sum the length of a track between START and STOP statuses for a given user and project
The expected result would be this:
user_id project_id datum TOTAL_KM
----------- ----------- ---------- -----------
1 1 2016-03-01 45
1 4 2016-03-01 35
2 2 2016-03-01 20
3 3 2016-03-01 200
4 5 2016-03-01 50
How can I achieve this without using a cluster?
The performance is an issue (I have over 1 million records per month and we have to keep data for several years)
Explanation:
We can ignore the records with the status "PASS". Basically we have to subtract the KM value of the START record from the STOP record for a given user and project.
There can be several hundred records between a START and STOP (like describes in the sample data)
The date should be the date of START (in case where we have an over midnight delivery)
I think I should have a SELECT with an OVER() clause but I don't know how to formulate my query to respect those conditions.
Any idea?
SELECT t.[user_id],
t.project_id,
cast(t.datum as date) as datum,
t1.KM- t.KM as KM
FROM #T t
INNER JOIN #T t1
ON t.[user_id]=t1.[user_id] and t.project_id = t1.project_id
WHERE t.[status] = 'START' and t1.[status] = 'STOP'
ORDER BY t.[user_id],
t.project_id,
cast(t.datum as date)
Output:
user_id project_id datum KM
----------- ----------- ---------- -----------
1 1 2016-03-01 45
1 4 2016-03-01 35
2 2 2016-03-01 20
3 3 2016-03-01 200
4 5 2016-03-01 50
(5 row(s) affected)
This could be achieved by simple self join.
One of the example: (this may not be exact query but just an idea)
Select
a.user_id,
a.project_id,
b.datum as StartDate,
a.KM-b.KM as TotalKM
From #T a
Where status = 'STOP'
Join
(
Select user_id, project_id, KM From #t Where
status = 'START'
) b ON b.user_id = a.user_id, b.project_id = a.project_id
#T b
Related
I have 2 tables.
DateTable1 (client-wise month start and end dates, an appropriate month number):
Client|MonthNumber|MonthStartDate|MonthEndDate
DateTable2 (1 row for each day, for each client, and the row holds an appropriare QuarterNumber):
Client|Date|QuarterNumber
I want to create 1 date table such that it has 1 row for each day, for each client showing the QuarterNumber (thus far it is the same as DateTable2), and additionally I want the MonthNumber from DateTable1.
I am thinking about 2 solutions:
Perform an inner join on client name, and apply the where criteria to filter such that DateTable2's date is between the start and end values of DateTable1.
Perform cross join (so without an on clause), and apply the same where criteria as above.
Please can I have guidance on how to choose from the above solutions?
The correct answer here is likely to utilize a calendar table.
If you can't or don't want to fully implement one, you can fake one with a little recursion:
DECLARE #DateTable1 TABLE (Client INT, MonthNumber INT, MonthStartDate DATE, MonthEndDate DATE);
INSERT INTO #DateTable1 (Client, MonthNumber, MonthStartDate, MonthEndDate) VALUES
(1, 1, '2022-01-01', '2022-01-31'),(1, 2, '2022-02-01', '2022-02-28'),(1, 3, '2022-03-01', '2022-03-31'),(1, 4, '2022-04-01', '2022-04-30'),(1, 5, '2022-05-01', '2022-05-31'),
(2, 1, '2022-01-01', '2022-01-31'),(2, 2, '2022-02-01', '2022-02-28'),(2, 3, '2022-03-01', '2022-03-31'),(2, 4, '2022-04-01', '2022-04-30'),
(3, 1, '2022-01-01', '2022-01-31'),(3, 2, '2022-02-01', '2022-02-28'),(3, 3, '2022-03-01', '2022-03-31'),(3, 4, '2022-04-01', '2022-04-30'),(3, 5, '2022-05-01', '2022-05-31'),(3, 6, '2022-06-01', '2022-06-30');
;WITH cte AS (
SELECT Client, MIN(MonthNumber) AS mnS, MAX(MonthNumber) AS mnE, MIN(MonthStartDate) AS Date, DATEPART(QUARTER,MIN(MonthStartDate)) AS Quarter
FROM #DateTable1
GROUP BY Client
UNION ALL
SELECT Client, mnS, mnE, DATEADD(DAY,1,Date) AS Date, DATEPART(QUARTER,DATEADD(DAY,1,Date)) AS Quarter
FROM cte
WHERE DATEPART(MONTH,DATEADD(DAY,1,Date)) <= mnE
)
SELECT Client, Date, Quarter
FROM cte
ORDER BY Client, Date
OPTION (MAXRECURSION 0)
Using just one of you tables we can iterate over the dates that should be available for your clients, and fill in the gaps.
Client Date Quarter
---------------------------
1 2022-01-01 1
1 2022-01-02 1
1 2022-01-03 1
...
1 2022-05-30 2
1 2022-05-31 2
2 2022-01-01 1
2 2022-01-02 1
...
2 2022-04-29 2
2 2022-04-30 2
3 2022-01-01 1
3 2022-01-02 1
...
3 2022-06-29 2
3 2022-06-30 2
Each client now has a row for each day (where they have data in the first table).
You can join to this as necessary.
Edit:
if the other table is as described and contains a non-standard quarter number you could just join to it:
DECLARE #DateTable1 TABLE (Client INT, MonthNumber INT, MonthStartDate DATE, MonthEndDate DATE);
INSERT INTO #DateTable1 (Client, MonthNumber, MonthStartDate, MonthEndDate) VALUES
(1, 1, '2022-01-01', '2022-01-31'),(1, 2, '2022-02-01', '2022-02-28'),(1, 3, '2022-03-01', '2022-03-31'),(1, 4, '2022-04-01', '2022-04-30'),(1, 5, '2022-05-01', '2022-05-31'),
(2, 1, '2022-01-01', '2022-01-31'),(2, 2, '2022-02-01', '2022-02-28'),(2, 3, '2022-03-01', '2022-03-31'),(2, 4, '2022-04-01', '2022-04-30'),
(3, 1, '2022-01-01', '2022-01-31'),(3, 2, '2022-02-01', '2022-02-28'),(3, 3, '2022-03-01', '2022-03-31'),(3, 4, '2022-04-01', '2022-04-30'),(3, 5, '2022-05-01', '2022-05-31'),(3, 6, '2022-06-01', '2022-06-30');
DECLARE #DataTable2 TABLE (Client INT, Date DATE, QuarterNumber INT)
INSERT INTO #DataTable2 (Client, Date, QuarterNumber) VALUES
(1, '2022-01-01', 1),(1, '2022-01-02', 1),(1, '2022-01-03', 1),(1, '2022-01-04', 1),
(1, '2022-05-30', 2),(1, '2022-05-31', 2),(2, '2022-01-01', 1),(2, '2022-01-02', 1),
(2, '2022-01-03', 1),(2, '2022-04-29', 2),(2, '2022-04-30', 2),(3, '2022-01-01', 3),
(3, '2022-01-02', 3),(3, '2022-01-03', 3),(3, '2022-06-28', 4),(3, '2022-06-29', 4),
(3, '2022-06-30', 4)
;WITH cte AS (
SELECT Client, MIN(MonthNumber) AS mnS, MAX(MonthNumber) AS mnE, MIN(MonthStartDate) AS Date, DATEPART(QUARTER,MIN(MonthStartDate)) AS Quarter
FROM #DateTable1
GROUP BY Client
UNION ALL
SELECT Client, mnS, mnE, DATEADD(DAY,1,Date) AS Date, DATEPART(QUARTER,DATEADD(DAY,1,Date)) AS Quarter
FROM cte
WHERE DATEPART(MONTH,DATEADD(DAY,1,Date)) <= mnE
)
SELECT c.Client, c.Date, d.QuarterNumber
FROM cte c
LEFT OUTER JOIN #DataTable2 d
ON c.Client = d.Client
AND c.Date = d.Date
ORDER BY Client, Date
OPTION (MAXRECURSION 0)
I did not include every date from the other table.
I have a table Emp:
Create table Emp
(
empno int,
ename varchar(50),
doj varchar(30),
salary int
);
insert into Emp
values (1, 'raj', '2010-06-30 08:10:45', 5000),
(2, 'kiran', '2018-12-05 18:20:24', 40000),
(3, 'akbar', '2015-04-12 20:02:45', 9000),
(4, 'nitin', '2010-03-11 02:10:23', 3000),
(5, 'Rahul', '2013-12-03 13:23:30', 15000);
Emp table:
-------+------------------+--------------------------+-----------------
empno ename doj salary
-------+------------------+--------------------------+-----------------
1 raj 2010-06-30 08:10:45 5000
2 kiran 2018-12-05 18:20:24 40000
3 akbar 2015-04-12 20:02:45 9000
4 nitin 2010-03-11 02:10:23 3000
5 Rahul 2013-12-03 13:23:30 15000
-------+------------------+-------------------------+-----------------
Here I want to subtract 4hrs from doj and should return the doj values.
I wrote this SQL query and it's working:
select
format(cast(doj as datetime) - cast('04:00' as datetime), 'yyyy-mm-dd HH:mm:ss') "4Hrs_Minus"
from emp;
Now I want to use a function which should return the o/p as above...
Query output:
-------+-----------------------+---------------------
empno doj 4Hrs_Minus
-------+-----------------------+---------------------
1 2010-06-30 08:10:45 2010-06-30 04:10:45
2 2018-12-05 18:20:24 2018-12-05 14:20:24
3 2015-04-12 20:02:45 2015-04-12 16:02:45
4 2010-03-11 02:10:23 2010-03-11 22:10:23
5 2013-12-03 13:23:30 2013-12-03 09:23:30
-------+-----------------------+----------------------
SELECT empno, doj, DATEADD(hh, -4, doj) as [4hrs_Minus] FROM Emp
Documentation:
https://learn.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql?view=sql-server-2017
I have a table that lists all users for my company. There are multiple entries for each staff member showing how they have been employed.
RowID UserID FirstName LastName Title StartDate Active EndDate
-----------------------------------------------------------------------------------
1 1 John Smith Manager 2017-01-01 0 2017-01-31
2 1 John Smith Director 2017-02-01 0 2017-02-28
3 1 John Smith CEO 2017-03-01 1 NULL
4 2 Sam Davey Manager 2017-01-01 0 2017-02-28
5 2 Sam Davey Manager 2017-03-01 0 NULL
6 3 Hugh Holland Admin 2017-02-01 1 NULL
7 4 David Smith Admin 2017-01-01 0 2017-02-28
I am trying to write a query that will tell me someones length of service at any given time.
The part I am having trouble with is as a single person is represented by multiple rows as their information changes over time I need combine multiple rows...
I have a query to report on who is employed at a point in time which is as far as I have gotten.
DECLARE #DateCheck datetime
SET #DateCheck = '2017/05/10'
SELECT *
FROM UsersTest
WHERE #DateCheck >= StartDate AND #DateCheck <= ISNULL(EndDate, #DateCheck)
You need to use the datediff function. The key will be choosing the appropriate number - days, months, years. The return value is an integer so if you choose years, it will be rounded (and remember, it will round for each record, not for the summary. I've chosen months below. The following has been added to get the most recent information for user name:
WITH CurrentName AS
(SELECT UserID, FirstName, LastName
from
UserStartStop
where Active = 1 -- You can replace this with a date check
)
SELECT uss.UserID,
MAX(cn.FirstName) as FirstName, -- the max is necessary because we are
-- grouping. Could include in group by
MAX(cn.LastName) as LastName,
SUM(DATEDIFF(mm,uss.StartDate,COALESCE(uss.EndDate,GETDATE())))
from UserStartStop uss
JOIN CurrentName cn
on uss.UserID = cn.UserID
GROUP BY UserID
order by UserID
For months in service, change 'd' to 'mm':
Create table #UsersTest (
RowId int
, UserID int
, FirstName nvarchar(100)
, LastName nvarchar(100)
, Title nvarchar(100)
, StartDate date
, Active bit
, EndDate date)
Insert #UsersTest values (1, 1, 'John', 'Smith', 'Manager', '2017-01-01', 0, '2017-01-31')
Insert #UsersTest values (1, 1, 'John', 'Smith', 'Director', '2017-02-01', 0, '2017-02-28')
Insert #UsersTest values (1, 1, 'John', 'Smith', 'CEO', '2017-03-01', 1, null)
Insert #UsersTest values (1, 2, 'Sam', 'Davey', 'Manager', '2017-01-01', 0, '2017-02-28')
Insert #UsersTest values (1, 2, 'Sam', 'Davey', 'Manager', '2017-03-01', 0, null)
Insert #UsersTest values (1, 3, 'Hugh', 'Holland', 'Admin', '2017-02-01', 1, null)
Insert #UsersTest values (1, 4, 'David', 'Smith', 'Admin', '2017-01-01', 0, '2017-02-28')
Declare #DateCheck as datetime = '2017/05/10'
Select UserID, FirstName, LastName
, Datediff(d, Min([StartDate]), iif(isnull(Max([EndDate]),'1900-01-01')<#DateCheck, #DateCheck ,Max([Enddate]))) as [LengthOfService]
from #UsersTest
Group by UserID, FirstName, LastName
Try it's
Select
FirstName,
LastName,
Min(StartDate)StartDate,
Max(isnull(EndDate,getdate()) as EndDate
from Table
this is the given tables data ,i want the output like this as given by me .
slno name salary
-----------------------------
1 raj 5000.0000
2 laba 4000.0000
3 silu 3000.0000
4 jaya 6000.0000
5 papu 7000.0000
6 tikan 9000.0000
7 susanta 6000.0000
8 chiku 4500.0000
9 micky 5500.0000
10 susa 2500.0000
11 musa 6500.0000
12 pi 6500.0000
13 luna 7500.0000
14 tuna 9500.0000
15 tina 3500.0000
Desired output
slno name salary
----------------------
1 raj 5000.0000
2 laba 4000.0000
3 silu 3000.0000
4 jaya 6000.0000
5 papu 7000.0000
6-10 ---- 27500.0000(total salary from 6-10)
6-15 ---- 61000.0000(total salary from 6-15)
Try this:
create table #table_name (slno int, name varchar(20), salary float);
insert into #table_name (slno, name, salary) values
(1, 'raj', 5000.0000),
(2, 'laba', 4000.0000),
(3, 'silu', 3000.0000),
(4, 'jaya', 6000.0000),
(5, 'papu', 7000.0000),
(6, 'tikan', 9000.0000),
(7, 'susanta', 6000.0000),
(8, 'chiku', 4500.0000),
(9, 'micky', 5500.0000),
(10, 'susa', 2500.0000),
(11, 'musa', 6500.0000),
(12, 'pi', 6500.0000),
(13, 'luna', 7500.0000),
(14, 'tuna', 9500.0000),
(15, 'tina', 3500.0000);
select cast(slno as varchar(10)) [slno]
, name
, salary
from #table_name where slno <= 5
union all
select '6-10'
, '----'
, sum(salary)
from #table_name where slno between 6 and 10
union all
select '6-15'
, '----'
, sum(salary)
from #table_name where slno between 6 and 15
Result
slno name salary
----------------------
1 raj 5000
2 laba 4000
3 silu 3000
4 jaya 6000
5 papu 7000
6-10 ---- 27500
6-15 ---- 61000
I am trying to work out how I can tag unique (what i am calling) blocks (or segments if you will) which have a start and end based consecutive 'Trip' rows ordered by 'epoch' sharing the same 'code'. In this case group by 'trip', 'code' will not work as I need to measure the duration of the 'code' remains constant for the trip. I've tried to use a CTE but I have been unable to partition the data in such a way that it gives desired result shown below. The block number I've shown could be any value, just so long as it is unique so that it tags the consecutive occurrences of the same 'code' on the trip in order of 'epoch'.
Any ideas?
declare #data table (id int, trip int, code int NULL, epoch int, value1 int, value2 int);
insert into #data (id, trip, code, epoch, value1, value2)
values
(1, 1, null, 31631613, 0, 0),
(2, 2, 1, 31631614, 10, 40),
(3, 1, 1, 31631616, 10, 60),
(4, 1, 1, 31631617, 40, 60),
(5, 2, 1, 31631617, 23, 40),
(6, 2, 2, 31631620, 27, 40),
(7, 2, 2, 31631629, 23, 40),
(9, 1, 1, 31631618, 39, 60),
(10, 1, null, 31631621, 38, 60),
(12, 1, null, 31631625, 37, 60),
(15, 1, null, 31631627, 35, 60),
(19, 1, 1, 31631630, 39, 60),
(20, 1, 1, 31631632, 40, 60),
(21, 2, 1, 31631629, 23, 40);
block id trip code epoch value1 value2
1 1 1 NULL 31631613 0 0
2 2 2 1 31631614 10 40
2 5 2 1 31631617 23 40
3 3 1 1 31631616 10 60
3 4 1 1 31631617 40 60
3 9 1 1 31631618 39 60
4 6 2 2 31631620 27 40
4 7 2 2 31631629 23 40
5 10 1 NULL 31631621 38 60
5 12 1 NULL 31631625 37 60
5 15 1 NULL 31631627 35 60
6 19 1 1 31631630 39 60
6 20 1 1 31631632 40 60
7 21 2 1 31631629 23 40
You didn't update your expected output so I'm still not 100% sure this is what you want, but give it a try...
SELECT
DENSE_RANK() OVER (ORDER BY trip, code),
*
FROM
#data
ORDER BY
trip, code, epoch
Ok, it's far from perfect by any means but it is a starter that at least identifies the start and end of a contiguous block where the 'code' has remained the same for the trip. For the sake of at least contributing something I'll post what I jerried up. If I ever get time to do a proper job I'll post it.
declare #minint int; set #minint = -2147483648;
declare #maxint int; set #maxint = 2147483647;
declare #id_data table (pk int IDENTITY(1,1), id int, trip int, code int NULL, epoch int, value1 int, value2 int);
insert into #id_data VALUES(#minint, #minint, #minint, #minint, #minint, #minint);
insert into #id_data
SELECT id, trip, coalesce(code,0), epoch, value1, value2
FROM #data
order by trip, epoch, code;
insert into #id_data VALUES(#maxint, #maxint, #maxint, #maxint, #maxint, #maxint);
WITH CTE as
(
SELECT pk, id, trip, code, epoch, value1, value2, ROW_NUMBER() OVER (PARTITION BY trip ORDER BY epoch) as row_num
FROM #id_data
)
SELECT B.*, A.code, C.min_next_code
FROM CTE A
INNER JOIN CTE B ON (B.pk = A.pk + 1) AND (A.code != B.code) -- SELECTS THE RECORDS THAT START A NEW GROUP
OUTER APPLY (
SELECT min_next_code = MIN(pk) - 1 -- LOCATION OF NEXT GROUP
FROM CTE
WHERE pk > B.pk AND (trip = B.trip) AND (code != B.code)
) C
WHERE B.id < #maxint