MySQL: Replace null with 0 in GROUP_CONCAT - mysql

I have a table called trx
trx_year trx_month Product number_of_trx
2018 4 A 100
2018 5 A 300
2018 3 A 500
2018 1 A 200
2018 2 A 150
2018 5 B 400
2018 2 B 200
2018 1 B 350
I want the result:
Product with the number of trx that order by month asc
I have a query like this:
select product,GROUP_CONCAT(number_of_trx order by trx_month)
from trx
where trx_year=2018
group by product
The Result of that query:
Product Data
A 200,150,500,100,300
B 350,200,400
But, I want Result like this: (that the null value of the month replaced by 0)
Product Data
A 200,150,500,100,300
B 350,200,0,0,400
I already try ifnull() and coalesce() like this: (but the result is same as before)
select product,GROUP_CONCAT(ifnull(number_of_trx,0) order by trx_month)
from trx
where trx_year=2018
group by product;
select product,GROUP_CONCAT(coalesce(number_of_trx,0) order by trx_month)
from trx
where trx_year=2018
group by product;
maybe you can help me, please check http://sqlfiddle.com/#!9/f1ed4/3

Generate all the rows you want using cross join. That would be all product/month combinations. Then use left join to bring in data and group by to condense it:
select p.product,
group_concat(coalesce(trx.number_of_trx, 0) order by trx_month)
from (select distinct product from trx) p cross join
(select distinct trx_year, trx_month
from trx
where trx_year = 2018
) yyyymm left join
trx
on trx.product = p.product and
trx.trx_year = yyyymm.trx_year
trx.trx_month = yyyymm.trx_month
group by p.product
Note the order by in the group_concat(). This is very important if you want the results in chronological order.

This is what I came up with. Probably could be more efficient but you can get ideas from it. Join to product table instead of selecting distinct products. Also expand to include months beyond 5.
SELECT trx2.product, GROUP_CONCAT(trx2.total order by trx2.trx_month)
FROM
(SELECT temp2.product, temp2.trx_month, SUM(temp2.number_of_trx) AS total
FROM
(SELECT products.product, temp1.trx_month, temp1.number_of_trx
FROM (select 1 as trx_month, 0 as number_of_trx
UNION select 2, 0
UNION select 3, 0
UNION select 4, 0
UNION select 5, 0) as temp1,
(SELECT distinct product from trx) AS products
UNION ALL
SELECT trx.product, trx.trx_month, trx.number_of_trx
FROM trx) as temp2
GROUP BY temp2.product, temp2.trx_month) AS trx2
GROUP BY product

Group by groups the table contents with available values.
FOR Product A: months 1 2 3 4 5 are available and
FOR Product B: months 1 2 5 are available
It will not fill table automatically with month 3 and 4 against product B
To resolve this
Either you will have to fill this table with B - 3 and B - 4 and 0 value for number of trx
OR you can create temp table and do the same in that.
To implement this,
1. you will have to use date function (convert your month and year value to date)
2. Increase month value for each loop, If the month value is not available against current product value, insert record in the table.
3. Then run your group by query on this updated table and it will give you required result.
This is bit complicated task to implement. You will have to do multiple checks.
If I will find any other solution, I will post. Thanks.

select product,GROUP_CONCAT(number_of_trx) order by trx_month),
group_concat(case when trx_year = 2018 then number_of_trx else 0 end)as data
from trx
group by product;

If concat is null then returns 0 else returns values
SET SESSION group_concat_max_len = 10000000;
SELECT coalesce(group_concat(DISTINCT columnname),0) as values

Related

MySQL Query to get each sales per month

