Help with cross join creating a week view calendar - sql

Hi all i have a table called bookings like this ( Ive bolded the columns to align)
CustID VenueID BookingDt Session
45 44 2010-03-20 00:00:00.000 PM
45 44 2010-03-27 00:00:00.000 PM
45 44 2009-10-18 00:00:00.000 PM
45 44 2009-10-24 00:00:00.000 PM
I have another table called Venues
oID oLocation oPitch
1 Left Park Rugby
2 Right Park Rugby
The tables are inter joined by Venues.oID=bookings .CustID
i want to make a table such as this
X Column = week days
Y column = locations
oID oSun oMon oTue oWed oThu oFri oSat
1 x x x x
2 x x x x x x x
I believe i have to do a cross join with the data from the bookings Database
Eg
select distinct v.olocation , b.BookingDt from oVenue V
cross join tblBookings B
Where B.VenueID=V.oID
and DATEPART( wk, b.BookingDt )='44'
and DATEPART( yy, b.BookingDt )='2009'
But this does oID and Date, i want it to do check to see if that date is there, if so place a x in its place other wise place a '' in its place.
Not sure the best way to proceed.
Any help is muchly appreciated.
Thanks in advance

Since you already restricted the week and year in your query, this is how to display it:
select
v.olocation,
max(case DATEPART(weekday, b.BookingDt) When 1 then 'x' else '' end) Sun,
max(case DATEPART(weekday, b.BookingDt) When 2 then 'x' else '' end) Mon,
max(case DATEPART(weekday, b.BookingDt) When 3 then 'x' else '' end) Tue,
max(case DATEPART(weekday, b.BookingDt) When 4 then 'x' else '' end) Wed,
max(case DATEPART(weekday, b.BookingDt) When 5 then 'x' else '' end) Thu,
max(case DATEPART(weekday, b.BookingDt) When 6 then 'x' else '' end) Fri,
max(case DATEPART(weekday, b.BookingDt) When 7 then 'x' else '' end) Sat
from
(
select distinct v.olocation , b.BookingDt
from oVenue V
LEFT JOIN tblBookings B on B.VenueID=V.oID
and DATEPART( wk, b.BookingDt )='44'
and DATEPART( yy, b.BookingDt )='2009'
) selweek
group by v.olocation

How the data is displayed should be a front-end issue, not a database issue. I wouldn't concentrate on things like putting "x" in a specific spot. Return the data that your application needs to fill in your calendar and have the front-end do that.
That said, in order to create results like what you're looking for, you're missing a set of data - the set of calendar days. You can do this with a temporary table, a CTE, or a permanent table in your database, but you basically need a table that gives you all of the days in question as a resultset. You can then LEFT OUTER JOIN from that table to your bookings table and use CASE to fill in values based on whether or not a matched bookings row was found.

Related

Outer Column reference in an aggregate function of a cross apply

In my query, I am using OUTER APPLY to get employee count in different scenarios like
Number of Employees Joined in each day of a period
Number of Employees Resigned in each day of a period
Number of employees leave on each day of a period... etc
Expected output (From:2017-01-10 to 2017-01-12 ) is
CDATE TOTAL_COUNT JOIN_COUNT RESIGNED _COUNT ...
2017-01-10 1204 10 2
2017-01-11 1212 5 1
2017-01-12 1216 3 0
Below is my query
DECLARE #P_FROM_DATE DATE = '2017-01-01', --From 1st Jan
#P_TO_DATE DATE = '2017-01-10' --to 10th jan
;WITH CTE_DATE
AS
(
SELECT #P_FROM_DATE AS CDATE
UNION ALL
SELECT DATEADD(DAY,1,CDATE)
FROM CTE_DATE
WHERE DATEADD(DAY,1,CDATE) <= #P_TO_DATE
)
SELECT [CDATE]
,[TOTAL_COUNT]
,[JOIN_COUNT]
FROM CTE_DATE
OUTER APPLY (
SELECT COUNT(CASE WHEN [EMP_DOJ] = [CDATE] THEN 1 ELSE NULL END) AS [JOIN_COUNT]
,COUNT(*) AS [TOTAL_COUNT]
,....
,...
FROM [EMPLOYEE_TABLE]
) AS D
But while executing my query, getting the below error.
Msg 8124, Level 16, State 1, Line 18 Multiple columns are specified in
an aggregated expression containing an outer reference. If an
expression being aggregated contains an outer reference, then that
outer reference must be the only column referenced in the expression.
Here the column [JOIN_COUNT] only producing the error, without this column the query is working. But i have more column pending to add like [JOIN_COUNT] (eg Resigned_Count, ...etc )
You do not need an outer apply to achieve this, simply join your CTE_DATE valus to your employee table and use a sum(case when <Conditions met> then 1 else 0 end) with a group by the CDate
select d.CDate
,sum(case when e.Emp_DoJ <= d.CDate
and e.EmployeeResignDate > d.CDate
then 1
else 0
end) as Total_Count
,sum(case when e.Emp_DoJ = d.CDate
then 1
else 0
end) as Join_Count
,sum(case when e.EmployeeResignDate = d.CDate
then 1
else 0
end) as Resign_Count
from CTE_DATE d
left join Employee_Table e
on(d.CDate between e.Emp_DoJ and e.EmployeeResignDate)
group by d.CDate
order by d.CDate

