Include NULL Results as 0 in MySQL - mysql

My original query is as follows:
SELECT COUNT(*) AS count, YEAR(created_at) AS "year", MONTH(created_at) AS "month"
FROM quotes WHERE
(YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())
OR YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE()) -1)
AND status_id = 1
GROUP BY YEAR(created_at), MONTH(created_at) DESC
This query basically retrieves the COUNT for this month and the previous month and works fine except when there are no results for either month.
I have two similar queries that do the same except for weeks and years.
I've tried to use COALESCE and IFNULL but it doesn't seem to include NULL results.
SELECT IFNULL(COUNT(*), 0) AS count, YEAR(created_at) AS "year", MONTH(created_at) AS "month"
FROM quotes
WHERE
(YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE()) OR YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE()) -1)
AND status_id = 1
GROUP BY YEAR(created_at), MONTH(created_at) DESC
Actual Result
count | year | month
-----------------------
1 | 2014 | 11
Expected Result
count | year | month
-----------------------
1 | 2014 | 11
0 | 2014 | 10
SQL Fiddle

You need to use a seperate data-set for the months you want to see. Your SQLfiddle simply is grouping the rows on Quotes, and as such shows no aggregate rows for months that don't show up in the result set at all.
Try Creating a temporary table with the months you want, and then do a simple OUTER JOIN to select any quotes that appear.

Just moving the IFNULL inside the SUM function, like a comment suggested, won't be enough.
You can only group by values that are available. If there is no entry for 2014-10 you won't get anything printed.
Otherwise you would see every possible combination.
Note: The where clause only limits the results, it has no effect on what to show. The Select doens't "know" that it has to show the month 10. becuas ethere is no value there.
I don't know what do you want to achieve, but personally in such a case I would just print 0 in the program when there is no matching result at all.

At its simplest...
SELECT x.month
, COUNT(q.id) total
FROM (SELECT 10 month UNION SELECT 11) x
LEFT
JOIN quotes q
ON MONTH(created_at) = month
GROUP
BY month;

Related

MySQL_Retrieving stock price based on max and min_date

I am trying to retrieve closed and opened stock prices from the first and last date per month.
For some reason, the output of the 'end_date_closed_stock_price' is NULL.
Do you know any idea why it is giving this result?
Also, could you tell me the appropriate queries to retrieve the last date of the month?
The followings are my queries and output.
Thanks in advance!
SELECT YEAR(date) AS years
, MONTH(date) AS months
, CASE WHEN date = MAX(date) THEN close END end_date_closed_stock_price
, CASE WHEN date = MIN(date) THEN open END first_date_opened_stock_price
FROM nasdaq_feb_25_1971_feb_25_2021
GROUP
BY 1,2
ORDER
BY 1 DESC;
---OUTPUT---
2020 5 NULL 9382.349609
2019 1 NULL 6947.459961
2019 2 NULL 7266.279785
2019 3 NULL 7582.290039
There is probably a more efficient solution, but this should work:
WITH dates as
(SELECT YEAR(date) as years
,MONTH(date) AS months
,MAX(date) as end_date
,MIN(date) as first_date
FROM nasdaq_feb_25_1971_feb_25_2021
GROUP BY 1, 2)
SELECT dates.years
, dates.months
, price1.close as end_date_closed_stock_price
, price2.open as first_date_opened_stock_price
FROM dates
JOIN nasdaq_feb_25_1971_feb_25_2021 price1
ON price1.date = dates.end_date
JOIN nasdaq_feb_25_1971_feb_25_2021 price2
ON price2.date = dates.first_date
ORDER
BY 1 DESC, 2 DESC;

MySQL ORDER BY FIELD for months

