MySQL Count and Convert Row to Colum Involve One Table Only - mysql

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

Related

How to insert a sum from a table in another table in sql

I have these tables
Categories: Materials:
Id|Name Id|Name|IdCateg
1 |C1 1 |N1 |1
2 |C2
3 |C3
Materials:
Id|Name|IdCateg
1 |N1 |1
2 |N2 |1
3 |N3 |2
4 |N4 |2
5 |N5 |3
6 |N6 |3
Acquisitions:
Id|IdMat|Amount
1 | 1 | 10
2 | 1 | 5
3 | 1 | 30
4 | 2 | 23
5 | 2 | 10
SO i want in table STOCK the sum of amounts from table Aquisitions where IdMat is the same like this
STOCK:
Id|IdMat|Amount
1 | 1 | 45
2 | 2 | 33
HOW CAN i DO this?
I have tried to do this but it update it with the same value on every id:
update STOCK
set Amount=
(
select sum(a.Amount)
from AQUISITIONS a JOIN STOCK s
ON s.IdMat=a.IdMat
WHERE(s.IdMat=1)
)
where exists
(
select 1
from AQUISITIONS a JOIN STOCK s
ON s.IdMat=a.IdMat
)
Your subquery is just getting the sum for idMat = 1, and setting the amount in all stocks to that same amount. To do it right, the subquery should be correlated to the main table, not use a join inside the subquery.
But an easier way is to write a grouped subquery and then just join that to the table you want to update.
UPDATE STOCK AS s
JOIN (
SELECT idMat, SUM(amount) AS total
FROM Aquisitions
GROUP BY idMat
) AS a ON s.idMat = a.idMat
SET s.Amount = a.total

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

Get number of rows with values less than current row's value

My mysql table is:
Id | value | count_of_past_lower_values
1 | 120 | 0
2 | 210 | 1
3 |150 | 1
4 |140 | 1
5 |200 | 3
Given id and values, I have to update 3rd col. I made a query using SUM(IF(value<X,1,0)) but not able to identify right expression for X.
Issuing a self-left-join with conditional sum would suffice:
select
a.id,
a.value,
sum(case when a.value > b.value then 1 else 0 end) as count_of_past_lower_values
from yourtable a
left join yourtable b on a.id > b.id
group by a.id, a.value
order by a.id
Result
id | value | count_of_past_lower_values
----+-------+----------------------------
1 | 120 | 0
2 | 210 | 1
3 | 150 | 1
4 | 140 | 1
5 | 200 | 3

Count rows with specific value over multiple rows

Its very hard for to set a proper title, because I dont know how I describe my problem.
I have a table like this:
dlID | dl_seID | dlEpisode | dlFlag
___________________________________
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 1
4 | 2 | 1 | 1
5 | 2 | 2 | 0
6 | 3 | 1 | 0
What i want is a select query where I get something like this:
dlID | dl_seID | dlEpisode | dlFlag | dlFlagCount
_________________________________________________
1 | 1 | 1 | 0 | 2
2 | 1 | 2 | 1 | 2
3 | 1 | 3 | 1 | 2
4 | 2 | 1 | 1 | 1
5 | 2 | 2 | 0 | 1
6 | 3 | 1 | 0 | 0
dlFlagCount shoud be a counter of dlFlag = 1 where dl_seID = dl_seID.
Second try:
I need a value where I see how many Flags have the value 1 with the same dl_seID.
Is that possible?
I hope you guys know what I want^^
Regards
Try this:
select
a.*,
ifnull(b.ctflags,0)
from
tablea a left join
( select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID ) b on (a.dl_seID = b.dl_seID)
The left join is just to get the registry with 0 flags
See the fiddle: http://sqlfiddle.com/#!2/ef9b0/5
EDIT:
As op requested some explanation, here it goes:
What you asked is to count the amount of flags by the dl_seID and to do that you need to do this you separeta your problems, first you get the count for the dl_seID by flags, this is this subquery:
select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID
This became a 'separe table' or a new group of data, whatever you wanna call it. Then you have to join this with your original data (from your table) like the query for answer.
The left join part is because maybe there are some data that wont complain with where dlFlag=1 therefore if you want to get then as 0 you have to bring all values from table that exists or not on our created subgroup. And this ifnull(b.ctflags,0) is for theese data data exists on your table but has no flags (for your problem). If you use just b.ctflags it will bring null.
SELECT x.*
, COALESCE(y.flagcount,0) flagcount
FROM my_table x
LEFT
JOIN
( SELECT seID
, COUNT(*) flagcount
FROM my_table
WHERE flag = 1
GROUP
BY seid
) y
ON y.seid = x.seid;

SQL Group By Date Conflicts

