SQL Query List of months between dates - sql

I've been searching without success for a way to list the months where my tables entries are in use.
Let's say we have a table with items in use between two dates :
ID StartDate EndDate as ItemsInUse
A 01.01.2013 31.03.2013
B 01.02.2013 30.04.2013
C 01.05.2013 31.05.2013
I need a way to query that table and return something like :
ID Month
A 01
A 02
A 03
B 02
B 03
B 04
C 05
I'm really stuck with this. Does anyone have any clues on doing this ?
PS : European dates formats ;-)

Create a calendar table then
SELECT DISTINCT i.Id, c.Month
FROM Calendar c
JOIN ItemsInUse i
ON c.ShortDate BETWEEN i.StartDate AND i.EndDate

Here should be the answer:
select ID,
ROUND(MONTHS_BETWEEN('31.03.2013','01.01.2013')) "MONTHS"
from TABLE_NAME;

I would use MONTH() http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_month
SELECT ID, MONTH(StartDate)

If your dates are padded in the way you suggest - i.e. where one digit days or months have an initial 0 - you can just use the SUBSTRING function as in:
SELECT SUBSTRING(StartDate, 3, 2) ...
Alternatively, if you do not have the padding you can use:
SELECT SUBSTRING_INDEX( SUBSTRING_INDEX(StartDate, '.',2), '.', -1) ...
(This latter command is in two parts. The inner SUBSTRING_INDEX generates a string containing your day and month, and the outer SUBSTRING_INDEX takes the month.)

Hmm... Dare say this can be improved on, but as a rough 'n' ready (assuming ms-sql)
create table #results (ID varchar(10), Month int, Year int);
declare #ID varchar(10);
declare #StartDate datetime;
declare #EndDate datetime;
declare myCursor cursor for select [ID], [StartDate],[EndDate] from ItemsInUse;
open myCursor;
delete from #results;
fetch next from myCursor into #ID, #StartDate, #EndDate;
declare #tempDate datetime;
while ##FETCH_STATUS = 0
begin
set #tempdate = CAST( CAST(year(#StartDate) AS varchar) + '-' + CAST(MONTH(#StartDate) AS varchar) + '-' + CAST(1 AS varchar) AS DATETIME);
while #tempDate < #EndDate
begin
insert into #results values (#ID, MONTH(#tempDate), YEAR(#tempDate));
set #tempDate = DATEADD(month,1,#tempdate);
end
fetch next from myCursor into #ID, #StartDate, #EndDate;
end
close myCursor;
deallocate myCursor;
select * from #results;
drop table #results;

Related

Using SQL Cursor, To find the date > 45 days difference from initial date, then again from 2nd date found, again > 45 and find next date

Declare #quoteReference NVARCHAR(20)
Declare #policyNo NVARCHAR(20)
Declare #todaysDate DATE
Declare #time int
Declare #ddiff int
Declare #cnt varchar(10)
Declare EmpCursor Cursor for
select quote_reference, Miscellaneous_TodaysDate, diff from #temp1
OPEN EmpCursor
FETCH NEXT FROM EmpCursor INTO #quoteReference ,#todaysDate,#ddiff WHILE(##FETCH_STATUS = 0)
BEGIN
select #quoteReference = quote_reference
--#policyno = policy_policyNo,
-- #time = Miscellaneous_CurrentTime
from #temp1 where
Miscellaneous_TodaysDate = #todaysDate and diff > 45
if
#ddiff < 45
update #temp1
set counts = 'N'
where Miscellaneous_TodaysDate = #todaysDate
else
update #temp1
set counts = 'Y'
where Miscellaneous_TodaysDate = #todaysDate
FETCH NEXT FROM EmpCursor INTO #quoteReference, #policyNo, #todaysDate
END
CLOSE EmpCursor
DEALLOCATE EmpCursor
The first time quote_reference entered in the database is 31/05/2021.
I need another date where the same record came again in the database after more than 45 days (so in this case its 64 days i.e 03/08/2021) then 03/08/2021 date becomes my 2nd initial date and from that date same rules and conditions, i.e. to find date where the same record
entered again after more than 45 days difference.
DATA:
have you tried using the LAG function. link here - https://learn.microsoft.com/en-us/sql/t-sql/functions/lag-transact-sql?view=sql-server-ver16
I dont remember when was the last time I used cursor, you can very much achieve what you want in a SQL statement