I have a table called months - this contains all 12 months of the calendar, the IDs correspond to the month number.
I will be running a query to retrieve 2 or 3 sequential months from this table, e.g
April & May
June, July, August
December & January
However I want to ensure that whenever December are January and retrieved, that it retrieves them in that order, and not January - December. Here is what I have tried:
SELECT * FROM `months`
WHERE start_date BETWEEN <date1> AND <date2>
ORDER BY
FIELD(id, 12, 1)
This works for December & January, but now when I try to retrieve January & February it does those in the wrong order, i.e "February - January" - I'm guessing because we specified 1 in the ORDER BY as the last value.
Anybody know the correct way to achieve this? As I mentioned this should also work for 3 months, so for example "November, December, January" and "December, January, February" should all be retrieved in that order.
If you want December first, but the other months in order, then:
order by (id = 12) desc, id
MySQL treats booleans as numbers, with "1" for true and "0" for false. The desc puts the 12s first.
EDIT:
To handle the more general case, you can use window functions. Assuming the numbers are consecutive, then the issue is trickier. This will work for 2 and 3-month spans:
order by (case min(id) over () > 1 then id end),
(case when id > 6 1 else 2 end),
id
I'm reluctant to think about a more general solution based only on months. After all, you can just use:
order by start_date
Or, if you have an aggregation query:
order by min(start_date)
to solve the real problem.
This is not "mysql solution" properly :
with cte (id, month) AS (
select id, month from months
union all
select id, month from months
)
, cte1 (id, month, r) as (select id, month, row_number() over() as r from cte )
select * from cte1
where id in (12, 1)
and r >= 12 order by r limit 2 ;
DECLARE
#monthfrom int = 12,
#monthto int = 1;
with months as (select 1 m
union all
select m+1 from months where m<12)
select m
from months
where m in (#monthfrom,#monthto)
order by
case when #monthfrom>#monthto
then
m%12
else
m
end
result:
12
1
Basically in MySQL this can be done the same way:
set #from =12;
set #to =1;
with recursive months(m) as (
select 1 m
union all
select m+1 from months where m<12)
select *
from months
where m in (#from,#to)
order by case when #from>#to then m%12 else m end;

SQL Combining Multiple SELECT Statements

I am trying to build an SQLite query that will collect statistics from a single table.
The table holds a log, of sorts, with several entries per day. I need to get a separate row for each day within the search parameters and then compile the totals of rows within those dates with certain boolean values.
Here is the query I have so far:
SELECT DATE(DateTime) AS SearchDate,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Total,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE NoMarket = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS NoMarkets,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE Complaint = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Complaints,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE Voicemail = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Voicemails
FROM CallRecords
WHERE DATE(DateTime) BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY SearchDate
And the output:
8/28/2017 175 27 11
8/29/2017 175 27 11
8/30/2017 175 27 11
8/31/2017 175 27 11
9/1/2017 175 27 11
As you can see, it is properly getting each individual date, but the totals for the columns is incorrect.
Obviously, I am missing something in my query, but I am not sure where. Is there a better way to perform this query?
EDIT: I have looked into several of the other questions with near-identical titles here, but I have not found anything similar to what I'm looking for. Most seem much more complicated than what I'm trying to accomplish.
It looks like you have a mess of columns in your CallRecords table with names like Complaint and Voicemail, each of which classifies a call.
It looks like those columns have the value 1 when relevant.
So this query should probably help you.
SELECT DATE(DateTime) AS SearchDate,
COUNT(*) AS Total,
SUM(NoMarket = 1) AS NoMarkets,
SUM(Complaint = 1) AS Complaints,
SUM(Voicemail = 1) AS Voicemails
FROM CallRecords
WHERE DateTime >= '2017-08-27'
AND DateTime < '2017-09-02' + INTERVAL 1 DAY
GROUP BY DATE(DateTime)
Why does this work? Because in MySQL a Boolean expression like Voicemail = 1 has the value 1 when it's true and 0 when it's false. You can sum those values up quite nicely.
Why is it faster than what you have? Because DATE(DateTime) BETWEEN this AND that can't exploit an index on DateTime.
Why is it correct for the end of your date range? Because DateTime < '2017-09-02' + INTERVAL 1 DAY pulls in all the records up until, but not including, midnight, on the day after your date range.
If you're using Sqlite, you need AND DateTime < date('2017-09-02', '+1 day'). The + INTERVAL 1 DAY stuff is slightly different there.
you can doing like this , although i wrote in SQL server
SELECT DATE(DateTime) AS SearchDate,
COUNT() AS TOTAL,
SUM(CASE WHEN NoMarket = 1 THEN 1 ELSE 0 END) AS NoMarkets,
SUM(CASE WHEN Complaint = 1 THEN 1 ELSE 0 END) AS Complaints,
SUM(CASE WHEN Voicemail = 1 THEN 1 ELSE 0 END) AS Voicemails
FROM CallRecords
WHERE DATE(DateTime) BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY SearchDate
SELECT DATE(DateTime) AS SearchDate, Total, NoMarkets, Complaints, Voicemails FROM
(SELECT COUNT() AS Total FROM CallRecords) CR
JOIN
(SELECT COUNT() AS NoMarkets FROM CallRecords WHERE NoMarket = 1) NM
ON CR.DateTime = NM.DateTime
JOIN
(SELECT COUNT() AS Complaints FROM CallRecords WHERE Complaint = 1) C
ON NM.DateTime = C.DateTime
JOIN
(SELECT COUNT() AS Voicemails FROM CallRecords WHERE Voicemail = 1) VM
ON C.DateTime = VM.DateTime
JOIN CallRecords CLR ON VM.DateTime=CLR.DateTime WHERE DATE(CLR.DateTime) >= '2017-08-27' AND DATE(CLR.DateTime) <= '2017-09-02'GROUP BY SearchDate;
This may Output correctly.

How to query rows by month and group by column using mysql?

I have a database that has two columns - result and time
I'm trying to get a count of how many rows exist of each result in a particular month. There are only two options for result success and failure
I've managed to get a count of how many rows there are in each month, but I can't get the individual count of how many success and how many failure there were in each month.
Here is what I have:
SELECT result, MONTH(time) MONTH, COUNT(*) COUNT
FROM mytable
WHERE YEAR(time)=2017
GROUP BY MONTH(time);
I'm looking for a result that provides me with something like there were 12 successes and 8 failures in a particular month.
Any help would be appreciated.
Use conditional aggregation
SELECT result, MONTH(time) MONTH,
sum(result = 'success') as success_count,
sum(result = 'failure') as failure_count
FROM mytable
WHERE YEAR(time) = 2017
GROUP BY result, MONTH(time);
I would use the following query:
SELECT,
DATE_FORMAT(time, '%m %Y') AS month_year,
SUM(CASE WHEN result = 'success' THEN 1 ELSE 0 END) AS success_count,
SUM(CASE WHEN result = 'failure' THEN 1 ELSE 0 END) AS failure_count
FROM mytable
WHERE YEAR(time) = 2017
GROUP BY
DATE_FORMAT(time, '%m %Y')
Note that you should be aggregating by time period alone, and not by the result, which instead is part of the sum in the CASE expression.

Query with sum case group by

I am facing problem with following query:
SELECT sum(CASE WHEN status.new_reg_yn='n'
AND month(status.visit_date)-1 = 8
AND year(status.visit_date) = 2015 THEN 1 ELSE 0 END)
FROM customer_visit_status_tbl status,
customer_details_tbl cust
WHERE status.customer_id = cust.customer_id
AND cust.client_id=65
GROUP BY status.customer_id
The problem is that this query is returning results for customer with same id though I used group by. For example, in the month of September, if same customer visits 5 times it is returning count as 5 instead of 1 though I used group by.
It is really unclear what you want... Yes, distinct customers for a given time period, but then you are taking the month of the date visited -1 and looking for that equal to 8. Being that current month is 9 (September), Are you just looking for those based on activity the month prior to whatever the current is? So, for example, if Sept, 2015, you want totals for Aug, 2015. In Jan, 2016, you would want Dec, 2015? If that is the case, you can use the current date to subtract 1 month and get that as basis of the query. Then you can have your additional specific client (65 in this case).
My (subselect sqlvars) pre-creates variables applied for the query. It computes one month ago by subtracting 1 month from whatever the current date it. Then uses that as basis of the month representing whatever was the prior month, and similarly for that respective year.
Since this will in essence create a single row return, there is no Cartesian result and you can just run with your original other tables for final counts.
select
count( distinct s.customer_id ) as UniqueCustomers
from
( select #oneMonthAgo := DATE_ADD(CURRENT_DATE, INTERVAL -1 MONTH),
#finalMonth := MONTH( #oneMonthAgo ),
#finalYear := YEAR( #oneMonthAgo ) sqlvars,
customer_visit_status_tbl s
JOIN customer_details_tbl c
on s.customer_id = c.customer_id
AND c.client_id = 65
where
s.new_reg_yn='n'
Update Ans -
Select count(*)
from
( SELECT distinct status.customer_id
FROM customer_visit_status_tbl status
, customer_details_tbl cust
WHERE status.customer_id = cust.customer_id
AND cust.client_id = 65
and status.new_reg_yn = 'n'
AND month(status.visit_date)-1 = 8
AND year(status.visit_date) = 2015
) customer_visited