SQL Server Date Range - sql

I have a SQL Server table that contains the following dates (OpenDate, ClosedDate, WinnerAnnouncedDate).
I have 3 rows, for 3 different categories.
I'm trying to figure out how I would get the following scenario:
Today is 14th March. I want to find out which category had the winner announced, but the following category hasn't started yet.
So if Row 1 had OpenDate = 12th Feb, ClosedDate = 10th March, WinnerAnnounced = 12th March
Row 2 had an OpenDate of 16th March I need it to find Row 1 because the winner has been announced, but the following category hasn't opened yet.
This may seem a little confusing, so I'll be ready to clear things up if required.

I'm not 100% clear on what you're saying, but I think it's something like:
Find the last winner announced from categories that have a start date earlier than now.
If that's the case then something like this might work for you. I'm assuming that your table is called #dates as you haven't included the table name
create table #dates (
id int identity(1,1) primary key,
openDate datetime,
closedDate datetime,
WinnerAnnouncedDate datetime
)
insert into #dates
values ('12 feb 2012', '10 march 2012', '13 march 2012')
insert into #dates
values ('12 feb 2012', '10 march 2012', null)
insert into #dates
values ('16 mar 2012', null, null)
select *
from #dates
where id = (select max(id) from #dates where openDate <= getdate() and winnerAnnouncedDate is not null)
--drop table #dates

SELECT TOP 1 WITH TIES *
FROM atable
WHERE WinnerAnnouncedDate <= GETDATE()
ORDER BY WinnerAnnouncedDate
WITH TIES will return several rows if several WinnerAnnouncedDate values match the condition and have the same top value.

Related

Subquery in SQL server

The below code creates a table in SQL server with keeps track of annual certifications my clients must submit on an annual basis. The certification is submitted after the calendar year (2018, 2019, etc.) has concluded. I need a query that, for each client, yields the date when the annual certification for the most recent calendar year was received, along with the calendar year for which the certification was intended. Some of my clients are lagers and they may submit an certification for a past year after certifications for most recent years have been received. The following table shows the intended results. I would like to employee one single query. The below query at the end of my code, intended for this situation, does not work. What am I missing here? Thanks.
These are the intended results:
CLIENT_ID DATE_RECEIVED CERTIFICATION_YEAR
1 2019-01-02 2018
2 2020-01-07 2019
3 2021-01-10 2020
Sample data:
CREATE TABLE [dbo].[CERTIFICATION](
[CERTIFICATION_ID] [numeric](11, 0) IDENTITY(1,1) NOT NULL,
[CLIENT_ID] [numeric](11, 0) NOT NULL,
[DATE_RECEIVED] [date] NOT NULL,
[CERTIFICATION_YEAR] [numeric](4, 0) NOT NULL,
CONSTRAINT [CERTIFICATION_PK] PRIMARY KEY CLUSTERED
(
[CERTIFICATION_ID] ASC
)
) ON [PRIMARY]
GO
DECLARE #YEAR_COUNTER int
DECLARE #YEAR_INC int
SET #YEAR_COUNTER = 2014
SET #YEAR_INC = 1
WHILE #YEAR_COUNTER <= 2018
BEGIN
INSERT INTO [dbo].[CERTIFICATION] (CLIENT_ID, DATE_RECEIVED, CERTIFICATION_YEAR)
VALUES (1, DATEADD(year, #YEAR_INC, '1-02-2014'),#YEAR_COUNTER)
INSERT INTO [dbo].[CERTIFICATION] (CLIENT_ID, DATE_RECEIVED, CERTIFICATION_YEAR)
VALUES (2, DATEADD(year, #YEAR_INC, '1-07-2015'),#YEAR_COUNTER+1)
INSERT INTO [dbo].[CERTIFICATION] (CLIENT_ID, DATE_RECEIVED, CERTIFICATION_YEAR)
VALUES (3, DATEADD(year, #YEAR_INC, '1-10-2016'),#YEAR_COUNTER+2)
SET #YEAR_COUNTER = #YEAR_COUNTER + 1
SET #YEAR_INC = #YEAR_INC + 1
END
GO
INSERT INTO [dbo].[CERTIFICATION] (CLIENT_ID, DATE_RECEIVED, CERTIFICATION_YEAR)
VALUES (1, '2-2-2020',2013)
GO
The below query will trigger an error. If the third column of the query is removed, the query will work but it will only retrieve the last year for which a certification was submitted.
SELECT CERTIFICATION_ID, MAX(CERTIFICATION_YEAR) AS LAST_CERTIFICATION_YEAR,
(SELECT DATE_RECEIVED
FROM dbo.CERTIFICATION AS CERT
WHERE (CLIENT_ID = dbo.CERTIFICATION.CLIENT_ID) AND (CERTIFICATION_YEAR = dbo.CERTIFICATION.CERTIFICATION_YEAR)) AS LAST_CERTIFICATION_DATE
FROM dbo.CERTIFICATION
GROUP BY CERTIFICATION_ID
I believe you want a single query like this one:
SELECT
C.CLIENT_ID,
C.DATE_RECEIVED,
C.CERTIFICATION_YEAR
FROM
dbo.CERTIFICATION C
WHERE
C.CERTIFICATION_YEAR =
(SELECT MAX(D.CERTIFICATION_YEAR) FROM dbo.CERTIFICATION D WHERE C.CLIENT_ID = D.CLIENT_ID)

how to get count of days when it not ran on Target date

I have an sample data like this :
DECLARE #T Table (ID INT, Name VARCHAR(10), DOB DATE)
INSERT INTO #T (ID,Name,DOB) VALUES (1,'Mohan','2016-11-11')
INSERT INTO #T (ID,Name,DOB) VALUES (2,'Raj','2016-11-07')
INSERT INTO #T (ID,Name,DOB) VALUES (3,'Manny','2016-10-30')
INSERT INTO #T (ID,Name,DOB) VALUES (4,'kamal','2016-11-01')
INSERT INTO #T (ID,Name,DOB) VALUES (5,'Raj','2016-11-08')
INSERT INTO #T (ID,Name,DOB) VALUES (6,'Manasa','2016-11-10')
My question is when I run the query on this table on Sunday (i.e 06/11/2016)
For example :
Select Count(*), Cnt
from #T /* how to write logic for missing days */
My output :
Cnt Days
6 0 Days
Same thing when I run it on Thursday (i.e 06/11/2016)
Cnt Days
6 4 Days
How I need to get this one. Every Sunday it will run if it ran on Saturday it should show 6 days and Sunday to Sunday calculation.
Please suggest some way of doing this - I'm unable to move forward
To get the number of days since Sunday, you can use the DATEPART function to get the day of the week as an integer with Sunday = 1, Saturday = 7. So for this case:
SELECT COUNT(*), DATEPART(WEEKDAY, GETDATE()) - 1
FROM #T

Find maximum and minimum days between several dates in SQL

I want to find max. and min. days between several records in my table. For example, in the following table I would like to have max. and min. days due to DATE field for each ID.
I'm using MS-SQL 2013
I know that there is dateiff to finding days between two dates but now, I want to find maximum and minimum days between several dates.
ID DATE
10 2016/01/13
10 2016/01/10
10 2016/11/01
10 2015/12/28
11 2015/12/11
11 2016/02/01
11 2015/01/01
Now, how can I find max. and min. days between DATEs for each ID?
Can you please help me to have the query in SQL?
This solution is a bit ugly (using two subqueries) but should get you started:
CREATE TABLE #DataTable (id INT, [date] DATETIME)
INSERT INTO #DataTable (id, [date])
VALUES (10, '20160113')
,(10, '20160110')
,(10, '20161101')
,(10, '20151211')
,(11, '20151211')
,(11, '20160201')
,(11, '20150101')
SELECT
id
, MIN([days]) AS mindays
, MAX([days]) AS maxdays
FROM (
SELECT
id
, DATEDIFF(DAY, [date], (SELECT MIN([date]) FROM #DataTable AS D1 WHERE D1.id = #DataTable.id AND D1.[date] > #DataTable.[date])) AS [days]
FROM #DataTable
) AS t
GROUP BY id
ORDER BY id
Easiest way to understand is by starting at the middle query (which can be run on its own). It delivers for each row the id, and the number of days between the date in the row and the next higher one from the same id.
The outer query is then a simple MIN MAX GROUP BY.
Ok, I've re-read your answer, are you looking for something like below;
Creating temp table and inserting data;
CREATE TABLE #DataTable (ID int, DATE DateTime)
INSERT INTO #DataTable (ID, DATE)
VALUES
(10, '2016-01-13')
,(10, '2016-01-10')
,(10, '2016-11-01')
,(10, '2015-12-11')
,(11, '2015-12-11')
,(11, '2016-02-01')
,(11, '2015-01-01')
Select statement to retrieve data;
DECLARE #StartDate DateTime; SET #StartDate = '2015-12-01'
DECLARE #EndDate DateTime; SET #EndDate = '2016-12-01'
SELECT
a.ID
,MIN(DATE) FirstDate
,MAX(DATE) LastDate
,DATEDIFF(day, MIN(DATE), MAX(DATE)) DayDiff
FROM #DataTable a
WHERE a.DATE BETWEEN #StartDate AND #EndDate
GROUP BY a.ID
You can remove the fields FirstDate and LastDate, this is just to show the dates that are being compared. You'll return a 0 value if there is only one date between your variable dates so you may have to account for this. Also probably a good idea to put something to check for a NULL.
Have you tried this:
Select ID, Min(Date), Max(Date) From MyTable Group By ID

Returning a set of the most recent rows from a table

I'm trying to retrieve the latest set of rows from a source table containing a foreign key, a date and other fields present. A sample set of data could be:
create table #tmp (primaryId int, foreignKeyId int, startDate datetime,
otherfield varchar(50))
insert into #tmp values (1, 1, '1 jan 2010', 'test 1')
insert into #tmp values (2, 1, '1 jan 2011', 'test 2')
insert into #tmp values (3, 2, '1 jan 2013', 'test 3')
insert into #tmp values (4, 2, '1 jan 2012', 'test 4')
The form of data that I'm hoping to retrieve is:
foreignKeyId maxStartDate otherfield
------------ ----------------------- -------------------------------------------
1 2011-01-01 00:00:00.000 test 2
2 2013-01-01 00:00:00.000 test 3
That is, just one row per foreignKeyId showing the latest start date and associated other fields - the primaryId is irrelevant.
I've managed to come up with:
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate
from #tmp
group by foreignKeyId
) s
on t.foreignKeyId = s.foreignKeyId and s.maxStartDate = t.startDate
but (a) this uses inner queries, which I suspect may lead to performance issues, and (b) it gives repeated rows if two rows in the original table have the same foreignKeyId and startDate.
Is there a query that will return just the first match for each foreign key and start date?
Depending on your sql server version, try the following:
select *
from (
select *, rnum = ROW_NUMBER() over (
partition by #tmp.foreignKeyId
order by #tmp.startDate desc)
from #tmp
) t
where t.rnum = 1
If you wanted to fix your attempt as opposed to re-engineering it then
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate, max(PrimaryId) as Latest
from #tmp
group by foreignKeyId
) s
on t.primaryId = s.latest
would have done the job, assuming PrimaryID increases over time.
Qualms about inner query would have been laid to rest as well assuming some indexes.

SQL count exposure of life time by age

(Using SQL Server 2008)
I need some help visualizing a solution. Let's say I have the following simple table for members of a pension scheme:
[Date of Birth] [Date Joined] [Date Left]
1970/06/1 2003/01/01 2007/03/01
I need to calculate the number of lives in each age group from 2000 to 2009.
NOTE: "Age" is defined as "age last birthday" (or "ALB") on 1 January of each of those yeasrs. e.g. if you are exactly 41.35 or 41.77 etc. years old on 1/1/2009 then you would be ALB 41.
So if the record above were the only entry in the database, then the output would be something like:
[Year] [Age ] [Number of Lives]
2003 32 1
2004 33 1
2005 34 1
2006 35 1
2007 36 1
(For 2000, 2001, 2002, 2008 and 2009 there are no lives on file since the sole member only joined on 1/1/2003 and left on 1/3/2007)
I hope I am making myself clear enough.
Anyone have any suggestions?
Thanks, Karl
[EDIT]
Adding another layer to the problem:
What if I had:
[Date of Birth] [Date Joined] [Date Left] [Gender] [Pension Value]
1970/06/1 2003/01/01 2007/03/01 'M' 100,000
and I want the output to be:
[Year] [Age ] [Gender] sum([Pension Value]) [Number of Lives]
2003 32 M 100,000 1
2004 33 M 100,000 1
2005 34 M 100,000 1
2006 35 M 100,000 1
2007 36 M 100,000 1
Any ideas?
WITH years AS
(
SELECT 1900 AS y
UNION ALL
SELECT y + 1
FROM years
WHERE y < YEAR(GETDATE())
),
agg AS
(
SELECT YEAR(Dob) AS Yob, YEAR(DJoined) AS YJoined, YEAR(DLeft) AS YLeft
FROM mytable
)
SELECT y, y - Yob, COUNT(*)
FROM agg
JOIN years
ON y BETWEEN YJoined AND YLeft
GROUP BY
y, y - Yob
OPTION (MAXRECURSION 0)
People born on same year always have the same age in your model
That's why if they go at all, they always go into one group and you just need to generate one row per year for the period they stay in the program.
You can try something like this
DECLARE #Table TABLE(
[Date of Birth] DATETIME,
[Date Joined] DATETIME,
[Date Left] DATETIME
)
INSERT INTO #Table ([Date of Birth],[Date Joined],[Date Left]) SELECT '01 Jun 1970', '01 Jan 2003', '01 Mar 2007'
INSERT INTO #Table ([Date of Birth],[Date Joined],[Date Left]) SELECT '01 Jun 1979', '01 Jan 2002', '01 Mar 2008'
DECLARE #StartYear INT,
#EndYear INT
SELECT #StartYear = 2000,
#EndYear = 2009
;WITH sel AS(
SELECT #StartYear YearVal
UNION ALL
SELECT YearVal + 1
FROM sel
WHERE YearVal < #EndYear
)
SELECT YearVal AS [Year],
COUNT(Age) [Number of Lives]
FROM (
SELECT YearVal,
YearVal - DATEPART(yy, [Date of Birth]) - 1 Age
FROM sel LEFT JOIN
#Table ON DATEPART(yy, [Date Joined]) <= sel.YearVal
AND DATEPART(yy, [Date Left]) >= sel.YearVal
) Sub
GROUP BY YearVal
Try the following sample query
SET NOCOUNT ON
Declare #PersonTable as Table
(
PersonId Integer,
DateofBirth DateTime,
DateJoined DateTime,
DateLeft DateTime
)
INSERT INTO #PersonTable Values
(1, '1970/06/10', '2003/01/01', '2007/03/01'),
(1, '1970/07/11', '2003/01/01', '2007/03/01'),
(1, '1970/03/12', '2003/01/01', '2007/03/01'),
(1, '1973/07/13', '2003/01/01', '2007/03/01'),
(1, '1972/06/14', '2003/01/01', '2007/03/01')
Declare #YearTable as Table
(
YearId Integer,
StartOfYear DateTime
)
insert into #YearTable Values
(1, '1/1/2000'),
(1, '1/1/2001'),
(1, '1/1/2002'),
(1, '1/1/2003'),
(1, '1/1/2004'),
(1, '1/1/2005'),
(1, '1/1/2006'),
(1, '1/1/2007'),
(1, '1/1/2008'),
(1, '1/1/2009')
;WITH AgeTable AS
(
select StartOfYear, DATEDIFF (YYYY, DateOfBirth, StartOfYear) Age
from #PersonTable
Cross join #YearTable
)
SELECT StartOfYear, Age, COUNT (1) NumIndividuals
FROM AgeTable
GROUP BY StartOfYear, Age
ORDER BY StartOfYear, Age
First some preparation to have something to test with:
CREATE TABLE People (
ID int PRIMARY KEY
,[Name] varchar(50)
,DateOfBirth datetime
,DateJoined datetime
,DateLeft datetime
)
go
-- some data to test with
INSERT INTO dbo.People
VALUES
(1, 'Bob', '1961-04-02', '1999-01-01', '2007-05-07')
,(2, 'Sadra', '1960-07-11', '1999-01-01', '2008-05-07')
,(3, 'Joe', '1961-09-25', '1999-01-01', '2009-02-11')
go
-- helper table to hold years
CREATE TABLE dimYear (
CalendarYear int PRIMARY KEY
)
go
-- fill-in years for report
DECLARE
#yr int
,#StartYear int
,#EndYear int
SET #StartYear = 2000
SET #EndYear = 2009
SET #yr = #StartYear
WHILE #yr <= #EndYear
BEGIN
INSERT INTO dimYear (CalendarYear) values(#yr)
SET #yr =#yr+1
END
-- show test data and year tables
select * from dbo.People
select * from dbo.dimYear
go
Then a function to return person's age for each year, if the person is still an active member.
-- returns [CalendarYear], [Age] for a member, if still active member in that year
CREATE FUNCTION dbo.MemberAge(#DateOfBirth datetime, #DateLeft datetime)
RETURNS TABLE
AS
RETURN (
SELECT
CalendarYear,
CASE
WHEN DATEDIFF(dd, cast(CalendarYear AS varchar(4)) + '-01-01',#DateLeft) > 0
THEN DATEDIFF(yy, #DateOfBirth, cast(CalendarYear AS varchar(4)) + '-01-01')
ELSE -1
END AS Age
FROM dimYear
);
go
And the final query:
SELECT
a.CalendarYear AS "Year"
,a.Age AS "Age"
,count(*) AS "Number Of Lives"
FROM
dbo.People AS p
CROSS APPLY dbo.MemberAge(p.DateOfBirth, p.DateLeft) AS a
WHERE a.Age > 0
GROUP BY a.CalendarYear, a.Age
Deal with this in pieces (some random thoughts) - create views to test you dev steps if you can:
ALB - do a query that, for a given year, gives you your memeber's ALB
Member in year - another bit of query that tell you whether a member was a member in a given year
Put those two together and you should be able to create a query that says whether a person was a member in a given year and what their ALB was for that year.
Hmm, tricky - following this chain of thought what you'd then want to do is generate a table that has all the years the person was a member and their ALB in that year (and a unique id)
From 4. select year, alb, count(id) group by year, alb
I'm not sure I'm going in the right direction from about 3 though it should work.
You may find a (temporary) table of years helpful - joining things to a table of dates makes all kinds of things possible.
Not really an answer, but certainly some direction...