get specific data in different time period in mysql - mysql

I have a table like this
--------------------------------
|name | time | city |value |
--------------------------------
|a | 2018| rasht | 1.5 |
--------------------------------
|a | 2017| rasht | 2 |
--------------------------------
|a | 2018| tehran| 4 |
--------------------------------
|a | 2017| rasht | 3 |
--------------------------------
|a | 2018| rasht | 5 |
--------------------------------
|a | 2017| rasht | 2 |
--------------------------------
|b | 2018| tehran| 7 |
--------------------------------
i like to get data from this table like this:
name | city | total 2018 | total 2017
a |rasht | 6.5 |7
a |theran| 4 |0
b |theran| 7 |0
I am using query like this:
select A.name,city,A.sum(value) ,B.sum(value) from
(select * from tbl where year=2018 group by name,city)A,
(select * from tbl where year=2017 group by name,city)B
where A.name=B.name and A.city=B.city
but it didn't work and for records like b that dont have value in 2017 it returns nothing
As u can see my example here is very simple i made exact db in fiddle if u can see it in this address Fiddle
I also use this code:
select subzone, mvFeederName, SUM(CASE WHEN faultStartDate >= 13970901 and faultEndDate <= 13971101 and (subzone=10 ) THEN energyLost ELSE 0 END) AS `unplannedCurrentEnergyLost`, SUM(CASE WHEN faultStartDate >= 13960901 and faultEndDate <= 13961101 and (subzone=10 ) THEN energyLost ELSE 0 END) AS `unplannedLastYearEnergyLost`, count(CASE WHEN faultStartDate >= 13970901 and faultEndDate <= 13971101 and (subzone=10 ) THEN mvFeederName ELSE 0 END) AS `unplannedCurrentCount`, count(CASE WHEN faultStartDate >= 13960901 and faultEndDate <= 13961101 and (subzone=10 ) THEN mvFeederName ELSE 0 END) AS `unplannedLastYearCount` from faults_mv group by subzone,mvFeederName
it return zero for energyLost sumation col in two time period and count of mvFeederName in two thime period is the same that is not correct.

Use conditional aggregation -
select name, city,
sum(case when time=2018 then value end) 'total 2018',
sum(case when time=2017 then value end) 'total 2017'
from tablename
group by name, city

You can use conditional aggregation to get the result you want:
SELECT name, city,
SUM(CASE WHEN time = 2018 THEN value ELSE 0 END) AS `total 2018`,
SUM(CASE WHEN time = 2017 THEN value ELSE 0 END) AS `total 2017`
FROM tbl
GROUP BY name, city
Output:
name city total 2018 total 2017
a rasht 6.5 7
a tehran 4 0
b tehran 7 0
Demo on dbfiddle

Related

How do I count values in different tables using JOIN and/or UNION in MYSQL?

