How to join the next date value of the same table - sql

I have a table in SQL with the following fields:
The timestamp field will have all the punches that an employee has in a day.
So having the following data:
I need to create 2 diferent queries.
need to select all the IN timestamps with their corresponding next OUT timestamp
need to select all the OUT timestamps with their corresponding previous IN timestamp
So, in the first query, I should get the following:
In the second query, I should get the following:
Any clue on how to build such queries?
HERE IS THE Fiddle: http://sqlfiddle.com/#!6/a137d/1

I believe this is what you're looking for. These queries should work on most DBMSs.
First
SELECT ea1.employeeid, ea1.timestamp AS instamp, ea2.timestamp AS outstamp
FROM employee_attendance ea1
LEFT JOIN employee_attendance ea2
ON ea2.employeeid=ea1.employeeid
AND ea2.accesscode = 'OUT'
AND ea2.timestamp = (
SELECT MIN(ea3.timestamp)
FROM employee_attendance ea3
WHERE ea3.timestamp > ea1.timestamp
AND ea3.employeeid = ea1.employeeid
)
WHERE ea1.accessCode = 'IN'
AND ea1.employeeid = 4;
Second
SELECT ea1.employeeid, ea1.timestamp AS outstamp, ea2.timestamp AS instamp
FROM employee_attendance ea1
LEFT JOIN employee_attendance ea2
ON ea2.employeeid=ea1.employeeid
AND ea2.accesscode = 'IN'
AND ea2.timestamp = (
SELECT MIN(ea3.timestamp)
FROM employee_attendance ea3
WHERE ea3.timestamp < ea1.timestamp
AND ea3.employeeid = ea1.employeeid
AND ea3.timestamp > ISNULL((
SELECT MAX(ea4.timestamp)
FROM employee_attendance ea4
WHERE ea4.accesscode = 'OUT'
AND ea4.timestamp < ea1.timestamp
AND ea4.employeeid = ea1.employeeid
), '2000-1-1')
)
WHERE ea1.accessCode = 'OUT'
AND ea1.employeeid = 4;

This looks like nice example for usage of LEAD, LAG ANALYTIC functions in SQL 2012.
SELECT * FROM
(
SELECT EMPLOYEEID, TIMESTAMP,
LEAD(timestamp) OVER (ORDER BY TIMESTAMP
) OUTTIMESTAMP, ACCESSCODE
FROM [dbo].[employee_attendance]
WHERE EMPLOYEEID =4
) T
where T.ACCESSCODE ='IN'
second query
SELECT * FROM
(
SELECT EMPLOYEEID, TIMESTAMP,
LAG(timestamp) OVER (ORDER BY TIMESTAMP
) INTIMESTAMP, ACCESSCODE
FROM [dbo].[employee_attendance]
WHERE EMPLOYEEID =4
) T
where T.ACCESSCODE ='OUT'

Related

SQL group by in Subquery

I'm trying to get monthly production using group by after converting the unix column into regular timestamp. Can you please tell how to use group by here in the code.
'''
With production(SystemId, dayof, monthof, yearof, powerwatts, productionwattshours) as
(
Select SystemId,
[dayof] = DAY(hrdtc),
[monthof] = MONTH(hrdtc),
[yearof] = YEAR(hrdtc),
powerwatts, productionwatthours
from (
Select * , dateadd(s, UnixTime, '19700101') as hrdtc from meterreading ) ds
)
Select * from production
where systemId = 2368252
'''
I think you're looking for this (technically you don't need a subquery but it allows you to avoid repeating the DATEADD() expression):
SELECT SystemId = 2368252,
[Month] = DATEFROMPARTS(YEAR(hrdtc), MONTH(hrdtc), 1),
powerwatts = SUM(powerwatts),
productionwatthours = SUM(productionwatthours)
FROM
(
SELECT powerwatts, productionwatthours,
DATEADD(SECOND, UnixTime, '19700101') as hrdtc
FROM dbo.enphasemeterreading
WHERE systemId = 2368252
) AS ds
GROUP BY DATEFROMPARTS(YEAR(hrdtc), MONTH(hrdtc), 1);
If you want to also avoid repeating the GROUP BY expression:
SELECT SystemId = 2368252,
[Month],
powerwatts = SUM(powerwatts),
productionwatthours = SUM(productionwatthours)
FROM
(
SELECT [Month] = DATEFROMPARTS(YEAR(hrdtc), MONTH(hrdtc), 1),
powerwatts, productionwatthours
FROM
(
SELECT powerwatts, productionwatthours,
DATEADD(SECOND, UnixTime, '19700101') as hrdtc
FROM dbo.enphasemeterreading
WHERE systemId = 2368252
) AS ds1
) AS ds2
GROUP BY [Month];
Personally I don't think that's any prettier or clearer. A couple of other tips:
Spell it out; shorthand is lazy and problematic
Always qualify tables and other objects with schema
Updated requirement (please state these up front): How would I join this query to another table?
SELECT * FROM dbo.SomeOtherTable AS sot
INNER JOIN
(
SELECT SystemId = 2368252,
[Month],
powerwatts = SUM(powerwatts),
productionwatthours = SUM(productionwatthours)
FROM
...
GROUP BY [Month]
) AS agg
ON sot.SystemId = agg.SystemId;