I have 2 tables in Mysql. I want to regroup and count the Number of Orderid per month for each customer. If there is no order, I would like to add 0.
Customer Table
CustomerID
1
2
3
Order Table
OrderId CustomerID Date
1 1 2022-01-02
2 1 2022-01-04
3 2 2022-02-03
4 2 2022-03-03
Expect results
CustomerID Date CountOrderID
1 2022-01 2
2 2022-01 1
3 2022-01 0
1 2022-02 0
2 2022-02 1
3 2022-02 0
1 2022-03 0
2 2022-03 1
3 2022-03 0
How I can do this in Mysql?
SELECT customer.CustomerID,
year_month.y_m AS `Date`,
COUNT(order.OrderId) AS CountOrderID
FROM customer
CROSS JOIN (
SELECT DISTINCT DATE_FORMAT(`date`, '%Y-%m') AS y_m
FROM order
) AS year_month
LEFT JOIN order ON order.CustomerID = customer.CustomerID
AND DATE_FORMAT(order.`date`, '%Y-%m') = year_month.y_m
GROUP BY 1, 2;
If order table does not contains for some year and month then according row won't present in the output. If you need in it then you'd generate calendar table instead of year_month subquery.
you can reduce the number of cte's I added more here to explain the steps:
first you need the format year and month, for that I used DATE_FORMAT() function
since you need to have all the combination of dates and the year month you need a cross join. This will produce all the distinct dates with all the distinct customer id's. In other words all the pairs between dates and customer id
once you have a table with all the combinations you need to pass the actual data with the left join this will produce null where you actually don't have rows and hence will produce 0 when the count is performed
the last step is simply count function
with main as (
select distinct DATE_FORMAT(date,'%Y-%m') as year_month from order
),
calendar as (
select * from customer
cross join main
),
joining_all as (
select
calendar.*,
order. OrderId
left join order
on calendar.CustomerID = order.CustomerID
and calendar.year_month = DATE_FORMAT(order.date,'%Y-%m')
)
select
CustomerID,
year_month as Date,
count(OrderId) as CountOrderID
from joining_all
group by 1,2
maybe the shorter version can work with the code below. if runs into syntax you can use the one above
with main as (
select distinct DATE_FORMAT(date,'%Y-%m') as year_month from order
cross join customer
)
select
main.CustomerID,
main.year_month as Date,
count(order.OrderId) as CountOrderID
from main
left join order
on main.CustomerID = order.CustomerID
and main.year_month = DATE_FORMAT(order.date,'%Y-%m')
group by 1,2

Mysql selecting values based on other column value