I want to count the product_name in these tables together:
jan_product feb_product
+------------+ +------------+
|product_name| |product_name|
+------------+ +------------+
|A | |A |
+------------+ +------------+
|A | |B |
+------------+ +------------+
|C | |C |
+------------+ +------------+
I want my result to look like:
+------------+---------+---------+
|product_name|jan_count|feb_count|
+------------+---------+---------+
|A |2 |1 |
+------------+---------+---------+
|B |0 |1 |
+------------+---------+---------+
|C |1 |1 |
+------------+---------+---------+
So I tried the query below (I'm using MYSQL so I couldnt try FULL JOIN):
SELECT
j.product_name,
count(j.product_name) as jan_count,
count(f.product_name) as feb_count
FROM jan_product as j
JOIN feb_product as f
ON j.product_name = f.product_name
group by j.product_name
UNION
SELECT
f.product_name,
count(j.product_name) as jan_count,
count(f.product_name) as feb_count
FROM jan_product as j
RIGHT OUTER JOIN feb_product as f
ON f.product_name = j.product_name
group by f.product_name
;
But i got this instead:
+------------+---------+---------+
|product_name|jan_count|feb_count|
+------------+---------+---------+
|A |2 |2 | --- A counts for FEB is wrong
+------------+---------+---------+
|B |0 |1 |
+------------+---------+---------+
|C |1 |1 |
+------------+---------+---------+
I do not know what to do to get to the expected result.
We can do this with count from Union all. I would rather advise you to have one table with a month column.
create table jan (pname char(1));
create table feb (pname char(1));
insert into jan values('A'),('A'),('C');
insert into feb values ('A'),('B'),('C');
select
pname,
count(j) jan,
count(f) feb
from
(select pname,pname j,null f from jan
union all
select pname,null,pname from feb) jf
group by pname
pname | jan | feb
:---- | --: | --:
A | 2 | 1
B | 0 | 1
C | 1 | 1
db<>fiddle here
Ignoring the table structure for a minute ... try using a UNION ALL, including an extra column to indicate the source month. Then use a conditional SUM to calculate the totals for each month.
See also db<>fiddle
SELECT t.product_name
, SUM( CASE WHEN t.month_number = 1 THEN 1 ELSE 0 END) AS jan_count
, SUM( CASE WHEN t.month_number = 2 THEN 1 ELSE 0 END) AS feb_count
FROM (
SELECT CAST(1 AS UNSIGNED) AS month_number, product_name
FROM jan_product
UNION ALL
SELECT CAST(2 AS UNSIGNED) AS month_number, product_name
FROM feb_product
) t
GROUP BY t.product_name
Results:
product_name | jan_count | feb_count
:----------- | --------: | --------:
A | 2 | 1
C | 1 | 1
B | 0 | 1
Having said that, you should normalize the model. You could greatly simplify things by storing everything in a single table, with a date (or month + year columns) instead of having a separate table for each month.
Also, it seems like you're storing information about events that occur to a specific product over time. If that's the case, you should have a separate table containing unique products:
ProductId
ProductName
1
Product A
2
Product B
3
Product C
Other tables that store information about products should store the "Product" table's unique PK (primary key) value - not a product's name. For example, if you had a ProductSales table
| ProductId | SaleDate | Quantity |
|-----------|-------------|----------|
| 1 | 02/01/2022 | 15 |
| 2 | 02/10/2022 | 4 |
| 1 | 02/12/2022 | 3 |
| 3 | 02/01/2022 | 20 |
To retrieve information about the sales by month, all you'd need is a simple JOIN between the two tables
See also db<>fiddle
SELECT p.product_name
, year(s.sales_date) AS sales_year
, SUM( CASE month(s.sales_date) WHEN 1 THEN 1 ELSE 0 END) AS jan_sales
, SUM( CASE month(s.sales_date) WHEN 2 THEN 1 ELSE 0 END) AS feb_sales
, SUM( CASE month(s.sales_date) WHEN 3 THEN 1 ELSE 0 END) AS mar_sales
-- ... etc
FROM product p LEFT JOIN product_sales s ON s.product_id = p.product_id
GROUP BY p.product_name
, year(s.sales_date)
;
Results:
product_name | sales_year | jan_sales | feb_sales | mar_sales
:----------- | ---------: | --------: | --------: | --------:
Product A | 2022 | 0 | 2 | 0
Product B | 2022 | 0 | 1 | 0
Product C | 2022 | 0 | 1 | 0
--Please try using below query
-------
WITH cte AS
( SELECT * FROM jan_product
UNION
SELECT * FROM feb_product)
SELECT cte.product_name,
j.Jan_count,
count(f.product_name) as February_count
FROM cte
LEFT JOIN (SELECT product_name,
COUNT(product_name) as Jan_count
FROM jan_product
GROUP BY product_name) j
ON cte.product_name=j.product_name
LEFT JOIN (SELECT product_name,
COUNT(product_name) as Feb_count
FROM feb_product
GROUP BY product_name) f
ON cte.product_name=f.product_name

MySQL Count and Convert Row to Colum Involve One Table Only

I have a table name histories that record user activities which consists of user_id, branch_id and duration.
The table look like this:
+++++++++++++++++++++++++++++++++++
id | user_id | branch_id | totHours
+++++++++++++++++++++++++++++++++++
|1 | 100 | 1 | 1 |
|2 | 199 | 1 | 1 |
|3 | 121 | 1 | 1 |
|4 | 140 | 1 | 1 |
|5 | 103 | 2 | 3 |
|6 | 107 | 2 | 1 |
|7 | 299 | 1 | 2 |
|8 | 209 | 2 | 2 |
|9 | 119 | 1 | 5 |
I would like to produce an output like this:
+++++++++++++++++++++++++++
Hours | Branch A | Branch B
+++++++++++++++++++++++++++
|1 | 4 | 1 |
|2 | 1 | 1 |
|3 | 0 | 1 |
|4 | 0 | 0 |
|5 | 1 | 0 |
I try make it using this query, but when i use group by on totHours column only, it return error because i need to include the branch_id in the group by.
Here is my query:
select totHours as Hours,
coalesce(case when branch_id = 1 then count(totHours) else 0 end) as 'Branch A',
coalesce(case when branch_id = 2 then count(totHours) else 0 end) as 'Branch B'
from histories
group by totHours, branch_id;
And if the totHours is not in the table (for example in this table 4), it will display 0 for both branch column.
Here is my db fiddle
Update: MySQL version 5.7.22
If you're using MySQL version 8+ (or any version support windows function), you can make use of the recursive common table expression to generate the hour values for you then LEFT JOIN table histories with it. After that you can do SUM() with CASE expression in SELECT to generate your expected output:
WITH RECURSIVE hours AS (
SELECT 1 AS hr, MAX(totHours) AS maxth FROM histories UNION ALL
SELECT hr+1, maxth FROM hours WHERE hr+1 <= maxth)
SELECT hours.hr,
SUM(CASE WHEN histories.branch_id=1 THEN 1 ELSE 0 END) AS Branch_A,
SUM(CASE WHEN histories.branch_id=2 THEN 1 ELSE 0 END) AS Branch_B
FROM hours
LEFT JOIN histories
ON hours.hr=histories.totHours
GROUP BY hours.hr;
If you're using version that doesn't support window function, you can create a subquery to represent the hours (including missing hour). This is a hard-coding approach where you may have to always update the subquery to include new hour value (if any):
SELECT hours.hr,
SUM(CASE WHEN histories.branch_id=1 THEN 1 ELSE 0 END) AS Branch_A,
SUM(CASE WHEN histories.branch_id=2 THEN 1 ELSE 0 END) AS Branch_B
FROM
(SELECT 1 hr UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5) AS hours
LEFT JOIN histories
ON hours.hr=histories.totHours
GROUP BY hours.hr;
Demo fiddle
Edit the hours subquery to add more, for example if you want until 7, you just add:
(SELECT 1 hr UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7) AS hours
to the subquery. Another way is to define the hours beforehand and create a reference table. Let's say you estimate the hour to be until 100, then it's better if you create a table that stores 1-100 as reference for the LEFT JOIN

Join two table and count, avoid zero if record is not available in second table

I have following tables products and tests.
select id,pname from products;
+----+---------+
| id | pname |
+----+---------+
| 1 | prd1 |
| 2 | prd2 |
| 3 | prd3 |
| 4 | prd4 |
+----+---------+
select pname,testrunid,testresult,time from tests;
+--------+-----------+------------+-------------+
| pname | testrunid | testresult | time |
+--------+-----------+------------+-------------+
| prd1 | 800 | PASS | 2017-10-02 |
| prd1 | 801 | FAIL | 2017-10-16 |
| prd1 | 802 | PASS | 2017-10-02 |
| prd1 | 803 | NULL | 2017-10-16 |
| prd1 | 804 | PASS | 2017-10-16 |
| prd1 | 805 | PASS | 2017-10-16 |
| prd1 | 806 | PASS | 2017-10-16 |
+--------+-----------+------------+-------------+
I like to count test results for products and if there is no result available,for a product just show a zero for it. something like following table:
+--------+------------+-----------+----------------+---------------+
| pname | total_pass | total_fail| pass_lastweek | fail_lastweek |
+--------+------------+-----------+----------------+---------------+
| prd1 | 5 | 1 | 3 | 1 |
| prd2 | 0 | 0 | 0 | 0 |
| prd3 | 0 | 0 | 0 | 0 |
| prd4 | 0 | 0 | 0 | 0 |
+--------+------------+-----------+----------------++--------------+
I have tried different queries like following, which is just working for one product and is incomplete:
SELECT pname, count(*) as pass_lastweek FROM tests where testresult = 'PASS' AND time
>= '2017-10-11' and pname in (select pname from products) group by pname;
+-------------+---------------+
| pname | pass_lastweek |
+-------------+---------------+
| prd1 | 3 |
+-------------+---------------+
it looks so basic but still I am unable to write it, any idea?
Use conditional aggregation. The COUNT function count NULL values as zeros automatically, therefore, there is no need to take care of that.
select p.pname,
count(case when testresult = 'PASS' then 1 end) as total_pass,
count(case when testresult = 'FAIL' then 1 end) as total_fail,
count(case when testresult = 'PASS' and time >= curdate() - INTERVAL 6 DAY then 1 end) as pass_lastweek ,
count(case when testresult = 'FAIL' and time >= curdate() - INTERVAL 6 DAY then 1 end) as fail_lastweek ,
from products p
left join tests t on t.pname = p.pname
group p.id, p.pname
Generally, you need to LEFT JOIN the first table with the second one before you group. The join will give you a row for each product (even if there are no test results to join it to; INNER JOIN would exclude products with no associated tests) + an additional row for each test result (beyond the first). Then you can group them.
SELECT products.*, tests.* FROM products
LEFT JOIN tests ON products.pname = tests.pname
GROUP BY products.id
Also, I would strongly recommend using a product_id column in the tests table, rather than using pname (if a products.pname changes, your whole DB breaks unless you also update the pname field in kind for every test result). The general query would then look like this:
SELECT products.*, tests.* FROM products
LEFT JOIN tests ON products.id = tests.product_id
GROUP BY products.id
I used 2 queries , the first with conditional count and the second one is to change all null values into 0 :
select pname,
case when total_pass is null then 0 else total_pass end as total_pass,
case when total_fail is null then 0 else total_fail end as total_fail,
case when pass_lastweek is null then 0 else pass_lastweek end as pass_lastweek,
case when fail_lastweek is null then 0 else fail_lastweek end asfail_lastweek from (
select products.pname,
count(case when testresult = 'PASS' then 1 end) as total_pass,
count(case when testresult = 'FAIL' then 1 end) as total_fail,
count(case when testresult = 'PASS' and time >= current_date -7 DAY then 1 end) as pass_lastweek ,
count(case when testresult = 'FAIL' and time >= current_date -7 DAY then 1 end) as fail_lastweek ,
from products
left join tests on tests.pname = products.pname
group 1 ) t1

SQL query to get columns based on value

I have a table like below
user_id | month, | value
---------+------------+--------
1 | 2013-02-01 | 1
1 | 2013-01-01 | 0
1 | 2013-03-01 | 5
2 | 2013-02-01 | 1
Supposedly a user_id can not have same month more than one.
Let's say I can put the months I want to query in the query statement. like below
SELECT user_id, (bla bla bla) AS '2013-03-01', (bla bla bla) AS '2013-02-01'
How do I get a result like below, with minimum number of queries and post-processing (e.g. using python or php)?
user_id | 2013-03-01 | 2013-02-01 | 2013-01-01
---------+------------+------------+------------
1 | 5 | 1 | 0
2 | NULL | 1 | NULL
You can use conditional aggregates to get the required result set:
SELECT user_id,
MAX(CASE WHEN month = '2013-03-01' THEN value END) AS '2013-03-01',
MAX(CASE WHEN month = '2013-02-01' THEN value END) AS '2013-02-01',
MAX(CASE WHEN month = '2013-01-01' THEN value END) AS '2013-01-01'
FROM mytable
GROUP BY user_id
This works as long there is a predefined set of month values. Otherwise you have to use dynamic SQL.
Fiddle Demo here

Mysql inner join query

I'm using two tables in the database.
The first contains data related to the successful and unsuccessful payments while the second table contains data regarding the status of services.
The result of the query should combine both tables and as a result list the successful and unsuccessful payments grouped by the days as well as the status of services grouped by days.
First table looks like:
id | charged | date
-----------------------------
8 | OK | 2011-12-03
7 | OK | 2011-12-03
9 | NO | 2011-12-03
11 | OK | 2011-12-04
14 | NO | 2011-12-04
The second table looks like:
id | status | date
--------------------------
8 | 1 | 2011-12-03
9 | 1 | 2011-12-03
11 | 0 | 2011-12-04
12 | 0 | 2011-12-04
14 | 1 | 2011-12-04
The correct query result should be:
date | not_charged | charged | status_1 | status_0
-----------------------------------------------------------
2011-12-04 | 1 | 1 | 1 | 2
2011-12-03 | 1 | 2 | 2 | 0
The query that I've tried looks like this:
SELECT i.date, SUM(
CASE WHEN i.charged = 'NO'
THEN 1 ELSE 0 END ) AS not_charged, SUM(
CASE WHEN i.charged = 'OK'
THEN 1 ELSE 0 END ) AS charged, SUM(
CASE WHEN s.status = '1'
THEN 1 ELSE 0 END ) AS status_1, SUM(
CASE WHEN s.status = '0' THEN 1 ELSE 0 END ) AS status_0
FROM charge i INNER JOIN status s ON s.date = i.date
GROUP BY i.date
But I get the wrong result that looks like this
date | not_charged | charged | status_1 | status_0
---------------------------------------------------------
2011-12-04 | 3 | 3 | 2 | 4
2011-12-03 | 2 | 4 | 6 | 0
What I'm doing wrong and how can I get the correct result?
Thanks for all suggestions.
Try this one -
SELECT date,
SUM(IF(charged = 'NO', 1, 0)) not_charged,
SUM(IF(charged = 'OK', 1, 0)) charged,
SUM(IF(status = 1, 1, 0)) status_1,
SUM(IF(status = 0, 1, 0)) status_0
FROM (
SELECT date, charged, NULL status FROM charge
UNION ALL
SELECT date, NULL charged, status FROM status
) t
GROUP BY date DESC;
+------------+-------------+---------+----------+----------+
| date | not_charged | charged | status_1 | status_0 |
+------------+-------------+---------+----------+----------+
| 2011-12-04 | 1 | 1 | 1 | 2 |
| 2011-12-03 | 1 | 2 | 2 | 0 |
+------------+-------------+---------+----------+----------+
This assumes the ID columns related that service status and payment status together...
SELECT
COALESCE(charge.date, status.date) AS date,
SUM(CASE WHEN charge.charged = 'NO' THEN 1 ELSE 0 END) AS not_charged,
SUM(CASE WHEN charge.charged = 'OK' THEN 1 ELSE 0 END) AS charged,
SUM(CASE WHEN status.status = '0' THEN 1 ELSE 0 END) AS status_0,
SUM(CASE WHEN status.status = '1' THEN 1 ELSE 0 END) AS status_1
FROM
charge
FULL OUTER JOIN
status
ON charge.id = status.id
GROUP BY
COALESCE(charge.date, status.date)
Note, I'm note sure how you want to deal with 7 (No status record) and 12 (no charge record). This currently just counts what is there.
Alternatively, if you don't want to related the records by ID, you can still relate by date but you need to change your logic.
At present you're getting this, because you only relate by date...
id | charged | date id | status | date
----------------------------- --------------------------
8 | OK | 2011-12-03 8 | 1 | 2011-12-03
8 | OK | 2011-12-03 9 | 1 | 2011-12-03
7 | OK | 2011-12-03 8 | 1 | 2011-12-03
7 | OK | 2011-12-03 9 | 1 | 2011-12-03
9 | NO | 2011-12-03 8 | 1 | 2011-12-03
9 | NO | 2011-12-03 9 | 1 | 2011-12-03
11 | OK | 2011-12-04 11 | 0 | 2011-12-04
11 | OK | 2011-12-04 12 | 0 | 2011-12-04
11 | OK | 2011-12-04 14 | 1 | 2011-12-04
14 | NO | 2011-12-04 11 | 0 | 2011-12-04
14 | NO | 2011-12-04 12 | 0 | 2011-12-04
14 | NO | 2011-12-04 14 | 1 | 2011-12-04
Instead you need to consolidate the data down to 1 per date per table, then join...
SELECT
COALESCE(charge.date, status.date) AS date,
charge.not_charged,
charge.charged,
status.status_0,
status.status_1
FROM
(
SELECT
date,
SUM(CASE WHEN charged = 'NO' THEN 1 ELSE 0 END) AS not_charged,
SUM(CASE WHEN charged = 'OK' THEN 1 ELSE 0 END) AS charged
FROM
charge
GROUP BY
date
)
AS charge
FULL OUTER JOIN
(
SELECT
date,
SUM(CASE WHEN charged = '0' THEN 1 ELSE 0 END) AS status_0,
SUM(CASE WHEN charged = '1' THEN 0 ELSE 1 END) AS status_1
FROM
status
GROUP BY
date
)
AS status
ON charge.date = status.date
There are other methods, but hopefully this explains a bit for you.
I suggest using a UNION ALL:
select date,
coalesce(sum(not_charged),0) not_charged,
coalesce(sum(charged),0) charged,
coalesce(sum(status_1),0) status_1,
coalesce(sum(status_0),0) status_0
from (select date,
case charged when 'NO' then 1 end not_charged,
case charged when 'OK' then 1 end charged,
0 status_1,
0 status_0
from charge
union all
select date,
0 not_charged,
0 charged,
case status when '1' then 1 end status_1,
case status when '0' then 1 end status_0
from status) sq
group by date