Adding unique identifier to an Insert statement - sql-server

I have this piece of code that I want to use to add an unique identifier to, but I cannot seem to get it to work correctly. Here is the code along with current output and desired output.
Begin
DECLARE #StartDate DATETIME,
#EndDate DATETIME,
#MonthList as Varchar(50),
#NewLeaseID as int,
#LeaseID as int,
#PropertyID as int,
#Amount as int,
#ExpectedID as int
SELECT
#StartDate = '20100501'
,#EndDate = '20100801'
,#leaseID = 6,
#PropertyID = 12,
#Amount = 600,
#ExpectedID = (SELECT ISNULL(MAX(ExpectedPaymentID) + 1, 1) FROM Payments_ExpectedPayments)
INSERT INTO Payments_ExpectedPayments(ExpectedPaymentID, Amount, PropertyID, LeaseID, Month)
SELECT
#ExpectedID as ExpectedPaymentID,
(x.number + 1) * #Amount as Amount,
#PropertyID as PropertyID,
#leaseID as Leaseid,
DATENAME(MONTH, DATEADD(MONTH, x.number, #StartDate)) AS Month
FROM
master.dbo.spt_values x
WHERE
x.type = 'P'
AND x.number <= DATEDIFF(MONTH, #StartDate, #EndDate);
End
Output wanted:
ExpecedID PropertyID LeaseID Month Amount
1 12 13 Jan 600
2 12 13 Feb 1200
3 12 13 March 1800
4 12 13 April 2400
Output I'm currently getting:
ExpecedID PropertyID LeaseID Month Amount
1 12 13 Jan 600
1 12 13 Feb 1200
1 12 13 March 1800
1 12 13 April 2400

You have two options:
Your table design needs to be fixed. You can use an identity column for ExpectedId. Just change the CREATE TABLE from Expected int to Expected int identity(1,1).
You can use a ROW_NUMBER() on your INSERT like that: ROW_NUMBER() OVER(ORDER BY x.number) as ExpectedId

This is failing because you set the #ExpectedID once, before your select statement, hence you get all the same values.
Try substituting the #ExpectedID parameter with the following line in your SELECT statement.
(SELECT ISNULL(MAX(ExpectedPaymentID) + 1, 1) FROM Payments_ExpectedPayments) as ExpectedPaymentID
This will execute the select statement once per row, which I guess should work.

Related

How can I convert YYWW to date format based on day[Ex: Monday's date in given YYWW ] in SqlServer?

I have a column in my table with YYWW format. I need to convert this YYWW and get Monday's date.
For Example:
Input YYWW: 1847
Expected Output: 2018-11-19 [Monday's date in 2018 Week 47]
Thanks in advance
I tried the below but does not work properly
declare #value int = 1519
SELECT CONVERT(VARCHAR(10), DATEADD(YEAR, 2000 + #value / 100-1900, 7 * (#value % 100)-7), 105);
1851 -Expected 17-12-2018[Monday] Works fine for this year 2018
1752 -Expected 25-12-2017[Monday] but shows 24-12-2017 [Sunday]
1652 -Expected 26-12-2016 [Monday] but shows 24-12-2016 [Saturday]
1519 -Expected 04-05-2015 [Monday] but shows 07-05-2015 [Thursday]
Try this:
DECLARE #t table(YYWW char(4))
INSERT #t values('1847'),('1752'),('1652'),('1519')
SELECT
CAST(DATEADD(wk,RIGHT(YYWW,2)+DATEDIFF(d,0,DATEADD(
d,-4,LEFT(YYWW,2)+'0101'))/7,0) as date)
FROM #t
Result:
2018-11-19
2017-12-25
2016-12-26
2015-05-04
EDIT:
To get the requested format DD-MM-YYYY:
SELECT
CONVERT(CHAR(10),DATEADD(wk,RIGHT(YYWW,2)+DATEDIFF(d,0,DATEADD(
d,-4,LEFT(YYWW,2)+'0101'))/7,0),105)
FROM #t
I would, personally, use a calendar table. Then you can do something like:
SELECT YT.YYWW,
CT.[date]
FROM YourTable YT
JOIN CalendarTable CT ON CT.[Year] = '20'+LEFT(YT.YYWW,2)
AND CT.WeekNo = RIGHT(YT.YYWW,2)
AND CT.DayOfWeek = 1; --Assumes Monday is day 1.
I think your calc may be off week 47 of 2018 starts on 2018-11-26 which is a Monday.
in any case the following should work if you disagree with the above simply subtract 1 from the number of weeks
DECLARE #Date date
DECLARE #Year int = 2000 +18
declare #week int = 47
SET #Date = DATEADD(YEAR, #Year - 1900, 0)
SELECT dateadd(ww,#week-1,DATEADD(DAY, (##DATEFIRST - DATEPART(WEEKDAY, #Date) + (8 - ##DATEFIRST) * 2) % 7, #Date))

Stored Procedure to check if four weeks with less then two records

I am working on a stored procedure. The input is going to be a date and ID and the procedure is going to set a value to true if there are 4 weeks with less then 2 inputs per week.
It has to take in consideration that I might pass an early date with no records in the database.
I couldn't format the code. I don't know why?
So far I got that with previous help from you guys:
CREATE proc [dbo].[sp_test] (#id int, #d date)
as
declare #WeekFirstRecord as int
declare #WeeksWithNoRecords as int
SET #WeekFirstRecord = datepart(week,(select Min(ZeroPointIncidentDate) from EmployeeZeroPointIncidents where ZeroPointIncidentDate > #d))
SET #WeeksWithNoRecords = datepart(week, #WeekFirstRecord) - datepart(week, #d)
select case when sum(c) + #WeeksWithNoRecords >= 4 then 'true' else 'false' end status
from (
select c = count(*) over (partition by EmpId, datepart(week, ZeroPointIncidentDate))
from EmployeeZeroPointIncidents
where EmpId = #id and ZeroPointIncidentDate >= #d
) a
where c = 1
In my data only the weeks with the stars have less than two inputs and if I pass the date 7-7-2015 is going to set the output value to true
Any help will be appreciated. Do I need to iterate through every record and set a counter if less then two inputs or there is an easier way ?
ID Date
1 7-7-2015
2 6-23-2015
3 6-12-2015
1 7-8-2015
1 7-14-2015 *
1 7-21-2015 *
1 7-27-2015
1 7-28-2015
1 7-29-2015
1 7-30-2015
1 8-3-2015 *
1 8-11-2015 *
If I had week Jul 13 - no records week Jul 20 - no records week Jul 27 - 2 records Week Aug 3 - no records Week Aug 10 - 2 records Week Aug 17 - no records And pass Jul 12 date should return true, if I pass jul 15 should return false
I had to see your sample data set from your last question along with explanation from your last question as well as the explanation given in this question to come up with this solution.
When you ask a question here put yourself in the reader's shoes and see if the question makes any sense, anyway I hope this solution will get you what you want. cheers
CREATE PROCEDURE get_output
#Date DATE
,#ID INT
,#Output INT OUTPUT -- 1 true , 0 false
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Count INT;
SELECT #Count = COUNT(*)
FROM TableName
WHERE CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, #Date), 0) AS DATE)
= CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, [Date]), 0) AS DATE)
AND ID = #ID
GROUP BY CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, [Date]), 0) AS DATE)
IF (#Count < 2)
SET #Output = 1;
ELSE
SET #Output = 0;
END

T-SQL to create one data point for each hour over past 24 hours

Please how may we do this:
1) Generate 24 rows one for each hour from current time back 24 hours
2) Aggregate data from another table over the past 24 hours into these 24 data points.
I have seen solutions suggesting number tables from 0-23, but these might make it difficult if we need this to start from NOW, then run back 24 hours Get every hour for a time range
e.g [5:00am, 4:00am, 3:00am ... 12:am, 11pm ... 7:00am,6:00am]
Source Table:
select d,h,count(1)cnt from msgs
where dt>= DateAdd(hh, -24, sysdatetime())
group by d,h order by 1 desc,2 desc
Sample Data
d h cnt
2015-06-05 16 11
2015-06-05 13 44
2015-06-05 12 16
2015-06-05 11 31
2015-06-05 10 10
2015-06-05 9 12
2015-06-05 8 1
2015-06-04 21 1
2015-06-04 20 2
2015-06-04 18 5
2015-06-04 16 2
I have missing hours, i would need a query that fills out the missing hours with 0
As an alternative solution, you could use this query to provide all 24 hour ranges. Then simply aggregate and sum these values against your original query to return only 24 rows.
;WITH hrs AS
(
SELECT h = 1
UNION ALL
SELECT h + 1
FROM hrs
WHERE h + 1 <= 24
)
SELECT
d = left(convert(varchar(50),DateAdd(hour, -1 * h, getdate()), 21),10),
h = DatePart(hour, DateAdd(hour, -1 * h, getdate())),
cnt = 0
FROM hrs
You could try joining to this function:
CREATE FUNCTION ufn_Last24Hrs
(
#start DateTime2(7)
)
RETURNS #Result TABLE (d char(10), h int)
AS
BEGIN
DECLARE #current DateTime2(7) = #start
WHILE (#current > DateAdd(hour, -24, #start))
BEGIN
INSERT INTO #Result
VALUES
(
REPLACE(CONVERT(char(10), #current, 102) , '.', '-'),
DATEPART(hour, #current)
)
SET #current = DateAdd(hour, -1, #current)
END
RETURN;
END;
GO
SELECT * FROM ufn_Last24Hrs(SYSDATETIME());
SELECT
d,h,COUNT(1)cnt
FROM
ufn_Last24Hrs(SYSDATETIME()) hrs
left join msgs
ON msgs.d = hrs.d
and msgs.h = hrs.h
WHERE dt>= DateAdd(hour, -24, SYSDATETIME())
GROUP BY d,h
ORDER BY 1 DESC, 2 DES

Apply week number to dates for whole weeks only

The time is: (m/d/yyyy) => 2009/01/04
Using this command using datepart(wk,'20090104') I can get the week number (for any given date).
So :
SELECT datepart(wk,'20090101') //1
SELECT datepart(wk,'20090102') //1
SELECT datepart(wk,'20090103') //1
SELECT datepart(wk,'20090104') //2
So far so good.
The problem :
Those 3 first dates are not part of a full week, so I can't put them in a fixed 52-week chart.
Our company needs to see information about each whole week in the 52 weeks of a year. (Each year has 52 whole weeks).
So 20090101 doesn't belong to the first week of 2009 !
It belongs to the previous year (which is irrelevant to my question)
So I need a UDF (I've been searching a lot, and ISOWEEK is not answering my needs) which by a given datetime, will give me the Week Number (week = whole week, so partial weeks aren't considered).
Example :
calcweekNum ('20090101') //52 ...from the last year
calcweekNum ('20090102') //52 ...from the last year
calcweekNum ('20090103') //52 ...from the last year
calcweekNum ('20090104') //1
..
..
calcweekNum ('20090110') //1
calcweekNum ('20090111') //2
calcweekNum ('20090112') //2
...
Here's a different approach. All you need to supply is the year:
DECLARE #year INT = 2009;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH n AS
(
SELECT TOP (366) -- in case of leap year
d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_objects
),
x AS
(
SELECT md = MIN(d) FROM n
WHERE DATEPART(WEEKDAY, d) = 1 -- assuming DATEFIRST is Sunday
),
y(d,wk) AS
(
SELECT n.d, ((DATEPART(DAYOFYEAR, n.d) - DATEDIFF(DAY, #start, x.md)-1)/7) + 1
FROM n CROSS JOIN x
WHERE n.d >= x.md
AND n.d < DATEADD(YEAR, 1, #start)
)
SELECT [date] = d, [week] = wk
FROM y WHERE wk < 53
ORDER BY [date];
Results:
date week
---------- ----
2009-01-04 1
2009-01-05 1
2009-01-06 1
2009-01-07 1
2009-01-08 1
2009-01-09 1
2009-01-10 1
2009-01-11 2
2009-01-12 2
...
2009-12-25 51
2009-12-26 51
2009-12-27 52
2009-12-28 52
2009-12-29 52
2009-12-30 52
2009-12-31 52
Note that week 52 won't necessarily be a full week, and that in some cases (e.g. 2012), the last day or two of the year might fall in week 53, so they're excluded.
An alternative approach is to repeat the MIN expression twice:
DECLARE #year INT = 2009;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH n AS
(
SELECT TOP (366) -- in case of leap year
d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_objects
),
y(d,wk) AS
(
SELECT n.d, ((DATEPART(DAYOFYEAR, n.d) - DATEDIFF(DAY, #start, (SELECT MIN(d)
FROM n WHERE DATEPART(WEEKDAY, d) = 1))-1)/7) + 1
FROM n
WHERE n.d >= (SELECT md = MIN(d) FROM n WHERE DATEPART(WEEKDAY, d) = 1)
AND n.d < DATEADD(YEAR, 1, #start)
)
SELECT [date] = d, [week] = wk
FROM y WHERE wk < 53
ORDER BY d;
Here's a function for you to calculate it on the fly:
CREATE FUNCTION dbo.WholeWeekFromDate (
#Date datetime
)
RETURNS tinyint
AS BEGIN
RETURN (
SELECT DateDiff(Day, DateAdd(Year, DateDiff(Year, 0, CalcDate), 0), CalcDate) / 7 + 1
FROM (SELECT DateAdd(Day, (DateDiff(Day, 0, #Date) + 1) / 7 * 7, 0)) X (CalcDate)
);
END;
I don't recommend you use it, as it may perform badly due to being called once for every row. If you absolutely must have a function to use in real queries, then convert it to an inline function returning a single column and row, and use it as so:
SELECT
OtherColumns,
(SELECT WeekNumber FROM dbo.WholeWeekFromDate(DateColumn)) WeekNumber
FROM
YourTable;
This will allow it to be "inlined" in the execution plan and perform significantly better.
But even better, as others have suggested, is to use a BusinessDate table. Here's a head start on creating one for you:
CREATE TABLE dbo.BusinessDate (
BusinessDate date NOT NULL CONSTRAINT PK_BusinessDate PRIMARY KEY CLUSTERED,
WholeWeekYear smallint NOT NULL
CONSTRAINT CK_BusinessDate_WholeWeekYear_Valid
CHECK (WholeWeekYear BETWEEN 1900 AND 9999),
WholeWeekNumber tinyint NOT NULL
CONSTRAINT CK_BusinessDate_WholeWeekNumber_Valid
CHECK (WholeWeekNumber BETWEEN 1 AND 53),
Holiday bit CONSTRAINT DF_BusinessDate_Holiday DEFAULT (0),
Weekend bit CONSTRAINT DF_BusinessDate_Weekend DEFAULT (0),
BusinessDay AS
(Convert(bit, CASE WHEN Holiday = 0 AND Weekend = 0 THEN 1 ELSE 0 END)) PERSISTED
);
And I'll even populate it from 1900-01-01 through 2617-09-22 (Is that enough for the projected life of your product? And it's only 7.8MB so don't fret over size):
WITH A (N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
B (N) AS (SELECT 1 FROM A F, A A, A L, A C, A O, A N),
C (N) AS (SELECT Row_Number() OVER (ORDER BY (SELECT 1)) FROM B),
Dates AS (
SELECT
N,
DateAdd(Day, N, '18991231') Dte,
DateAdd(Day, N / 7 * 7, '19000101') CalcDate
FROM C
)
INSERT dbo.BusinessDate
SELECT
Dte,
Year(CalcDate),
DateDiff(Day, DateAdd(Year, DateDiff(Year, 0, CalcDate), 0), CalcDate) / 7 + 1,
0,
(N + 6) % 7 / 5 -- calculate weekends
FROM Dates; -- 3-7 seconds or so on my VM server
Then join to the table on the date, and use the WholeWeekNumber column for your output. You might also consider adding a WeekNumberYear because it's going to be a tad difficult to figure out that the 52 of 2009-01-01 really belongs to 2008 without this... a strange data point in there for sure if you don't (laugh).
Example table contents:
BusinessDate WholeWeekYear WholeWeekNumber Holiday Weekend BusinessDay
------------ ------------- --------------- ------- ------- -----------
1/1/2009 2008 52 0 0 1
1/2/2009 2008 52 0 0 1
1/3/2009 2008 52 0 1 0
1/4/2009 2009 1 0 1 0
1/5/2009 2009 1 0 0 1
1/6/2009 2009 1 0 0 1
1/7/2009 2009 1 0 0 1
1/8/2009 2009 1 0 0 1
1/9/2009 2009 1 0 0 1
1/10/2009 2009 1 0 1 0
1/11/2009 2009 2 0 1 0
If you really don't want to use this as a general business date calculation table, you can drop the last 3 columns, otherwise, update the Holiday column to 1 for company holidays.
Note: if you actually make the above table, and your access to it most often uses JOIN or WHERE conditions on a different column than BusinessDate, then make the primary key nonclustered and add a clustered index starting with the alternate column.
Some of the above scripts require SQL 2005 or higher.
It would be relatively easy to setup a custom calendar table with one row for each date of the year in it, and then have other fields that will allow you to rollup however you want. I do this when I have clients using varying calendars, i.e. fiscal years, and it makes the query logic very simple.
Then you just join date-to-date and get the week-number that you want.
date | reporting year | reporting week
-----------|----------------|---------------
2009-01-01 | 2008 | 52
2009-01-02 | 2008 | 52
2009-01-03 | 2008 | 52
2009-01-04 | 2009 | 01
2009-01-05 | 2009 | 01
etc.
and then to use it ( for example to get total sales rollup by your custom weeks, didn't validated my sql):
select reporting_year, reporting_month, sum(sales)
from sales
inner join custom_date_table cdt on cdt.sysdate = sales.sysdate
group by reporting_year, reporting_month
where report_year=2009
DECLARE #StartDate DATE;
SET #StartDate = '20120101';
WITH Calendar AS (
SELECT #StartDate AS DateValue
,DATEPART(DW, #StartDate) AS DayOfWeek
,CASE WHEN DATEPART(DW, #StartDate) = 1 THEN 1 ELSE 0 END AS WeekNumber
UNION ALL
SELECT DATEADD(d, 1, DateValue)
,DATEPART(DW, DATEADD(d, 1, DateValue)) AS DayOfWeek
,CASE WHEN DayOfWeek = 7 THEN WeekNumber + 1 ELSE WeekNumber END
FROM Calendar
WHERE DATEPART(YEAR, DateValue) = DATEPART(YEAR, #StartDate)
)
SELECT DateValue, WeekNumber
FROM Calendar
WHERE WeekNumber BETWEEN 1 AND 52
AND DATEPART(YEAR, DateValue) = DATEPART(YEAR, #StartDate)
OPTION (MAXRECURSION 0);
Don't use a UDF, use a calendar table instead, then you can define week numbers exactly as your company requires and simply query them from the table, which will be much easier and possibly much faster than using a UDF.
A calendar table has numerous uses in SQL (search this site or Google) so you should probably create it anyway.
There is no good answer for this.
A year is NOT 52 weeks long.
It is 52 weeks and one day in normal years, and 52 weeks and two days in leap years.

SQL Query to return 24 hour, hourly count even when no values exist?

I've written a query that groups the number of rows per hour, based on a given date range.
SELECT CONVERT(VARCHAR(8),TransactionTime,101) + ' ' + CONVERT(VARCHAR(2),TransactionTime,108) as TDate,
COUNT(TransactionID) AS TotalHourlyTransactions
FROM MyTransactions WITH (NOLOCK)
WHERE TransactionTime BETWEEN CAST(#StartDate AS SMALLDATETIME) AND CAST(#EndDate AS SMALLDATETIME)
AND TerminalId = #TerminalID
GROUP BY CONVERT(VARCHAR(8),TransactionTime,101) + ' ' + CONVERT(VARCHAR(2),TransactionTime,108)
ORDER BY TDate ASC
Which displays something like this:
02/11/20 07 4
02/11/20 10 1
02/11/20 12 4
02/11/20 13 1
02/11/20 14 2
02/11/20 16 3
Giving the number of transactions and the given hour of the day.
How can I display all hours of the day - from 0 to 23, and show 0 for those which have no values?
Thanks.
UPDATE
Using the tvf below works for me for one day, however I'm not sure how to make it work for a date range.
Using the temp table of 24 hours:
-- temp table to store hours of the day
DECLARE #tmp_Hours TABLE ( WhichHour SMALLINT )
DECLARE #counter SMALLINT
SET #counter = -1
WHILE #counter < 23
BEGIN
SET #counter = #counter + 1
--print
INSERT INTO #tmp_Hours
( WhichHour )
VALUES ( #counter )
END
SELECT MIN(CONVERT(VARCHAR(10),[dbo].[TerminalTransactions].[TransactionTime],101)) AS TDate, [#tmp_Hours].[WhichHour], CONVERT(VARCHAR(2),[dbo].[TerminalTransactions].[TransactionTime],108) AS TheHour,
COUNT([dbo].[TerminalTransactions].[TransactionId]) AS TotalTransactions,
ISNULL(SUM([dbo].[TerminalTransactions].[TransactionAmount]), 0) AS TransactionSum
FROM [dbo].[TerminalTransactions] RIGHT JOIN #tmp_Hours ON [#tmp_Hours].[WhichHour] = CONVERT(VARCHAR(2),[dbo].[TerminalTransactions].[TransactionTime],108)
GROUP BY [#tmp_Hours].[WhichHour], CONVERT(VARCHAR(2),[dbo].[TerminalTransactions].[TransactionTime],108), COALESCE([dbo].[TerminalTransactions].[TransactionAmount], 0)
Gives me a result of:
TDate WhichHour TheHour TotalTransactions TransactionSum
---------- --------- ------- ----------------- ---------------------
02/16/2010 0 00 4 40.00
NULL 1 NULL 0 0.00
02/14/2010 2 02 1 10.00
NULL 3 NULL 0 0.00
02/14/2010 4 04 28 280.00
02/14/2010 5 05 11 110.00
NULL 6 NULL 0 0.00
02/11/2010 7 07 4 40.00
NULL 8 NULL 0 0.00
02/24/2010 9 09 2 20.00
So how can I get this to group properly?
The other issue is that for some days there will be no transactions, and these days also need to appear.
Thanks.
You do this by building first the 23 hours table, the doing an outer join against the transactions table. I use, for same purposes, a table valued function:
create function tvfGetDay24Hours(#date datetime)
returns table
as return (
select dateadd(hour, number, cast(floor(cast(#date as float)) as datetime)) as StartHour
, dateadd(hour, number+1, cast(floor(cast(#date as float)) as datetime)) as EndHour
from master.dbo.spt_values
where number < 24 and type = 'p');
Then I can use the TVF in queries that need to get 'per-hour' basis data, even for missing intervals in the data:
select h.StartHour, t.TotalHourlyTransactions
from tvfGetDay24Hours(#StartDate) as h
outer apply (
SELECT
COUNT(TransactionID) AS TotalHourlyTransactions
FROM MyTransactions
WHERE TransactionTime BETWEEN h.StartHour and h.EndHour
AND TerminalId = #TerminalID) as t
order by h.StartHour
Updated
Example of a TVF that returns 24hours between any arbitrary dates:
create function tvfGetAnyDayHours(#dateFrom datetime, #dateTo datetime)
returns table
as return (
select dateadd(hour, number, cast(floor(cast(#dateFrom as float)) as datetime)) as StartHour
, dateadd(hour, number+1, cast(floor(cast(#dateFrom as float)) as datetime)) as EndHour
from master.dbo.spt_values
where type = 'p'
and number < datediff(hour,#dateFrom, #dateTo) + 24);
Note that since master.dbo.spt_values contains only 2048 numbers, the function will not work between dates further apart than 2048 hours.
You have just discovered the value of the NUMBERS table. You need to create a table with a single column containing the numbers 0 to 23 in it. Then you join again this table using an OUTER join to ensure you always get 24 rows returned.
So going back to using Remus' original function, I've re-used it in a recursive call and storing the results in a temp table:
DECLARE #count INT
DECLARE #NumDays INT
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #CurrentDay DATE
DECLARE #tmp_Transactions TABLE
(
StartHour DATETIME,
TotalHourlyTransactions INT
)
SET #StartDate = '2000/02/10'
SET #EndDate = '2010/02/13'
SET #count = 0
SET #NumDays = DateDiff(Day, #StartDate, #EndDate)
WHILE #count < #NumDays
BEGIN
SET #CurrentDay = DateAdd(Day, #count, #StartDate)
INSERT INTO #tmp_Transactions (StartHour, TotalHourlyTransactions)
SELECT h.StartHour ,
t.TotalHourlyTransactions
FROM tvfGetDay24Hours(#CurrentDay) AS h
OUTER APPLY ( SELECT COUNT(TransactionID) AS TotalHourlyTransactions
FROM [dbo].[TerminalTransactions]
WHERE TransactionTime BETWEEN h.StartHour AND h.EndHour
AND TerminalId = 4
) AS t
ORDER BY h.StartHour
SET #count = #Count + 1
END
SELECT *
FROM #tmp_Transactions
group by datepart('hour', thetime). to show those hours with no values you'd have to left join a table of times against the grouping (coalesce(transaction.amount, 0))
I've run into a version of this problem before. The suggestion that worked the best was to setup a table (temporary, or not) with the hours of the day, then do an outer join to that table and group by datepart('h', timeOfRecord).
I don't remember why, but probably due to lack of flexibility because of the need for the other table, I ended up using a method where I group by whatever datepart I want and order by the datetime, then loop through and fill any spaces that are skipped with a 0. This approach worked well for me because I'm not reliant on the database to do all my work for me, and it's also MUCH easier to write an automated test for it.
Step 1, Create #table or a CTE to generate a hours days table. Outer loop for days and inner loop hours 0-23. This should be 3 columns Date, Days, Hours.
Step 2, Write your main query to also have days and hours columns and alias it so you can join it. CTE's have to be above this main query and pivots should be inside CTE's for it to work naturally.
Step 3, Do a select from step 1 table and Left join this Main Query table
ON A.[DATE] = B.[DATE]
AND A.[HOUR] = B.[HOUR]
You can also create a order by if your date columns like
ORDER BY substring(CONVERT(VARCHAR(15), A.[DATE], 105),4,2)
Guidlines
This will then give you all data for hours and days and including zeros for hours with no matches to do that use isnull([col1],0) as [col1].
You can now graph facts against days and hours.

Resources