Struggling with MySQL subquery - mysql

I have two tables, image and gradeReason. Each image is awarded a grade for it's quality and the user can select up 4 different reasons (reasonID_1, reasonID_2, reasonID_3, reasonID_4) by using selecting a reasonID. The breakdown of the reason is stored in the gradeReason table.
image
imageID auditID reasonID_1 reasonID_2 reasonID_3 reasonID_4
------- ------- ---------- ---------- ---------- ----------
1 123 1 13 7 3
2 124 8 13 8 6
4 125 3 2 5 6
5 125 7 4 2 3
gradeReason
reasonID category name
-------- -------- ----
1 exposure overexposed
2 exposure underexposed
3 patient patient moved
4 equipment sensor too big
5 equipment sensor too small
What I would like is a query that will return the number of times each reasonID has been used in an audit and what the name of gradeReason was
e.g.
audit 125 -
reasonID 3 was used twice - name 'patient moved',
reasonID 2 used twice - name 'underexposed'.
I'll be honest and say I have struggled with this for days and I can't even think where to begin.

This is harder because your data is not properly normalized. The following approach first normalizes the data, then does the join and aggregation:
select ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
from (select i.imageID, i.auditID,
(case when n.n = 1 then ReasonID_1
when n.n = 2 then ReasonID_2
when n.n = 3 then ReasonID_3
when n.n = 4 then ReasonID_4
end) as ReasonId
from image i cross join
(select 1 as n union all select 2 union all select 3 union all select 4
) n
) ir join
gradeReason gr
on ir.ReasonId = gr.ReasonId
group by ir.auditId, gr.ReasonId, gr.category, gr.name
order by cnt desc;

For your select, you have to join gradeReason once for every foreign key to gradeReason:
SELECT imageID
, auditID
, r1.name
, r2.name
, r3.name
, r4.name
FROM image i
LEFT JOIN gradeReason r1 on i.reasonID_1 = r1.reasonID
LEFT JOIN gradeReason r2 on i.reasonID_2 = r2.reasonID
LEFT JOIN gradeReason r3 on i.reasonID_3 = r3.reasonID
LEFT JOIN gradeReason r4 on i.reasonID_4 = r4.reasonID
Your example data is not sensible, as the foreign keys to gradeReason in your table image is not in gradeReason, but I guess you have more gradeReasons up your sleeve.
Moreover,your structure is not normalized:
gradeReason has duplicate entries in rows for category. This is not too bad, but it would be good practice to have a separate gradeReasonCategories table.

Another option using UNIONS:-
SELECT auditId, ReasonId, category, name, SUM(cnt)
FROM
(
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_1 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
UNION ALL
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_2 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
UNION ALL
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_3 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
UNION ALL
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_4 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
) Sub1
GROUP BY auditId, ReasonId, category, name

Related

how to get max rows from 2 transactional tables. MySQL

I have two mysql transactional tables and and two lookup tables. I want to select max(id) from each of the transactional tables, combine the results with lookup tables and combine into one row. I seem unable to find solutions so far. Here is my tables. Stocks and Prices are transactional while Vehicle and Models are lookup tables.
Vehicles table
id name
1 Toyota
2 Suzuki
Models table
id vehicle_id name
1 1 Corolla
2 2 Swift
3 1 Prado
4 2 Vitara
Stocks table
id vehicle_id model_id qty
1 1 1 50
2 2 2 77
3 1 1 40
4 2 2 30
Prices table
id vehicle_id model_id price
1 1 1 500
2 2 2 777
3 1 1 600
4 2 2 1000
Expected results
id vehicle_id model_id qty price vname mname
1 1 1 40 600 Toyota Corolla
2 2 2 30 1000 Suzuki Swift
Here is what I've tried among countless trials
select s.*, b.name vehicle, m.name model, p.price
from stocks s, vehicles b, models m, prices p
where s.id in (select max(id) id from stocks
where s.vehicle_id = b.id and s.model_id = m.id and s.vehicle_id = p.vehicle_id and s.model_id = p.model_id
group by vehicle_id, model_id)
order by id;
Running the above query doesn't give me what I want and it crushes the PC. I have to restart. How can I achieve the expected outcome?
If you are using MySQL 8 you can use window functions and common table expressions for latest(based on maximum id per vehicle and model group) prices and qty for vehicle and models
with pricescte as (select *,
rank() over (partition by vehicle_id,model_id order by id desc) AS price_rank
from prices),
stockcte as (select *,
rank() over (partition by vehicle_id,model_id order by id desc) AS stock_rank
from stocks)
select v.id,
v.name,
m.id as model_id,
m.name,
s.qty,
p.price
from vehicles v
join models m on v.id = m.vehicle_id
join stockcte s on v.id = s.vehicle_id
and m.id = s.model_id
join pricescte p on v.id = p.vehicle_id
and m.id = p.model_id
where s.stock_rank = 1
and p.price_rank = 1
DEMO
If you are not on latest version of MySQL < 8 you could use a query like
select v.id,
v.name,
m.id as model_id,
m.name,
s.qty,
p.price
from vehicles v
join models m on v.id = m.vehicle_id
join (
select *
from stocks st
where id = (
select max(id)
from stocks
where st.vehicle_id =vehicle_id
and st.model_id = model_id
)
) s
on v.id = s.vehicle_id
and m.id = s.model_id
join (
select *
from prices pr
where id = (
select max(id)
from prices
where pr.vehicle_id =vehicle_id
and pr.model_id = model_id
)
) p on v.id = p.vehicle_id
and m.id = p.model_id
DEMO

