How do I use a UDF with a select statement? - sql-server

I created a scalar user defined function that will accept a date of birth and return the age a person will be in 15 years from now.
CREATE FUNCTION AgeIn15 (#DateOfBirth DATE)
RETURNS INT
AS
BEGIN
RETURN Convert(INT, DateAdd(year, 15, DateDiff(year, #DateOfBirth, GetDate())))
END
Now I want to Use the UDF created to show the age of all persons in my table marketing_list along with their original information. How do I do this? I tried this but I got an error
SELECT *
FROM marketing_list AgeIn15(marketing_list.DateOfBirth)
SELECT *
,AgeIn15(marketing_list.Date_of_Birth)
FROM marketing_list
WHERE AgeIn15(Date_of_Birth) > 45

Add schema name:
SELECT *, dbo.AgeIn15(m.Date_of_Birth) AS col_name
FROM marketing_list AS m;
SELECT *
,dbo.AgeIn15(m.Date_of_Birth) AS col_name
FROM marketing_list m
WHERE dbo.AgeIn15(m.Date_of_Birth) > 45
Optionally you can use CROSS APPLY:
Select *
from marketing_list AS m
CROSS APPLY (SELECT dbo.AgeIn15(m.Date_of_Birth)) AS c(col_name)
Demo
SELECT *, dbo.AgeIn15(t.c) AS col_name
FROM (SELECT '2000-01-01') AS t(c);
SELECT *
FROM (SELECT '2000-01-01') AS t(c)
CROSS APPLY (SELECT dbo.AgeIn15(t.c)) AS c(col_name)
Your function should looks like:
CREATE FUNCTION dbo.AgeIn15 (#DateOfBirth DATE)
RETURNS INTEGER
AS
BEGIN
RETURN DATEDIFF(year,#DateOfBirth,GETDATE()) + 15
END
What you did doesn't make any sense:
Convert(integer,DateAdd(year,15,DateDiff(year,#DateOfBirth,GetDate())))
Calculate DATEDIFF - it returns number years OK
DATEADD(year, 15, number) - it returns date 15 years + of 1900-01-number FAIL
CONVERT(INTEGER, DATE) - it returns number of days starting at 1900-01-01 FAIL

CREATE FUNCTION AgeIn15 (#DateOfBirth DATE)
RETURNS INT
AS
BEGIN
RETURN
-- get the year today + 15 years
(
(DATEPART(YEAR, GETDATE() ) + 15) -
-- minus the year of date of birth
DATEPART(YEAR,#DateOfBirth)
)
-- its like saying (2015 + 15)-09-29 - (1990)-09-19 = 40
END
SELECT *
, dbo.AgeIn15(mlist.Date_of_Birth) AS [AgeAfterFifteenYears]
FROM marketing_list AS mlist
hopefully this helps

Related

Is it possible to use the SQL DATEADD function but exclude dates from a table in the calculation?

Is it possible to use the DATEADD function but exclude dates from a table?
We already have a table with all dates we need to exclude. Basically, I need to add number of days to a date but exclude dates within a table.
Example: Add 5 days to 01/08/2021. Dates 03/08/2021 and 04/08/2021 exist in the exclusion table. So, resultant date should be: 08/08/2021.
Thank you
A bit of a "wonky" solution, but it works. Firstly we use a tally to create a Calendar table of dates, that exclude your dates in the table, then we get the nth row, where n is the number of days to add:
DECLARE #DaysToAdd int = 5,
#StartDate date = '20210801';
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT 0 AS I
UNION ALL
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3), --Up to 1,000
Calendar AS(
SELECT DATEADD(DAY,T.I, #StartDate) AS D,
ROW_NUMBER() OVER (ORDER BY T.I) AS I
FROM Tally T
WHERE NOT EXISTS (SELECT 1
FROM dbo.DatesTable DT
WHERE DT.YourDate = DATEADD(DAY,T.I, #StartDate)))
SELECT D
FROM Calendar
WHERE I = #DaysToAdd+1;
A best solution is probably a calendar table.
But if you're willing to traverse through every date, then a recursive CTE can work. It would require tracking the total iterations and another column to substract if any traversed date was in the table. The exit condition uses the total difference.
An example dataset would be:
CREATE TABLE mytable(mydate date); INSERT INTO mytable VALUES ('20210803'), ('20210804');
And an example function run in it's own batch:
ALTER FUNCTION dbo.fn_getDays (#mydate date, #daysadd int)
RETURNS date
AS
BEGIN
DECLARE #newdate date;
WITH CTE(num, diff, mydate) AS (
SELECT 0 AS [num]
,0 AS [diff]
,DATEADD(DAY, 0, #mydate) [mydate]
UNION ALL
SELECT num + 1 AS [num]
,CTE.diff +
CASE WHEN DATEADD(DAY, num+1, #mydate) IN (SELECT mydate FROM mytable)
THEN 0 ELSE 1 END
AS [diff]
,DATEADD(DAY, num+1, #mydate) [mydate]
FROM CTE
WHERE (CTE.diff +
CASE WHEN DATEADD(DAY, num+1, #mydate) IN (SELECT mydate FROM mytable)
THEN 0 ELSE 1 END) <= #daysadd
)
SELECT #newdate = (SELECT MAX(mydate) AS [mydate] FROM CTE);
RETURN #newdate;
END
Running the function:
SELECT dbo.fn_getDays('20210801', 5)
Produces output, which is the MAX(mydate) from the function:
----------
2021-08-08
For reference the MAX(mydate) is taken from this dataset:
n diff mydate
----------- ----------- ----------
0 0 2021-08-01
1 1 2021-08-02
2 1 2021-08-03
3 1 2021-08-04
4 2 2021-08-05
5 3 2021-08-06
6 4 2021-08-07
7 5 2021-08-08
You can use the IN clause.
To perform the test, I used a W3Schools Test DB
SELECT DATE_ADD(BirthDate, INTERVAL 10 DAY) FROM Employees WHERE FirstName NOT IN (Select FirstName FROM Employees WHERE FirstName LIKE 'N%')
This query shows all the birth dates + 10 days except for the only employee with name starting with N (Nancy)

DATEDIFF across several months grouped by months

I need to get this output at the bottom.
You can achieve this by using MASTER..SPT_VALUES this default SQL Server table which is used for creating VIRTUAL TABLES
DECLARE #T TABLE(
ID INT ,ST_DATE DATE,ED_DATE DATE,DURATION INT)
INSERT INTO #T VALUES(5555,'30-Mar-2020','02-Apr-2020',4)
Declare #T1 Table(
ID INT,WK_MONTH VARCHAR(10))
Insert into #T1 values(5555,'Mar-2020')
Insert into #T1 values(5555,'Apr-2020')
select * from #T
select * from #T1
Main Query:
SELECT T.ID,FORMAT(DATEADD(D, NUMBER, T.ST_DATE),'MMM-yy') as [Work
Month],COUNT(MONTH(DATEADD(D, NUMBER, T.ST_DATE) )) as [Absent Days]
FROM MASTER..SPT_VALUES cross apply #T t
WHERE TYPE = 'P' AND NUMBER BETWEEN 0 AND DATEDIFF(DD, T.ST_DATE, T.ED_DATE)
GROUP BY FORMAT(DATEADD(D, NUMBER, T.ST_DATE),'MMM-yy'),T.ID
Output
ID Work Month Absent Days
5555 Apr-20 2
5555 Mar-20 2
There are a few parts to this, however the first part I am assuming that the work month is a date field on the first of each month. I am also assuming start and end date are date fields.
The code I would use is below. I have used "E" for employee table and "L" for Leave table for easy reading. I have also added comments in the code which should help understand.
-- Assuming the workmonth table is date field set to 1st of each month
SELECT E.EmpID,
E.WorkMonth,
--- Below case statement is when start and end month are the same.
CASE
WHEN(DATEADD(month, DATEDIFF(month, (0), CONVERT([DATETIME], L.StartDate)), (0))) = (DATEADD(month, DATEDIFF(month, (0), CONVERT([DATETIME], L.EndDate)), (0)))
THEN DATEDIFF(day, L.StartDate, L.EndDate) + 1
---- Below case statement is when you want to calculate start to end of month.
WHEN(DATEADD(month, DATEDIFF(month, (0), CONVERT([DATETIME], L.StartDate)), (0))) = E.WorkMonth
THEN DATEDIFF(day, L.StartDate, DATEADD(month, 1, E.WorkMonth))
---- Below case statement is when you want to calculate start of month to end date.
WHEN(DATEADD(month, DATEDIFF(month, (0), CONVERT([DATETIME], L.endDate)), (0))) = E.WorkMonth
THEN DATEDIFF(day, E.WorkMonth, L.EndDate) + 1
END StartMonth
FROM dbo.EmployeeTable AS E
inner JOIN dbo.LeavTable AS L ON E.EmpID = L.EMPID
-- The join is where you can combine the months joining tyo the first of the month.
AND E.WorkMonth >= (DATEADD(month, DATEDIFF(month, (0), CONVERT([DATETIME], L.StartDate)), (0)))
AND E.WorkMonth <= (DAETEADD(month, DATEDIFF(month, (0), CONVERT([DATETIME], L.EndDate)), (0)))

SQL Server- Get Date 6 months in past

Hey how would I get data for a date column that's older than 6 months?
select * from myTable where dateColumn >
Thanks
Use DATEDIFF function.
Read more here: https://msdn.microsoft.com/en-us/library/ms189794.aspx
SELECT *
FROM myTable
WHERE DATEDIFF(MM, dateColumn, GETDATE()) > 6
SELECT * FROM myTable WHERE DATEDIFF(day, NOW(), dateColumn) > 180
select *
from table
where
date_column >=
DATEADD(m, -6, convert(date, convert(varchar(6), getdate(),112) + '01'))
I would use
select * from myTable where dateColumn > DATEADD(mm,GETDATE(),-6)
This way you arent applying a function on the lookup column, which in some cases can result in performance issues
Dateadd is very simple to use.
the first parameter is the interval, m means month, d means day ect. the second parameter is the increment, and the last one is obviously the date
select dateadd(m,6,getdate())
more info here
http://www.w3schools.com/sql/func_dateadd.asp
Once you understand dateadd then you can simply use it in where clause as such
Select * from Table1 where date1 >= dateadd(m,6,date1)

Set based solution for processing rows in a SQL table

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

Convert Date Stored as NUMERIC to DATETIME

I am currently working on a query that needs to calculate the difference in days between two different dates. I've had issues with our DATE columns before, because they are all being stored as numeric columns which is a complete pain.
I tried using CONVERT as I had done in the past to try and get the different pieces of the DATETIME string built, but I am not having any luck.
The commented line --convert(datetime,) is where I am having the issue. Basically, I need to convert PO_DATE and LINE_DOCK_DATE to a format that is usable, so I can calculate the difference between the two in days.
USE BWDW
GO
SELECT
[ITEM_NO]
,[ITEM_DESC]
,[HEADER_DUE_DATE]
,[BWDW].[dbo].[DS_tblDimWhs].WHS_SHORT_NAME AS 'Warehouse'
,[BWDW].[dbo].[DS_tblFactPODtl].[PO_NO] AS 'PO NUMBER'
,[BWDW].[dbo].[DS_tblFactPODtl].[PO_DATE] AS 'Start'
,[BWDW].[dbo].[DS_tblFactPODtl].[PO_STATUS] AS 'Status'
,[BWDW].[dbo].[DS_tblFactPODtl].[LINE_DOCK_DATE] AS 'End'
--,(SELECT CONVERT(DATETIME, CONVERT(CHAR(8), [BWDW].[dbo].[DS_tblFactPODtl].[PO_DATE])) FROM dbo.DS_tblFactPODtl)
FROM [BWDW].[dbo].[DS_tblFactPODtl]
INNER JOIN [BWDW].[dbo].[DS_tblDimWhs] ON [BWDW].[dbo].[DS_tblFactPODtl].WAREHOUSE = [BWDW].[dbo].[DS_tblDimWhs].WAREHOUSE
INNER JOIN [BWDW].[dbo].[DS_tblFactPO] ON [BWDW].[dbo].[DS_tblFactPODtl].PO_NO = [BWDW]. [dbo].[DS_tblFactPO].PO_NO
WHERE [BWDW].[dbo].[DS_tblFactPODtl].[PO_STATUS] = 'Closed'
AND [BWDW].[dbo].[DS_tblFactPODtl].[LINE_DOCK_DATE] <> 0
I have a snippet I saved from a previous project I worked on that needed to only display results from today through another year. That had a bunch of CAST and CONVERTS in it, but I tried the same methodology with no success.
In the long run, I want to add a column to each database table to contain a proper datetime column that is usable in the future... but that is another story. I have read numerous posts on stackoverflow that talk about converting to NUMERIC and such, but nothing out of a NUMERIC back to DATETIME.
Example data:
Start | End | Difference
--------------------------------
20110501 | 20111019 | 171
20120109 | 20120116 | 7
20120404 | 20120911 | 160
Just trying to calculate the difference..
MODIFIED PER AARON:
SELECT
FPODtl.[ITEM_NO] AS [Item]
,FPODtl.[ITEM_DESC] AS [Description]
,D.WHS_SHORT_NAME AS [Warehouse]
,FPODtl.[PO_NO] AS [PO NUMBER]
,FPODtl.[PO_DATE] AS [Start]
,FPODtl.[PO_STATUS] AS [Status]
,FPODtl.[LINE_DOCK_DATE] AS [End]
,DATEDIFF
(
DAY,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.PO_DATE)) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.PO_DATE)) END,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) END
)
FROM [dbo].[DS_tblFactPODtl] AS FPODtl
INNER JOIN [dbo].[DS_tblDimWhs] AS D
ON FPODtl.WAREHOUSE = D.WAREHOUSE
INNER JOIN [dbo].[DS_tblFactPO] AS FPO
ON FPODtl.PO_NO = FPO.PO_NO
WHERE FPODtl.[PO_STATUS] = 'Closed'
AND FPODtl.[LINE_DOCK_DATE] <> 0;
DECLARE #x NUMERIC(10,0);
SET #x = 20110501;
SELECT CONVERT(DATETIME, CONVERT(CHAR(8), #x));
Result:
2011-05-01 00:00:00.000
To compare two:
DECLARE #x NUMERIC(10,0), #y NUMERIC(10,0);
SELECT #x = 20110501, #y = 20111019;
SELECT DATEDIFF
(
DAY,
CONVERT(DATETIME, CONVERT(CHAR(8), #x)),
CONVERT(DATETIME, CONVERT(CHAR(8), #y))
);
Result:
171
More importantly, fix the table. Stop storing dates as numbers. Store them as dates. If you get errors with this conversion, it's because your poor data choice has allowed bad data into the table. You can get around that - potentially - by writing the old version of TRY_CONVERT():
SELECT DATEDIFF
(
DAY,
CASE WHEN ISDATE(col1)=1 THEN CONVERT(DATETIME, col1) END,
CASE WHEN ISDATE(col2)=1 THEN CONVERT(DATETIME, col2) END
)
FROM
(
SELECT
col1 = CONVERT(CHAR(8), col1),
col2 = CONVERT(CHAR(8), col2)
FROM dbo.table
) AS x;
This will produce nulls for any row where there is garbage in either column. Here is a modification to your original query:
SELECT
[ITEM_NO] -- what table does this come from?
,[ITEM_DESC] -- what table does this come from?
,[HEADER_DUE_DATE] -- what table does this come from?
,D.WHS_SHORT_NAME AS [Warehouse] -- don't use single quotes for aliases!
,FPODtl.[PO_NO] AS [PO NUMBER]
,FPODtl.[PO_DATE] AS [Start]
,FPODtl.[PO_STATUS] AS [Status]
,FPODtl.[LINE_DOCK_DATE] AS [End]
,DATEDIFF
(
DAY,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.PO_DATE)) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.PO_DATE)) END,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) END
)
FROM [dbo].[DS_tblFactPODtl] AS FPODtl
INNER JOIN [dbo].[DS_tblDimWhs] AS D
ON FPODtl.WAREHOUSE = D.WAREHOUSE
INNER JOIN [dbo].[DS_tblFactPO] AS FPO
ON FPODtl.PO_NO = FPO.PO_NO
WHERE FPODtl.[PO_STATUS] = 'Closed'
AND FPODtl.[LINE_DOCK_DATE] <> 0;
If the date stored as a number is like this: 20130226 for today, then the simpler way to convert to DATE or DATETIME would be:
SELECT CONVERT(DATETIME,CONVERT(VARCHAR(8),NumberDate),112)
Here is a quick formula to create a date from parts :
DateAdd( Month, (( #Year - 1900 ) * 12 ) + #Month - 1, #Day - 1 )
Simply use substrings from your original field to extract #Year, #Month and #Day. For instance, if you have a numeric like 19531231 for december 31th, 1953, you could do :
DateAdd( Month, (( SubString(Cast(DateField As Varchar(8)), 1, 4) - 1900 ) * 12 ) +
SubString(Cast(DateField As Varchar(8)), 5, 2) - 1,
SubString(Cast(DateField As Varchar(8)), 7, 2) - 1 )

Resources