SQL - find row with closest date but different column value

i'm new to SQL and i would need an help.
I have a TAB and I need to find for any item B in the TAB the item A with the closest date. In this case the A with 02.09.2021 04:25:30
Date.
Item
07.09.2021 05:02:05
A
06.09.2021 05:01:02
A
05.09.2021 05:00:02
A
04.09.2021 04:59:01
A
03.09.2021 04:58:03
A
02.09.2021 04:56:55
A
02.09.2021 04:33:56
B
02.09.2021 04:25:30
A
WITH CTE(DATE,ITEM)AS
(
SELECT '20210907 05:02:05' , 'A'UNION ALL
SELECT '20210906 05:01:02' , 'A'UNION ALL
SELECT '20210905 05:00:02' , 'A'UNION ALL
SELECT'20210904 04:59:01' , 'A'UNION ALL
SELECT'20210903 04:58:03' , 'A'UNION ALL
SELECT'20210902 04:56:55' , 'A'UNION ALL
SELECT'20210902 04:33:56' , 'B'UNION ALL
SELECT'20210902 04:25:30' , 'A'
)
SELECT
CAST(C.DATE AS DATETIME)X_DATE,C.ITEM,Q.CLOSEST
FROM CTE AS C
OUTER APPLY
(
SELECT TOP 1 CAST(X.DATE AS DATETIME)CLOSEST
FROM CTE AS X
WHERE X.ITEM='A'AND CAST(X.DATE AS DATETIME)<CAST(C.DATE AS DATETIME)
ORDER BY CAST(X.DATE AS DATETIME) ASC
)Q
WHERE C.ITEM='B'
You can use OUTER APPLY-approach as in the above query.
Please also take a look that datetime-column (DATE)is written in the ISO-compliant form
Your data has only two columns. If you want the only the closest A timestamp, then the fastest way is probably window functions:
select t.*,
(case when prev_a_date is null then next_a_date
when next_a_date is null then prev_a_date
when datediff(second, prev_a_date, date) <= datediff(second, date, next_a_date) then prev_a_date
else next_a_date
end) as a_date
from (select t.*,
max(case when item = 'A' then date end) over (order by date) as prev_a_date,
min(case when item = 'A' then date end) over (order by date desc) as next_a_date
from t
) t
where item = 'B';
This uses seconds to measure the time difference, but you can use a smaller unit if appropriate.
You can also do this using apply if you have more columns from the "A" rows that you want:
select tb.*, ta.*
from t b outer apply
(select top (1) ta.*
from t ta
where item = 'A'
order by abs(datediff(second, a.date, b.date))
) t
where item = 'B';

SQL - Adding conditions to SELECT