How to count every half hour?

I have a query that its counting every hour, using a pivot table.
How would it be possible to get the count for every 30 minutes?
for example 8:00-8:29,8:30-8:59,9:00-9:29 etc. until 5:00
SELECT CONVERT(varchar(8),start_date,1) AS 'Day',
SUM(CASE WHEN DATEPART(hour,start_date) = 8 THEN 1 ELSE 0 END) as eight ,
SUM(CASE WHEN DATEPART(hour,start_date) = 9 THEN 1 ELSE 0 END) AS nine,
SUM(CASE WHEN DATEPART(hour,start_date) = 10 THEN 1 ELSE 0 END) AS ten,
SUM(CASE WHEN DATEPART(hour,start_date) = 11 THEN 1 ELSE 0 END) AS eleven,
SUM(CASE WHEN DATEPART(hour,start_date) = 12 THEN 1 ELSE 0 END) AS twelve,
SUM(CASE WHEN DATEPART(hour,start_date) = 13 THEN 1 ELSE 0 END) AS one_clock,
SUM(CASE WHEN DATEPART(hour,start_date) = 14 THEN 1 ELSE 0 END) AS two_clock,
SUM(CASE WHEN DATEPART(hour,start_date) = 15 THEN 1 ELSE 0 END) AS three_clock,
SUM(CASE WHEN DATEPART(hour,start_date) = 16 THEN 1 ELSE 0 END) AS four_clock
FROM test
where user_id is not null
GROUP BY CONVERT(varchar(8),start_date,1)
ORDER BY CONVERT(varchar(8),start_date,1)
I use sql server 2012 (version Microsoft SQL Server Management Studio 11.0.3128.0)
Try using iif as below:
SELECT CONVERT(varchar(8),start_date,1) AS 'Day', SUM(iif(DATEPART(hour,start_date) = 8 and
DATEPART(minute,start_date) >= 0 and
DATEPART(minute,start_date) =< 29,1,0)) as eight_tirty
FROM test where user_id is not null GROUP BY
CONVERT(varchar(8),start_date,1) ORDER BY
CONVERT(varchar(8),start_date,1)
To get counts by day and half hour, something like this should work.
SELECT day, half_hour, count(1) AS half_hour_count
FROM (
SELECT
CAST(start_date AS date) AS day,
DATEPART(hh, start_date)
+ 0.5*(DATEPART(n,start_date)/30) AS half_hour
FROM test
WHERE user_id IS NOT NULL
) qry
GROUP BY day, half_hour
ORDER BY day, half_hour;
Formatting the result could be done later.
You need a few things, and then this query just falls together.
First, assuming you need multiple dates, you're going to want what's known as a Calendar Table (hands down, probably the most useful analysis table).
Next, you're going to want either an existing Numbers table if you have one, or just generate the first on the fly:
WITH Halfs AS (SELECT CAST(0 AS INT) m
UNION ALL
SELECT m + 1
FROM Halfs
WHERE m < 24 * 2)
SELECT m
FROM Halfs
(recursive CTE - generates a table with a list of numbers starting at 0).
These two tables will provide the basis for a range query based on the timestamps in your main table. This will make it very easy for the optimizer to bucket rows for whatever aggregation you're doing. That's done by CROSS JOINing the two tables together in a subquery, as well as adding a couple of other derived columns:
WITH Halfs AS (SELECT CAST(0 AS INT) m
UNION ALL
SELECT m + 1
FROM Halfs
WHERE m < 24 * 2)
SELECT calendarDate, m, rangeStart, rangeEnd
FROM (SELECT Calendar.calendarDate, Halfs.m rangeGroup,
DATEADD(minutes, m * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeStart,
DATEADD(minutes, (m + 1) * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeEnd
FROM Calendar
CROSS JOIN Halfs
WHERE Calendar.calendarDate >= CAST('20160823' AS DATE)
AND Calendar.calendarDate < CAST('20160830' AS DATE)
-- OR whatever your date range actually is.
) Range
ORDER BY rangeStart
(note that, if the range of dates is sufficiently large, it may be beneficial to save this off as a temporary table with indicies. For small tables and datasets, the performance gain isn't likely to be noticeable)
Now that we have our ranges, it's trivial to get our groups, and pivot the table.
Oh, and SQL Server has a specific operator for PIVOTing.
WITH Halfs AS (SELECT CAST(0 AS INT) m
UNION ALL
SELECT m + 1
FROM Halfs
WHERE m < 3 * 2)
-- Intentionally limiting range for example only
SELECT calendarDate AS day, [0], [1], [2], [3], [4], [5], [6]
-- If you're displaying "nice" names,
-- do it at this point, or in the reporting application
FROM (SELECT Range.calendarDate, Range.rangeGroup
FROM (SELECT Calendar.calendarDate, Halfs.m rangeGroup,
DATEADD(minutes, m * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeStart,
DATEADD(minutes, (m + 1) * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeEnd
FROM Calendar
CROSS JOIN Halfs
WHERE Calendar.calendarDate >= CAST('20160823' AS DATE)
AND Calendar.calendarDate < CAST('20160830' AS DATE)
-- OR whatever your date range actually is.
) Range
LEFT JOIN Test
ON Test.user_id IS NOT NULL
AND Test.start_date >= Range.rangeStart
AND Test.start_date < Range.rangeEnd
) AS DataTable
PIVOT (COUNT(*)
FOR Range.rangeGroup IN ([0], [1], [2], [3], [4], [5], [6])) AS PT
-- Only covers the first 6 groups,
-- or the first three hours.
ORDER BY day
The pivot should take care of the getting individual columns, and COUNT will automatically resolve null rows. Should be all you need.

SQL Server Sum table fields based on params

I have a table containing values from 12 different months
Table
{
January INT,
February INT,
etc.
}
and I need to sum values from specific months which numbers I keep in table (the number of months to sum can vary from 1 to 12):
DECLARE #Months TABLE
(
Number INT
)
so I'll surely need a big case and CTE but I don't really know how to achieve this.
The case statement isn't that bad:
select sum((case when m.month = 1 then Jan else 0 end) +
(case when m.month = 2 then Feb else 0 end) +
. . .
(case when m.month = 12 then Dec else 0 end)
)
from atable a cross join
#Months m;
You might want a group by as well. The above will return only one row for the entire table.

Grabbing/rearranging data from SQL for table

I have data in sql that looks like so:
Month PersonID Level
01 102 2
01 506 1
02 617 3
02 506 1
03 297 2
And I need to query this data to receive it for use in a table that would look like this
Jan Feb March ...etc
Level 1
Level 2
Level 3
with the values being how many people are in each level each month.
I'm a complete noob with SQL so any help and relevant links to explain answers would be much appreciated.
Try this:
SELECT 'Level' + CAST(level as varchar), [January], [February], [March]
FROM (SELECT DATENAME(month, '2013'+Month+'01') Month, PersonID, Level FROM Tbl) T
PIVOT
(
COUNT(PersonID) FOR Month IN ([January], [February], [March])
) A
SQL FIDDLE DEMO
SELECT 'Level ' + CAST("Level" AS VARCHAR(2)),
SUM(CASE Month WHEN '01' THEN 1 ELSE 0 END) AS Jan,
SUM(CASE Month WHEN '02' THEN 1 ELSE 0 END) AS Feb,
SUM(CASE Month WHEN '03' THEN 1 ELSE 0 END) AS Mar,
...
FROM myTable
GROUP BY "Level"
SQL Fiddle Example
This is basically a poor man's pivot table, which should work on most RDBMS. What it does is use a SUM with a CASE to achieve a count-if for each month. That is, for January, the value for each row will be 1 if Month = '01', or 0 otherwise. Summing these values gets the total count of all "January" rows in your table.
The GROUP BY Level clause tells the engine to produce one result row for each distinct value in Level, thus separating your data by the different levels.
Since you are using SQL Server 2005, which supports PIVOT, you can simply do:
SELECT 'Level ' + CAST("Level" AS VARCHAR(2)),
[01] AS [Jan], [02] AS [Feb], [03] AS [Mar], ...
FROM myTable
PIVOT
(
COUNT(PersonId)
FOR Month IN ([01], [02], [03], ...)
) x
SQL Fiddle Example

SQL statement to get record datetime field value as column of result

I have the following two tables
activity(activity_id, title, description, group_id)
statistic(statistic_id, activity_id, date, user_id, result)
group_id and user_id come from active directory. Result is an integer.
Given a user_id and a date range of 6 days (Mon - Sat) which I've calculated on the business logic side, and the fact that some of the dates in the date range may not have a statistic result for the particular date (ie. day1 and day 4 may have entered statistic rows for a particular activity, but there may not be any entries for days 2, 3, 5 and 6) how can I get a SQL result with the following format? Keep in mind that if a particular activity doesn't have a record for the particular date in the statistics table, then that day should return 0 in the SQL result.
activity_id group_id day1result day2result day3result day4result day5result day6 result
----------- -------- ---------- ---------- ---------- ---------- ---------- -----------
sample1 Secured 0 5 1 0 2 1
sample2 Unsecured 1 0 0 4 3 2
Note: Currently I am planning on handling this in the business logic, but that would require multiple queries (one to create a list of distinct activities for that user for the date range, and one for each activity looping through each date for a result or lack of result, to populate the 2nd dimension of the array with date-related results). That could end up with 50+ queries for each user per date range, which seems like overkill to me.
I got this working for 4 days and I can get it working for all 6 days, but it seems like overkill. Is there a way to simplify this?:
SELECT d1d2.activity_id, ISNULL(d1d2.result1,0) AS day1, ISNULL(d1d2.result2,0) AS day2, ISNULL(d3d4.result3,0) AS day3, ISNULL(d3d4.result4,0) AS day4
FROM
(SELECT ISNULL(d1.activity_id,0) AS activity_id, ISNULL(result1,0) AS result1, ISNULL(result2,0) AS result2
FROM
(SELECT ISNULL(statistic_result,0) AS result1, ISNULL(activity_id,0) AS activity_id
FROM statistic
WHERE user_id='jeremiah' AND statistic_date='11/22/2011'
) d1
FROM JOIN
(SELECT ISNULL(statistic_result,0) AS result2, ISNULL(activity_id,0) AS activity_id
FROM statistic WHERE user_id='jeremiah' AND statistic_date='11/23/2011'
) d2
ON d1.activity_id=d2.activity_id
) d1d2
FULL JOIN
(SELECT d3.activity_id AS activity_id, ISNULL(d3.result3,0) AS result3, ISNULL(d4.result4,0) AS result4
FROM
(SELECT ISNULL(statistic_result,0) AS result3, ISNULL(activity_id,0) AS activity_id
FROM statistic WHERE user_id='jeremiah' AND statistic_date='11/24/2011'
) d3
FULL JOIN
(SELECT ISNULL(statistic_result,0) AS result4, ISNULL(activity_id,0) AS activity_id
FROM statistic WHERE user_id='jeremiah' AND statistic_date='11/25/2011'
) d4
ON d3.activity_id=d4.activity_id
) d3d4
ON d1d2.activity_id=d3d4.activity_id
ORDER BY d1d2.activity_id
Here is a typical approach for this kind of thing:
DECLARE #minDate DATETIME,
#maxdate DATETIME,
#userID VARCHAR(200)
SELECT #minDate = '2011-11-15 00:00:00',
#maxDate = '2011-11-22 23:59:59',
#userID = 'jeremiah'
SELECT A.activity_id, A.group_id,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 0 THEN S.Result ELSE 0 END) AS Day1Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 1 THEN S.Result ELSE 0 END) AS Day2Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 2 THEN S.Result ELSE 0 END) AS Day3Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 3 THEN S.Result ELSE 0 END) AS Day4Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 4 THEN S.Result ELSE 0 END) AS Day5Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 5 THEN S.Result ELSE 0 END) AS Day6Result
FROM activity A
LEFT OUTER JOIN statistic S
ON A.activity_id = S.activity_ID
AND S.user_id = #userID
WHERE S.date between #minDate AND #maxDate
GROUP BY A.activity_id, A.group_id
First, I'm using group by to reduce the resultset to one row per activity_id/group_id, then I'm using CASE to separate values for each individual column. In this case I'm looking at which day in the last seven, but you can use whatever logic there to determine what date. The case statements will return the value of S.result if the row is for that particular day, or 0 if it's not. SUM will add up the individual values (or just the one, if there is only one) and consolidate that into a single row.
You'll also note my date range is based on midnight on the first day in the range and 11:59PM on the last day of the range to ensure all times are included in the range.
Finally, I'm performing a left join so you will always have a 0 in your columns, even if there are no statistics.
I'm not entirely sure how your results are segregated by group in addition to activity (unless group is a higher level construct), but here is the approach I would take:
SELECT activity_id
day1result = SUM(CASE DATEPART(weekday, date) WHEN 1 THEN result ELSE 0 END)
FROM statistic
GROUP BY activity_id
I will leave the rest of the days and addition of group_id to you, but you should see the general approach.