Need stored procedure or cursor in sql server - sql-server-2012

This query will insert date and count of sales in a table.
SELECT
replace(CONVERT(varchar(10), DueDate, 112),'-',''),
count( distinct sale)
FROM
sale s
JOIN
Organisation o ON s.Organisationid = o.OrganisationID
WHERE
CAST(duedate AS DATE) ='1 Jan 2015'
AND '01 Jan 2015' BETWEEN StartDateUTC AND EndDateUTC
GROUP BY
replace(CONVERT(varchar(10), DueDate, 112),'-','')
I want my query to run for each day of the month (Jan-2015) and insert the count of sales for each day in a table.
Is there a way like..
Creating a cursor OR a LOOP which keeps running with a parameter which has a starting value as 1st jan-2015 and ending value 31st-Jan-2015 and with the first run it insets the count for 1st Jan and the parameter keeps incrementing for a day with every run and finally stops after 31st jan .
And the final result should have a table with count of sales for each days of the month Jan-2015.
For example:
1stJan- 10
2nd jan -20
3rd Jan - 15
and so on and so forth till 31st Jan
I'm using SQL Server 2012

You can do this by using while loop. Use FromDate and ToDate and increment the FromDate until to reach the EndDate. Insert the result into a temp table and select the result after the loop ends from the temp table (#ResultTable).
Try some this like this.
DECLARE #FromDate DATE = '01/01/2015',
#EndDate DATE = '01/31/2015'
DECLARE #ResultTable TABLE(DueDate VARCHAR(50), Sales INT)
WHILE (#FromDate <= #endDate)
BEGIN
INSERT INTO #ResultTable
SELECT
replace(CONVERT(varchar(10), DueDate, 112),'-',''),
count( distinct sale)
FROM
sale s
JOIN
Organisation o ON s.Organisationid = o.OrganisationID
WHERE
CAST(duedate AS DATE) ='1 Jan 2015'
AND '01 Jan 2015' BETWEEN StartDateUTC AND EndDateUTC
GROUP BY
replace(CONVERT(varchar(10), DueDate, 112),'-','')
--Incrementing to next date
SELECT #FromDate = DATEADD(DAY, 1, #FromDate)
END
SELECT * FROM #ResultTable

Related

How to split revenue by day between two months

We have group bookings held in our system as blocks. I can get total revenue held on the block per group by arrival and departure date.
The problem is that the revenue per group is the total revenue shown is that generated during the whole period the bedrooms are blocked for. This would be ok if each group arrived and left in the same month. However, there are groups that arrive in a month and leave on the following month.
I need to split the revenue for these groups by month. So, for example if a group arrives on 28/06 and leaves on 3/07, I need to know how much of that revenue is generated in June (3 nights) and how much in July (2 nights).
The query below is correct but will give me total revenue based on EndDate (check out date), so revenue will all go on the month the group departs.
I am also comparing same time last year
SELECT DAILYREV = ProjectedRevenueAccomNett/ DATEDIFF(Day,BeginDate, EndDate), DATEDIFF(DAY,BeginDate, EndDate) AS StayNights, Year(BeginDate) ArrivalYear, GroupRef, GAStatus, SourceSiteId, BeginDate, EndDate, CreatedTimestamp, DefMarketSegmentCode,ProjectedRevenueAccomNett, ProjectedRevenueFBNett
From SyncGroupRoomBlockHeaders
WHERE CreatedTimestamp <= '2019-03-28'
AND BeginDate BETWEEN '2019-03-28' AND '2019-12-31'
or
CreatedTimestamp <= '2018-03-28'
AND BeginDate BETWEEN '2018-03-28' AND '2018-12-31'
Order By YEAR (BeginDate)
Your question can be interpreted in different ways. I think the following query will point you in the right direction. Maybe you can use this approach and edit it to your own needs:
USE TEMPDB
IF OBJECT_ID ('TEMPDB..Bookings') IS NOT NULL DROP TABLE Bookings;
CREATE TABLE Bookings (ID INT,
StartDate DATE,
EndDate Date,
Total DECIMAL (8,2),
NumberDays AS DATEDIFF (DAY, StartDate, EndDate))
INSERT INTO Bookings VALUES (1, '20190130', '20190202', 210.15)
IF OBJECT_ID ('TEMPDB..BookingNew') IS NOT NULL DROP TABLE BookingNew;
SELECT *, Total / NumberDays AS PricePerDay
INTO BookingNew
FROM Bookings
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SET #StartDate = '2000-01-01' -- << user input >>
SET #EndDate = '2040-12-31' -- << user input >>
IF OBJECT_ID ('TEMPDB..#Date') IS NOT NULL DROP TABLE #Date
IF OBJECT_ID ('TEMPDB..#Date') IS NULL CREATE TABLE #Date (Date DATE)
INSERT INTO #Date VALUES (#StartDate)
WHILE #StartDate < #EndDate
BEGIN
INSERT INTO #Date
SELECT DATEADD (DD, 1, #StartDate) AS Date
SET #StartDate = DATEADD (DD, 1, #StartDate)
END
SELECT *
FROM #Date AS D
INNER JOIN BookingNew AS B ON D.Date BETWEEN B.StartDate AND B.EndDate

SQL Creating a list of date values based on starting date and ending dates

I have a data set with one observation:
id Starting date Ending date
23 18/8/2013 26/4/2014
How would I be able to create a list of dates? i.e.
id Date
23 Aug 2013
23 Sep 2013
23 Oct 2013
23 Nov 2013
23 Dec 2013
23 Jan 2014
23 Feb 2014
23 Mar 2014
23 Apr 2014
Create a SQL Table-Valued Function as below, this will generate the dates.
CREATE FUNCTION fn_GenerateDates
(
#StartDate DATETIME,
#EndDate DATETIME
)
RETURNS #Output TABLE
(
Value NVARCHAR(4000)
)
AS
BEGIN
INSERT INTO #Output
SELECT TOP (DATEDIFF(MONTH, #StartDate, #EndDate)+1) --get only the dates where dates are between the source startdate and enddate
DATENAME(MONTH, DATEADD(MONTH, number, #StartDate)) + ' ' + CONVERT(VARCHAR(10), YEAR(DATEADD(MONTH, number, #StartDate))) AS Months
FROM [master].dbo.spt_values
WHERE [type] = N'P'
ORDER BY number
RETURN
END
Then your SELECT statement
SELECT tn.id,
dates.Value
FROM TableName tn
CROSS APPLY dbo.fn_GenerateDates(tn.StartDate, tn.EndDate) AS dates
ORDER BY tn.id
This link uses DB2 as reference, but same concept can be used for SQL Server with little modifications.
This Approach is Similar to the answer provided by #mvisser
Using Sys.columns to generate the dates
Create A SQL Function to Generate the Dates
CREATE FUNCTION fn_GenerateDates
(
#StartDate DATETIME,
#EndDate DATETIME
)
RETURNS #Outputdates TABLE
(
Dates NVARCHAR(4000)
)
AS
BEGIN
INSERT INTO #Outputdates
-- Uses Sys.columns Table to Add Months to Given Startdate -----
SELECT DATENAME(MONTH,gen_date) + ' ' + CONVERT(VARCHAR(10),YEAR(gen_date)) FROM
(SELECT DATEADD(month,(ROW_NUMBER() OVER(ORDER BY name) - 1),#StartDate) gen_date FROM SYS.COLUMNS) a
WHERE gen_date between #StartDate and #EndDate
RETURN
END
Then use Select statement
SELECT t.id,gen_dates.Dates
FROM TableName t
CROSS APPLY dbo.fn_GenerateDates(t.StartDate, t.EndDate) AS gen_dates
ORDER BY t.id

Find dates that are not on the first or last day of the month

I have a table named Locations that has column named effective_date with many dates from many years, and I want to retrieve only those that are not on the first day of the month or the last day of that month.
Here is a SQL Fiddle Demo with the detail below.
Generate a table and some sample test data:
CREATE TABLE Locations(
effective_date DATETIME
)
INSERT INTO Locations
VALUES('2014-01-01') -- First day so we would expect this NOT to be returned
INSERT INTO Locations
VALUES('2014-01-02') -- This should be returned
INSERT INTO Locations
VALUES('2014-01-31') -- Last day of January so this should NOT be returned
Then the query below works out the last day of the month for each date in the table, records are only returned is if the effective_date is not the first or last day of the month as calculated.
SELECT effective_date FROM Locations
WHERE -- not the first day (the easy bit!)
DATEPART(day, effective_date) <> 1
-- not the last day (slightly more complex)
AND DATEPART(day, effective_date) <>
DATEPART(day, DATEADD(second,-1,DATEADD(month, DATEDIFF(month,0,effective_date)+1,0)))
When executed only January, 02 2014 00:00:00+0000 is returned as expected.
The clever bit here is the function to calculate the last day of the current month when given a date, lets examine that and break it down:
DECLARE #sampleDate DATETIME
SET #sampleDate = '2014-01-02'
-- Calculate the number of months between '1900-01-01' and the #sampleDate
-- +1 as we want to shift into the following month so we can work back:
SELECT DATEDIFF(month,0,#sampleDate) + 1
-- Result --> 1369
-- Create a new date by adding the result of the previous step in
-- months to '1900-01-01'
SELECT DATEADD(month, DATEDIFF(month,0,#sampleDate)+1,0)
-- Result --> '2014-02-01' (giving first day of the following month)
-- Subtract one second from this
SELECT DATEADD(second,-1,DATEADD(month, DATEDIFF(month,0,#sampleDate)+1,0))
-- Result --> '2014-01-31 23:59:59' (giving the very end of the original month)
-- Finally extract the day of the month
SELECT DATEPART(day, DATEADD(second,-1,DATEADD(month, DATEDIFF(month,0,#sampleDate)+1,0)))
-- Result --> 31
If your effective_date columns is of type date, then this SQL query will return all rows with a non-null effective_date value that is other than the 1st or last day of the month:
select t.effective_date , count(*)
from dbo.foo t
where 1 = 1 -- just for clarity
-- after the 1st day of the month
and t.effective_date > dateadd(day ,
1-day( t.effective_date ) ,
t.effective_date
)
-- and prior to the last day of the month
and t.effective_date < dateadd( day ,
-day( dateadd(month,1,t.effective_date) ) ,
dateadd(month,1,t.effective_date)
)
If your column carries a time component with it, that is, any of:
datetime
smalldatetime
datetime2
datetimeoffset
You'll want to cover your bases and modify the query, something like
select *
from dbo.foo t
where 1=1 -- added for clarity
-- effective date on or after the 2nd of the month
and t.effective_date >= convert(date,
dateadd(day ,
2-day( t.effective_date ) ,
t.effective_date
)
)
-- and prior to the last day of the month
and t.effective_date < convert(date,
dateadd(day,
-day( dateadd(month,1,t.effective_date) ) ,
dateadd(month,1,t.effective_date)
)
)
The first day of the month will always be 1.
You should be able to adapt this to find the last day of the current month:
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))
Source: http://blog.sqlauthority.com/2007/08/18/sql-server-find-last-day-of-any-month-current-previous-next/
T-SQL to display dates in a month for only first & last day. Also display dates in month excluding first & last day.
declare #EndOfMonth as DateTime
set #EndOfMonth = ( SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0)) )
select #EndOfMonth as EndOfMonths
declare #EndOfMonthMinus1 as DateTime
set #EndOfMonthMinus1 = (SELECT DATEAdd(DAY, -1, #EndOfMonth) )
select #EndOfMonthMinus1 as EndOfMonths
declare #BeginingOfMonth as DateTime
set #BeginingOfMonth = (SELECT CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()),101))
select #BeginingOfMonth as EndOfMonths
declare #BeginingOfMonthPlus1 as DateTime
set #BeginingOfMonthPlus1 = (SELECT CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(GETDATE())-2),GETDATE()),101))
select #BeginingOfMonthPlus1 as EndOfMonths
-- select dates in month exclude first and last day
SELECT TOP 1000 [effective_date]
FROM [Locations]
where effective_date <= #EndOfMonthMinus1
and effective_date >= #BeginingOfMonthPlus1
-- select only first of month and last of month
SELECT TOP 1000 [effective_date]
FROM [Locations]
where [effective_date]<= #EndOfMonth
and [effective_date] >= #EndOfMonthMinus1
and [effective_date] >= #BeginingOfMonth
and [effective_date] <= #BeginingOfMonthPlus1
Hope this helps!

Need SQL Server Query help, Newbie

I have a table that has the following columns.
id,
compid(used to identify a piece of equipment),
startalarmdate,
endalarmdate
when a piece of equipment goes into alarm, I insert the compid and startalarmdate(with the current time) into the table and when the equipment comes out of alarm I fill in the null in that same row in the endalarmdate column with the current time.
so I end up with something like this in the table
417,6,Sun Oct 30 18:48:17 CDT 2011,Mon Oct 31 09:49:21 CDT 2011
422,6,Mon Oct 31 10:19:19 CDT 2011,Mon Oct 31 12:19:22 CDT 2011
427,6,Mon Oct 31 20:19:56 CDT 2011,Tue Nov 01 09:50:59 CDT 2011
429,6,Tue Nov 01 21:51:41 CDT 2011,Wed Nov 02 09:52:37 CDT 2011
432,6,Wed Nov 02 21:23:23 CDT 2011,Fri Nov 04 16:26:29 CDT 2011
I was able to build a query that gives me a total downtime in hours for each event, but what I would like to do now is build a query that gives me a total hours in downtime for each day of a month. Id like it to have the compid all the way to the left, then have each day of the month to the right of the compid in a column on the same row. Id like the days with no downtime to be null. Is it possible to do that with the way this table is setup?
Step 1: set up a temp table containing the desired "time blocks" that you want to total for. These blocks could be for any range of time; in your example, it would be one entry for ever day (24-hour period) in the month.
CREATE TABLE #TimeRanges
(
RangeStart datetime not null
,RangeEnd datetime not null
)
Left-outer-joining this table on your data ensures you get at least one row per time block (day), even if there were no alarms occuring that day:
SELECT
tr.RangeStart -- Use start of each time block to identify the block
,md.CompId -- With left outer join, will be 0 or more rows for each block
,sum(datediff(hh
,case
when tr.RangeStart > md.StartAlarmDate then tr.RangeStart
else md.StartAlarmDate
end
,case
when tr.RangeEnd > md.EndAlarmDate then tr.RangeEnd
else md.EndAlarmDate
end)) HoursInRange
from #TimeRanges tr
left outer join MyData md
on md.StartAlarmDate < tr.RangeEnd
and md.EndAlarmDate > tr.From
group by
tr.RangeStart
,md.CompId
(I can't test this code, some debugging may be required--but the concept is solid. I'll let you worry about rounding partial hours, and whether you want > and <, or >= and <= (things may get tricky if an alarm starts and/or ends at the exact same point in time as a block boundary).
Edit/Addenda
Here's a fairly basic way to set up the temp table used in the routine (this code, I tested):
-- Set up and initialize some variables
DECLARE
#FirstDay datetime
,#NumberOfDays int
SET #FirstDay = 'Oct 1, 2011' -- Without time, this makes it "midnight the morning of" that day
SET #NumberOfDays = 91 -- Through Dec 31
-- Creates a temporary table that will persist until it is dropped or the connection is closed
CREATE TABLE #TimeRanges
(
RangeStart datetime not null
,RangeEnd datetime not null
)
-- The order in which you add rows to the table is irrelevant. By adding from last to first, I
-- only have to fuss around with one variable, instead of two (the counter and the end-point)
WHILE #NumberOfDays >= 0
BEGIN
INSERT #TimeRanges (RangeStart, RangeEnd)
values ( dateadd(dd, #NumberOfDays, #FirstDay) -- Start of day
,dateadd(dd, #NumberOfDays + 1, #FirstDay)) -- Start of the next day
SET #NumberOfDays = #NumberOfDays - 1
END
-- Review results
SELECT *
from #TimeRanges
order by RangeStart
-- Not necessary, but doesn't hurt, especially when testing code
DROP TABLE #TimeRanges
Note that by making RangeEnd the start of the next day, you have to be careful with your greaterthans and lessthans. The details can get very finicky and fussy there, and you'll want to do a lot of edge-case testing (what if alarm starts, or ends, exactly at Dec 16 2011 00:00.000). I'd go with that, because overall it's simpler to code for than for junk like 'Dec 16, 2011 23:59.997'
As mentionned by #paulbailey, you want to use the DATEDIFF function to get the amount of downtime.
To extract the dates and downtime period (I'm adding a bit more columns that you might need)..
SELECT compid,
YEAR(startalarmdate) AS [Year],
MONTH(startalarmdate) AS [Month],
DAY(startalarmdate) AS [Day],
DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use
FROM YourTable
/* WHERE CLAUSE - Most probably a date range */
Now this gives you the downtime in seconds for each days that had a downtime.
To get the amount of downtime per day is easy as grouping by day and SUMing up the downtimes (again adding more columns that you might need)..
SELECT compid,
[Year],
[Month],
[Day],
SUM(DowntimeInSeconds) AS TotalDowntimeInSeconds
FROM (SELECT compid,
YEAR(startalarmdate) AS [Year],
MONTH(startalarmdate) AS [Month],
DAY(startalarmdate) AS [Day],
DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use
FROM YourTable
/* WHERE CLAUSE - Most probably a date range */) AS GetDowntimes
GROUP BY compid, [Year], [Month], [Day]
ORDER BY [Year], [Month], [Day], compid
And I believe this should help you get where you want to.
Edit:
To have the days that have no downtime included in this result, you need to first have a list of ALL days present in a month. You take this list and you LEFT OUTER JOIN the result from the above query (you will have to remove the ORDER BY first).
The case statement in Philip Kelley's answer does not work, although the main principal of filling a temp table with dates and left joining stands true. For my version I've used the same variable to start - an input date and the number of days to report on.
DECLARE #StartDate DATETIME, #Days INT
SELECT #StartDate = GETDATE(),
#Days = 5
-- REMOVE ANY TIME FROM THE STARTDATE
SET #StartDate = DATEADD(DAY, 0, DATEDIFF(DAY, 0, #StartDate))
-- CREATE THE TEMP TABLE OF DATES USING THE SAME METHODOLOGY
DECLARE #Dates TABLE (AlarmDate SMALLDATETIME NOT NULL PRIMARY KEY)
WHILE (#Days > 0)
BEGIN
INSERT #Dates VALUES (DATEADD(DAY, #Days, #StartDate))
SET #Days = #Days - 1
END
-- NOW SELECT THE DATA
SELECT AlarmDate,
CompID,
CONVERT(DECIMAL(10, 2), ISNULL(SUM(DownTime), 0) / 3600.0) [DownTime]
FROM #Dates
LEFT JOIN
( SELECT DATEADD(DAY, 0, DATEDIFF(DAY, 0, StartAlarmDate)) [StartAlarmDate],
CompID,
DATEDIFF(SECOND, StartAlarmDate, CASE WHEN EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) THEN DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) ELSE EndAlarmDate END) [DownTime]
FROM [yourTable]
UNION ALL
SELECT DATEADD(DAY, 0, DATEDIFF(DAY, 0, EndAlarmDate)) [Date],
CompID,
DATEDIFF(SECOND, DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)), EndAlarmDate) [DownTime]
FROM [yourTable]
WHERE EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate))
) data
ON StartAlarmDate = AlarmDate
GROUP BY AlarmDate, CompID
I have used seconds for the date diff and divided by 3600.0 after the seconds have been summed up as 60 rows each with a difference of a minute would sum to 0 when using hours for a datediff.

SQL Server Non-Standard Date Based Histogram

I have user login data with timestamps and what I would like to do is get a histogram of logins by year, but with the year starting at an arbitrary date. For example, I want the following sort of information:
1 May 2005 - 30 Apr 2006 | 525
1 May 2006 - 30 Apr 2007 | 673
1 May 2007 - 30 Apr 2008 | 892
1 May 2006 - 30 Apr 2009 | 1047
The labels in the first column are not important, but the date ranges are. I know I can break it down by strait years with:
SELECT YEAR([date]) AS [year], COUNT(*) AS cnt
FROM logins
GROUP BY YEAR([date])
ORDER BY [year]
But that doesn't give me the data ranges I want. How can this be done?
declare #baseDate datetime
set #baseDate = '1 May 2005'
SELECT
datediff(year, #baseDate, [date]) AS YearBucket
,COUNT(*) AS cnt
FROM logins
GROUP BY datediff(year, #baseDate, [date])
ORDER BY datediff(year, #baseDate, [date])
EDIT - apologies, you are correct. Here is a fixed version (I should have used a test table to start with...)
create table logins (date datetime, foo int)
insert logins values ('1 may 2005', 1)
insert logins values ('1 apr 2006', 2)
insert logins values ('1 may 2006', 3)
declare #baseDate datetime
set #baseDate = '1 May 2005'
SELECT
datediff(day, #baseDate, [date]) / 365 AS YearBucket
,COUNT(*) AS cnt
FROM logins
GROUP BY datediff(day, #baseDate, [date]) / 365
ORDER BY datediff(day, #baseDate, [date]) / 365
Change the datediff units if you want more granularity than days.
EDIT #2 - ok, here is a more robust solution that handles leap years :)
EDIT #3 - Actually this doesn't handle leap years, instead it allows for variable intervals of time to be specified. Go with dateadd(year, 1, #baseDate) for the leap year safe approach.
declare #baseDate datetime, #interval datetime
--#interval is expressed as time above 0 time (1/1/1900)
select #baseDate = '1 May 2005', #interval = '1901'
declare #timeRanges table (beginIntervalInclusive datetime, endIntervalExclusive datetime)
declare #i int
set #i = 1
while #i <= 10
begin
insert #timeRanges values(#baseDate, #baseDate + #interval)
set #baseDate = #baseDate + #interval
set #i = #i + 1
end
SELECT
tr.beginIntervalInclusive,
tr.endIntervalExclusive,
COUNT(*) AS cnt
FROM logins join #timeRanges as tr
on logins.date >= tr.beginIntervalInclusive
and logins.date < tr.endIntervalExclusive
GROUP BY tr.beginIntervalInclusive, tr.endIntervalExclusive
ORDER BY tr.beginIntervalInclusive
If you can find a way to define your date ranges in a separate table then select out a label and two columns of dates and join on that from your main query something like this depending on your tables.
Select Count(*) as NoLogons, DateRangeLabel
From logins a
inner join
(
Select
DateRangeLabel, StartDate, EndDate
From tblMyDates
) b
on a.date between b.startdate and b.enddate
Group by DateRangeLabel