SQL Server - Null row not Showing? - sql

Is there a way how to add null values rows, replacing with 0?
Ideally,I want to show data of all Month, if there is no data then count column set to zero.
Currently SQL query is not Returning Empty Rows and I'm getting output like this...
I'm using the code below to get the result set below that:
SELECT
DATENAME(MONTH,A.Date) 'Month', COUNT(LateStatus) 'Count', 'Late' 'Type'
FROM
Attendance1 A
WHERE
A.EnrollId = '10000' AND A.Date BETWEEN '2016-01-01' AND '2016-08-31' AND LateStatus = 'L'
GROUP BY
DATENAME(MONTH,A.Date)

Use a recursive CTE (dynamically makes a calendar table):
With Months(mon) As
(Select dateAdd(month, datediff(month, 365, getdate()), 0)
Union All
Select DateAdd(month, 1, mon) from Months
Where mon < getdate())
Select DATENAME(MONTH, m.mon ) 'Month',
COUNT(LateStatus) 'Count', 'Late' 'Type'
FROM months m
left join Attendance1 a
on a.Date between m.mon and dateadd(month, 1, m.mon)
and a.EnrollId = '10000'
and a.LateStatus = 'L'
GROUP BY DATENAME(MONTH, m.Mon)
A more generic function, to generate a range of integers which can be used for any expression, is this:
create function dbo.NumberList (#start integer, #end integer)
returns table
as Return
(
With ints(aInt) As
(Select #start Union All
Select aInt + 1 From ints
Where aInt < #end)
Select aInt from ints
)
Once this function has been created, you can use it to dynamically create any range of values of any type:
For months from 8 months ago to 3 months ago
Select i.aInt, Dateadd(month, i.aInt,
DateAdd(month, datediff(month(0, getdate()), 0))
From dbo.NumberList(-8, -3) i

Assuming you have some data for all months, you can do this using conditional aggregation. That is, move the WHERE conditions to the SELECT using CASE:
SELECT DATENAME(MONTH,A.Date) as Mon,
SUM(CASE WHEN A.EnrollId = '10000' AND LateStatus = 'L'
THEN 1 ELSE 0
END) as cnt,
'Late' as 'Type'
FROM Attendance1 A
WHERE AND A.Date BETWEEN '2016-01-01' AND '2016-08-31'
GROUP BY DATENAME(MONTH, A.Date)
ORDER BY MIN(A.Date);
There are other methods, usually involving LEFT JOIN. But this is the simplest if it solves the problem.

Related

Merging two SELECT queries with same date fields

I have a table of Tasks where I have records for a particular date. I want to have all dates in one month displayed with numbers of tasks per date. If on some date there were no record of a task it should be written 0.
I have results with duplicating records from the same date when there were tasks on a given day.
Table:
Date Tasks
2021-08-01 0
2021-08-02 0
2021-08-03 0
2021-08-03 25
2021-08-04 0
2021-08-04 18
2021-08-05 0
2021-08-05 31
2021-08-06 0
SQL code I am using:
Declare #year int = 2021, #month int = 8;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value +1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year, #month, 1)))
)
SELECT datefromparts(#year, #month, numbers.value) AS 'Datum', 0 AS 'Tasks' FROM numbers
UNION
SELECT CONVERT(date, added_d) AS 'Datum', COUNT(*) AS 'Tasks' FROM Crm.Task
WHERE YEAR(added_d) = '2021' AND MONTH(added_d) = '8' GROUP BY CONVERT(date, added_d)
How can I remove duplicates that I will have only one date record 21-08-03 with 25 tasks?
Thank you for your help
You requires OUTER JOIN :
WITH numbers as (
Select datefromparts(#year, #month, 1) as value
UNION ALL
Select DATEADD(DAY, 1, value) as value
from numbers
where value < EOMONTH(value)
)
select num.value, COUNT(tsk.added_d) AS Tasks
from numbers num left join
Crm.Task tsk
on CONVERT(date, tsk.added_d) = num.value
GROUP BY num.value;
If you want all dates for one month, you can do:
with dates as (
select datefromparts(#year, #month, 1) as dte
union all
select dateadd(day, 1, dte)
from dates
where dte < eomonth(dte)
)
You can then incorporate this into the logic using an outer join or subquery:
with dates as (
select datefromparts(#year, #month, 1) as dte
union all
select dateadd(day, 1, dte)
from dates
where dte < eomonth(dte)
)
select d.dte, count(t.added_d)
from dates d left join
Crm.Task t
on convert(date, t.added_d) = d.dte
group by d.dte
order by d.dte;
You can easily extend the logic for the CTE for more than one month, by adjusting the where clause in the second clause.

How do I change the below query to pull by day and not month?

How do I change the below query to pull by day and not month?
select CONVERT(CHAR(4), dCompletedDate, 100) + CONVERT(CHAR(4), dCompletedDate, 120) as MonthYear,
count(case when dCompletedDate is not null then 1 else 0 end) as 'Total Completes'
from TProfile a
left join TStudyTable b
on a.lhouseholdid=b.lhouseholdid
where
dcompleteddate >= '1/1/1990' and dcompleteddate < '1/1/2050'
group by CONVERT(CHAR(4), dCompletedDate, 100) + CONVERT(CHAR(4), dCompletedDate, 120)
order by MonthYear
Assuming that dcompleteddate is of datetime datatype, you can just cast it to a date:
select
cast(dcompleteddate as date) as dcompletedday,
count(*) as total_completes
from tprofile
left join tstudytable s on p.lhouseholdid = s.lhouseholdid
where dcompleteddate >= '1990-01-01' and dcompleteddate < '2050-01-01'
group by cast(dcompleteddate as date)
order by dcompletedday
Side notes:
I changed the conditional count() to just count(*); you are filtering on dcompleteddate so it is guaranteed to be not null (and even if it was, you current expression would still count null values)
I also changed the where clause to use literal dates that are more standard

How to loop SQL statement multiple times

I try to loop one sql statement multiple times to retrieve the weekly revenue of on particular store. Here is my best guess which does not work.
SELECT *
DECLARE #i int = 0
WHILE #i < 52 BEGIN
SET #i = #i + 1
FROM dbo.revenue
WHERE DATEPART(WW, date) = #i
AND storenumber = '005'
END
You actually never want to write loops in SQL.
SELECT
SUM(earnings) weekly_earnings
FROM
dbo.revenue
WHERE
storenumber = '005'
AND date >= '2015-01-01'
AND date < '2016-01-01'
GROUP BY
DATEPART(WW, date)
Left join that against a helper table that contains 52 rows (1 through 52) representing the weeks to fill in the blanks (weeks with no revenue).
Note that the date >= '2015-01-01' AND date < '2016-01-01' exists because:
you must limit the query to one year, or the week number becomes ambiguous
it is superior to DATEPART(YY, date) = 2015 because, being a calculation, that expression would not be able to use an index on the date column, whereas >= and < can use an index
EDIT: Instead of a temporary table you can use a recursive CTE on SQL Server:
WITH WeekNumbers (WkNum) AS (
SELECT 1 AS WkNum
UNION ALL
SELECT w.WkNum + 1 FROM WeekNumbers w WHERE w.WkNum <= 52
)
SELECT
w.WkNum,
SUM(r.earnings) weekly_earnings
FROM
WeekNumbers w
LEFT JOIN dbo.revenue r ON w.WkNum = DATEPART(WW, r.date)
WHERE
r.storenumber = '005'
AND r.date >= '2015-01-01'
AND r.date < '2016-01-01'
GROUP BY
w.WkNum
You could just group your data and use some aggregation?
Select Sum(Revenue) AS [TotalRevenuePerWeek],
DatePart(ww, RevenueDate) as [WeekNumber]
From dbo.Revenue
Where StoreNumber = '005'
And DatePart(year, RevenueDate) = 2015
Group By DatePart(ww, RevenueDate)

SQL - Display running count of appointments grouped by time frame

I have a table
tblAppointment {
App_ID,
App_Date,
User_ID }
I currently have a statement that returns number of appointments grouped by year and month
SELECT
YEAR(App_Date) AS Year,
MONTH(App_Date) AS Month,
count(*) AS "No of Appointments"
FROM
tblapplication
GROUP BY
YEAR(App_Date),
MONTH(App_Date)
Im not sure how to write a select statement to return it with headings {time frame, No of applications}, and then have data in
row 1: time frame = week thus far, no of app = x.
row 2: time frame = month thus far, no of app = y.
ro3 3: time frame = year so far, no of app - z.
I would like to know how many appointments there are for 1. the current week, 2. the current month. 3. the current year, And have each result in its own row.
Any help in the right direction would be greatly appreciated. The actual problem is much greater than this but believe I have simplified it to the crux of the matter for now.
If you're okay with the results on one row, it's relatively easy:
select count(case when datepart(wk, App_Date) = datepart(wk, getdate())
then 1 end) as WeekSofFar
, count(case when datepart(m, App_Date) = datepart(m, getdate())
then 1 end) as MonthSofFar
, count(*) as YearSoFar
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
If the separate rows are a must-have, try something like:
select 'WeekSoFar' as Period
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
and datepart(wk, App_Date) = datepart(wk, getdate())
) as NumberOfApps
union all
select 'MonthSoFar'
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
and datepart(m, App_Date) = datepart(m, getdate())
)
union all
select 'YearSoFar'
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
)
I assumed SQL 2008 as you didn't specify.
SELECT
X.TimePeriod,
Count(
CASE X.Which
WHEN 3 THEN
CASE
WHEN App_Date > DateAdd(Day, -DatePart(Weekday, GetDate()), GetDate())
THEN 1
END
WHEN 2 THEN CASE WHEN Month(App_Date) = Month(GetDate()) THEN 1 END
ELSE 1 END
) [No of Appointments]
FROM
tblApplication
CROSS JOIN (
VALUES (1, 'Year to date'), (2, 'Month to date'), (3, 'Week to date')
) X (Which, TimePeriod)
WHERE
App_Date < Convert(date, GetDate() + 1)
AND App_Date >= DateAdd(Year, DateDiff(Year, '19000101', App_Date), '19000101')
GROUP BY
X.Which,
X.TimePeriod
ORDER BY
X.Which
If you have a lot of data in your table and an index on App_Date, this query will perform hugely better than one using date functions on the entire table without filtering.
I also have an embedded assumption that your App_Date values have no time portion (they are all set to 12am). If this is not true please let me know so I can modify case 3 to be correct.
If anyone wants to try the code here's some setup:
CREATE TABLE tblApplication (
App_Date datetime
)
INSERT tblApplication
VALUES
('2/1/2012'), ('2/5/2012'), ('2/10/2012'), ('1/2/2012'), ('1/9/2012'),
('1/15/2012'), ('1/28/2012'), ('12/1/2012'), ('12/5/2012'), ('12/10/2012'),
('11/2/2012'), ('11/9/2012'), ('11/15/2012'), ('11/28/2012')
Sorry about not using 'YYYYMMDD', I wasn't thinking when I typed it out.

What is the most efficient way to create an order count summary by hour, day, month in SQL Server 2005?

Given a table:
create table #orders (
orderid int,
orderdatetime datetime
)
What is the best way to write sql to output a report containing the count of orders from the current and previous 24 hours, total orders for the current day and previous 7 days, total orders for the current week and previous 4 weeks, and total orders for the month and previous 6 months?
I'm wondering if this can be efficiently rolled up into a single sql using analytical functions, or if 4 sql statements generating the 4 groups of data is the only (or best) way.
Also, given the hourly/day/week grouping, how would one do that in sql server? Datetimes seem to be a pain in the ass everytime I have to do something like this with them...
Ideas? Put into a SSAS cube and do it from there maybe?
SELECT DATEPART(month, orderdatetime), DATEPART(week, orderdatetime), DATEPART(day, orderdatetime), COUNT(*)
FROM #orders
GROUP BY
DATEPART(month, orderdatetime), DATEPART(week, orderdatetime), DATEPART(day, orderdatetime) WITH ROLLUP
This will group the COUNT's by day, week and month in a single query.
The week rollups will have a NULL in DATEPART(day, orderdatetime) column, the month rollups will have a NULL in both DATEPART(day, orderdatetime) and DATEPART(week, orderdatetime) columns.
To make it for every hour, day, week or month from the current without gaps, use CTE's:
WITH q_hours AS
(
SELECT 0 AS col_hour
UNION ALL
SELECT col_hour + 1
FROM q_hours
WHERE col_hour < 22
),
q_days AS
(
SELECT 0 AS col_day
UNION ALL
SELECT col_day + 1
FROM q_days
WHERE col_day < 31
),
q_months AS
(
SELECT 0 AS col_month
UNION ALL
SELECT col_month + 1
FROM q_months
WHERE col_month < 12
)
SELECT col_month, col_day, col_hour, COUNT(orderid)
FROM q_hours
CROSS JOIN
q_days
CROSS JOIN
q_months
LEFT JOIN
#orders
ON DATEDIFF(month, orderdatetime, GETDATE()) = col_month
AND DATEDIFF(day, orderdatetime, GETDATE()) % 31 = col_day
AND DATEDIFF(hour, orderdatetime, GETDATE()) % 24 = col_hour
GROUP BY
col_month, col_day, col_hour WITH ROLLUP
HAVING (
col_month = 0
AND col_day = 0
AND col_hour IS NOT NULL
) -- all hours within 24 hours from now
OR
(
col_month = 0
AND col_day <= 7
AND col_hour IS NULL
) -- all days within 7 days from now
OR
(
col_month <= 6
AND col_day IS NULL
AND col_hour IS NULL
) -- all months within 6 months from now
You could run the four selects from a "dummy table" or an "identity" table that consists of a single row.
You could have:
SELECT
(<query count of orders current/prev 24 hours>) as <name1>,
(<total orders current + 7 days>) as <name2>,
(<total orders current week + 4 weeks>) as <name3>,
(<total orders month + 6 months>) as <name4>
FROM
<IDENTITY table>;
Because you want different timeframes for each datepart type, using a single query with rollup probably won't give you what you want. I'd consider just unioning them all together similar to something like this...
SELECT DatePartValue = DATEPART(HH, orderdatetime),
Type = 'Hourly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(HH, -25, GETDATE())
GROUP BY DATEPART(HH, orderdatetime)
UNION
SELECT DATEPART(DD, orderdatetime),
Type = 'Daily',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(DD, -8, GETDATE())
GROUP BY DATEPART(DD, orderdatetime)
UNION
SELECT DATEPART(WEEK, orderdatetime),
Type = 'Weekly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(WEEK, -5, GETDATE())
GROUP BY DATEPART(WEEK, orderdatetime)
ORDER BY Type, DatePartValue
UNION
SELECT DATEPART(MM, orderdatetime),
Type = 'Monthly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(MM, -7, GETDATE())
GROUP BY DATEPART(MM, orderdatetime)
ORDER BY Type, DatePartValue
For results in one row, something like this:
select
orders_day = sum(case when datediff(hour,orderdatetime,getdate()) < 24 then 1 else 0 end)
, orders_week = sum(case when datediff(day,orderdatetime,getdate()) < 7 then 1 else 0 end)
, orders_month = sum(case when datediff(week,orderdatetime,getdate()) < 4 then 1 else 0 end)
, orders_half = sum(case when datediff(month,orderdatetime,getdate()) < 6 then 1 else 0 end)
from #orders
You may want to fine tune the date criteria to get appropriate behavior.
For multiple rows, take the results above and transpose it with UNPIVOT or CASE .. CROSS JOIN.
SELECT
sum(case when orderdatetime between GetDate() - 1 and GetDate() then 1 else 0 end) as Current24Hours,
sum(case when orderdatetime between GetDate() - 2 and GetDate() - 1 then 1 else 0 end) as Previous24Hours,
sum(case when orderdatetime between GetDate() - 7 and GetDate() then 1 else 0 end) as Current7Days,
sum(case when orderdatetime between GetDate() - 14 and GetDate() - 7 then 1 else 0 end) as Previous7Days,
sum(case when DATEDIFF (m, OrderDate, #now) <= 1 then 1 else 0 end) as PreviousMonth,
sum(case when DATEDIFF (m, OrderDate, #now) <= 6 then 1 else 0 end) as PreviousSixMonths
FROM orders
I think you want grouping sets. I've understand that sql server supports grouping sets.
EDIT1: I've read that sql server 2005 doesn't support grouping sets but sql server 2008 does. Here an interesting read on a presumed but not existing difference between mapreduce and an rdbms like Oracle and Sql Server. Please read the comments too!! http://www.data-miners.com/blog/2008/01/mapreduce-and-sql-aggregations.html