I am calculating Age of a user based on his date of birth.
select UserId, (Convert(int,Convert(char(8),GETDATE(),112))-Convert(char(8),[DateOfBirth],112))/10000 AS [Age] FROM dbo.[User]
This gives me the UserId and his age.
Now I want to group this result.
How many users are in 30's, How many users in 40's and how many users in their 50's.. need the count of users with their age groups
If the user is > 0 and less than 30 he should be grouped to 20's
If the user is >= 30 and < 40 then he should be added to 30's list, same with 40's and 50's
Can this be achieved without creating any temp table?
I believe this will get you what you want.
Anything < 30 will be placed in the '20' group.
Anything >= 50 will be placed in the '50' group.
If they are 30-39 or 40-49, they will be placed in their appropriate decade group.
SELECT y.AgeDecade, COUNT(*)
FROM dbo.[User] u
CROSS APPLY (SELECT Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000) x
CROSS APPLY (SELECT AgeDecade = CASE
WHEN x.Age <= 29 THEN 20
WHEN x.Age BETWEEN 30 AND 39 THEN 30
WHEN x.Age BETWEEN 40 AND 49 THEN 40
WHEN x.Age >= 50 THEN 50
ELSE NULL
END
) y
GROUP BY y.AgeDecade
Placing the logic into CROSS APPLY makes it easier to reuse the logic within the same query, this way you can use it in SELECT, GROUP BY, ORDER BY, WHERE, etc, without having to duplicate it. This could also be done using a cte, but in this scenario, this is my preference.
Update:
You asked in the comments how it would be possible to show a count of 0 when no people exist for an age group. In most cases the answer is simple, LEFT JOIN. As with everything, there's always more than one way to bake a cake.
Here are a couple ways you can do it:
The simple left join, take the query from my original answer, and just do a left join to a table. You could do this in a couple ways, CTE, temp table, table variable, sub-query, etc. The takeaway is, you need to isolate your User table somehow.
Simple Sub-query method, nothing fancy. Just stuck the whole query into a sub-query, then left joined it to our new lookup table.
DECLARE #AgeGroup TABLE (AgeGroupID tinyint NOT NULL);
INSERT INTO #AgeGroup (AgeGroupID) VALUES (20),(30),(40),(50);
SELECT ag.AgeGroupID, TotalCount = COUNT(a.AgeDecade)
FROM #AgeGroup ag
LEFT JOIN (
SELECT y.AgeDecade
FROM #User u
CROSS APPLY (SELECT Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000) x
CROSS APPLY (SELECT AgeDecade = CASE
WHEN x.Age <= 29 THEN 20
WHEN x.Age BETWEEN 30 AND 39 THEN 30
WHEN x.Age BETWEEN 40 AND 49 THEN 40
WHEN x.Age >= 50 THEN 50
ELSE NULL
END
) y
) a ON a.AgeDecade = ag.AgeGroupID
GROUP BY ag.AgeGroupID;
This would be the exact same thing as using a cte:
DECLARE #AgeGroup TABLE (AgeGroupID tinyint NOT NULL);
INSERT INTO #AgeGroup (AgeGroupID) VALUES (20),(30),(40),(50);
WITH cte_Users AS (
SELECT y.AgeDecade
FROM #User u
CROSS APPLY (SELECT Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000) x
CROSS APPLY (SELECT AgeDecade = CASE
WHEN x.Age <= 29 THEN 20
WHEN x.Age BETWEEN 30 AND 39 THEN 30
WHEN x.Age BETWEEN 40 AND 49 THEN 40
WHEN x.Age >= 50 THEN 50
ELSE NULL
END
) y
)
SELECT ag.AgeGroupID, TotalCount = COUNT(a.AgeDecade)
FROM #AgeGroup ag
LEFT JOIN cte_Users a ON a.AgeDecade = ag.AgeGroupID
GROUP BY ag.AgeGroupID;
Choosing between the two is purely preference. There's no performance gain to using a CTE here.
Bonus:
If you wanted to table drive your groups and also have 0 counts, you could do something like this...though I will warn you to be careful using APPLY operators because they can be tricky with performance sometimes.
IF OBJECT_ID('tempdb..#User','U') IS NOT NULL DROP TABLE #User; --SELECT * FROM #User
WITH c1 AS (SELECT x.x FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) x(x)) -- 10
, c2(x) AS (SELECT 1 FROM c1 x CROSS JOIN c1 y) -- 10 * 10
SELECT UserID = IDENTITY(int,1,1)
, DateOfBirth = CONVERT(date, GETDATE()-(RAND(CHECKSUM(NEWID()))*18500))
INTO #User
FROM c2 u;
IF OBJECT_ID('tempdb..#AgeRange','U') IS NOT NULL DROP TABLE #AgeRange; --SELECT * FROM #AgeRange
CREATE TABLE #AgeRange (
AgeRangeID tinyint NOT NULL IDENTITY(1,1),
RangeStart tinyint NOT NULL,
RangeEnd tinyint NOT NULL,
RangeLabel varchar(100) NOT NULL,
);
INSERT INTO #AgeRange (RangeStart, RangeEnd, RangeLabel)
VALUES ( 0, 29, '< 29')
, (30, 39, '30 - 39')
, (40, 49, '40 - 49')
, (50, 255, '50+');
-- Using an OUTER APPLY
SELECT ar.RangeLabel, COUNT(y.UserID)
FROM #AgeRange ar
OUTER APPLY (
SELECT u.UserID
FROM #User u
CROSS APPLY (SELECT Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000) x
WHERE x.Age BETWEEN ar.RangeStart AND ar.RangeEnd
) y
GROUP BY ar.RangeLabel, ar.RangeStart
ORDER BY ar.RangeStart;
-- Using a CTE
WITH cte_users AS (
SELECT u.UserID
, Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000
FROM #User u
)
SELECT ar.RangeLabel, COUNT(u.UserID)
FROM #AgeRange ar
LEFT JOIN cte_users u ON u.Age BETWEEN ar.RangeStart AND ar.RangeEnd
GROUP BY ar.RangeStart, ar.RangeLabel
ORDER BY ar.RangeStart;
I would start by putting the age computation in a lateral join, so it can easily be referred to. Then, if you want the age groups as rows, you can join a derived table that describes the intervals:
select v.age_group, count(*) as cnt_users
from dbo.[User] u
cross apply (values
((convert(int, convert(char(8), getdate(),112)) - convert(char(8), u.[DateOfBirth], 112))/10000)
) a(age)
inner join (values
( 0, 30, '0-30'),
(30, 40, '30-40'),
(40, 50, '40-50'),
(50, null, '50+')
) v(min_age, max_age, age_group)
on a.age >= v.min_age
and (a.age < v.max_age or v.max_age is null)
group by v.age_group
On the other hands, if you want the counts in columns, use conditional aggregation:
select
sum(case when a.age < 30 then 1 else 0 end) as age_0_30,
sum(case when a.age >= 30 and a.age < 40 then 1 else 0 end) as age_30_40,
sum(case when a.age >= 40 and a.age < 50 then 1 else 0 end) as age_40_50,
sum(case when a.age >= 50 then 1 else 0 end) as age_50
from dbo.[User] u
cross apply (values
((convert(int, convert(char(8), getdate(),112)) - convert(char(8), [DateOfBirth], 112))/10000)
) a(age)
yes you can.
this query should work with you
SELECT STR(ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) + 's' AS [Age Group], COUNT(UserId) AS Count
FROM dbo.User
GROUP BY STR(ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) + 's'
for your updated question
SELECT CASE
WHEN (ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) < 30 THEN '20s'
WHEN (ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) >= 50 THEN '50s'
ELSE str(ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) + 's'
END AS [Age Group], COUNT(UserId) AS Count
FROM dbo.User
GROUP BY CASE
WHEN (ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) < 30 THEN '20s'
WHEN (ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) >= 50 THEN '50s'
ELSE str(ROUND(DATEDIFF(year, DateOfBirth, GETDATE()), - 1) - 10) + 's'
END
You could use round with a length argument of -1 and a non-zero function argument to truncate the value to "tens", and group by it:
SELECT UserId,
Round((Convert(int,Convert(char(8),GETDATE(),112))-Convert(char(8),[DateOfBirth],112))/10000, -1, 1) AS [Rounded Age],
Count(*)
FROM dbo.[User]
GROUP BY Round((Convert(int,Convert(char(8),GETDATE(),112))-Convert(char(8),[DateOfBirth],112))/10000, -1, 1)
Related
Using below query i am trying to get how many users are falling under which age group, for this i applied cross apply.
SELECT y.AgeDecade, COUNT(u.[UserId]) AS [TotalUsers]
FROM dbo.[User] u
CROSS APPLY (SELECT Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000) x
CROSS APPLY (SELECT AgeDecade = CASE
WHEN x.Age <= 29 THEN 20
WHEN x.Age BETWEEN 30 AND 39 THEN 30
WHEN x.Age BETWEEN 40 AND 49 THEN 40
WHEN x.Age >= 50 THEN 50
ELSE NULL
END
) y
GROUP BY y.AgeDecade
Now besides [TotalUsers] i want to have another column [Percentage] which should give me the Percentage of that row across entire column.
I tried using 100 * COUNT(U.[UserId]) / SUM(U.[UserId]) but i see an erros stating UserId is not present in the select clause.. how do i get the percentage in this case ?
Use window functions:
SELECT y.AgeDecade, COUNT(*) AS [TotalUsers],
100.0 * COUNT(*) / SUM(COUNT(*)) OVER() as [Percent]
FROM dbo.[User] u
CROSS APPLY (SELECT Age = (CONVERT(int, CONVERT(char(8), GETDATE(), 112)) - CONVERT(int, CONVERT(char(8), u.DateOfBirth, 112))) / 10000) x
CROSS APPLY (SELECT AgeDecade = CASE
WHEN x.Age <= 29 THEN 20
WHEN x.Age BETWEEN 30 AND 39 THEN 30
WHEN x.Age BETWEEN 40 AND 49 THEN 40
WHEN x.Age >= 50 THEN 50
ELSE NULL
END
) y
GROUP BY y.AgeDecade
Note: presumaby u.[UserId] is never NULL, so COUNT(u.[UserId]) can be simplified as COUNT(*).
I get a list of new accounts twice a month. Majority of my accounts have either gone through the funnel or can be categorized as a bad lead by the end of the third month. So I am working on sql query that will tell me the number of accounts in my funnel at each date when I received a new list.
Here is what I have been working with (Yes I joined table1 on itself):
select t.receiveddate, count(*)
from table1 t
join table1 t2 on t2.number = t.number
and (t2.receiveddate > Dateadd(month , -3, t.received) AND t2.receiveddate<=
t.receiveddate)
group by t.receiveddate
What I am hoping to end up with is a list of the dates that I receive new business, with a count of how many accounts are in the funnel (accounts that I received no more than 3 months ago). The count should include the new accounts received on that date as well.
Here is an example, lets assume the business started on 1/1/2000, there is no one in the funnel for the first count. Lets also assume that I get 100 new accounts every time, just to make things simple for this example.
receiveddate Count
1/1/2000 100
1/15/2000 200
2/1/2000 300
2/15/2000 300
3/1/2000 300
3/15/2000 300
Welcome to Stack Overflow!
I see where you were going with that, but I would use a correlated sub-query, instead. I've included my sample data, which I randomized to make it similar to the real world - you can use an Update statement to change them all to 100 to validate the query):
create table Pipeline
(
ID int identity
, Received_Date Date
, Received_Count Int
)
Insert Into Pipeline
select '01/01/2018', 75 + (RAND() * 50)
union all select '01/15/2018', 75 + (RAND() * 50)
union all select '02/01/2018', 75 + (RAND() * 50)
union all select '02/15/2018', 75 + (RAND() * 50)
union all select '03/01/2018', 75 + (RAND() * 50)
union all select '03/15/2018', 75 + (RAND() * 50)
union all select '04/01/2018', 75 + (RAND() * 50)
union all select '04/15/2018', 75 + (RAND() * 50)
union all select '05/01/2018', 75 + (RAND() * 50)
union all select '05/15/2018', 75 + (RAND() * 50)
union all select '06/01/2018', 75 + (RAND() * 50)
union all select '06/15/2018', 75 + (RAND() * 50)
select * from Pipeline -- so you can see the values you got
select
P1.Received_Date
, (Select Sum(P2.Received_Count)
from Pipeline as P2
Where P2.Received_Date > DateAdd(MONTH, -3, P1.Received_Date)
and P2.Received_Date <= P1.Received_Date
) As Pipeline_Total
from Pipeline as P1
Your question is not entirely clear. I assume that you have a table of accounts, and for each account you have the received-date.
-- One record per account, each with a received-date
create table Account ( AccountID int identity(1,1),
ReceivedDate date )
-- Populate with 1000 random received-dates.
-- DateAdd(month,... gives a range of 60 months
-- DateAdd(day,... forces either 1st or 15th of the month
-- Of course, the query below will work regardless of the
-- distribution of dates
declare #k int
set #k = 0
while #k < 1000
begin
insert into Account ( ReceivedDate ) values
( DateAdd ( day, 14*cast( 2*rand()as int),
DateAdd ( month, cast(60*rand()as int), '2000-01-01' ) ) )
set #k = #k + 1
end
-- Let's see the list of dates
select * from Account
-- T1 is the list of DISTINCT received-dates
-- Other than that this query almost matches your own attempt
select T1.ReceivedDate, count(*) as InFunnel
from
(select distinct ReceivedDate from Account) T1,
Account T2
where T2.ReceivedDate > DateAdd ( month, -3, T1.ReceivedDate )
and T2.ReceivedDate <= T1.ReceivedDate
group by T1.ReceivedDate
order by T1.ReceivedDate
DBMS is intersystems-cache!
Here is my full query:
SELECT m.Name AS MessageType, COUNT(l.name) AS MessageCount, CAST(AVG(ResponseTime) AS DECIMAL(5, 2)) AS AvgResponseTime
FROM
(SELECT DISTINCT(name) FROM ENSLIB_HL7.Message) m LEFT JOIN
(
SELECT CAST(li.SessionId AS Bigint) AS session_id, li.name, MIN(li.TimeCreated) AS SessionStart, MAX(lo.TimeCreated) AS SessionEnd, CAST(DATEDIFF(s, MIN(li.TimeCreated), MAX(lo.TimeCreated)) AS DECIMAL(5, 2)) AS ResponseTime
FROM (SELECT h1.SessionId, h1.TimeCreated, $PIECE(RawContent, '|', 4), m1.name FROM ens.messageheader h1, ENSLIB_HL7.Message m1 WHERE h1.MessageBodyId = m1.id AND h1.TimeCreated > DATEADD(mi, -30, GETUTCDATE())) li
JOIN (SELECT h2.SessionId, h2.TimeCreated FROM ens.messageheader h2, ENSLIB_HL7.Message m2 WHERE h2.MessageBodyId = m2.id AND h2.TimeCreated > DATEADD(mi, -30, GETUTCDATE())) lo
ON li.SessionId = lo.SessionId
GROUP BY li.SessionId
) l on m.name = l.name
GROUP BY l.Name
This gives me 4 results:
VXU_V04 0 (null)
ADT_A03 3 0.01
ADT_A04 3 0.01
ADT_A08 143 0.01
Given that there is one result with 0 records, it seems like it is working. However, if I run SELECT DISTINCT(name) FROM ENSLIB_HL7.Message I get 10 results:
VXU_V04
ADT_A08
ACK_A08
ADT_A03
ACK_A03
ADT_A04
ACK_A04
ACK_V04
ADT_A01
ACK_A01
Why don't I get ten rows with my full query?
Change the GROUP BY to:
GROUP BY m.Name
You are aggregating by the column in the second table, so you only get one row for all the NULL values.
Most databases would reject this syntax, but apparently Intersystems Cache allows it.
I have a simplified table called Bookings that has two columns BookDate and BookSlot. The BookDate column will have dates only (no time) and the BookSlot column will contain the time of the day in intervals of 30 minutes from 0 to 1410 inclusive. (i.e. 600 = 10:00am)
How can I find the first slot available in the future (not booked) without running through a loop?
Here is the table definition and test data:
Create Table Bookings(
BookDate DateTime Not Null,
BookSlot Int Not Null
)
Go
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',0);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',30);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',60);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',630);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-02',60);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-02',90);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-02',120);
I want a way to return the first available slot that is not in the table and that is in the future (based on server time).
Based on above test data:
If the current server time was 1st Jul, 00:10am, the result should be 1st Jul, 90min (01:30am).
If the current server time was 2nd Jul, 01:05am, the result should be 2nd Jul, 150min (02:30am).
If there are no bookings in the future, the function would simply return the closest half-hour in the future.
--
SQL Fiddle for this is here:
http://sqlfiddle.com/#!6/0e93d/1
Below is one method that will allow bookings up to 256 days in the future, and allow for an empty Booking table. I assume you are using SQL Server 2005 since your BookDate is dateTime instead of date.
In any case, you might consider storing the slots as a complete datetime instead of separate columns. That will facilitate queries and improve performance.
DECLARE #now DATETIME = '2014-07-01 00:10:00';
WITH T4
AS (SELECT N
FROM (VALUES(0),
(0),
(0),
(0),
(0),
(0),
(0),
(0)) AS t(N)),
T256
AS (SELECT Row_number()
OVER(
ORDER BY (SELECT 0)) - 1 AS n
FROM T4 AS a
CROSS JOIN T4 AS b
CROSS JOIN T4 AS c),
START_DATE
AS (SELECT Dateadd(DAY, Datediff(DAY, '', #now), '') AS start_date),
START_TIME
AS (SELECT Dateadd(MINUTE, Datediff(MINUTE, '', #now) / 30 * 30, '') AS
start_time),
DAILY_INTERVALS
AS (SELECT N * 30 AS interval
FROM T256
WHERE N < 48)
SELECT TOP (1) Dateadd(DAY, future_days.N, START_DATE) AS BookDate,
DAILY_INTERVALS.INTERVAL AS BookSlot
FROM START_DATE
CROSS APPLY START_TIME
CROSS APPLY DAILY_INTERVALS
CROSS APPLY T256 AS future_days
WHERE Dateadd(MINUTE, DAILY_INTERVALS.INTERVAL,
Dateadd(DAY, future_days.N, START_DATE)) > START_TIME
AND NOT EXISTS(SELECT *
FROM DBO.BOOKINGS
WHERE BOOKDATE = START_DATE
AND BOOKSLOT = DAILY_INTERVALS.INTERVAL)
ORDER BY BOOKDATE,
BOOKSLOT;
See this SQL Fiddle
It's a bit complicated but try this:
WITH DATA
AS (SELECT *,
Row_number()
OVER (
ORDER BY BOOKDATE, BOOKSLOT) RN
FROM BOOKINGS)
SELECT CASE
WHEN T.BOOKSLOT = 1410 THEN Dateadd(DAY, 1, BOOKDATE)
ELSE BOOKDATE
END Book_Date,
CASE
WHEN T.BOOKSLOT = 1410 THEN 0
ELSE BOOKSLOT + 30
END Book_Slot
FROM (SELECT TOP 1 T1.*
FROM DATA T1
LEFT JOIN DATA t2
ON t1.RN = T2.RN - 1
WHERE t2.BOOKSLOT - t1.BOOKSLOT > 30
OR ( t1.BOOKDATE != T2.BOOKDATE
AND ( t2.BOOKSLOT != 0
OR t1.BOOKSLOT != 630 ) )
OR t2.BOOKSLOT IS NULL)T
Here is the SQL fiddle example.
Explanation
This solution contains 2 parts:
Comparing each line to the next and checking for a gap (can be done easier in SQL 2012)
Adding a half an hour to create the next slot, this includes moving to the next day if needed.
Edit
Added TOP 1 in the query so that only the first slot is returned as requested.
Update
Here is the updated version including 2 new elements (getting current date+ time and dealing with empty table):
DECLARE #Date DATETIME = '2014-07-01',
#Slot INT = 630
DECLARE #time AS TIME = Cast(Getdate() AS TIME)
SELECT #Slot = Datepart(HOUR, #time) * 60 + Round(Datepart(MINUTE, #time) / 30,
0) * 30
+ 30
SET #Date = Cast(Getdate() AS DATE)
;WITH DATA
AS (SELECT *,
Row_number()
OVER (
ORDER BY BOOKDATE, BOOKSLOT) RN
FROM BOOKINGS
WHERE BOOKDATE > #Date
OR ( BOOKDATE = #Date
AND BOOKSLOT >= #Slot ))
SELECT TOP 1 BOOK_DATE,
BOOK_SLOT
FROM (SELECT CASE
WHEN RN = 1
AND NOT (#slot = BOOKSLOT
AND #Date = BOOKDATE) THEN #Date
WHEN T.BOOKSLOT = 1410 THEN Dateadd(DAY, 1, BOOKDATE)
ELSE BOOKDATE
END Book_Date,
CASE
WHEN RN = 1
AND NOT (#slot = BOOKSLOT
AND #Date = BOOKDATE) THEN #Slot
WHEN T.BOOKSLOT = 1410 THEN 0
ELSE BOOKSLOT + 30
END Book_Slot,
1 AS ID
FROM (SELECT TOP 1 T1.*
FROM DATA T1
LEFT JOIN DATA t2
ON t1.RN = T2.RN - 1
WHERE t2.BOOKSLOT - t1.BOOKSLOT > 30
OR ( t1.BOOKDATE != T2.BOOKDATE
AND ( t2.BOOKSLOT != 0
OR t1.BOOKSLOT != 1410 ) )
OR t2.BOOKSLOT IS NULL)T
UNION
SELECT #date AS bookDate,
#slot AS BookSlot,
2 ID)X
ORDER BY X.ID
Play around with the SQL fiddle and let me know what you think.
In SQL Server 2012 and later, you can use the lead() function. The logic is a bit convoluted because of all the boundary conditions. I think this captures it:
select top 1
(case when BookSlot = 1410 then BookDate else BookDate + 1 end) as BookDate,
(case when BookSlot = 1410 then 0 else BookSlot + 30 end) as BookSlot
from (select b.*,
lead(BookDate) over (order by BookDate) as next_dt,
lead(BookSlot) over (partition by BookDate order by BookSlot) as next_bs
from bookings b
) b
where (next_bs is null and BookSlot < 1410 or
next_bs - BookSlot > 30 or
BookSlot = 1410 and (next_dt <> BookDate + 1 or next_dt = BookDate and next_bs <> 0)
)
order by BookDate, BookSlot;
Using a tally table to generate a list of originally available booking slots out 6 weeks (adjustable below):
declare #Date as date = getdate();
declare #slot as int = 30 * (datediff(n,#Date,getdate()) /30);
with
slots as (
select (ROW_NUMBER() over (order by s)-1) * 30 as BookSlot
from(
values (1),(1),(1),(1),(1),(1),(1),(1) -- 4 hour block
)slots(s)
cross join (
values (1),(1),(1),(1),(1),(1) -- 6 blocks of 4 hours each day
)QuadHours(t)
)
,days as (
select (ROW_NUMBER() over (order by s)-1) + getdate() as BookDate
from (
values (1),(1),(1),(1),(1),(1),(1) -- 7 days in a week
)dayList(s)
cross join (
-- set this to number of weeks out to allow bookings to be made
values (1),(1),(1),(1),(1),(1) -- allow 6 weeks of bookings at a time
)weeks(t)
)
,tally as (
select
cast(days.BookDate as date) as BookDate
,slots.BookSlot as BookSLot
from slots
cross join days
)
select top 1
tally.BookDate
,tally.BookSlot
from tally
left join #Bookings book
on tally.BookDate = book.BookDate
and tally.BookSlot = book.BookSlot
where book.BookSlot is null
and ( tally.BookDate > #Date or tally.BookSlot > #slot )
order by tally.BookDate,tally.BookSlot;
go
try this:
SELECT a.bookdate, ((a.bookslot/60.)+.5) * 60
FROM bookings a LEFT JOIN bookings b
ON a.bookdate=b.bookdate AND (a.bookslot/60.)+.50=b.bookslot/60.
WHERE b.bookslot IS null
This is piece of a larger query I have. I want to return a list of all profit centers, with the incident count for those within the date range. I want the profit center listed, even if no incidents were reported in the date range. This has been working fine.
However now some records have been introduced where the profit center is NULL. I would like for those to show up in the list as 'N/A' with the incident count for the NULLs.
SELECT DISTINCT cast(plants.profit_ctr as varchar(50)) + ' - ' + cast(plants.profit_ctr_name as varchar(50)) AS Profit_Center, COALESCE(h.Incident_Count, 0) AS Incident_Count
FROM JMART.[safety].vwHELPForms AS plants
LEFT OUTER JOIN
(SELECT profit_ctr, COUNT(*) AS Incident_Count
FROM JMART.[safety].vwHELPForms
WHERE cast(rec_date as date) >= cast(#startdate as date)
AND cast(rec_date as date) <= cast(#enddate as date) AND ratings > 0
GROUP BY profit_ctr) AS h
ON h.profit_ctr = plants.profit_ctr
How can I accomplish this?
EDIT
If I run
SELECT profit_ctr, COUNT(*) AS Incident_Count
FROM JMART.[safety].vwHELPForms
WHERE cast(rec_date as date) >= cast(#startdate as date)
AND cast(rec_date as date) <= cast(#enddate as date) AND ratings > 0
GROUP BY profit_ctr
I get
NULL 295
101100 7483
101150 116
101200 445
101400 3784
I've tried
SELECT DISTINCT cast(plants.profit_ctr as varchar(50)) + ' - ' + cast(plants.profit_ctr_name as varchar(50)) AS Profit_Center,
COALESCE((SELECT COUNT(*) AS Incident_Count FROM JMART.[safety].vwHELPForms AS h
WHERE profit_ctr = plants.profit_ctr AND cast(rec_date as date) >= cast(#startdate as date)
AND cast(rec_date as date) <= cast(#enddate as date) AND ratings > 0
GROUP BY profit_ctr), 0) AS Incident_Count
FROM JMART.[safety].vwHELPForms AS plants
order by Profit_Center
which gives me (same as what I'm currently getting)
NULL 0
101100 7483
101150 116
101200 445
101400 3784
I want
N/A 295
101100 7483
101150 116
101200 445
101400 3784
The NULL values are going into the second table in your join, rather than the first. So, they are lost by a left outer join. Instead, you want to use a full outer join:
SELECT DISTINCT
coalesce(cast(plants.profit_ctr as varchar(50)) + ' - ' + cast(plants.profit_ctr_name as varchar(50)), 'N/A') AS Profit_Center,
COALESCE(h.Incident_Count, 0) AS Incident_Count
FROM JMART.[safety].vwHELPForms plants full OUTER JOIN
(SELECT profit_ctr, COUNT(*) as Incident_Count
FROM JMART.[safety].vwHELPForms
WHERE cast(rec_date as date) >= cast(#startdate as date) AND
cast(rec_date as date) <= cast(#enddate as date) AND ratings > 0
GROUP BY profit_ctr
) h
ON h.profit_ctr = plants.profit_ctr
Assuming that profit_ctr is not repeated in the plants table, you can dispense with the distinct. It adds unnecessary processing and may not be needed.
Since you are using LEFT JOIN, null profit centers will show up in result as NULL. And since you are using DISTINCT they will be collapsed to one row. And for the Incident_Count column you can use coalesce(cast(h.Incident_Count as varchar(50)), 'N/A')
PS. I assumed you are using MS SQL Server 2012.