I have a table with data like below, and I'm trying to sum up column b and group by day. This works great. However some days have no data in the table at all. I want to show these days as having the sum 0, but I'm a bit confused on how to get there.
Date, column b
05/24/90, 5
05/24/90, 27
05/26/90, 19
05/27/90, 24
What I want to have in the end is
05/24/90, 32
05/25/90, 0
05/25/90, 19
05/27/90, 24
etc...
Something like this should work:
DECLARE #MinDate DATE = (SELECT MIN([date]) FROM yourTable);
DECLARE #MaxDate DATE = (SELECT MAX([date]) FROM yourTable);
WITH CTE_dates
AS
(
SELECT #MinDate dates
UNION ALL
SELECT DATEADD(DAY,1,dates)
FROM CTE_dates
WHERE dates < #MaxDate
)
SELECT A.dates,
SUM(ISNULL(B.[column b],0))
FROM CTE_dates A
LEFT JOIN yourTable
ON A.dates = B.dates
GROUP BY A.dates
Related
I'm trying to get a complete set of buckets for a given dataset, even if no records exist for some buckets.
For example, I want to display totals by day of week, with zero total for days with no records.
SELECT
WEEKDAY(transaction_date) AS day_of_week,
SUM(sales) AS total_sales
FROM table1
GROUP BY day_of_week
If I have sales every day, I'll get 7 rows in my result representing total sales on days 0-6.
If I don't have sales on Day 2, I get no result for Day 2.
What's the most efficient way to force a zero value for day 2?
Should I join to a temporary table or array of defined buckets? ['0','1','2','3','4','5','6']
Or is it better to insert zeros outside of MySQL, after I've done the query?
I am using MySQL, but this is a general SQL question.
In MySQL, you could simply use a derived table of numbers from 1 to 7, left join it with the table, then aggregate:
select d.day_of_week, sum(sales) AS total_sales
from (
select 1 day_of_week union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7
) d
left join table1 t1 on weekday(t1.transaction_date) = d.day_of_week
group by day_of_week
Very recent versions have the values(row...) syntax, which shortens the query:
select d.day_of_week, sum(sales) AS total_sales
from (values row(1), row(2), row(3), row(4), row(5), row(6), row(7)) d(day_of_week)
left join table1 t1 on weekday(t1.transaction_date) = d.day_of_week
group by day_of_week
Basically you want the answer to be 0 when the data is actually null for that bucket, therefore you want the max(null, 0). A max function wouldn't natively work with NULL in this way, however, you can use COALESCE to force it:
COALESCE(MAX(SUM(sales)),0)
as suggested by this answer
First off you need a calendar table; something like this or this. Or create calendar subset on the fly. I am not sure of the mySQL syntax, but here is what it would look like in SQL Server.
DECLARE
#FromDate DATE
, #ToDate DATE
-- set these variables to appropriate values
SET #FromDate = '2020-03-01';
SET #ToDate = '2020-03-31';
;WITH cteCalendar (MyDate) AS
(
SELECT CONVERT(DATE, #FromDate) AS MyDate
UNION ALL
SELECT DATEADD(DAY, 1, MyDate)
FROM cteCalendar
WHERE DATEADD(DAY, 1, MyDate) <= #ToDate
)
SELECT WEEKDAY(cte.MyDate) AS day_of_week,
SUM(sales) AS total_sales
FROM cteCalendar cte
LEFT JOIN table1 t1 ON cte.MyDate = t1.transaction_date
GROUP BY day_of_week
I'm trying to display all dates in a month, and also in the reservation detail, I only have check_in_date and check_out_date, so I have to create left join inside a left join, below is my script
SELECT
*
FROM
(
SELECT
#dt:= DATE_ADD( #dt, interval 1 day ) myDate
FROM
(
SELECT
#dt := '2020-01-31'
) vars, tb_dummy
LIMIT 29
) JustDates
LEFT JOIN
(
SELECT
DATE_FORMAT(d.myDate2,'%Y-%m-%d') AS `myDate2`,
COALESCE(count(rdt.reservation_detail_id), 0) AS `RNS`,
FORMAT(SUM(rdt.subtotal_amount/COALESCE(DATEDIFF(DATE(DATE(rdt.check_out_date)), DATE(rdt.check_in_date)), 0)), 2) AS `REVENUE`,
FORMAT(SUM(rdt.subtotal_amount/COALESCE(DATEDIFF(DATE(DATE(rdt.check_out_date)), DATE(rdt.check_in_date)), 0))/COALESCE(count(rdt.reservation_detail_id), 0), 2) AS `AVGREV`
FROM
(
SELECT
#dt:= DATE_ADD( #dt, interval 1 day ) myDate2
FROM
(
SELECT
#dt := '2020-01-31'
) vars2, tb_dummy
LIMIT 29
) d
LEFT JOIN
tb_reservation_detail rdt
ON d.myDate2 BETWEEN DATE(rdt.check_in_date) AND DATE(DATE(rdt.check_out_date) - INTERVAL 1 DAY)
INNER JOIN
tb_reservation R
ON rdt.reservation_id = R.reservation_id
WHERE
rdt.reservation_status_id <> 3
AND
R.property_id = 57
GROUP BY d.myDate2
ORDER BY d.myDate2 ASC
) Resv
ON
JustDates.myDate = Resv.myDate2
ORDER BY
JustDates.myDate ASC
when i run it only return dates from the left table like : Left join result
but when I change
SELECT
*
FROM
(
SELECT
#dt:= DATE_ADD( #dt, interval 1 day ) myDate
FROM
(
SELECT
#dt := '2020-01-31'
) vars, tb_dummy
LIMIT 29
) JustDates
**LEFT JOIN**
(
to
SELECT
*
FROM
(
SELECT
#dt:= DATE_ADD( #dt, interval 1 day ) myDate
FROM
(
SELECT
#dt := '2020-01-31'
) vars, tb_dummy
LIMIT 29
) JustDates
**RIGHT JOIN**
(
it returns data from the right table like this: Right join result
What is wrong with my code?
welcome to StackOverflow. I think your problem is that you don't quite understand the difference between RIGHT JOIN and LEFT JOIN. Check out this StackOverflow post that goes over the differences.
As far as wanting to display all of the dates in a month, here's a link to an answer I posted that I believe does what you want it to. In my answer I provide an example query that contains a derived table you can select from and then LEFT JOIN your tables to so it will show all the days in the month regardless if there is data in your tables for a given day or not.
Hope this helps.
Consider May is current month
I have list of dates
Ex:
Date No of Items
05/3/2016 4
05/3/2016 5
05/4/2016 7
05/10/2016 10
05/11/2016 50
05/30/2016 100
I want to display all dates in may and sum of the items in their date and if there is no record in the date then it should be left blank
Ex:
Date No of Items
05/1/2016
05/2/2016
05/3/2016 9
05/4/2016 7
05/5/2016
.
.
.
05/10/2016 10
05/11/2016 50
05/12/2016
05/13/2016
.
.
.
.
.
05/30/2016 100
Any Help on this
There's not a way to do this in SSRS.
Usually when I have a similar situation, I would make a table of the dates needed and then LEFT JOIN my data to it so the dates would appear when the date wasn't in the data.
I use a CTE to create the table in SQL:
DECLARE #START_DATE DATE = '01/01/2016'
DECLARE #END_DATE DATE = '05/31/2016'
;WITH GETDATES AS
(
SELECT #START_DATE AS THEDATE
UNION ALL
SELECT DATEADD(DAY,1, THEDATE) FROM GETDATES
WHERE THEDATE < #END_DATE
)
Then use the table with your data (maybe put your results from your current query in a #TEMP_TABLE).
SELECT *
FROM GETDATES D
LEFT JOIN #TEMP_TABLE T ON T.DATE_FIELD = D.THEDATE
Exactly, we cannot do this things in SSRS.
So to achieve this thing, we need to make a table of the Dates and then by making LEFT JOIN we can achieve our goal.
Let me show you one sample example:
DECLARE #month AS INT = 5
DECLARE #Year AS INT = 2016
CREATE TABLE #Temp ( Dates Date)
;WITH N(N)AS
(SELECT 1 FROM(VALUES(1),(1),(1),(1),(1),(1))M(N)),
tally(N)AS(SELECT ROW_NUMBER()OVER(ORDER BY N.N)FROM N,N a)
INSERT INTO #Temp
SELECT DATEFROMPARTS(#year,#month,N) dates FROM tally
WHERE N <= DAY(EOMONTH(datefromparts(#year,#month,1)))
SELECT Date, SUM(ISNULL(TotalCount,0)) NoOfItems FROM #Temp T
LEFT JOIN TableName S ON S.Date = T.Dates
GROUP BY Dates
DROP TABLE #Temp
And this will return all dates with NoOfItems. Yes, you have to change above query as per your requirement. Thanks
I've got a SQL Server CE 3.5 table (Transactions) with the following Schema:
ID
Transaction_Date
Category
Description
Amount
Query:
SELECT Transaction_Date, SUM(Amount)
FROM Transactions
GROUP BY Transaction_Date;
I'm trying to do a SUM(Amount) and group by transaction_date just so I can get the total amount for each day but I want to get back values even for days there were no transactions so basically the record for a day with no transactions would just have $0.00 for amount.
Thanks for the help!
You need a Calendar table to select over the dates. Alternatively, if you have a Numbers table, you could turn that effectively into a Calendar table. Basically, it's just a table with every date in it. It's easy enough to build and generate the data for it and it comes in very handy for these situations. Then you would simply use:
SELECT
C.calendar_date,
SUM(T.amount)
FROM
Calendar C
LEFT OUTER JOIN Transactions T ON
T.transaction_date = C.calendar_date
GROUP BY
C.calendar_date
ORDER BY
C.calendar_date
A few things to keep in mind:
If you're sending this to a front-end or reporting engine then you should just send the dates that you have (your original query) and have the front end fill in the $0.00 days itself if that's possible.
Also, I've assumed here that the date is an exact date value with no time component (hence the "=" in the join). Your calendar table could include a "start_time" and "end_time" so that you can use BETWEEN for working with dates that include a time portion. That saves you from having to strip off time portions and potentially ruining index usage. You could also just calculate the start and end points of the day when you use it, but since it's a prefilled work table it's easier IMO to include a start_time and end_time.
You'll need to upper and lower bound your statement somehow, but perhaps this will help.
DECLARE #Start smalldatetime, #End smalldatetime
SELECT #Start = 'Jan 1 2010', #End = 'Jan 18 2010';
--- make a CTE of range of dates we're interested in
WITH Cal AS (
SELECT CalDate = convert(datetime, #Start)
UNION ALL
SELECT CalDate = dateadd(d,1,convert(datetime, CalDate)) FROM Cal WHERE CalDate < #End
)
SELECT CalDate AS TransactionDate, ISNULL(SUM(Amount),0) AS TransactionAmount
FROM Cal AS C
LEFT JOIN Transactions AS T On C.CalDate = T.Transaction_Date
GROUP BY CalDate ;
Once you have a Calendar table (more on that later) you can then do an inner join on the range of your data to fill in missing dates:
SELECT CalendarDate, NULLIF(SUM(t.Amount),0)
FROM (SELECT CalendardDate FROM Calendar
WHERE CalendarDate>= (SELECT MIN(TransactionDate) FROM Transactions) AND
CalendarDate<= (SELECT MAX(TransactionDate) FROM Transactions)) c
LEFT JOIN
Transactions t ON t.TransactionDate=c.CalendarDate
GROUP BY CalendarDate
To create a calendar table, you can use a CTE:
WITH CalendarTable
AS
(
SELECT CAST('20090601' as datetime) AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= '20090630' /* last date */
)
SELECT [date] FROM CTE_DatesTable
OPTION (MAXRECURSION 0);
Combining the two, we have
WITH CalendarTable
AS
(
SELECT MIN(TransactionDate) FROM Transactions AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= (SELECT MAX(TransactionDate) FROM Transactions)
)
SELECT c.[date], NULLIF(SUM(t.Amount),0)
FROM Calendar c
LEFT JOIN
Transactions t ON t.TransactionDate=c.[date]
GROUP BY c.[date]
Not sure if any this works with CE
With common table expressions
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2010-07-10'
SET #EndDate = '2010-07-20'
;WITH Dates AS (
SELECT #StartDate AS DateValue
UNION ALL
SELECT DateValue + 1
FROM Dates
WHERE DateValue + 1 <= #EndDate
)
SELECT Dates.DateValue, ISNULL(SUM(Transactions.Amount), 0)
FROM Dates
LEFT JOIN Transactions ON
Dates.DateValue = Transactions.Transaction_Date
GROUP BY Dates.DateValue;
With loop + temporary table
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2010-07-10'
SET #EndDate = '2010-07-20'
SELECT #StartDate AS DateValue INTO #Dates
WHILE #StartDate <= #EndDate
BEGIN
SET #StartDate = #StartDate + 1
INSERT INTO #Dates VALUES (#StartDate)
END
SELECT Dates.DateValue, ISNULL(SUM(Transactions.Amount), 0)
FROM #Dates AS Dates
LEFT JOIN Transactions ON
Dates.DateValue = Transactions.Transaction_Date
GROUP BY Dates.DateValue;
DROP TABLE #Dates
If you want dates that don't have transactions to appear
you can add a DUMMY transaction for each day with the amount of zero
it won't interfere with SUM and would so what you want
I've got IP address, email address and date in table. I need to find out how many mails was sent (grouped by IP and email together) during each month of each year (so group by month and year to). Tricky part is, that if no mail was sent, I need to get zero value on results (now I don't get empty rows). How can I do that?
Select distinct X.Sender, X.IP, MONTH(s.date) as Month, YEAR (s.date) as Year, Count(s.ID)
From
(
Select TOP 10 a.Sender, a.SenderIP as IP, COUNT(a.ID) as C
From AMA a Where a.Summary = 'SPAM'
Group by a.Sender, a.SenderIP
Order by C desc
)As X left outer join AMAs
on X.Sender = s.Sender and X.IP = s.SenderIP
Where s.Summary = 'SPAM'
Group by X.Sender, X.IP, YEAR(s.date), MONTH(s.date)
Order by X.Sender,X.IP,YEAR(s.date) asc, Month(s.date) asc`
The way to do this is to OUTER JOIN from a set of months onto your existing resultset. This way, all months with data will be present, and any months without data will have empty values.
You could create a table with that information, or you could use a CTE.
DECLARE #firstDate DATE = '2010-01-01';
WITH Months (fulldate) AS
(
SELECT #firstdate
UNION ALL
SELECT DATEADD(MONTH, 1, fulldate)
FROM Months
WHERE fulldate < GETDATE()
)
SELECT
m.*, q.*
FROM Months m
LEFT OUTER JOIN
(
-- your existing query
) q
ON q.[Year] = DATEPART(YEAR, fulldate)
AND q.[Month] = DATEPART(MONTH, fulldate)
-- you may need to increase the max recursion, if you want more than 100 months...
-- OPTION (MAXRECURSION 1000)