Getting data form 2 tables and summing values of the result

I have 2 tables with information: ID, persona_id, total_amount
The persona ID can repeat dozen of times. So i get all the one persons id total_amount with query:
select d.id as debt_id, p.name as persona, sum(d.total_amount) as total_amount
from debt d
join persons p on d.persona_id = p.id group by p.name
I want to get data from each table in one query and do aritmethic propertys with the total_amount column and return it as 1 tabel.
TABLE 1
id persons_id total_amount
1 2 50
2 3 100
3 2 200
4 5 300
5 1 500
TABLE 2
id persons_id total_amount
1 2 25
2 1 100
3 5 50
4 3 100
5 4 300
As a result i want to get the 2 tables comined with arithmetic operation (-, +, / , * ) of Total amount columns.Basicaly a change to get the ending result total amount in form i want for different cases.
What worked for me based on JohnHC answear was :
select c.id, c.persona_id, c.total_amount - d.total_amount as new_total
from ( select c.id , c.persona_id, sum(c.total_amount) as total_amount from credit c
join persons p on c.persona_id = p.id
group by p.name) c
inner join ( select d.id, d.persona_id, sum(d.total_amount) as total_amount from debt d
join persons p on d.persona_id = p.id
group by p.name) d
on c.persona_id = d.persona_id
group by c.id, c.persona_id
If you want the total, try:
select id, person_id, sum(total_amount)
from
(
select id, person_id, total_amount
from table1
union all
select id, person_id, total_amount
from table2
)
group by id, person_id
If you want to do other things, try:
select t1.id, t1.person_id, t1.total_amount [+ - / *] t2.total_amount as new_total
from table1 t1
inner join table2 t2
on t1.id = t2.person_id
group by t1.id, t1.person_id

MySQL - Display null column from child table if all values are not distinct

I have the following tables, for example:
invoices
ID Name
1 A
2 B
3 C
4 D
5 E
transactions
ID Invoice_ID User_ID
1 1 10
2 1 10
3 1 10
4 2 30
5 3 20
6 3 40
7 2 30
8 2 30
9 4 40
10 3 50
Now I want to make a select that will pull the invoices and the user_id from the related transactions, but of course if I do that I won't get all the ids, since they may be distinct but there will be only one column for that. What I want to do is that if there are distinct User_ids, I will display a pre-defined text in the column instead of the actual result.
select invoices.id, invoices.name, transactions.user_id(if there are distinct user_ids -> return null)
from invoices
left join transactions on invoices.id = transactions.invoice_id
and then this would be the result
ID Name User_ID
1 A 10
2 B 30
3 C null
4 D 40
5 E null
Is this possible?
You can do the following :
select
invoices.id,
invoices.name,
IF (
(SELECT COUNT(DISTINCT user_id) FROM transactions WHERE transactions.invoice_id = invoices.id) = 1,
(SELECT MAX(user_id) FROM transactions WHERE transactions.invoice_id = invoices.id),
null
) AS user_id
from invoices
Or, alternatively, you can use the GROUP_CONCAT function to output a comma-separated list of users for each invoice. It is not exactly what you asked, but maybe in fact it will be more useful :
select
invoices.id,
invoices.name,
GROUP_CONCAT(DISTINCT transactions.user_id SEPARATOR ',') AS user_ids
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
Try somethingh like:
select i.id, i.name, t.user_id
from invoices i left join
(
select invoice_ID, User_ID
from transactions
group by invoice_ID
having count(invoice_ID)=1
) t on i.id=t.invoice_id
SQL fiddle
You could list all the transactions that have multiple user ids, like this:
select invoices.id, invoices.name, null
from invoices
left join transactions on invoices.id = transactions.invoice_id having count(distinct transactions.user_id) > 1
Also, I think this CASE might suit your needs here:
select invoices.id, invoices.name,
case when count(distinct transactions.user_id) > 1 then null else transactions.user_id end
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
although, I'm not sure this is syntactically correct

SELECT COUNT() GROUP BY Set of data in IN() mysql

