How to compare data with a multiple selection in SQL Server? - sql

I have these dates as example:
date1: 2013-01-01
date2: 2013-01-25
I need to create a procedure that will insert the product special offer in the database if date1 is between these 2 dates.
create procedure CreateOfferProduct(
#codP varchar(5),
#date1 date,
#date2 date,
)
as
if(not exists(
select * from OfferProducts
where Products.codP = #codP
and #date1 <= (select date2 from OfferProducts where codP = #codP)
)
)
begin
insert into OfferProducts(codP, date1, date2)
values(#codP, #date1, #date2);
end
But since
select date2 from Products where codP = #codP
returns multiple values it doesn't work. Any help is appreciated.

This is one way to insert multiple rows that don't already exist in the destination, instead of doing this row-by-row technique where you assign values to a variable (which is slower and inflexible).
INSERT dbo.OfferProducts(codP, date1, date2)
SELECT p.codP, #date, #date2
FROM dbo.Products AS p
WHERE codP = #codP
AND NOT EXISTS
(
SELECT 1 FROM dbo.OfferProducts
WHERE codP = p.codP
AND date2 >= #date1 -- this logic seems funny
);
If you show sample data and desired results, including existing data that you want excluded from the insert, we might be able to formulate better logic for you.

You are looking for the intersection of two time periods. The following may be what you want:
. . .
if not exists (
select *
from OfferProducts op
where op.codP = #codP and
(op.date1 <= #date2 and
op.date2 >= #date1)
)
. . .
The exact definition of overlap depends on whether the end points are included in the date range.
On re-reading the question, you explicitly state "if its date1 is between these 2 dates". If so, then try:
if not exists (
select *
from OfferProducts op
where op.codP = #codP and
op.date1 between #date1 and #date2
)
. . .

Related

creating max date function using sql in databricks

I am writing queries in databricks using sql on views and would like to calculate max of dates of update timestamp column across multiple views. for instance i am joining table a with table b and would like to know max(a.updt_ts,b.updt_ts). since max function can not have more than one columns mentioned, i want to create a function. Any help is greatly appreciated.
below is what i have:
CREATE temporary FUNCTION ufnGetMaxDt (#Date1 DATETIME2,#Date2 DATETIME2)
BEGIN
DECLARE #ret DATETIME2
, #MinDt datetime2;
SET #MinDt = cast('1900-01-01' as datetime2);
IF (#Date1) is null SET #Date1 = #MinDt;
IF (#Date2) is null SET #Date2 = #MinDt;
SET #ret = CASE When #Date1 >= #Date2
Then #Date1
else #Date2
END;
IF (#ret IS NULL)
SET #ret = #MinDt; -- Dummy date
RETURN #ret;
END
GO
You could just use greatest? eg
SELECT *, GREATEST( date1, date2 ) xmax
FROM tmp
Or put them in an array, explode it and then max that? eg something like this:
%sql
WITH cte AS
(
SELECT *, EXPLODE( ARRAY( date1, date2 ) ) xmax
FROM tmp
)
SELECT MAX( xmax )
FROM cte
Seems a bit excessive when you can just use greatest though? It's also worth having a read through the list of Spark SQL built-in functions. You don't have to remember them all but at least if you know something is possible it's useful:
https://spark.apache.org/docs/2.3.0/api/sql/index.html

SQL Server - Efficient generation of dates in a range

Using SQL Server 2016.
I have a stored procedure that produces a list of options against a range of dates. Carriage options against days for clarity but unimportant to the specifics here.
The first step in the stored procedure generates a list of dates to store additional data against, and generating this list is taking substantially longer than the balance of the code. While this process is individual short, the number of calls means that this one piece of code is putting the system under more load than anything else.
With that in mind I have been testing efficiency of several options.
Iterative common table expression:
CREATE FUNCTION [dbo].[udf_DateRange_CTE] (#StartDate DATE,#EndDate DATE)
RETURNS #Return TABLE (Date DATE NOT NULL)
AS
BEGIN
WITH dates(date)
AS (SELECT #StartDate [Date]
UNION ALL
SELECT DATEADD(dd, 1, [Date])
FROM dates
WHERE [Date] < #EndDate
)
INSERT INTO #Return
SELECT date
FROM dates
OPTION (MAXRECURSION 0)
RETURN
END
A while loop:
CREATE FUNCTION [dbo].[udf_DateRange_While] (#StartDate DATE,#EndDate DATE)
RETURNS #Retun TABLE (Date DATE NOT NULL,PRIMARY KEY (Date))
AS
BEGIN
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Retun
VALUES (#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
END
RETURN
END
A lookup from a pre-populated table of dates:
CREATE FUNCTION [dbo].[udf_DateRange_query] (#StartDate DATE,#EndDate DATE)
RETURNS #Return TABLE (Date DATE NOT NULL)
AS
BEGIN
INSERT INTO #Return
SELECT Date
FROM DateLookup
WHERE Date >= #StartDate
AND Date <= #EndDate
RETURN
END
In terms of efficiency I have test generating a years worth of dates, 1000 times and had the following results:
CTE: 10.0 Seconds
While: 7.7 Seconds
Query: 2.6 Seconds
From this the query is definitely the faster option but does require a permanent table of dates that needs to be created and maintained. This means that the query is no loner "self-contained" and it would be possible to request a date outside of the given date range.
Does anyone know of any more efficient ways of generating dates for a range, or any optimisation I can apply to the above?
Many thanks.
You can try like following. This should be fast compared CTE or WHILE loop.
DECLARE #StartDate DATETIME = Getdate() - 1000
DECLARE #EndTime DATETIME = Getdate()
SELECT *
FROM (SELECT #StartDate + RN AS DATE
FROM (SELECT ROW_NUMBER()
OVER (
ORDER BY (SELECT NULL)) RN
FROM master..[spt_values]) T) T1
WHERE T1.DATE <= #EndTime
ORDER BY DATE
Note: This will work for day difference <= 2537 days
If you want to support more range, you can use CROSS JOIN on master..[spt_values] to generate range between 0 - 6436369 days like following.
DECLARE #StartDate DATETIME = Getdate() - 10000
DECLARE #EndTime DATETIME = Getdate()
SELECT #StartDate + RN AS DATE FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..[spt_values] T1
CROSS JOIN master..[spt_values] T2
) T
WHERE RN <= DATEDIFF(DAY,#StartDate,#EndTime)

Use something like LEAST in T-SQL on a datetime field [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Getting the minimum of two values in sql
Okay what I Have a table with two datetime fields and I want to select the rows where the oldest date is equal to some date variable. I saw the LEAST function used somewhere but I can't use this in T-SQL
I need something like this
SELECT LEAST(date1, date2) as theDate FROM theTable WHERE theDate = '2012-09-24'
but that will work in T-SQL. Also date1 or date2 can sometimes be null so that may be important to know.
The CASE solution others have provided works well for the case (no pun intended) of two values. For future reference, it becomes a bit unweildy for three or more values and you might then want to do something like this:
SELECT (SELECT MIN(mydate)
FROM (VALUES(date1),(date2),(date3)) mylist(mydate)
) AS theDate
FROM theTable
WHERE theDate = '2012-09-24'
There is no such function in T-SQL. Try:
SELECT theDate = CASE WHEN date1 < date2 THEN date1 ELSE date2 END FROM ... ;
To handle NULLs you may want:
SELECT theDate = NULLIF(CASE WHEN date1 < date2 THEN date1 ELSE date2 END, '20301231')
FROM
(
SELECT
date1 = COALESCE(date1, '20301231'),
date2 = COALESCE(date2, '20301231')
FROM ...
) AS x;
Also, you can't reference the alias theDate in the WHERE clause, it doesn't exist yet. You might want to say:
WHERE '20120924' IN (date1, date2);
You could use a case to mimic least, and a subquery so you can refer to the case result in the where clause:
select *
from (
select case
when date1 > date2 or date1 is null then date2
else date1
end as theDate
, *
from TheTable
)
where theDate = '2012-09-24'
While the other answers are indeed valid, here it is in the form of a UDF, which is closer to what you asked for:
CREATE FUNCTION [dbo].[LeastDate] (#d1 datetime, #d2 datetime)
RETURNS datetime
AS
BEGIN
DECLARE #least datetime
IF #d1 is null or #d2 is null
SET #least = null
ELSE IF #d1 < #d2
SET #least = #d1
ELSE
SET #least = #d2
RETURN #least
END
Usage:
SELECT dbo.LeastDate(date1, date2) as theDate
FROM theTable
WHERE dbo.LeastDate(date1, date2) = '2012-09-24'
Or you can use a nested select to do it one time only:
SELECT * FROM (
SELECT dbo.LeastDate(date1, date2) as theDate FROM theTable
) t
WHERE theDate = '2012-09-24'
Note that the choice of calling the function separately in the where clause or using a nested select is the same as it would be for any SQL function, not just this one.

Looping in SELECT statement in ms sqlserver [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
SQL Server 2008 Generate a Series of date times
I have to Loop through a startDate and endDate
The SELECT statement should produce result as..
Expected Output :
------------
Date
------------
09/01/2012 -> startDate
09/02/2012
09/03/2012
.
.
.
.
09/30/2012 -> endDate
i tried
declare #startDate datetime , #endDate endDate
set #startDate='09/01/2012'
set #endDate='09/30/2012'
while DATEDIFF(#startDate,#endDate)!=-1
begin
select #startDate as Date
set #startDate = DATEADD(day,2,#startDate)
end
But its not working out..
it generates 30 outputs..
i want the dates in a single output as in the expected output..
where am i going wrong here guys ?
That will give you a resultset for each loop iteration as you select per iteration.
If you want a single resultset insert into a temp table/variable per iteration then select from it or
;with T(day) as
(
select #startDate as day
union all
select day + 1
from T
where day < #endDate
)
select day as [Date] from T
If you want to use a WHILE loop:
declare #startDate datetime , #endDate datetime
set #startDate='09/01/2012'
set #endDate='09/30/2012'
create table #temp (startDate datetime)
while #startDate <= #endDate
begin
insert into #temp
select #startDate as Date
set #startDate = DATEADD(day,1,#startDate)
end
select *
from #temp
drop table #temp
see SQL Fiddle with Demo
You could create a temp table for the values and select from that in the end, after the iteration.
declare #temp table (TheDate date)
declare #startDate datetime , #endDate datetime
set #startDate='09/01/2012'
set #endDate='09/30/2012'
while DATEDIFF(day, #startDate, #endDate)!=-1
begin
insert into #temp (thedate) values (#startDate)
set #startDate = DATEADD(day,2,#startDate)
end
select * from #temp
edit: The cte Alex suggest is imo a much cleaner way to do it, and more of a sql way to do it, without using loops or cursors.

How can I generate a temporary table filled with dates in SQL Server 2000?

I need to make a temporary table that holds of range of dates, as well as a couple of columns that hold placeholder values (0) for future use. The dates I need are the first day of each month between $startDate and $endDate where these variables can be several years apart.
My original sql statement looked like this:
select dbo.FirstOfMonth(InsertDate) as Month, 0 as Trials, 0 as Sales
into #dates
from customer
group by dbo.FirstOfMonth(InsertDate)
"FirstOfMonth" is a user-defined function I made that pretty much does what it says, returning the first day of the month for the provided date with the time at exactly midnight.
This produced almost exactly what I needed until I discovered there were occasionally gaps in my dates where I had a few months were there were no records insert dates. Since my result must still have the missing months I need a different approach.
I have added the following declarations to the stored procedure anticipating their need for the range of the dates I need ...
declare $startDate set $startDate = select min(InsertDate) from customer
declare $endDate set $endDate = select max(InsertDate) from customer
... but I have no idea what to do from here.
I know this question is similar to this question but, quite frankly, that answer is over my head (I don't often work with SQL and when I do it tends to be on older versions of SQL Server) and there are a few minor differences that are throwing me off.
I needed something similar, but all DAYS instead of all MONTHS.
Using the code from MatBailie as a starting point, here's the SQL for creating a permanent table with all dates from 2000-01-01 to 2099-12-31:
CREATE TABLE _Dates (
d DATE,
PRIMARY KEY (d)
)
DECLARE #dIncr DATE = '2000-01-01'
DECLARE #dEnd DATE = '2100-01-01'
WHILE ( #dIncr < #dEnd )
BEGIN
INSERT INTO _Dates (d) VALUES( #dIncr )
SELECT #dIncr = DATEADD(DAY, 1, #dIncr )
END
This will quickly populate a table with 170 years worth of dates.
CREATE TABLE CalendarMonths (
date DATETIME,
PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2000',
#offset = 1
WHILE (#offset < 2048)
BEGIN
INSERT INTO CalendarMonths SELECT DATEADD(MONTH, #offset, date) FROM CalendarMonths
SELECT #offset = #offset + #offset
END
You can then use it by LEFT joining on to that table, for the range of dates you require.
I would probably use a Calendar table. Create a permanent table in your database and fill it with all of the dates. Even if you covered a 100 year range, the table would still only have ~36,525 rows in it.
CREATE TABLE dbo.Calendar (
calendar_date DATETIME NOT NULL,
is_weekend BIT NOT NULL,
is_holiday BIT NOT NULL,
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (calendar_date)
)
Once the table is created, just populate it once in a loop, so that it's always out there and available to you.
Your query then could be something like this:
SELECT
C.calendar_date,
0 AS trials,
0 AS sales
FROM
dbo.Calendar C
WHERE
C.calendar_date BETWEEN #start_date AND #end_date AND
DAY(C.calendar_date) = 1
You can join in the Customers table however you need to, outer joining on FirstOfMonth(InsertDate) = C.calendar_date if that's what you want.
You can also include a column for day_of_month if you want which would avoid the overhead of calling the DAY() function, but that's fairly trivial, so it probably doesn't matter one way or another.
This of course will not work in SQL-Server 2000 but in a modern database where you don't want to create a permanent table. You can use a table variable instead creating a table so you can left join the data try this. Change the DAY to HOUR etc to change the increment type.
declare #CalendarMonths table (date DATETIME, PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2014',
#offset = 1
INSERT INTO #CalendarMonths SELECT #basedate
WHILE ( DATEADD(DAY, #offset, #basedate) < CURRENT_TIMESTAMP)
BEGIN
INSERT INTO #CalendarMonths SELECT DATEADD(HOUR, #offset, date) FROM #CalendarMonths where DATEADD(DAY, #offset, date) < CURRENT_TIMESTAMP
SELECT #offset = #offset + #offset
END
A starting point of a useful kludge to specify a range or specific list of dates:
SELECT *
FROM
(SELECT CONVERT(DateTime,'2017-1-1')+number AS [Date]
FROM master..spt_values WHERE type='P' AND number<370) AS DatesList
WHERE DatesList.Date IN ('2017-1-1','2017-4-14','2017-4-17','2017-12-25','2017-12-26')
You can get 0 to 2047 out of master..spt_values WHERE type='P', so that's five and a half year's worth of dates if you need it!
Tested below and it works, though it's a bit convoluted.
I assigned arbitrary values to the dates for the test.
DECLARE #SD smalldatetime,
#ED smalldatetime,
#FD smalldatetime,
#LD smalldatetime,
#Mct int,
#currct int = 0
SET #SD = '1/15/2011'
SET #ED = '2/02/2012'
SET #FD = (DATEADD(dd, -1*(Datepart(dd, #SD)-1), #sd))
SET #LD = (DATEADD(dd, -1*(Datepart(dd, #ED)-1), #ED))
SET #Mct = DATEDIFF(mm, #FD, #LD)
CREATE TABLE #MyTempTable (FoM smalldatetime, Trials int, Sales money)
WHILE #currct <= #Mct
BEGIN
INSERT INTO #MyTempTable (FoM, Trials, Sales)
VALUES
(DATEADD(MM, #currct, #FD), 0, 0)
SET #currct = #currct + 1
END
SELECT * FROM #MyTempTable
DROP TABLE #MyTempTable
For SQL Server 2000, this stackoverflow post looks promising for a way to temporarily generate dates calculated off of a start and end date. It's not exactly the same but quite similar. This post has a very in-depth answer on truncating dates, if needed.
In case anyone stumbles on this question and is working in PostgreSQL instead of SQL Server 2000, here is how you might do it there...
PostgreSQL has a nifty series generating function. For your example, you could use this series of all days instead of generating an entire calendar table, and then do groupings and matchups from there.
SELECT current_date + s.a AS dates FROM generate_series(0,14,7) AS s(a);
dates
------------
2004-02-05
2004-02-12
2004-02-19
(3 rows)
SELECT * FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00', '10 hours');
generate_series
---------------------
2008-03-01 00:00:00
2008-03-01 10:00:00
2008-03-01 20:00:00
2008-03-02 06:00:00
2008-03-02 16:00:00
2008-03-03 02:00:00
2008-03-03 12:00:00
2008-03-03 22:00:00
2008-03-04 08:00:00
(9 rows)
I would also look into date_trunc from PostgreSQL using 'month' for the truncator field to maybe refactor your original query to easily match with a date_trunc version of the calendar series.
select top (datediff(D,#start,#end)) dateadd(D,id-1,#start)
from BIG_TABLE_WITH_NO_JUMPS_IN_ID
declare #start datetime
set #start = '2016-09-01'
declare #end datetime
set #end = '2016-09-30'
create table #Date
(
table_id int identity(1,1) NOT NULL,
counterDate datetime NULL
);
insert into #Date select top (datediff(D,#start,#end)) NULL from SOME_TABLE
update #Date set counterDate = dateadd(D,table_id - 1, #start)
The code above should populate the table with all the dates between the start and end. You would then just join on this table to get all of the dates needed. If you only needed a certain day of each month, you could dateadd a month instead.
SELECT P.Id
, DATEADD ( DD, -P.Id, P.Date ) AS Date
FROM (SELECT TOP 1000 ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) AS Id, CAST(GETDATE () AS DATE) AS Date FROM master.dbo.spt_values) AS P
This query returns a table calendar for the last 1000 days or so. It can be put in a temporary or other table.
Create a table variable containing a date for each month in a year:
declare #months table (reportMonth date, PRIMARY KEY (reportMonth));
declare #start date = '2018', #month int = 0; -- base 0 month
while (#month < 12)
begin
insert into #months select dateAdd(month, #month, #start);
select #month = #month + 1;
end
--verify
select * from #months;
This is by far the quickest method I have found (much quicker than inserting rows 1 by 1 in a WHILE loop):
DECLARE #startDate DATE = '1900-01-01'
DECLARE #endDate DATE = '2050-01-01'
SELECT DATEADD(DAY, sequenceNumber, #startDate) AS TheDate
INTO #TheDates
FROM (
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n AS sequenceNumber
FROM
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tenthousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n <= DATEDIFF(day, #startDate, #endDate)
) theNumbers
SELECT *
FROM #TheDates
ORDER BY TheDate
The recursive answer:
DECLARE #startDate AS date = '20220315';
DECLARE #endDate AS date = '20230316'; -- inclusive
WITH cte_minutes(dt)
AS (
SELECT
DATEFROMPARTS(YEAR(#startDate), MONTH(#startDate), 1)
UNION ALL
SELECT
DATEADD(month, 1, dt)
FROM
cte_minutes
WHERE DATEADD(month, 1, dt) < #endDate
)
SELECT
dt
into #dates
FROM
cte_minutes
WHERE
dt >= #startDate
AND
dt <= #endDate
OPTION (MAXRECURSION 2000);
DROP TABLE dbo.#dates