Attach max from group to every row of results ? - mysql

I have similar to the following database structure:
ApplicationService table :
+-----+--------------+----------+---------------+
| id | name | status | application_id
| 1 | Service 1 | 1 | 24
| 2 | Service 2 | 2 | 24
| 3 | Service 3 | 3 | 25
+-----+--------------+----------+----------------
And there is other table with status definitions:
CustomerStatus
+------------+--------------+----------+-----------+
| status_id | name | level | is_closed
| 1 | Status 1 | 2 | 1
| 2 | Status 2 | 1 | 0
| 3 | Status 3 | 3 | 1
+------------+--------------+----------+----------
The status for each row of the ApplicationServices is calculated as the max level status within the records grouped by application_id.
So to get all records from ApplicationServices with statuses would result in something like this:
+-----+--------------+----------+----------------+-------------+-----------
| id | name | status | application_id | status_name | is_closed
| 1 | Service 1 | 1 | 24 | Status 1 | 1
| 2 | Service 2 | 2 | 24 | Status 1 | 1
| 3 | Service 3 | 3 | 25 | Status 3 | 1
+-----+--------------+----------+----------------+-------------+-----------
Is there an efficient way to attach the results with max(level) grouped by application_id to every row of the result set ?

Try this:
SELECT A.id, A.name, A.status, A.application_id, CS.name AS status_name, CS.is_closed
FROM ApplicationService A
INNER JOIN (SELECT AA.application_id, MAX(CS.level) AS maxLevel
FROM ApplicationService AA
INNER JOIN CustomerStatus CS ON AA.status = CS.status_id
GROUP BY AA.application_id
) AS AA ON A.application_id = AA.application_id
INNER JOIN CustomerStatus CS ON AA.maxLevel = CS.level;

See working SQLfiddle here.
You will probably have to perform a sub-query. The subquery would simply be joining the two tables together, grouping/collapsing by application_id and then fetching the maximum is_closed value:
SELECT
t1.status AS `status`,
t1.application_id AS `app_id`,
MAX(t2.is_closed) AS `max_is_closed`
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
GROUP BY t1.application_id
The max is_closed value should then be accessible by the max_is_closed alias when you incorporate the subquery:
SELECT
t1.id AS id,
t1.name AS name,
t1.status AS `status`,
t1.application_id AS application_id,
t2.name AS status_name,
t3.max_is_closed
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
LEFT JOIN
(SELECT
t1.status AS `status`,
t1.application_id AS `app_id`,
MAX(t2.is_closed) AS `max_is_closed`
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
GROUP BY t1.application_id) AS t3 ON
t1.application_id = t3.app_id
The output from the query:

Related

How to track previous row status count

I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.

Is there better way to get the difference between two table with same fields?

I have two tables as below:
table1:
+----+----------+-------+
| id | order_id | price |
+----+----------+-------+
| 1 | 1024 | 20 |
| 2 | 1025 | 30 |
| 3 | 1026 | 35 |
| 4 | 1027 | 45 |
+----+----------+-------+
table2
+----+----------+-------+------+
| id | order_id | price | name |
+----+----------+-------+------+
| 1 | 1024 | 20 | a |
| 2 | 1025 | 30 | b |
| 3 | 1026 | 35 | c |
| 4 | 1027 | 40 | d |
+----+----------+-------+------+
What I want to do is just camparing fields order_id and price, and get the different content when order_id = 1027
Here is my humble opinion:
SELECT * FROM (
SELECT order_id, price FROM table1
UNION ALL
SELECT order_id, price FROM table2
) t
GROUP BY order_id, price
HAVING COUNT(*) = 1
# result
+----------+-------+
| order_id | price |
+----------+-------+
| 1027 | 40 |
| 1027 | 45 |
+----------+-------+
Is there any better way to get it.
Any commentary is very welcome. great thanks.
Another alternative would be to use a JOIN to find non-matching prices:
SELECT t1.order_id, t1.price AS table1_price, t2.price AS table2_price
FROM table1 t1
JOIN table2 t2 ON t2.order_id = t1.order_id AND t2.price != t1.price
Output:
order_id table1_price table2_price
1027 45 40
Demo on dbfiddle
If you also want to capture rows which exist in one table but not the other, then you will need a FULL OUTER JOIN, which MySQL doesn't support, and must be emulated using a UNION of a LEFT JOIN and a RIGHT JOIN:
SELECT *
FROM (SELECT t1.order_id AS order_id, t1.price AS table1_price, t2.price AS table2_price
FROM table1 t1
LEFT JOIN table2 t2 ON t2.order_id = t1.order_id
UNION
SELECT t2.order_id, t1.price AS table1_price, t2.price AS table2_price
FROM table1 t1
RIGHT JOIN table2 t2 ON t2.order_id = t1.order_id) t
WHERE table1_price != table2_price OR
table1_price IS NULL OR
table2_price IS NULL
Output:
order_id table1_price table2_price
1027 45 40
1028 50 null
1029 null 45
Demo on dbfiddle
You can use left join to get the values
SELECT table1.order_id, table1.price
FROM table1
LEFT JOIN table2 ON table2.order_id = table1.order_id AND table2.price != table1.price