My table is like
Loves table (loves)
animal_id | user_id | time
1 1
2 1
1 3
1 5
2 3
3 1
Animals table(animal)
id | type | name
1 air animal 1
2 ground animal 2
3 water animal 3
4 space animal 4
5 air animal 5
6 ground animal 6
My Query
( select count(*) from loves where animal_id = a.animal_id) as n_l,
a.name
FROM animal a ,loves lp
WHERE a.animal_id = lp.animal_id
AND lp.user_id = $MYUSERID
AND a.type IN ($MYANIMALTYPES)
ORDER BY lp.time DESC
LIMIT 5
Now if $MYUSERID = 1 and $MYANIMALTYPES = 'air','ground'
I should get output as from above example
n_l | name
3 animal 1
2 animal 2
But for some reason i want to get combinations of
$MYANIMALTYPES = 'air','ground' ,
$MYANIMALTYPES = 'air','water' ,
$MYANIMALTYPES = 'space',
and
$MYANIMALTYPES = 'space','water
seperately in groups i have to run the above query 4 times for each $MYANIMALTYPES.
My problem is that I'm using too many queries for what i want. Is there any way to get what i want in One single query ?
UPDATE
For a simple understanding how do i combine these two queries below into one query ?
( select count(*) from loves where animal_id = a.animal_id) as n_l,
a.name
FROM animal a ,loves lp
WHERE a.animal_id = lp.animal_id
AND lp.user_id = $MYUSERID
AND a.type IN ('ground','air')
ORDER BY lp.time DESC
LIMIT 5
AND
( select count(*) from loves where animal_id = a.animal_id) as n_l,
a.name
FROM animal a ,loves lp
WHERE a.animal_id = lp.animal_id
AND lp.user_id = $MYUSERID
AND a.type IN IN ('air','space')
ORDER BY lp.time DESC
LIMIT 5
You could use this adaptation of your query. It adds a join to the 4 type combinations you are interested in:
SELECT ( SELECT count(*)
FROM loves
WHERE animal_id = a.animal_id) as n_l,
a.name,
trim(concat(types.type1, ' ', types.type2)) grp
FROM animal a
INNER JOIN loves lp
ON a.animal_id = lp.animal_id
INNER JOIN ( SELECT 'air' type1, 'ground' type2
UNION ALL
SELECT 'air', 'water'
UNION ALL
SELECT 'space', ''
UNION ALL
SELECT 'space', 'water' ) AS types
ON a.type IN (types.type1, types.type2)
WHERE lp.user_id = $MYUSERID
GROUP BY a.name,
trim(concat(types.type1, ' ', types.type2))
ORDER BY 3, lp.time DESC
LIMIT 15
I also set the limit higher as you will now get all the results in one query.
Here is a fiddle.
The problem is you cant use string directly
if you string is
$MYANIMALTYPES = "'air','ground'"
That translate into
a.type IN ('air,ground');
and you need
a.type IN ('air', 'ground');
Try with FIND_IN_SET
WHERE FIND_IN_SET(a.type, #MYANIMALTYPES) > 0

mySQL - GROUP BY but get the most recent row

I've got a budget table:
user_id product_id budget created
-----------------------------------------------------------------
1 1 300 2011-12-01
2 1 400 2011-12-01
1 1 500 2011-12-03
2 2 400 2011-12-04
I've also got a manager_user table, joining a manager with the user
user_id manager_id product_id
------------------------------------
1 5 1
1 9 2
2 5 1
2 5 2
3 5 1
What I'd like to do is grab each of the user that's assigned to Manager #5, and also get their 'budgets'... but only the most recent one.
Right now my statement looks like this:
SELECT * FROM manager_user mu
LEFT JOIN budget b
ON b.user_id = mu.user_id AND b.product_id = mu.product_id
WHERE mu.manager_id = 5
GROUP BY mu.user_id, mu.product_id
ORDER BY b.created DESC;
The problem is it doesn't pull the most recent budget. Any suggestions? Thanks!
To accomplish your task you can do as follows:
select b1.user_id,
b1.budget
from budget b1 inner join (
select b.user_id,
b.product_id,
max(created) lastdate
from budget b
group by b.user_id, b.product_id ) q
on b1.user_id=q.user_id and
b1.product_id=q.product_id and
b1.created=q.lastdate
where b1.user_id in
(select user_id from manager_user where manager_id = 5);
I'm assuming here that your (user_id, product_id, created) combination is unique.
For what it's worth, here's the code that returned what I was looking for:
SELECT DISTINCT(b1.id),mu.user_id,mu.product_id,b1.budget,b1.created
FROM budget b1
INNER JOIN (
SELECT b.user_id, b.product_id, MAX(created) lastdate
FROM budget b
GROUP BY b.user_id, b.product_id) q
ON b1.user_id=q.user_id AND
b1.product_id=q.product_id AND
b1.created=q.lastdate
RIGHT JOIN manager_user mu
ON mu.user_id = b1.user_id AND
mu.product_id = b1.product_id
WHERE mu.manager_id = 5;
Thanks for the help Andrea!