SQL query loops twice through each row?

When I trigger my stored procedure from a web app, it loops twice and creates two identical entries of the same row. I cannot work out why :/
The query is supposed to INSERT (re-schedule) all submitted rows. It uses a cursor to go through each row and SELECT, then INSERT, the correct data from/for each row.
Here is my SQL:
CREATE PROCEDURE [cil].[executeCIL_updateComplDate_And_ReSchedule]
#equipID INT,
#date DATE,
#ip VARCHAR(15)
AS
/* add completion date */
UPDATE cil.schedule
SET completionDate = CAST(GETUTCDATE() AS SMALLDATETIME),
complIP = #ip
WHERE schedule.id IN (SELECT schedule.id
FROM cil.schedule
LEFT JOIN cil.task ON cil.schedule.taskFK = cil.task.id
--WHERE CAST(scheduledDate AS DATE)<=CAST(GetDate() AS DATE)
WHERE CAST(scheduledDate as DATE) = #date
AND completionDate IS NULL
AND result IS NOT NULL
AND equipFK = #equipID);
/* reschedule tasks */
DECLARE #nextTaskID AS INT;
DECLARE #nextScheduledDate AS DATETIME2(6);
DECLARE #nextRotaCycle AS INT;
DECLARE db_cursor CURSOR FOR
SELECT taskFK, scheduledDate, rotaCycle
FROM cil.schedule
LEFT JOIN cil.task ON cil.schedule.taskFK = cil.task.id
WHERE completionDate = CAST(GETUTCDATE() AS SMALLDATETIME)
AND equipFK = #equipID;
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #nextTaskID, #nextScheduledDate, #nextRotaCycle;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff with scalar values
INSERT INTO cil.schedule (taskFK, scheduledDate, rotaCycle)
VALUES (#nextTaskID,
DATEADD(dd, #nextRotaCycle, #nextScheduledDate),
#nextRotaCycle)
FETCH NEXT FROM db_cursor INTO #nextTaskID, #nextScheduledDate,
#nextRotaCycle;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
GO
It seems that you have duplicates caused by a join of the query of the cursor
If you run this query externally, in SSMS, will it produce only the one row?
SELECT taskFK, scheduledDate, rotaCycle
FROM cil.schedule
LEFT JOIN cil.task ON cil.schedule.taskFK=cil.task.id
WHERE completionDate=CAST(GETUTCDATE() AS SMALLDATETIME)
AND equipFK=#equipID ;
and doubles comes from a table task.
If this table is not in use, consider to remove it

How to turn potential loop query to set based?

I have something like this currently -
declare #thisDate smalldatetime
create table #theseDates (thisDate smalldatetime)
insert into #theseDates(thisDate)
values ('11/1/2016', '5/1/2017', '9/30/2017)
create table #someData (productid int, productname varchar(32), productprice float, productsolddate smalldatetime)
declare date_cursor cursor for
select thisDate from #theseDates
open date_cursor
fetch next for date_cursor into #thisDate
while ##fetch_status = 0
begin
insert into #someData(soldid, productid, productname, productprice, productsolddate)
select soldid, productid, productname, productprice, productsolddate
from soldproducts
where productsolddate between #thisDate and getdate()
fetch next from date_cursor into #thisDate
end
close date_cursor
deallocate date_cursor
How do I change this from a cursor to a set-based solution?
NOTE: added fetch line
You don't need a cursor to solve this OP. Use CROSS Join instead
This way faster than Cursor.
See below.
FYI. the mydates table is just a random date example because I don't want type them
declare #myproducts table (prodid int, proddesc varchar(30), prodprice money, solddate date)
insert into #myproducts
values
(1,'prod 1',1,dateadd(day,-1,getdate())),
(2,'prod 2',10,dateadd(day,-10,getdate())),
(3,'prod 3',100,dateadd(day,-5,getdate())),
(1,'prod 1',1.5,dateadd(day,-6,getdate())),
(2,'prod 2',8.99,dateadd(day,-20,getdate())),
(3,'prod 3',95,dateadd(day,-15,getdate())),
(1,'prod 1',0.89,dateadd(day,-2,getdate()))
select * from #myproducts
declare #mydates table (mydate date)
/* reporting datefrom */
insert into #mydates
values (dateadd(day,-11,getdate())),(dateadd(day,-20,getdate())),(dateadd(day,-3,getdate()))
select * from #mydates
/* apply cross join to distribute the datefrom but should only be to data where solddate is between datefrom and today */
-- insert statement to dump all qualified data or not
select p.*,d.mydate [DateFrom] from #myproducts p
cross join #mydates d
where solddate between mydate and cast(getdate() as date)

SQL Server FOR EACH Loop

I have the following SQL query:
DECLARE #MyVar datetime = '1/1/2010'
SELECT #MyVar
This naturally returns '1/1/2010'.
What I want to do is have a list of dates, say:
1/1/2010
2/1/2010
3/1/2010
4/1/2010
5/1/2010
Then i want to FOR EACH through the numbers and run the SQL Query.
Something like (pseudocode):
List = 1/1/2010,2/1/2010,3/1/2010,4/1/2010,5/1/2010
For each x in List
do
DECLARE #MyVar datetime = x
SELECT #MyVar
So this would return:-
1/1/2010
2/1/2010
3/1/2010
4/1/2010
5/1/2010
I want this to return the data as one resultset, not multiple resultsets, so I may need to use some kind of union at the end of the query, so each iteration of the loop unions onto the next.
edit
I have a large query that accepts a 'to date' parameter, I need to run it 24 times, each time with a specific to date which I need to be able to supply (these dates are going to be dynamic) I want to avoid repeating my query 24 times with union alls joining them as if I need to come back and add additional columns it would be very time consuming.
SQL is primarily a set-orientated language - it's generally a bad idea to use a loop in it.
In this case, a similar result could be achieved using a recursive CTE:
with cte as
(select 1 i union all
select i+1 i from cte where i < 5)
select dateadd(d, i-1, '2010-01-01') from cte
Here is an option with a table variable:
DECLARE #MyVar TABLE(Val DATETIME)
DECLARE #I INT, #StartDate DATETIME
SET #I = 1
SET #StartDate = '20100101'
WHILE #I <= 5
BEGIN
INSERT INTO #MyVar(Val)
VALUES(#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
SET #I = #I + 1
END
SELECT *
FROM #MyVar
You can do the same with a temp table:
CREATE TABLE #MyVar(Val DATETIME)
DECLARE #I INT, #StartDate DATETIME
SET #I = 1
SET #StartDate = '20100101'
WHILE #I <= 5
BEGIN
INSERT INTO #MyVar(Val)
VALUES(#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
SET #I = #I + 1
END
SELECT *
FROM #MyVar
You should tell us what is your main goal, as was said by #JohnFx, this could probably be done another (more efficient) way.
You could use a variable table, like this:
declare #num int
set #num = 1
declare #results table ( val int )
while (#num < 6)
begin
insert into #results ( val ) values ( #num )
set #num = #num + 1
end
select val from #results
This kind of depends on what you want to do with the results. If you're just after the numbers, a set-based option would be a numbers table - which comes in handy for all sorts of things.
For MSSQL 2005+, you can use a recursive CTE to generate a numbers table inline:
;WITH Numbers (N) AS (
SELECT 1 UNION ALL
SELECT 1 + N FROM Numbers WHERE N < 500
)
SELECT N FROM Numbers
OPTION (MAXRECURSION 500)
declare #counter as int
set #counter = 0
declare #date as varchar(50)
set #date = cast(1+#counter as varchar)+'/01/2013'
while(#counter < 12)
begin
select cast(1+#counter as varchar)+'/01/2013' as date
set #counter = #counter + 1
end
Off course an old question. But I have a simple solution where no need of Looping, CTE, Table variables etc.
DECLARE #MyVar datetime = '1/1/2010'
SELECT #MyVar
SELECT DATEADD (DD,NUMBER,#MyVar)
FROM master.dbo.spt_values
WHERE TYPE='P' AND NUMBER BETWEEN 0 AND 4
ORDER BY NUMBER
Note : spt_values is a Mircrosoft's undocumented table. It has numbers for every type. Its not suggestible to use as it can be removed in any new versions of sql server without prior information, since it is undocumented. But we can use it as quick workaround in some scenario's like above.
[CREATE PROCEDURE [rat].[GetYear]
AS
BEGIN
-- variable for storing start date
Declare #StartYear as int
-- Variable for the End date
Declare #EndYear as int
-- Setting the value in strat Date
select #StartYear = Value from rat.Configuration where Name = 'REPORT_START_YEAR';
-- Setting the End date
select #EndYear = Value from rat.Configuration where Name = 'REPORT_END_YEAR';
-- Creating Tem table
with [Years] as
(
--Selecting the Year
select #StartYear [Year]
--doing Union
union all
-- doing the loop in Years table
select Year+1 Year from [Years] where Year < #EndYear
)
--Selecting the Year table
selec]

How to determine when a time stamp does not exist in a table

I have a table that receives data on an hourly basis. Part of this import process writes the timestamp of the import to the table. My question is, how can I build a query to produce a result set of the periods of time when the import did not write to the table?
My first thought is to have a table of static int and just do an outer join and look for nulls on the right side, but this seems kind of sloppy. Is there a more dynamic way to produce a result set for the times the import failed based on the timestamp?
This is a MS SQL 2000 box.
Update: I think I've got it. The two answers already provided are great, but instead what I'm working on is a function that returns a table of the values I am looking for for a given time frame. Once I get it finished I'll post the solution here.
Here's a slightly modified solution from this article in my blog:
Flattening timespans: SQL Server
DECLARE #t TABLE
(
q_start DATETIME NOT NULL,
q_end DATETIME NOT NULL
)
DECLARE #qs DATETIME
DECLARE #qe DATETIME
DECLARE #ms DATETIME
DECLARE #me DATETIME
DECLARE cr_span CURSOR FAST_FORWARD
FOR
SELECT s_timestamp AS q_start,
DATEADD(minute, 1, s_timestamp) AS q_end
FROM [20090611_timespans].t_span
ORDER BY
q_start
OPEN cr_span
FETCH NEXT
FROM cr_span
INTO #qs, #qe
SET #ms = #qs
SET #me = #qe
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT
FROM cr_span
INTO #qs, #qe
IF #qs > #me
BEGIN
INSERT
INTO #t
VALUES (#ms, #me)
SET #ms = #qs
END
SET #me = CASE WHEN #qe > #me THEN #qe ELSE #me END
END
IF #ms IS NOT NULL
BEGIN
INSERT
INTO #t
VALUES (#ms, #me)
END
CLOSE cr_span
This will return you the consecutive ranges when updates did happen (with a minute resolution).
If you have an index on your timestamp field, you may issue the following query:
SELECT *
FROM records ro
WHERE NOT EXISTS
(
SELECT NULL
FROM records ri
WHERE ri.timestamp >= DATEADD(minute, -1, ro.timestamp)
AND ri.timestamp < ro.timestamp
)
I was thinking something like this:
select 'Start' MissingStatus, o1.LastUpdate MissingStart
from Orders o1
left join Orders o2
on o1.LastUpdate between
dateadd(ss,1,o2.LastUpdate) and dateadd(hh,1,o2.LastUpdate)
where o2.LastUpdate is null
union all
select 'End', o1.LastUpdate MissingEnd
from Orders o1
left join Orders o2
on o1.LastUpdate between
dateadd(hh,-1,o2.LastUpdate) and dateadd(ss,-1,o2.LastUpdate)
where o2.LastUpdate is null
order by 2