I have a table which has a timestamp and inCycle status of a machine. I'm using two CTE's and doing an INNER JOIN on row number so I can easily compare the timestamp of one row to the next. I have the DATEDIFF working and now I need to look at the inCycle status. Basically, if the inCycleThis and inCycleNext both = 1, I need to add it to an InCycle total.
Similarly (Shown table will make this clear):
incycleThis/next = 0,1 = not in cycle
incycleThis/next = 0,0 = not in cycle
incycleThis/next = 1,1 = in cycle
If I was doing this client side, this would be pretty simple. I need to do this in a stored procedure though due to there being a lot of records. I'd love to use an 'IF' in the SELECT section, but it seems that's not how it works.
The result I'm looking for at the end is simply: InCycle = Xtime. Something like:
SUM(Diff_seconds if((InCycleThis = 1 AND InCycleNext = 1) OR (InCycleThis = 1 AND InCycleNext = 0))
This is what I have so far:
WITH History_CTE (DT, MID, FRO, IC, RowNum)
AS
(
SELECT DateAndTime
,MachineID
,FeedRateOverride
,InCycle
,ROW_NUMBER()OVER(ORDER BY MachineID, DateAndTime) AS "row number"
FROM History
WHERE DateAndTime >= '2020-11-15'
AND DateAndTime < '2020-11-16'
),
History2_CTE (DT2, MID2, FRO2, IC2, RowNum2)
AS
(
SELECT DateAndTime
,MachineID
,FeedRateOverride
,InCycle
,ROW_NUMBER()OVER(ORDER BY MachineID, DateAndTime) AS "row number"
FROM History
WHERE DateAndTime >= '2020-11-15'
AND DateAndTime < '2020-11-16'
)
SELECT DT as 'TimeStamp'
,DT2 as 'TimeStamp Next Row'
,MID
,FRO
,IC as 'InCycle this'
,IC2 as 'InCycle next'
,RowNum
,DATEDIFF(s, History2_CTE.DT2, History_CTE.DT) AS 'Diff_seconds'
FROM History_CTE
INNER JOIN
History2_CTE ON History_CTE.RowNum = History2_CTE.RowNum2 + 1
Consider adding a third CTE to first conditionally calculate your needed value. Then aggregate for final statement. Recall CTEs can reference previously defined CTEs. Be sure to always quailfy columns with table aliases in JOIN queries.
WITH
... first two ctes...
, sub AS (
SELECT h1.DT AS 'TimeStamp'
, h2.DT2 AS 'TimeStamp Next Row'
, h1.MID
, h1.FRO
, h1.IC AS 'InCycle this'
, h2.IC2 AS 'InCycle next'
, h1.RowNum
, DATEDIFF(s, h2.DT2, h1.DT) AS 'Diff_seconds'
, CASE
WHEN (h1.IC = 1 AND h2.IC2 = 1) OR (h1.IC= 1 AND h2.IC2 = 0)
THEN DATEDIFF(s, h2.DT2, h1.DT)
END AS 'IC_Diff_seconds'
FROM History_CTE h1
INNER JOIN History2_CTE h2
ON h1.RowNum = h2.RowNum2 + 1
)
SELECT SUM([Diff_seconds]) AS Diff_seconds_Total
, SUM([IC_Diff_seconds]) AS IC_Diff_seconds_Total
FROM sub
And if needing to add groupings, incorporate GROUP BY:
SELECT h1.MID
, h1.FRO
, SUM([Diff_seconds]) AS Diff_seconds_Total
, SUM([IC_Diff_seconds]) AS IC_Diff_seconds_Total
FROM sub
GROUP BY h1.MID
, h1.FRO
Even aggregate calculations by day:
SELECT CONVERT(date, [TimeStamp]) AS [Day]
, SUM([Diff_seconds]) AS Diff_seconds_Total
, SUM([IC_Diff_seconds]) AS IC_Diff_seconds_Total
FROM sub
GROUP BY CONVERT(date, [TimeStamp])
The result I'm looking for at the end is simply: InCycle = Xtime. Something like:
SUM(Diff_seconds if((InCycleThis = 1 AND InCycleNext = 1) OR (InCycleThis = 1 AND InCycleNext = 0))
As I understand your question, you just need to sum the difference betwen the timestamp of "in cycle" rows and the timestamp of the next row.
select machineid,
sum(datediff(s, dateandtime, lead_dateandtime)) as total_in_time
from (
select h.*,
lead(dateandtime) over(partition by machineid order by dateandtime) as lead_dateandtime
from history h
) h
where inclycle = 1
group by machineid

calculate time difference of consecutive row dates in SQL

Hello I am trying to calculate the time difference of 2 consecutive rows for Date (either in hours or Days), as attached in the image
Highlighted in Yellow is the result I want which is basically the difference of the date in that row and 1 above.
How can we achieve it in the SQL? Attached is my complex code which has the rest of the fields in it
with cte
as
(
select m.voucher_no, CONVERT(VARCHAR(30),CONVERT(datetime, f.action_Date, 109),100) as action_date,f.col1_Value,f.col3_value,f.col4_value,f.comments,f.distr_user,f.wf_status,f.action_code,f.wf_user_id
from attdetailmap m
LEFT JOIN awftaskfin f ON f.oid = m.oid and f.client ='PC'
where f.action_Date !='' and action_date between '$?datef' and '$?datet'
),
.*select *, ROW_NUMBER() OVER(PARTITION BY action_Date,distr_user,wf_Status,wf_user_id order by action_Date,distr_user,wf_Status,wf_user_id ) as row_no_1 from cte
cte2 as
(
select *, ROW_NUMBER() OVER(PARTITION BY voucher_no,action_Date,distr_user,wf_Status,wf_user_id order by voucher_no ) as row_no_1 from cte
)
select distinct(v.dim_value) as resid,c.voucher_no,CONVERT(datetime, c.action_Date, 109) as action_Date,c.col4_value,c.comments,c.distr_user,v.description,c.wf_status,c.action_code, c.wf_user_id,v1.description as name,r.rel_value as pay_office,r1.rel_value as site
from cte2 c
LEFT OUTER JOIN aagviuserdetail v ON v.user_id = c.distr_user
LEFT OUTER JOIN aagviuserdetail v1 ON v1.user_id = c.wf_user_id
LEFT OUTER JOIN ahsrelvalue r ON r.resource_id = v.dim_Value and r.rel_Attr_id = 'P1' and r.period_to = '209912'
LEFT OUTER JOIN ahsrelvalue r1 ON r1.resource_id = v.dim_Value and r1.rel_Attr_id = 'Z1' and r1.period_to = '209912'
where c.row_no_1 = '1' and r.rel_value like '$?site1' and voucher_no like '$?trans'
order by voucher_no,action_Date
The key idea is lag(). However, date/time functions vary among databases. So, the idea is:
select t.*,
(date - lag(date) over (partition by transaction_no order by date)) as diff
from t;
I should note that this exact syntax might not work in your database -- because - may not even be defined on date/time values. However, lag() is a standard function and should be available.
For instance, in SQL Server, this would look like:
select t.*,
datediff(second, lag(date) over (partition by transaction_no order by date), date) / (24.0 * 60 * 60) as diff_days
from t;

Performance issues in T-SQL function

I have a table which provides me daily inventory status. I.e Inventory Item X, on a particular date had Y amount of quantity. I have created a function to obtain the purchase price on a particular day based on the last purchases.
When I run the query on the dailyinventorystatus table it completes within 3 minute for date > 2014-01-01. However, when I add in the function as a subquery it causes huge performance issues. It has been over 1.5 hours and the query is still running.
How do I improve this?
Here is the query:
SELECT
*,
RWReports.dbo.FindPurchasePrice(InventoryKey, Date , warehouse) as SalesPurchasePrice
FROM
DailyInventoryStatus
WHERE
Warehouse IN ('NYC,', 'CHICAGO', 'CHINA', 'ATLANTA')
AND Date >= '2014-01-01'
Here is the function:
CREATE FUNCTION [dbo].[FindPurchasePrice]
(#InventoryKey varchar(8), #InDate Date , #Warehouse varchar(30))
RETURNS REAL
AS
BEGIN
DECLARE #oPurchasePrice AS REAL ;
SELECT TOP (1)
#oPurchasePrice = UnitPurchasePrice
FROM
PurchaseTransactions
WHERE
InventoryKey = #InventoryKey
AND TransactionDate <= #InDate
AND Warehouse = #Warehouse
ORDER BY
TransactionDate DESC;
IF #oPurchasePrice IS NULL
SELECT
#oPurchasePrice = mw.cost
FROM
Rentalworks.dbo.masterwh mw
JOIN
Rentalworks.dbo.warehouse w ON w.warehouseid = mw.warehouseid
AND mw.masterid = #InventoryKey
AND w.warehouse = #Warehouse;
RETURN #oPurchasePrice;
END;
GO
This is how you could possibly convert this into an inline table valued function.
CREATE FUNCTION [dbo].[FindPurchasePrice]
(
#InventoryKey varchar(8)
, #InDate Date
, #Warehouse varchar(30)
)
RETURNS TABLE
AS
RETURN
SELECT ISNULL(pt.UnitPurchasePrice ,mw.cost) AS PurchasePrice
FROM Rentalworks.dbo.masterwh mw
JOIN Rentalworks.dbo.warehouse w on w.warehouseid = mw.warehouseid
AND mw.masterid = #InventoryKey
AND w.warehouse = #Warehouse
OUTER APPLY
(
SELECT TOP (1) UnitPurchasePrice
FROM PurchaseTransactions
WHERE InventoryKey = #InventoryKey
AND TransactionDate <= #InDate
AND Warehouse=#Warehouse
ORDER BY TransactionDate DESC
) pt
I of course can't test this but it syntax checks fine.
Now to include that in your original select statement you would do something like this.
SELECT dis.*
, fp.PurchasePrice
FROM DailyInventoryStatus dis
CROSS APPLY dbo.FindPurchasePrice(dis.InventoryKey, dis.Date, dis.warehouse) fp
WHERE Warehouse IN ('NYC,', 'CHICAGO', 'CHINA', 'ATLANTA')
AND Date >= '2014-01-01'
Here's one way to re-write without the function by incorporating all the logic into a single query:
with data as (
select
dis.*, pt.TransactionDate, pt.UnitPurchasePrice,
row_number() over (
partition by dis.InventoryKey, dis.Warehouse
order by TransactionDate desc
) as TransNumber
from
DailyInventoryStatus dis left outer join
PurchaseTransactions pt
on pt.InventoryKey = dis.InventoryKey
and pt.Warehouse = dis.Warehouse
and pt.TransactionDate < dis.Date
where dis.Date >= ?
)
select
*,
coalesce(
UnitPurchasePrice,
(
select mw.cost
from
Rentalworks.dbo.masterwh mw inner join
Rentalworks.dbo.warehouse w
on w.warehouseid = mw.warehouseid
where mw.masterid = data.InventoryKey
and w.warehouse = data.Warehouse
)
) as PurchasePrice
from data
where TransNumber = 1