Calculate sum on the records while joining two tables with a third table

I have three tables:
mysql> select * from a;
+----+---------+
| ID | Name |
+----+---------+
| 1 | John |
| 2 | Alice |
+----+---------+
mysql> select * from b;
+------+------------+----------+
| UID | date | received |
+------+------------+----------+
| 1 | 2017-10-02 | 5 |
| 1 | 2017-09-30 | 1 |
| 1 | 2017-09-29 | 4 |
+------+------------+----------+
mysql> select * from c;
+------+------------+------+
| UID | date | sent |
+------+------------+------+
| 1 | 2017-09-25 | 7 |
| 1 | 2017-09-30 | 2 |
| 1 | 2017-09-29 | 3 |
+------+------------+------+
If I try to calculate the total number of sent for John, it would be 12. And for received, it would be 10.
But if I try to join all three tables, the result is weird. Here is my query to join three tables:
mysql> select sum(sent), sum(received) from a
-> join c on c.UID = a.ID
-> join b on b.UID = a.ID
-> where a.ID = 1;
+-----------+---------------+
| sum(sent) | sum(received) |
+-----------+---------------+
| 36 | 30 |
+-----------+---------------+
But I need correct numbers (12 and 10, respectively). How can I have correct numbers?
You should join the aggregated result and not the raw tables
select a.uid, t1.received, t2.sent
from a
inner join (
select uid, sum(received) received
from b
group by uid
) t1 on t1.uid = a.id
inner join (
select uid, sum(sent) sent
from c
group by uid
) t2 on t2.uid = a.id
where a.id = 1
You could try below
select bx.id, recieved, sum(c.sent) sent from
(
SELECT a.id, sum(b.received) recieved
from a
INNER JOIN b
ON a.id=b.uid
group by a.id
) bx
INNER JOIN c
ON c.uid=bx.id
group by bx.id, bx.recieved;
>>>Demo<<<
This gets rid of the subquery, but introduces something else you might not want:
( SELECT uid, 'Received' AS direction, SUM(received) AS HowMany
WHERE uid = 1
GROUP BY uid )
UNION ALL
( SELECT uid, 'Sent' AS direction, SUM(sent) AS HowMany
WHERE uid = 1
GROUP BY uid )

MySQL join query with multiple where condition

I have 3 tables. I need to join those 3 and get 2 fields from each table. And there will be few where conditions. Where condition is for date range. Even if one table has the the result I need to show it along with other table data showing as 0.
I have tried using inner join. But what it does is taking only 1st where condition and if no result in first it will not go for next conditions. My table structures and required output are shown below.
table1
+--------+---------+------------+
| amount | site_id | date |
+--------+---------+------------+
| 10 | 1 | 12/12/2014 |
| 50 | 2 | 10/12/2014 |
| 30 | 3 | 05/11/2014 |
+--------+---------+------------+
table2
+--------+---------+------------+
| amount | site_id | date |
+--------+---------+------------+
| 100 | 1 | 2/11/2014 |
| 40 | 2 | 10/10/2014 |
| 30 | 3 | 05/11/2014 |
+--------+---------+------------+
table3
+--------+---------+------------+
| amount | site_id | date |
+--------+---------+------------+
| 60 | 1 | 12/12/2014 |
| 50 | 3 | 11/12/2014 |
| 70 | 4 | 05/09/2014 |
+--------+---------+------------+
output : total amounts between 01/12/2014 and 31/12/2014
+---------+---------------+---------------+---------------+-------+
| site_id | table1_amount | table2_amount | table3_amount | total |
+---------+---------------+---------------+---------------+-------+
| 1 | 60 | 0 | 60 | 120|
| 3 | 0 | 0 | 50 | 50 |
+---------+---------------+---------------+---------------+-------+
Can anyone suggest a query to get this output?
This is what I have done so far
select sum(table1.amount),sum(table2.amount),sum(table3.amount),(sum(table1.amount)+sum(table2.amount)+sum(table3.amount)) from table1 inner join table2 on table1.site_id=table2.site_id inner join table3 on table3.site_id=table2.site_id where table1.date>='01/12/2014' and table1.date<='31/12/2014' or table2.date>='01/12/2014' and table2.date<='31/12/2014' or table3.date>='01/12/2014' and table3.date<='31/12/2014' group by table1.site_id
Try this:
SELECT S.site_id,
IFNULL(t1.table1_Amount, 0) AS table1_Amount,
IFNULL(t2.table2_Amount, 0) AS table2_Amount,
IFNULL(t3.table3_Amount, 0) AS table3_Amount,
(IFNULL(t1.table1_Amount, 0) + IFNULL(t2.table2_Amount, 0) + IFNULL(t3.table3_Amount, 0)) AS total
FROM Site S
LEFT OUTER JOIN ( SELECT t1.site_id, SUM(t1.amount) AS table1_Amount
FROM table1 t1
WHERE t1.date >= '01/12/2014' AND t1.date <= '31/12/2014'
GROUP BY t1.site_id
) AS t1 ON S.site_id = t1.site_id
LEFT OUTER JOIN ( SELECT t2.site_id, SUM(t2.amount) AS table2_Amount
FROM table2 t2
WHERE t2.date >= '01/12/2014' AND t2.date <= '31/12/2014'
GROUP BY t2.site_id
) AS t2 ON S.site_id = t2.site_id
LEFT OUTER JOIN ( SELECT t3.site_id, SUM(t3.amount) AS table3_Amount
FROM table1 t3
WHERE t3.date >= '01/12/2014' AND t3.date <= '31/12/2014'
GROUP BY t3.site_id
) AS t3 ON S.site_id = t3.site_id;

