TSQL - Find the 1st or 2nd day of the week - sql-server

I have a table of stock prices and need to get prices for the 1st day of each week. This SQL in the WHERE clause works well,
DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
except when the market is closed on Monday. Labor Day is a good example. I thought using COALESCE would give me the price on Tuesday if one were unavailable for Monday, but this didn't work.
coalesce(DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0), DATEADD(ww, DATEDIFF(ww,0,PriceDt), 1)).
Can someone help with this?
declare #t table (PriceDt datetime, Symbol nvarchar(10), OpenPric float, ClosePrice float)
insert #t values ('2010-08-02 00:00:0.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-09 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-16 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-23 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-30 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-07 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-13 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-20 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-27 00:00:00.000', 'SYM', 15.00, 15.10)
select * from #t
where PriceDt = coalesce(DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0), DATEADD(ww, DATEDIFF(ww,0,PriceDt), 1))
(missing 2010-09-07 00:00:00.000 in the result)
2010-08-02 00:00:00.000 SYM 15 15.1
2010-08-09 00:00:00.000 SYM 15 15.1
2010-08-16 00:00:00.000 SYM 15 15.1
2010-08-23 00:00:00.000 SYM 15 15.1
2010-08-30 00:00:00.000 SYM 15 15.1
2010-09-13 00:00:00.000 SYM 15 15.1
2010-09-20 00:00:00.000 SYM 15 15.1
2010-09-27 00:00:00.000 SYM 15 15.1

This will give you the earliest date that exists in the table for each week (assuming that your week starts on Monday):
select min(Pricedt) Pricedt
from #t
group by DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
Now you can just join that result to your table to get the prices for whatever is the first day of the week that has data entered:
select t.Pricedt, t.Symbol, t.OpenPric, t.ClosePrice
from
(
select min(Pricedt) Pricedt
from #t
group by DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
) d
join #t t on d.Pricedt = t.PriceDt

Related

Dynamically set the Start and End time of Day

I have this table in which I am storing TimeIn and Time Out of Employee.
When I get Total Hours any Employee have worked in certain day, it works fine date wise. But in out organization the issue is that a day is considered from 6 AM till 5:59 AM (next day).
Here is my table and sample data.
CREATE TABLE [dbo].[Attendance]
(
[Employee] [varchar](50) NULL,
[TimeIn] [datetime] NULL,
[TimeOut] [datetime] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-01 06:30:00.000' AS DateTime), CAST(N'2018-12-01 18:22:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-01 20:12:00.000' AS DateTime), CAST(N'2018-12-01 23:50:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-02 00:12:00.000' AS DateTime), CAST(N'2018-12-02 04:50:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-02 07:21:00.000' AS DateTime), CAST(N'2018-12-02 19:54:00.000' AS DateTime))
GO
Here is the query and output of the query I am executing.
SELECT Employee, CAST(COALESCE(TimeIn, TimeOut) AS DATE) DATE, DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut)) [Hours Worked]
FROM [dbo].[Attendance]
GROUP BY Employee, CAST(COALESCE(TimeIn, TimeOut) AS DATE)
Output:
Employee DATE Hours Worked
----------------- ---------- ------------
Lewis 2018-12-01 17
Lewis 2018-12-02 19
What I want is to get the working hours calculated from 6 AM to 5:59 AM next day. So the expected output is as below:
Employee DATE Hours Worked
----------------- ---------- ------------
Lewis 2018-12-01 22:20
Lewis 2018-12-02 12:33
Hope this is possible..
You should probably have a calendar table which contains all the dates which you want to appear in your report. In the absence of that, we can just assume that all dates are covered by the time, and we can group by the time in, shifted earlier by 6 hours. The trick here is that we can shift all times backwards by 6 hours, to align everything with the usual 24 hour day. Something like this should work:
SELECT
Employee,
CONVERT(date, DATEADD(HOUR, -6, TimeIn)) AS DATE,
CONVERT(VARCHAR(10), DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut))) + ':' +
CONVERT(VARCHAR(10), DATEDIFF(MINUTE, MIN(TimeIn), MAX(TimeOut)) % 60) AS [Hours Worked]
FROM Attendance
GROUP BY
Employee,
CONVERT(date, DATEADD(HOUR, -6, TimeIn));
Demo

SQL Server check if particular field is updated in a table