I have a table with columns start_date and end_date. What we need to do is Select everything and group them by date conflicts for each Object_ID.
A date conflict is when a row's start date and/or end date pass through another rows'. For instance, here are some examples of conflicts:
Row 1 has dates 1st through the 5th, Row 2 has dates 2nd through the 3rd.
Row 1 has dates 2nd through the 5th, Row 2 has dates 1st through the 3rd.
Row 1 has dates 2nd through the 5th, Row 2 has dates 3rd through the 6th.
Row 1 has dates 2nd through the 5th, Row 2 has dates 1st through the 7th.
So for example, if we have some sample data (assume the numbers are just days of the month for simplicity):
id | object_id | start_date | end_date
1 | 1 | 1 | 5
2 | 1 | 2 | 4
3 | 1 | 6 | 8
4 | 2 | 2 | 3
What i would expect to see is this:
object_id | start_date | end_date | numconflicts
1 | <na> | <na> | 2
1 | 6 | 8 | 0 or null
2 | 2 | 3 | 0 or null
And for a Second Test Case, Here is some sample data:
id | object_id | start_date | end_date
1 | 1 | 1 | 5
2 | 1 | 2 | 4
3 | 1 | 6 | 8
4 | 2 | 2 | 3
5 | 2 | 4 | 5
6 | 1 | 2 | 3
7 | 1 | 10 | 12
8 | 1 | 11 | 13
And for the second Test Case, what I would expect to see as output:
object_id | start_date | end_date | numconflicts
1 | <na> | <na> | 3
1 | 6 | 8 | 0 or null
2 | 2 | 3 | 0 or null
2 | 4 | 5 | 0 or null
1 | <na> | <na> | 2
Yes, I will need some way of differentiating the first and the second grouping (the first and last rows) but I haven't quite figured that out. The goal is to view this list, and then when you click on a group of conflicts you can view all of the conflicts in that group.
My first thought was to attempt some GROUP BY CASE ... clause but I just wrapped by head around itself.
The language I am using to call mysql is php. So if someone knows of a php-loop solution rather than a large mysql query i am all ears.
Thanks in advance.
Edit: Added in primary Keys to provide a little less confusion.
Edit: Added in a Test case 2 to provide some more reasoning.
This query finds the number of duplicates:
select od1.object_id, od1.start_date, od1.end_date, sum(od2.id is not null) as dups
from object_date od1
left join object_date od2
on od2.object_id = od1.object_id
and od2.end_date >= od1.start_date
and od2.start_date <= od1.end_date
and od2.id != od1.id
group by 1,2,3;
You can use this query as the basis of a query that gives you exactly what you asked for (see below for output).
select
object_id,
case dups when 0 then start_date else '<na>' end as start_date,
case dups when 0 then end_date else '<na>' end as end_date,
sum(dups) as dups
from (
select od1.object_id, od1.start_date, od1.end_date, sum(od2.id is not null) as dups
from object_date od1
left join object_date od2
on od2.object_id = od1.object_id
and od2.end_date >= od1.start_date
and od2.start_date <= od1.end_date
and od2.id != od1.id
group by 1,2,3) x
group by 1,2,3;
Note that I have used an id column to distinguish the rows. However, you could replace the test of id's not matching with comparisons on every column, ie replace od2.id != od1.id with tests that every other column is not equal, but that would require a unique index on all the other columns to make sense, and having an id column is a good idea anyway.
Here's a test using your data:
create table object_date (
id int primary key auto_increment,
object_id int,
start_date int,
end_date int
);
insert into object_date (object_id, start_date, end_date)
values (1,1,5),(1,2,4),(1,6,8),(2,2,3);
Output of first query when run against this sample data:
+-----------+------------+----------+------+
| object_id | start_date | end_date | dups |
+-----------+------------+----------+------+
| 1 | 1 | 5 | 1 |
| 1 | 2 | 4 | 1 |
| 1 | 6 | 8 | 0 |
| 2 | 2 | 3 | 0 |
+-----------+------------+----------+------+
Output of second query when run against this sample data:
+-----------+------------+----------+------+
| object_id | start_date | end_date | dups |
+-----------+------------+----------+------+
| 1 | 6 | 8 | 0 |
| 1 | <na> | <na> | 2 |
| 2 | 2 | 3 | 0 |
+-----------+------------+----------+------+
Oracle : This could be done with a subquery in a group by CASE statement.
https://forums.oracle.com/forums/thread.jspa?threadID=2131172
Mysql : You could have a view which had all the conflicts .
select distinct a1.appt, a2.appt from appointment a1, appointment a2 where a1.start < a2.end and a1.end > a2.start.
and then simply do a count(*) on that table.
Something like the following should work:
select T1.object_id, T1.start_date, T1.end_date, count(T1.object_id) as numconflicts
from T1
inner join T2 on T1.start_date between T2.start_date and T2.end_date
inner join T3 on T1.end_date between T2.start_date and T2.end_date
group by T1.object_id
I might be off a little bit, but it should help you get started.
Edit: Indented it properly