Query MySQL Group by and Having

I am creating an application for my school and I am in trouble constructing the right query.
I have 2 tables,table1 and table2.
table1
---------------------------------------------------------
| StudentID | SubjectID | Present | Type |
---------------------------------------------------------
| 2 | 3 | yes | 1 |
| 2 | 2 | yes | 2 |
| 3 | 1 | no | 3 |
---------------------------------------------------------
table2
---------------------------------------------------------
| SubjectID | SubjectName | Number1 | Number2 |
---------------------------------------------------------
| 1 | Name1 | 6 | 4 |
| 2 | Name2 | 4 | 8 |
| 3 | Name3 | 5 | 2 |
---------------------------------------------------------
SubjectID in table1 is foreign key references table2.
I want to build a query sql that gives me the StudentID`s from table1
that didnt miss any Type 3 subject (i.e no row like this
---------------------------------------------------------
| StudentID | SubjectID | Present | Type |
---------------------------------------------------------
| 3 | 1 | no | 3 |
---------------------------------------------------------
And have completed 75 percent of type 1 (i.e
I find it like this
SELECT t1.StudentID,t1.SubjectID ,t1.Type,t2.Number1 as num
FROM table1 as t1,table2 as t2
WHERE t1.Present=yes and t2.SubjectID=t1.SubjectID
GROUP BY StudentID,SubjectID
HAVING COUNT(*)/num >= 75/100
But I cant combine the two things together.
You can combine queries by giving them aliases and joining as subqueries...
SELECT finisher.StudentID FROM
(
SELECT DISTINCT StudentID
FROM table1 t1
JOIN table2 t2 ON t2.SubjectID = t1.SubjectID
WHERE t1.Present = 'yes' AND t1.Type1 = 1
GROUP BY t1.StudentID, t2.SubjectID
HAVING COUNT(*) / t2.Number2 >= 0.75
) finisher
JOIN
(
SELECT DISTINCT t1.StudentID
FROM table1 t1
LEFT JOIN
(
SELECT DISTINCT StudentID
FROM table1
WHERE Type = 3 AND Present = 'no'
) missed ON missed.StudentID = t1.StudentID
WHERE t1.Type = 3
AND missed.StudentID IS NULL
) notmissed ON finisher.StudentID = notmissed.StudentID
"StudentID`s from table1 that didnt miss any Type 3"... I assume here you don't want to include students without any type 3 rows.
Seems like this is done and duste, but how about...
SELECT x.*
FROM
( SELECT t1.StudentID
, t1.SubjectID
, t1.Type
, t2.Number1 num
FROM table1 t1
JOIN table2 t2
ON t2.SubjectID=t1.SubjectID
WHERE t1.Present='yes'
GROUP
BY t1.StudentID
, t1.SubjectID
HAVING COUNT(*)/num >= 0.75
) x
LEFT
JOIN table1 y
ON y.student_id = x.student_id
AND y.subject_id = x.subject_id
AND y.type = 3
AND y.present = 'no'
WHERE y.student_id IS NULL;