So I am learning about TSQL and have been given an assignment to do an App that details stuff about a certain series of movies. One of the things I would like to add to the finished product is a DATEDIFF where I have the birthdate of the main actor and the Premiere date of the movie but would like to return the age of the actor at the time of the Premiere.
My question is, HOW do I specify the dates, given a DATEDIFF statement if those dates already exist in a table?
Examples of the tables follow.
Bond
BondID FirstName LastName Birthdate
1 Sean Connery 8/25/1930
2 George Lazenby 9/5/1939
3 Roger Moore 10/14/1927
4 Timothy Dalton 3/21/1946
5 Pierce Brosnan 5/16/1953
6 Daniel Craig 3/2/1968
Film
ID BondID FilmTitle Premiere
1 1 Dr. No 1963-05-08
2 1 From Russia With Love 1974-04-08
3 1 Goldfinger 1964-12-22
4 1 Thunderball 1965-12-29
Here is what I have presently.
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle
FROM Bond b INNER JOIN FilmID f ON b.BondID = f.BondID
WHERE b.BondID = 1
SELECT DATEDIFF(YEAR, GETDATE(), GETDATE()) AS "YearDif"
WHERE GETDATE() = Bond.Birthdate AND GETDATE() = f.Premiere
Data
Birthdate Premiere FirstName LastName FilmTitle
1 1930-08-25 1963-05-08 Sean Connery Dr. No
2 1930-08-25 1974-04-08 Sean Connery From Russia With Love
3 1930-08-25 1964-12-22 Sean Connery Goldfinger
4 1930-08-25 1965-12-29 Sean Connery Thunderball
I would like to add a column that states the age of the actor at the time of the premiere...I think I have a handle on that one... But...
I get that the second part of the DATEDIFF part isn't correct, but how do I tell the DATEDIFF where to get the date values?
Any help would be wonderous!
Your query:
SELECT DATEDIFF(YEAR, GETDATE(), GETDATE()) AS "YearDif"
WHERE GETDATE() = Bond.Birthdate AND GETDATE() = f.Premiere
will not work for what you want. GETDATE() will only return today's date so in essence the above will only return a row if the birthdate and the premiere were today at the very moment you ran the query.
What you need to do is is the QUERY section below:
SAMPLE DATA:
IF OBJECT_ID('tempdb..#Bond') IS NOT NULL
DROP TABLE #Bond
CREATE TABLE #Bond
(BondID INT ,FirstName VARCHAR(20),LastName VARCHAR(20),Birthdate DATE)
INSERT INTO #Bond
VALUES
(1 ,'Sean' ,'Connery' ,'8/25/1930')
,(2 ,'George' ,'Lazenby' ,'9/5/1939')
,(3 ,'Roger' ,'Moore' ,'10/14/1927')
,(4 ,'Timothy' ,'Dalton' ,'3/21/1946')
,(5 ,'Pierce' ,'Brosnan' ,'5/16/1953')
,(6 ,'Daniel' ,'Craig' ,'3/2/1968')
IF OBJECT_ID('tempdb..#Film') IS NOT NULL
DROP TABLE #Film
CREATE TABLE #Film
(ID INT , BondID INT , FilmTitle VARCHAR(50), Premiere DATE)
INSERT INTO #Film
VALUES (1 , 1,'Dr. No' ,'1963-05-08')
,(2 , 1,'From Russia With Love','1974-04-08')
,(3 , 1,'Goldfinger' ,'1964-12-22')
,(4 , 1,'Thunderball' ,'1965-12-29')
QUERY:
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle , DATEDIFF(YEAR, b.Birthdate, Premiere) AS AgeOfActor
FROM #Bond b INNER JOIN #Film f ON b.BondID = f.BondID
RESULT:
RESPONSE TO COMMENT:
you can always count the days and then devide by 365:
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle , DATEDIFF(day, b.Birthdate, Premiere)/365 AS AgeOfActor
FROM #Bond b INNER JOIN #Film f ON b.BondID = f.BondID
or for more accuracy you can do it in seconds :P
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle , DATEDIFF(second, b.Birthdate, Premiere)/(365.25*24*60*60) AS AgeOfActor
FROM #Bond b INNER JOIN #Film f ON b.BondID = f.BondID
Such things can be done in SQL in just one step. There is no need to store the intermediate results anywhere to pass them later on.
You just need to pass your column values from the table into the function call like that:
SELECT
b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle,
DATEDIFF(YEAR, b.Birthdate, f.Premiere) AS actor_age_at_premiere
FROM
Bond b
INNER JOIN FilmID f
ON b.BondID = f.BondID
Assuming that an actor was born before the premiere date of a film he/she played in (which is a fair assumption) the result will be a positive Integer.
If you'd like to include the actor age as of now, then your third argument would be GETDATE() instead of Premiere column:
DATEDIFF(Year, b.Birthdate, GETDATE()) AS actor_age_current
Related
I have an sql table that has transaction history of all the clients. I want to find what is the average difference in time between two transactions.
ClientCode Date
DL2xxx 2016-04-18 00:00:00.000
DL2xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
JDZxxx 2016-04-18 00:00:00.000
Given above are the first few lines of the table the date given is the date transaction happened. I want to take an average of difference in days when successive transactions happen. Say for a client he makes transactions of Day 1, Day 3, Day 10, and Day 15. So differences are {2, 7, 5} average of which is 4.66. If only one transaction takes place this should be 0.
ClientCode AverageDays
DL2xxx <float_value>
DL2xxx <float_value>
E19xxx <float_value>
This is what the output should look like where each unique client code occurs only once.
You can use a query like below if you table name is T
see live demo
select
ClientCode,
AvgDays =ISNULL(AVG(d),0)
from
(
select
*,
d=DATEDIFF(
d,
dateofT,
LEAD(DateofT) over(
partition by ClientCode
order by DateofT asc ))
from t
)t
group by ClientCode
If Windowing functions aren't available to you, here's an alternative
--CREATE SAMPLE DATA
CREATE TABLE #TMP(ClientID INT, EventDate DATE)
GO
INSERT INTO #TMP VALUES
(1,DATEADD(DD,RAND()*365,'20180101'))
,(2,DATEADD(DD,RAND()*365,'20180101'))
,(3,DATEADD(DD,RAND()*365,'20180101'))
,(4,DATEADD(DD,RAND()*365,'20180101'))
,(5,DATEADD(DD,RAND()*365,'20180101'))
GO 50
--PRE SQL 2012 Compatible
SELECT A.ClientID
,AVG(DATEDIFF(DD,C.EventDate,A.Eventdate)) AS ClientAvg
FROM #TMP A
CROSS APPLY (SELECT ClientID, MAX(EventDate) EventDate FROM #TMP B
WHERE A.ClientID = B.ClientID AND A.EventDate > B.EventDate
GROUP BY ClientID) C
GROUP BY A.ClientID
ORDER BY A.ClientID
You can use LAG() function to compare a date to it's previous date by client, then group by client and calculate the average.
IF OBJECT_ID('tempdb..#Transactions') IS NOT NULL
DROP TABLE #Transactions
CREATE TABLE #Transactions (
ClientCode VARCHAR(100),
Date DATE)
INSERT INTO #Transactions (
ClientCode,
Date)
VALUES
('DL2', '2016-04-18'),
('DL2', '2016-04-19'),
('DL2', '2016-04-26'),
('E19', '2016-01-01'),
('E19', '2016-01-11'),
('E19', '2016-01-12')
;WITH DayDifferences AS
(
SELECT
T.ClientCode,
T.Date,
DayDifference = DATEDIFF(
DAY,
LAG(T.Date) OVER (PARTITION BY T.ClientCode ORDER BY T.Date ASC),
T.Date)
FROM
#Transactions AS T
)
SELECT
D.ClientCode,
AverageDayDifference = AVG(ISNULL(CONVERT(FLOAT, D.DayDifference), 0))
FROM
DayDifferences AS D
GROUP BY
D.ClientCode
Using the observation that the sum of differences within a group is simply the max - min of that group, you can use the simple group by select:
select IIF(COUNT(*) > 1,
(CAST(DATEDIFF(day, MIN(DateofT), MAX(DateofT)) AS FLOAT)) / (COUNT(*) - 1), 0.0)
AS AVGDays, ClientCode
FROM t GROUP BY ClientCode
I am editing this to clarify my question.
Let's say I have a table that holds patient information. I need to find new patients for this year, and the date of their prescription first prescription when they were considered new. Anytime there is a six month gap they are considered a new patient.
How do I accomplish this using SQL. I can do this in Java and any other imperative language easily enough, but I am having problems doing this in SQL. I need this script to be run in Crystal by non-SQL users
Table:
Patient ID Prescription Date
-----------------------------------------
1 12/31/16
1 03/13/17
2 10/10/16
2 05/11/17
2 06/11/17
3 01/01/17
3 04/20/17
4 01/31/16
4 01/01/17
4 07/02/17
So Patients 2 and 4 are considered new patients. Patient 4 is considered a new patient twice, so I need dates for each time patient 4 was considered new 1/1/17 and 7/2/17. Patients 1 and 3 are not considered new this year.
So far I have the code below which tells me if they are new this year, but not if they had another six month gap this year.
SELECT DISTINCT
this_year.patient_id
,this_year.date
FROM (SELECT
patient_id
,MIN(prescription_date) as date
FROM table
WHERE prescription_date BETWEEN '2017-01-01 00:00:00.000' AND '2017-
12-31 00:00:00.000'
GROUP BY [patient_id]) AS this_year
LEFT JOIN (SELECT
patient_id
,MAX(prescription_date) as date
FROM table
WHERE prescription_date BETWEEN '2016-01-01 00:00:00.000' AND '2016-
12-31 00:00:00.000'
GROUP BY [patient_id]) AS last_year
WHERE DATEDIFF(month, last_year.date, this_year.date) > 6
OR last_year.date IS NULL
Patient 2 in your example does not meet the criteria you specified ... that being said ...
You can try something like this ... untested but should be similar (assuming you can put this in a stored procedure):
WITH ordered AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY [Prescription Date]) rn
FROM table1
)
SELECT o1.[PatientID], DATEDIFF(s, o1.[Prescription Date], o2.[Prescription Date]) diff
FROM ordered o1 JOIN ordered o2
ON o1.rn + 1 = o2.rn
WHERE DATEDIFF(m, o1.[Prescription Date], o2.[Prescription Date]) > 6
Replace table1 with the name of your table.
I assume that you mean the patient has not been prescribed in the last 6 months.
SELECT DISTINCT user_id
FROM table_name
WHERE prescribed_date >= DATEADD(month, -6, GETDATE())
This gives you the list of users that have been prescribed in the last 6 months. You want the list of users that are not in this list.
SELECT DISTINCT user_id
FROM table_name
WHERE user_id NOT IN (SELECT DISTINCT user_id
FROM table_name
WHERE prescribed_date >= DATEADD(month, -6, GETDATE()))
You'll need to amend the field and table names.
I am trying to grab a series of dates and the corresponding values (if any) that exist in my database.
I have two parameters - today (date using getDate()) - and a number of days (integer). For this example, I'm using the value 10 for the days.
Code to get the sequential dates for 10 days after today:
SELECT top 10 DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(getDate(),'-','')) as Alldays
FROM sys.all_objects
I now need to look up several values for each day in the sequential days code, which may or may not exist in the time table (we assume 8 hours for all dates, unless otherwise specified). The lookup would be on the field recordDateTime. If no "hours" value exists in the table cap_time for that date, I need to return a default value of 8 as the number of hours. Here's the base query:
SELECT u.FullName as UserName, d2.department,
recordDateTime, ISNULL(hours,8) as hours
FROM cap_time c
left join user u on c.userID = u.userid
left join dept d2 on u.deptID = d2.DeptID
WHERE c.userid = 38 AND u.deptID = 1
My end result for the next 10 days should be something like:
Date (sequential), Department, UserName, Number of Hours
I can accomplish this using TSQL and a temp table, but I'd like to see if this can be done in a single statement. Any help is appreciated.
Without any DDL or sample data it's hard to determine exactly what you need.
I think this will get you pretty close (note my comments):
-- sample data
------------------------------------------------------------------------------------------
DECLARE #table TABLE
(
fullName varchar(10),
department varchar(10),
[hours] tinyint,
somedate date
);
INSERT #table VALUES
('bob', 'sales', 5, getdate()+1),
('Sue', 'marketing', 3, getdate()+2),
('Sue', 'sales', 12, getdate()+4),
('Craig', 'sales', 4, getdate()+8),
('Joe', 'sales', 18, getdate()+9),
('Fred', 'sales', 10, getdate()+10);
--SELECT * FROM #table
;
-- solution
------------------------------------------------------------------------------------------
WITH alldays([day]) AS -- logic to get your dates for a LEFT date table
(
SELECT TOP (10)
CAST(DATEADD
(
DAY,
ROW_NUMBER() OVER (ORDER BY object_id),
getdate()
) AS date)
FROM sys.all_objects
)
SELECT d.[day], t.fullName, department, [hours] = ISNULL([hours], 8)
FROM alldays d
LEFT JOIN #table t ON d.[day] = t.somedate;
Results:
day fullName department hours
---------- ---------- ---------- -----
2017-04-12 bob sales 5
2017-04-13 Sue marketing 3
2017-04-14 NULL NULL 8
2017-04-15 Sue sales 12
2017-04-16 NULL NULL 8
2017-04-17 NULL NULL 8
2017-04-18 NULL NULL 8
2017-04-19 Craig sales 4
2017-04-20 Joe sales 18
2017-04-21 Fred sales 10
Maybe a subquery and the in statement, like:
SELECT u.FullName as UserName, d2.department,
recordDateTime, ISNULL(hours,8) as hours
FROM cap_time c
left join user u on c.userID = u.userid
left join dept d2 on u.deptID = d2.DeptID
WHERE c.userid = 38 AND u.deptID = 1 and recordDateTime in
(SELECT top 10 DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(getDate(),'-','')) as Alldays
FROM sys.all_objects)
There was one other SIMILAR answer but it is 2 pages long and my requirement doesn't need that. I have 2 tables, tableA and a tableB, and I need to find the COUNTS of rows that are present in tableA but are not present in tableB OR if update_on in tableB is not today's date.
My tables:
tableA:
release_id book_name release_begin_date
----------------------------------------------------
1122 midsummer 2016-01-01
1123 fool's errand 2016-06-01
1124 midsummer 2016-04-01
1125 fool's errand 2016-08-01
tableB:
release_id book_name updated_on
-----------------------------------------
1122 midsummer 2016-08-17
1123 fool's errand 2016-08-16**
Expected result: Since each book is missing one release id, 1 is count. But in addition fool's errand's existing row in tableB has updated_on date of yesterday and not today, it needs to be counted in count_of_not_updated.
book_name count_of_missing count_of_not_updated
-------------------------------------------------------
midsummer 1 0
fool's errand 1 1
Note: Even though fool's errand is present in tableB, I need to show it in count_of_missing because it's updated_on date is yesterday and not today. I know it has to be a combination of a left join and something else, but the kicker here is not only getting the missing rows from left table but at the same time checking if the updated_on table was today's date and if not, count that row in count_of_not_updated.
select sum(case when b.release_id is null then 1 else 0 end) as noReleaseID
, sum(case when datediff(d, b.release_date, getdate()) > 0 then 1 else 0 end) as releaseDateNotToday
, a.release_id
from tableA a
left outer join tableB b on a.release_id = b.release_id
Group by a.release_id
This example uses a sum function on a case statement to add up the instances where the case statement returns true. Note that the current code assumes, as in your example, that you are looking to count all old release dates from table b - more steps would be required if each book has multiple old release dates in table b, and you only want to compare to the most recent release date.
Try this
DECLARE #tableA TABLE (release_id INT, book_name NVARCHAR(50), release_begin_date DATETIME)
DECLARE #tableB TABLE (release_id INT, book_name NVARCHAR(50), updated_on DATETIME)
INSERT INTO #tableA
VALUES
(1122, 'midsummer', '2016-01-01'),
(1123, 'fool''s errand', '2016-06-01'),
(1124, 'midsummer', '2016-04-01'),
(1125, 'fool''s errand', '2016-08-01')
INSERT INTO #tableB
VALUES
(1122, 'midsummer', '2016-08-17'),
(1123, 'fool''s errand', '2016-08-16')
;WITH TmpTableA
AS
(
SELECT
book_name,
COUNT(1) CountOfTableA
FROM
#tableA
GROUP BY
book_name
), TmpTableB
AS
(
SELECT
book_name,
COUNT(1) CountOfTableB,
SUM(CASE WHEN CONVERT(VARCHAR(11), updated_on, 112) = CONVERT(VARCHAR(11), GETDATE(), 112) THEN 0 ELSE 1 END) count_of_not_updated
FROM
#tableB
GROUP BY
book_name
)
SELECT
A.book_name ,
A.CountOfTableA - ISNULL(B.CountOfTableB, 0) AS count_of_missing,
ISNULL(B.count_of_not_updated, 0) AS count_of_not_updated
FROM
TmpTableA A LEFT JOIN
TmpTableB B ON A.book_name = B.book_name
Result:
book_name count_of_missing count_of_not_updated
-------------------- ---------------- --------------------
fool's errand 1 1
midsummer 1 1
Can someone steer me in the right direction for solving this issue with a set-based solution versus cursor-based?
Given a table with the following rows:
Date Value
2013-11-01 12
2013-11-12 15
2013-11-21 13
2013-12-01 0
I need a query that will give me a row for each date between 2013-11-1 and 2013-12-1, as follows:
2013-11-01 12
2013-11-02 12
2013-11-03 12
...
2013-11-12 15
2013-11-13 15
2013-11-14 15
...
2013-11-21 13
2013-11-21 13
...
2013-11-30 13
2013-11-31 13
Any advice and/or direction will be appreciated.
The first thing that came to my mind was to fill in the missing dates by looking at the day of the year. You can do this by joining to the spt_values table in the master DB and adding the number to the first day of the year.
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
SELECT
DateAdd(D, v.number, MinDate) Date
FROM (SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
Next I would wrap that to make a derived table, and add a subquery to get the most recent number. Your end result may look something like:
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
-- Uncomment the following line to see how it behaves when the date range spans a year end
--UPDATE #Table SET ADate = DateAdd(d, 45, ADate)
SELECT
AllDates.Date
,(SELECT TOP 1 ANumber FROM #Table t WHERE t.ADate <= AllDates.Date ORDER BY ADate DESC)
FROM (
SELECT
DateAdd(D, v.number, MinDate) Date
FROM
(SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
) AllDates
Another solution, not sure how it compares to the two already posted performance wise but it's a bit more concise:
Uses a numbers table:
Linky
Query:
DECLARE #SDATE DATETIME
DECLARE #EDATE DATETIME
DECLARE #DAYS INT
SET #SDATE = '2013-11-01'
SET #EDATE = '2013-11-29'
SET #DAYS = DATEDIFF(DAY,#SDATE, #EDATE)
SELECT Num, DATEADD(DAY,N.Num,#SDATE), SUB.[Value]
FROM Numbers N
LEFT JOIN MyTable M ON DATEADD(DAY,N.Num,#SDATE) = M.[Date]
CROSS APPLY (SELECT TOP 1 [Value]
FROM MyTable M2
WHERE [Date] <= DATEADD(DAY,N.Num,#SDATE)
ORDER BY [Date] DESC) SUB
WHERE N.Num <= #DAYS
--
SQL Fiddle
It's possible, but neither pretty nor very performant at scale:
In addition to your_table, you'll need to create a second table/view dates containing every date you'd ever like to appear in the output of this query. For your example it would need to contain at least 2013-11-01 through 2013-12-01.
SELECT m.date, y.value
FROM your_table y
INNER JOIN (
SELECT md.date, MAX(my.date) AS max_date
FROM dates md
INNER JOIN your_table my ON md.date >= my.date
GROUP BY md.date
) m
ON y.date = m.max_date