简体   繁体   中英

sql to select one record for every month with a sum of that months records

I have a database that has an "appointments table" and "services table". Each appt has a service and each service has a price. What I would like is one query, that will always return 12 rows (one row for each month) and contain a sum of the months appts (based on it's service id). So far I have:

select sum(service_price) as monthly_total, 
       year(appt_date_time) as year, 
       monthname(appt_date_time) as month 
from appt_tbl 
     join services_tbl on appt_tbl.service_id = services_tbl.service_id 
group by month(appt_date_time), 
         year(appt_date_time) 
order by month(appt_date_time) asc;

Currently, this returns something like:

+---------------+------+-------+
| monthly_total | year | month |
+---------------+------+-------+
|        120.00 | 2012 | July  |
+---------------+------+-------+

The problem is that if a month doesn't have any appts, I do not get that month returned in the query. I would like that month to have a record, just have it's "monthly_total" equal zero.

Below is what I would like the query to return:

+---------------+------+-------+
| monthly_total | year | month |
+---------------+------+-------+
|          0.00 | 2012 | Jan   |
+---------------+------+-------+
|          0.00 | 2012 | Feb   |
+---------------+------+-------+
|          0.00 | 2012 | March |
+---------------+------+-------+
|          0.00 | 2012 | April |
+---------------+------+-------+
|          0.00 | 2012 | May   |
+---------------+------+-------+
|          0.00 | 2012 | June  |
+---------------+------+-------+
|        120.00 | 2012 | July  |
+---------------+------+-------+
|          0.00 | 2012 | August|
+---------------+------+-------+
|          0.00 | 2012 | Sept  |
+---------------+------+-------+
|          0.00 | 2012 | Oct   |
+---------------+------+-------+
|          0.00 | 2012 | Nov   |
+---------------+------+-------+
|          0.00 | 2012 | Dec   |
+---------------+------+-------+

Any ideas?

You're on the right track, just LEFT JOIN the result of your query to a table of monthnames. Then the names that exist in your result will return a match, and a NULL will be returned for those months that are not in your result. A COALESCE or NVL or CASE construct, whatever you like, can be used to turn that NULL into a straight 0 .

You don't need make a real table of monthts, you can get by using an inline view - simply a query that generates all the month data.

select     months.month, coalesce(appts.monthly_total, 0) monthly_total
from       (
                      select 'January' as month
           union all  select 'February' 
           union all  select 'March'

           ...etc...

           union all  select 'December'
           ) months
left join  (
            select     sum(service_price)        as monthly_total, 
                       monthname(appt_date_time) as month 
            from       appt_tbl 
            inner join services_tbl 
            on         appt_tbl.service_id = services_tbl.service_id
            where      appt_date_time between '2012-01-01 00:00:00'
                                          and '2012-12-31 23:59:59'         
            group by   month(appt_date_time)
           ) appts
on         months.month = appts.month 

As for returning everyting for a particular year, look at the WHERE condition. I am asuming appt_date_time is a datetime or timestamp. You don't want to write YEAR(appt_date_time) = 2012 because that will ruin the chances of using an index on that column (I am assuming that column appears as a first column in an index). By using a BETWEEN...AND and comparing against literal datetimes, you can have a pretty efficient query that meets the requirement.

Also, if you GROUP BY on month(appt_date_time) mysql will ensure results will by sorted ascending by month too. So no need for a separate ORDER BY . The order of the 'legs' of the UNION query will also be preserved by mysql.

Try this, it will work for you. All you need to do is to create a lookup table for better approach.

CREATE TABLE MONTHS(MON CHAR(3))
INSERT INTO MONTHS 
SELECT 'Jan' UNION ALL
SELECT 'Feb' UNION ALL
SELECT 'Mar' UNION ALL
SELECT 'Apr' UNION ALL
SELECT 'May' UNION ALL
SELECT 'Jun' UNION ALL
SELECT 'Jul' UNION ALL
SELECT 'Aug' UNION ALL
SELECT 'Sep' UNION ALL
SELECT 'Oct' UNION ALL
SELECT 'Nov' UNION ALL
SELECT 'Dec'

CREATE  TABLE Appointments(App int, Year int, Month CHAR(3))
INSERT INTO Appointments 
SELECT 120,2012,'Jul' UNION ALL 
SELECT 120,2013,'Apr'

SELECT sum(isnull(App,0)) monthly_total, b.Mon , b.Year FROM Appointments a
RIGHT JOIN (SELECT DISTINCT Year,Mon FROM Appointments CROSS JOIN MONTHS) b 
ON a.Month=b.MON AND a.Year=b.Year
GROUP BY b.Mon,b.Year

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM