SELECT and GROUP BY month efficiently - mysql

I have a column of type date. I want to group rows per month for a particular year.
I did the following:
SELECT SUM(price), DATE_FORMAT(production_date, '%Y%m')
FROM TABLE
WHERE YEAR(production_date) = ?
GROUP BY 2
Is there a better/more efficient way to do this?

Instead of specifying the year using the YEAR function
SELECT SUM(price), DATE_FORMAT(production_date, '%Y%m')
FROM TABLE
WHERE YEAR(production_date) = 2014
GROUP BY 2
specify it like this
SELECT SUM(price), DATE_FORMAT(production_date, '%Y%m')
FROM TABLE
WHERE production_date BETWEEN '2014-01-01' AND '2014-12-31'
GROUP BY 2
The 2nd query enables the db to use an index on production_date

you are almost there :)
SELECT SUM(price), MONTH(production_date)
FROM TABLE
WHERE YEAR(production_date) = ?
GROUP BY 2

Related

How to show months if it has no record and force it to zero if null on MySQL

i have an orders table, and i need to fetch the orders record by month. but i have terms if there is no data in a month it should still show the data but forcing to zero like this:
what i have done is using my query:
select sum(total) as total_orders, DATE_FORMAT(created_at, "%M") as date
from orders
where is_active = 1
AND tenant_id = 2
AND created_at like '%2021%'
group by DATE_FORMAT(created_at, "%m")
but the result is only fetched the existed data:
can anyone here help me to create the exactly query?
Thank you so much
Whenever you're trying to use a value that doesn't exist in the table, one option is to use a reference; whether it's from a table or a query-generated value.
I'm guessing that in terms of date data, the column created_at in table orders may have a complete list all the 12 months in a year regardless of which year.
Let's assume that the table data for orders spans from 2019 to present date. With that you can simply create a 12 months reference table for a LEFT JOIN operation. So:
SELECT MONTHNAME(created_at) mnt FROM orders GROUP BY MONTHNAME(created_at);
You can append that into your query like:
SELECT IFNULL(SUM(total),0) as total_orders, mnt
from (SELECT MONTHNAME(created_at) mnt FROM orders GROUP BY MONTHNAME(created_at)) mn
LEFT JOIN orders o
ON mn.mnt=MONTHNAME(created_at)
AND is_active = 1
AND tenant_id = 2
AND created_at like '%2021%'
GROUP BY mnt;
Apart from adding the 12 months sub-query and a LEFT JOIN, there are 3 other changes from your original query:
IFNULL() is added to the SUM() operation in SELECT to return 0 if the value is non-existent.
All the WHERE conditions has been switched to ON since remaining it as WHERE will make the LEFT JOIN becoming a normal JOIN.
GROUP BY is using the sub-query generated month (mnt) value instead.
Taking consideration of table orders might not have the full 12 months, you can generate it from query. There are a lot of ways of doing it but here I'm only going to show the UNION method that works with most MySQL version.
SELECT MONTHNAME(CONCAT_WS('-',YEAR(NOW()),mnt,'01')) dt
FROM
(SELECT 1 AS mnt UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION
SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12) mn
If you're using MariaDB version that supports SEQUENCE ENGINE, the same query above is much shorter:
SELECT MONTHNAME(CONCAT_WS('-',YEAR(NOW()),mnt,'01'))
FROM (SELECT seq AS mnt FROM seq_1_to_12) mn
I'm using MariaDB 10.5 in this demo fiddle however it seems like the month name ordering is based on the name value rather than the month itself so it looks un-ordered. It's in the correct order if it's in MySQL 8.0 fiddle though.
Thanks all for the answers & comments i really appreciate it.
i solved it by create table helper for static months then use union and aliasing, since i need the months in indonesia, i create case-when function too.
so, the query is like this:
SELECT total_orders,
(CASE date WHEN 01 THEN 'Januari'
WHEN 02 THEN 'Februari'
WHEN 03 THEN 'Maret'
WHEN 04 THEN 'April'
WHEN 05 THEN 'Mei'
WHEN 06 THEN 'Juni'
WHEN 07 THEN 'Juli'
WHEN 08 THEN 'Agustus'
WHEN 09 THEN 'September'
WHEN 10 THEN 'Oktober'
WHEN 11 THEN 'November'
WHEN 12 THEN 'Desember'
ELSE date END ) AS date
FROM (SELECT SUM(total) AS total_orders,
DATE_FORMAT(created_at, "%m") AS date
FROM orders
WHERE is_active = 1
AND tenant_id = 2
AND created_at like '%2021%'
GROUP BY DATE_FORMAT(created_at, "%m")
UNION
SELECT 0 AS total_orders,
code AS date
FROM quantum_default_months ) as Q
GROUP BY date
I still don't know if this query is fully correct or not, but I get my exact result.
cmiiw.
thanks all

Mysql Select group by and without group by in one query

How can I select group by and without group by in one select query? I have a query like this
SELECT date, sum(total) as quotation_total
FROM suspended_bills
WHERE type='quotation'
GROUP BY YEAR(date), MONTH(date)
Here it is taking a group by year and month but I want a sum(total) without group by year and month. So I am able to get full sum(total) without a division of month and year.
This is not a duplicate entry as rollup is generating extra rows that I dont need, I only need the total of sum, so when I run this query with rollup it gave me more rows that is not desired. So all is wrong with roll up query.
In MySQL, the easiest way is a join or subquery:
select YEAR(sb.date), MONTH(sb.date), sum(sb.total) as quotation_total,
t.total_total
from suspended_bills sb cross join
(select sum(total) as total_totla
from suspended_bills
where type = 'quotation'
) t
where sb.type = 'quotation'
group by YEAR(sb.date), MONTH(sb.date), t.total_total;

How to get record with earliest date per group

I need to get the earliest date from the following table based on column 'ItemNo'.
ItemNo PO_number Date
110913 PO-8048 9/15/2015
110913 PO-8036 9/30/2015
110652 PO-1011 10/19/2015
110652 PO-1011 10/10/2015
110009 PO-1016 7/1/2015
110009 PO-1087 6/20/2015
110888 PO-7171 4/1/2015
Your query result should be look like this.
ItemNo PO_number Date
110913 PO-8048 9/15/2015
110652 PO-1011 10/10/2015
110009 PO-1087 6/20/2015
110888 PO-7171 4/1/2015
Any help would be greatly appreciated.
Couple of different ways you could approach this, one reasonable approach would be something like:
with min_rec as
(
select t.ItemNo, t.PO_number, t.Date, row_number() over(partition by t.ItemNo order by t.Date asc) as rn
from your_table t
)
select m.ItemNo, m.PO_number, m.Date
from min_rec m
where m.rn = 1;
Leveraging a CROSS APPLY would be another approach that would work as well, though in this particular case it likely wouldn't be a better performing approach (though as always, it depends):
select distinct c.ItemNo, c.PO_number, c.Date
from your_table t
cross apply (
select top 1 i.ItemNo, i.PO_number, i.Date
from your_table i
where i.ItemNo = t.ItemNo
order by i.Date asc) c;
And naturally, you could simply use a self-joining subquery (I'll skip the example on that one).

How to select the compute the range value of a given date?

I have this table below. I want to count and select the values within a month range.
Here's my query
select count(*) as c, monthname(file_date) as mn
from baguio_patrolcase
where monthname(file_date) between 'January' and 'July'
group by monthname(file_date)
order by file_date
What I want to achive is that it will also count and select the values from February an June. How will I do this?
When you convert the date to a month name, you are comparing strings. Here are two options to do what you want:
where month(file_date) between 2 and 6
where file_date >= '2015-02-01' and file_date < '2015-07-01'
The second is better, because it allows the engine to use an index on file_date (if available).
Also, between keeps the end values (it is inclusive). So, if you want February through June, then use those months, rather than 1 and 7.
What I have understood, you can use in clause:
select count(*) as c, monthname(file_date) as mn
from baguio_patrolcase
where monthname(file_date) in('January', 'July')
group by monthname(file_date)
order by file_date
Add the name of all the required months in the in clause.
You can see the SQLFiddle Demo here.

How to use query results in another query?

I am trying to write a query which will give me the last entry of each month in a table called transactions. I believe I am halfway there as I have the following query which groups all the entries by month then selects the highest id in each group which is the last entry for each month.
SELECT max(id),
EXTRACT(YEAR_MONTH FROM date) as yyyymm
FROM transactions
GROUP BY yyyymm
Gives the correct results
id yyyymm
100 201006
105 201007
111 201008
118 201009
120 201010
I don’t know how to then run a query on the same table but select the balance column where it matches the id from the first query to give results
id balance date
120 10000 2010-10-08
118 11000 2010-09-29
I've tried subqueries and looked at joins but i'm not sure how to go about using them.
You can make your first select an inline view, and then join to it. Something like this (not tested, but should give you the idea):
SELECT x.id
, t.balance
, t.date
FROM your_table t
/* here, we make your select an inline view, then we can join to it */
, (SELECT max(id) id,
EXTRACT(YEAR_MONTH FROM date) as yyyymm
FROM transactions
GROUP BY yyyymm) x
WHERE t.id = x.id