I have a table with following structure
UpdateDate Rate
2015-08-26 00:00:00.000 310.000000
2016-06-02 00:00:00.000 310.000000
2017-02-01 00:00:00.000 310.000000
2017-09-15 00:00:00.000 320.000000
2018-01-31 00:00:00.000 310.000000
2018-02-16 00:00:00.000 310.000000
2018-02-23 00:00:00.000 310.000000
2018-03-09 00:00:00.000 310.000000
2018-04-15 00:00:00.000 320.000000
I am passing FromDate as report criteria.
I want to get only those rows in which Rate field is greater than previous rate after FromDate criteria.
For example, if I pass 2018-04-01 as FromDate selection criteria,
then I will get following output becuase on 2018-04-15 rate is greater than that on 2018-03-09
2018-04-15 00:00:00.000 320.000000
But if I pass 2018-05-01 as FromDate parameter, then I will not get any output as
Rate is not updated from 2018-05-01 till today.
Thanks.
This uses window functions:
declare #test table ([UpdateDate] datetime, [Rate] Decimal(9,6))
insert into #test values
('2015-08-26 00:00:00.000', 310.000000)
, ('2016-06-02 00:00:00.000', 310.000000)
, ('2017-02-01 00:00:00.000', 310.000000)
, ('2017-09-15 00:00:00.000', 320.000000)
, ('2018-01-31 00:00:00.000', 310.000000)
, ('2018-02-16 00:00:00.000', 310.000000)
, ('2018-02-23 00:00:00.000', 310.000000)
, ('2018-03-09 00:00:00.000', 310.000000)
, ('2018-04-15 00:00:00.000', 320.000000)
declare #date as date = '2018-04-01'
;with cte1 as (select [UpdateDate], [Rate], iif(lag(Rate) OVER (ORDER BY [UpdateDate]) < [Rate], 1, 0) as [Changed] from #test),
cte2 as (Select top 1 * from cte1 where [UpdateDate] > #date order by [UpdateDate])
select [UpdateDate], [Rate] from cte2 where [Changed] = 1
set #date = '2018-03-01'
;with cte1 as (select [UpdateDate], [Rate], iif(lag(Rate) OVER (ORDER BY [UpdateDate]) < [Rate], 1, 0) as [Changed] from #test),
cte2 as (Select top 1 * from cte1 where [UpdateDate] > #date order by [UpdateDate])
select [UpdateDate], [Rate] from cte2 where [Changed] = 1
Thanks, it's resolved.
SELECT *
FROM (
SELECT *, LAG(Rate, 1, 0) OVER (ORDER BY UpdateDate) AS PrevRate
FROM #TempRate
) A
WHERE Rate > PrevRate

Sum days between differing date/times progromatically

I have a query that works perfectly fine, by summing the values for a day.
SELECT CAST(fldDateTime AS DATE) AS DayValue, SUM(fldValue) AS Val
FROM [dbo].[Data.tblMeterData]
GROUP BY CAST(fldDateTime AS DATE)
ORDER BY DayValue
The problem I have is that I needed to have the data greater than midnight and up until midnight of the next day. To test I can do this I tested the following code, which helped me deal with the first lot of values that do not have a full day.
DECLARE #a DATETIME
DECLARE #b DATETIME
SET #a = CAST('2016-03-21 00:01:00' AS DATETIME)
SET #b = DATEADD(SECOND,-86399 ,#a)
SELECT #a AS a, #b AS b
SELECT CAST(fldDateTime AS DATE) AS DayValue, SUM(fldValue) AS Val
FROM [dbo].[Data.tblMeterData]
WHERE fldDateTime BETWEEN #b AND #a
GROUP BY CAST(fldDateTime AS DATE)
ORDER BY DayValue
The problem with this is that I do not want to loop through dates, as I have thousands of ID's that I need to process in this way.I cannot include midnight for the first date as the first data is recorded after midnight and the final daily reading is at midnight the following day e.g.
Date > '2016-03-20 00:00:00' AND <= 2016-03-21 00:00:00
How can I do what I need to do the following:
Find the first date for an ID and move up to the first midnight of the following day and repeat this for all following days.
Sum these values so that they are greater than midnight and up to midnight of the next day.
My understanding of the requirements is you are looking for the sum of a value group by meter ID and date, but for each date we also want to include the next day's values. This means each value would count in the sum for its day and the previous day.
Code:
--generate test data
declare #tblMeterData table (
[ID] [int] IDENTITY(1,1) NOT NULL,
[tblMeterData_Id] [int] NOT NULL,
[fldDateTime] [datetime] NOT NULL,
[fldValue] [decimal](18, 2) NULL,
[fldBatchId] [uniqueidentifier] NOT NULL);
insert #tblMeterData (tblMeterData_Id, fldDateTime, fldValue, fldBatchId) values
(18, '2016-12-19 23:59:59', 1.0, newid()),
(18, '2016-12-20 00:00:00', 2.0, newid()),
(18, '2016-12-20 00:30:00', 3.0, newid()),
(18, '2016-12-20 01:00:00', 4.0, newid()),
(18, '2016-12-20 01:30:00', 5.0, newid()),
(18, '2016-12-21 00:00:00', 6.0, newid()),
(18, '2016-12-21 00:30:00', 7.0, newid()),
(18, '2016-12-22 00:00:00', 8.0, newid()),
(18, '2016-12-23 00:00:00', 9.0, newid()),
(19, '2016-12-20 00:00:00', 10.0, newid());
--select * from #tblMeterData order by ID;
--main query
with cte as (
--0:00:00 reports on previous day
select *, cast(dateadd(S, -1, fldDateTime) as date) group_date
from #tblMeterData
union all
--duplicate all records to also group on previous day
select *, cast(dateadd(D, -1, dateadd(S, -1, fldDateTime)) as date) group_date
from #tblMeterData
)
select tblMeterData_Id, group_date, sum(fldValue) sum_value
from cte
group by tblMeterData_Id, group_date
order by tblMeterData_Id, group_date;
Results:
tblMeterData_Id group_date sum_value
18 2016-12-18 3.00
18 2016-12-19 21.00
18 2016-12-20 33.00
18 2016-12-21 24.00
18 2016-12-22 9.00
19 2016-12-18 10.00
19 2016-12-19 10.00
The answer is as follows:
WITH cte AS (
SELECT *, CAST(DATEADD(S, -1, fldDateTime) AS DATE) group_date
FROM [dbo].[Data.tblMeterData]
)
SELECT tblMeterData_Id, group_date, SUM(fldValue) sum_value
FROM cte
GROUP BY tblMeterData_Id, group_date
ORDER BY tblMeterData_Id, group_date;
It works perfectly.

Update table based on Dates in SQL Server?

I got below 2 tables:
if object_id('tempdb..#t1') is not null
drop table #t1
create table #t1
(
ID int,
opendate datetime,
closedate datetime,
[ADDRESS] varchar(50)
)
insert into #t1 (ID, opendate, closedate)
values (111, '1930-05-01 00:00:00.000', '2004-10-23 00:00:00.000'),
(111, '2004-10-23 00:00:00.000', '2006-03-26 00:00:00.000'),
(111, '2006-10-23 00:00:00.000', '2009-03-26 00:00:00.000'),
(111, '2009-03-26 00:00:00.000', '2013-05-21 00:00:00.000'),
(111, '2013-05-21 00:00:00.000', '2013-06-18 00:00:00.000'),
(111, '2013-06-18 00:00:00.000', '2016-04-11 00:00:00.000'),
(111, '2016-04-11 00:00:00.000', '2016-06-16 00:00:00.000'),
(111, '2016-06-16 00:00:00.000', '2016-06-21 00:00:00.000'),
(111, '2016-06-21 00:00:00.000', NULL)
select
*
from
#t1
if object_id('tempdb..#t2') is not null
drop table #t2
create table #t2
(
ID int,
opendate datetime,
closedate datetime,
[ADDRESS] varchar(50)
)
insert into #t2 (ID, opendate, closedate, [ADDRESS])
values
(111,'1930-05-01 00:00:00.000','2004-10-23 00:00:00.000','1 AVENUE' )
,(111,'2004-10-23 00:00:00.000','2009-03-26 00:00:00.000','2 AVENUE' )
,(111,'2009-03-26 00:00:00.000','2013-05-21 00:00:00.000','3 AVENUE' )
,(111,'2013-05-21 00:00:00.000' ,NULL ,'5 AVENUE' )
,(111,'2016-04-11 00:00:00.000' ,'2016-06-16 00:00:00.000','6 AVENUE' )
,(111,'2016-06-16 00:00:00.000' ,NULL ,'7 AVENUE' )
,(111,'2016-06-21 00:00:00.000' ,NULL ,'8 AVENUE' )
select
*
from
#t2
I want update first table like below:
111 1930-05-01 00:00:00.000 2004-10-23 00:00:00.000 '1 AVENUE'
111 2004-10-23 00:00:00.000 2006-03-26 00:00:00.000 '2 AVENUE'
111 2006-03-26 00:00:00.000 2009-03-26 00:00:00.000 '2 AVENUE'
111 2009-03-26 00:00:00.000 2013-05-21 00:00:00.000 '3 AVENUE'
111 2013-05-21 00:00:00.000 2013-06-18 00:00:00.000 '5 AVENUE'
111 2013-06-18 00:00:00.000 2016-04-11 00:00:00.000 '5 AVENUE'
111 2016-04-11 00:00:00.000 2016-06-16 00:00:00.000 '6 AVENUE'
111 2016-06-16 00:00:00.000 2016-06-21 00:00:00.000 '7 AVENUE'
111 2016-06-21 00:00:00.000 NULL '8 AVENUE'
I have tried some ways but it is not returning the correct results because of nulls.
Thanks.
update t1 set address = tmp.address
from (select t1.ID, t1.opendate, ROW_NUMBER() over (partition by t1.opendate order by t2.opendate desc) row, t2.ADDRESS
from #t1 t1
inner join #t2 t2 on t1.ID = t2.ID and t1.opendate between t2.opendate and isnull(t2.closedate, t1.opendate)) tmp
inner join #t1 t1 on t1.ID = tmp.ID and t1.opendate = tmp.opendate and tmp.row = 1

Return previous record for holiday/weekends

A table has stock name, values and effective date for the stocks and there wont be any entry for holidays/weekends. I would like to return a previous date record if I pass the range of dates contains holidays/weekends.
E.G.
Table name: Stock
ID Name Value EffectiveDate
1 IBM 200.0000 2015-12-31 00:00:00.000
2 IBM 201.4500 2016-01-04 00:00:00.000
3 IBM 201.0000 2016-01-05 00:00:00.000
4 IBM 202.0000 2016-01-06 00:00:00.000
SELECT Name, Value, EffectiveDate FROM Stock WHERE Name = 'IBM' AND EffectiveDate >= '20151231' AND EffectiveDate <= '20160105'
The above query returns top 3 records but I would like to return the below results:
Name Value EffectiveDate ActualDate
IBM 200.0000 2015-12-31 2015-12-31
IBM 200.0000 2015-12-31 2016-01-01
IBM 200.0000 2015-12-31 2016-01-02
IBM 200.0000 2015-12-31 2016-01-03
IBM 201.4500 2016-01-04 2015-01-04
IBM 201.0000 2016-01-05 2015-01-05
01/01/2016 to 03/01/2016 are holidays/weekends. I've a function which returns the previous date if I pass the holiday/weekend date. Could anyone help to write the query in SQL Server to achieve the above?
DECLARE #table TABLE (
id int,
name varchar(20),
value decimal(10, 4),
EffectiveDate datetime
)
INSERT INTO #table
VALUES (1, 'IBM', 200.0000, '2015-12-31 00:00:00.000')
, (2, 'IBM', 201.4500, '2016-01-04 00:00:00.000')
, (3, 'IBM', 201.0000, '2016-01-05 00:00:00.000')
, (4, 'IBM', 202.0000, '2016-01-06 00:00:00.000')
DECLARE #MinDate datetime = '20151231',
#MaxDate datetime = '20160105';
WITH Dates AS (
SELECT #MinDate AS ActualDate
UNION ALL
SELECT DATEADD(day, 1, ActualDate)
FROM Dates
WHERE ActualDate < #MaxDate
)
SELECT [Table].name
,[Table].value
,[Table].EffectiveDate
,[Dates].ActualDate
FROM Dates
CROSS APPLY (
SELECT MAX(EffectiveDate) AS LastEffectiveDate
FROM #table AS [Table]
WHERE [Table].EffectiveDate <= Dates.ActualDate
) AS CA1
INNER JOIN #table AS [Table]
ON [Table].EffectiveDate = CA1.LastEffectiveDate
The following code will return an ActualDate as required for the previous date if the EffectiveDate is a Weekend, however to include logic for bank holidays you will need to define these in a table and then add more logic to the below.
SELECT Name, Value, EffectiveDate,
CASE WHEN datename(dw,EffectiveDate) = 'Saturday'
THEN DATEADD(DAY, -1, EffectiveDate)
WHEN datename(dw,EffectiveDate) = 'Sunday'
THEN DATEADD(DAY, -2, EffectiveDate)
END AS ActualDate
FROM Stock
WHERE Name = 'IBM'
AND EffectiveDate BETWEEN '20151231' AND '20160105'
This might not be a perfect solution but this does the trick.
DECLARE #table TABLE (
id int,
name varchar(20),
value decimal(10, 4),
EffectiveDate datetime
)
INSERT INTO #table
VALUES (1, 'IBM', 200.0000, '2015-12-31 00:00:00.000')
, (2, 'IBM', 201.4500, '2016-01-04 00:00:00.000')
, (3, 'IBM', 201.0000, '2016-01-05 00:00:00.000')
, (4, 'IBM', 202.0000, '2016-01-06 00:00:00.000')
DECLARE #MinDate datetime = '20151231',
#MaxDate datetime = '20160105';
SELECT
id,
(CASE
WHEN Data.Value IS NULL THEN (SELECT TOP (1)
value
FROM #table AS T1
WHERE T1.EffectiveDate < Data.FinalDate
ORDER BY FinalDate DESC)
ELSE Data.value
END),
FinalDate
FROM (SELECT
id,
name,
value,
(CASE
WHEN EffectiveDate IS NULL THEN Date
ELSE EffectiveDate
END) AS FinalDate
FROM #table T
FULL OUTER JOIN (SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b) H
ON T.EffectiveDate = H.Date) Data
ORDER BY finaldate

Resources