Hi i have a table like so it includes a user id, date, amount and active flag
id
date
amount
active
1001
2017-07-12
10
1
1001
2017-07-12
5
0
1001
2017-07-12
12
0
1001
2017-05-05
5
0
1001
2017-06-01
11
0
my requirement is to get the total amount for this particular user for the whole day that he was active, so since the user was active on the date of '2017-07-12' i should be able to get all the amount for that particular date so my amount for this particular user would be 27.
What would be a right query to perform this action in mysql by looking at the active flag and how would i go about to do it?
We can use an aggregation approach here:
SELECT id, SUM(amount) AS total_amount
FROM
(
SELECT *
FROM yourTable t1
WHERE active = 1 OR
EXISTS (SELECT 1
FROM yourTable t2
WHERE t2.date = t1.date AND
t2.active = 1)
) t
GROUP BY id
ORDER BY id;
Demo
Either
SELECT id, `date`, SUM(amount) amount
FROM table
GROUP BY 1, 2
HAVING SUM(active)
or
SELECT id, `date`, SUM(amount) * (SUM(active) > 0) amount
FROM table
GROUP BY 1, 2
depends on desired output (does "non-active" dates must be skipped at all, or they'd be returned with zero amount).
Another solution:
select tbl.id,
tbl.`date`,
sum(amount) as tot_date_amount
from tbl
inner join (select `date`
from tbl
where active = 1
) as t2 on tbl.`date`=t2.`date`
group by id,`date`;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=c173dcc9a72490146ff7c094a10b08b6
The subquery will select only the dates where active = 1 .Using inner join will return only the sum for the active = 1 dates

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

SQL nested query alternative

SQL newbie here. I have a table where I have OrderID and State of the order.
OrderID, State, TimeStamp
1 0 20210502151515
1 1 20210502161616
1 2 20210502171717
2 0 20210502151617
2 1 20210502161718
2 3 20210502171819
3 0 20210502121617
3 4 20210502121718
4 0 20210502131617
5 0 20210502141718
6 0 20210502151515
6 2 20210502171717
7 0 20210502151515
7 1 20210502171717
Where 0 = OPEN, 1=Partially Completed, 2=Fully Completed, 3=Cancelled, 4=Rejected
I want to run a query where it would return orders that are OPEN (state=0) or Partially Completed (state=1). If the order is Fully completed, Cancelled or Rejected, I want to exclude those orders.
If I run to select orders with state 0,1 then it would return some orders that are fully done or cancelled or rejected. I need to run query where order states anything but 0 or 1.
I have this query which works but I am wondering if there is a better way to do it.
SELECT *
FROM myTable
WHERE OrderID NOT IN (select OrderId from myTable where state not in (0, 1))
Thank you!
If you just want orders, you can use aggregation:
select orderid
from mytable
group by orderid
having max(state) = 1;
If you want the details of the rows, you can use join, in or exists along with this query.
There is a better way, but not with sql. Maybe you want to create another table to store the current state of the order. It is much easier to get what you want.
Old-fashioned sql you would easily solve this with a correlated sub-query:
Select * from Mytable a
Where a.Timestamp=(Select max(Timestamp) from Mytable b
Where a.OrderId=b.OrderID)
and state<2
This selects only the most recent record by order (max(Timestamp)) and further only keeps it if that most recent record is 0 or 1.
Might something like this work or would it be end up being too brutal as the recordset grows?
select Mytable.orderid, Mytable.State, Mytable.TimeStamp
from Mytable
inner join
(
select orderid, max(Timestamp) newesttimestamp
from Mytable
group by orderid
) newestorderdetails
on Mytable.orderid = newestorderdetails.orderid and Mytable.Timestamp = newestorderdetails.newesttimestamp
where Mytable.state IN (0, 1)
order by Mytable.orderid, Mytable.state

Adding Row Values when there are no results - MySQL

Problem Statement: I need my result set to include records that would not naturally return because they are NULL.
I'm going to put some simplified code here since my code seems to be too long.
Table Scores has Company_type, Company, Score, Project_ID
Select Score, Count(Project_ID)
FROM Scores
WHERE company_type= :company_type
GROUP BY Score
Results in the following:
Score Projects
5 95
4 94
3 215
2 51
1 155
Everything is working fine until I apply a condition to company_type that does not include results in one of the 5 score categories. When this happens, I don't have 5 rows in my result set any more.
It displays like this:
Score Projects
5 5
3 6
1 3
I'd like it to display like this:
Score Projects
5 5
4 0
3 6
2 0
1 3
I need the results to always display 5 rows. (Scores = 1-5)
I tried one of the approaches below by Spencer7593. My simplified query now looks like this:
SELECT i.score AS Score, IFNULL(count(*), 0) AS Projects
FROM (SELECT 5 AS score
UNION ALL
SELECT 4
UNION ALL
SELECT 3
UNION ALL
SELECT 2
UNION ALL
SELECT 1) i
LEFT JOIN Scores ON Scores.score = i.score
GROUP BY Score
ORDER BY i.score DESC
And gives the following results, which is accurate except that the rows with 1 in Projects should actually be 0 because they are derived by the "i". There are no projects with a score of 5 or 2.
Score Projects
5 1
4 5
3 6
2 1
1 3
Solved! I just needed to adjust my count to specifically look at the project count - count(project) rather than count(*). This returned the expected results.
If you always want your query to return 5 rows, with Score values of 5,4,3,2,1... you'll need a rowsource that supplies those Score values.
One approach would be to use a simple query to return those fixed values, e.g.
SELECT 5 AS score
UNION ALL SELECT 4
UNION ALL SELECT 3
UNION ALL SELECT 2
UNION ALL SELECT 1
Then use that query as inline view, and do an outer join operation to the results from your current query
SELECT i.score AS `Score`
, IFNULL(q.projects,0) AS `Projects`
FROM ( SELECT 5 AS score
UNION ALL SELECT 4
UNION ALL SELECT 3
UNION ALL SELECT 2
UNION ALL SELECT 1
) i
LEFT
JOIN (
-- the current query with "missing" Score rows goes here
-- for completeness of this example, without the query
-- we emulate that result with a different query
SELECT 5 AS score, 95 AS projects
UNION ALL SELECT 3, 215
UNION ALL SELECT 1, 155
) q
ON q.score = i.score
ORDER BY i.score DESC
It doesn't have to be the view query in this example. But there does need to be a rowsource that the rows can be returned from. You could, for example, have a simple table that contains those five rows, with those five score values.
This is just an example approach for the general approach. It might be possible to modify your existing query to return the rows you want. But without seeing the query, the schema, and example data, we can't tell.
FOLLOWUP
Based on the edit to the question, showing an example of the current query.
If we are guaranteed that the five values of Score will always appear in the Scores table, we could do conditional aggregation, writing a query like this:
SELECT s.score
, COUNT(IF(s.company_type = :company_type,s.project_id,NULL)) AS projects
FROM Scores s
GROUP BY s.score
ORDER BY s.score DESC
Note that this will require a scan of all the rows, so it may not perform as well. The "trick" is the IF function, which returns a NULL value in place of project_id, when the row would have been excluded by the WHERE clause.)
If we are guaranteed that project_id is non-NULL, we could use a more terse MySQL shorthand expression to achieve an equivalent result...
, IFNULL(SUM(s.company_type = :company_type),0) AS projects
This works because MySQL returns 1 when the comparison is TRUE, and otherwisee returns 0 or NULL.
Try something like this:
select distinct score
from (
select distinct score from scores
) s
left outer join (
Select Score, Count(Project_ID) cnt
FROM Scores
WHERE company_type= :company_type
) x
on s.score = x.score
Your posted query would not work without a group by statement. However, even there, if you don't have those particular scores for that company type, it wouldn't work either.
One option is to use an outer join. That would require a little more work though.
Here's another option using conditional aggregation:
select Score, sum(company_type=:company_type)
from Scores
group by Score