Joining and nesting queries in mysql - mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?

You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.

Related

Nested JOIN to create custom dynamic columns

I have a table veicoli (vehicles) like this:
-------------------------------
| ID | Modello | Targa |
-------------------------------
| 1 | IVECO | XA123WE |
-------------------------------
| 2 | IVECO | CF556XD |
-------------------------------
| 3 | FIAT | AS332ZZ |
-------------------------------
| 4 | GOLF | GF567YU |
-------------------------------
For each vehicle I have none, one or multiple revisioni_veicolo (revisions) (the one with bigger DateExpiring is the one I need to check if revision is still valid or not based on today date)
-------------------------------------------------------------------
| ID | veicoli_ID | DateExpiring | Pass_Success |
-------------------------------------------------------------------
| 1 | 1 | 2019-07-01 | 1
------------------------------------------------------------------
| 2 | 1 | 2020-10-01 | 0
-------------------------------------------------------------------
| 3 | 2 | 2019-11-25 | 1
-------------------------------------------------------------------
| 4 | 2 | 2018-10-20 | 1
-------------------------------------------------------------------
| 5 | 4 | 2017-10-20 | 1
-------------------------------------------------------------------
Based on my example above (today is 2019-10-29):
Vehicle: ID = 1 has a revision still active (2020-10-01) but not passed (Pass_success = 0)
Vehicle: ID = 2 has a revision still active (2019-11-25) and passed (Pass_success = 1)
Vehicle: ID = 3 has no revision yet
Vehicle: ID = 4 has revision, but no active revision (last expired on 2017-10-20) but the last one passed the check (Pass_success = 1)
What I need is to have 3 new custom columns created dynamically on my query result:
-------------------------------------------------------------------------------------------
| ID | Modello | Targa | RevisionPresent | RevisionStillActive | LastRevisionPassed |
-------------------------------------------------------------------------------------------
| 1 | IVECO | XA123WE | true | true | false
-------------------------------------------------------------------------------------------
| 2 | IVECO | CF556XD | true | true | true
-------------------------------------------------------------------------------------------
| 3 | FIAT | AS332ZZ | false | false | false
-------------------------------------------------------------------------------------------
| 4 | GOLF | GF567YU | true | false | true
-------------------------------------------------------------------------------------------
I tried to start with my old post: MYSQL INNER JOIN to get 3 types of result
But I'm very confused using nested JOIN
I tried starting a fiddle but i'm stuck on syntax error: http://sqlfiddle.com/#!9/3c70bf/2
You need a LEFT JOIN of the tables and conditional aggregation:
select v.ID, v.Modello, v.Targa,
max(r.DataScadenzaRevisione is not null) RevisionPresent,
coalesce(max(r.DataScadenzaRevisione >= current_date()), 0) RevisionStillActive,
max(case when r.DataScadenzaRevisione = g.maxdate then r.EsitoPositivo else 0 end) LastRevisionPassed
from veicoli v
left join revisioni_veicolo r on r.veicoli_ID = v.id
left join (
select veicoli_id, max(DataScadenzaRevisione) maxdate
from revisioni_veicolo
group by veicoli_id
) g on g.veicoli_ID = v.id
group by v.ID, v.Modello, v.Targa
See the demo.
Results:
| ID | Modello | Targa | RevisionPresent | RevisionStillActive | LastRevisionPassed |
| --- | ------- | ------- | --------------- | ------------------- | ------------------ |
| 1 | IVECO | XA123WE | 1 | 1 | 0 |
| 2 | IVECO | CF556XD | 1 | 1 | 1 |
| 3 | FIAT | AS332ZZ | 0 | 0 | 0 |
| 4 | GOLF | GF567YU | 1 | 0 | 1 |
...
LEFT JOIN (SELECT a.veicoli_ID, a.EsitoPositivo AS StatoUltimaRevisione,
a.DataScadenzaRevisione FROM revisioni_veicolo) a
...
There's two things wrong with this.
The alias a is defined for this subquery, so you can't reference it inside the subquery. But you don't need to qualify the columns in this subquery anyway - you didn't do this in other subqueries, so I'm not sure why you did it in this case.
You don't have any join condition for this join. MySQL is a little bit inconsistent about when join conditions are required. But in this case, you need one.
After I tested the query with these two corrections, it works.
Basically you just need to look at the last revision of each vehicule to produce that resultset.
You can do the filtering with a correlated subquery:
select
v.ID,
v.Modello,
v.Targa,
(DataScadenzaRevisione >= now()) RevisionPresent,
(DataScadenzaRevisione >= now() and EsitoPositivo = 1) RevisionStillActive,
(EsitoPositivo = 1) LastRevisionPassed
from
veicoli v
left join revisioni_veicolo r
on r.veicoli_ID = v.ID
and r.DataScadenzaRevisione = (
select max(DataScadenzaRevisione)
from revisioni_veicolo r1
where r1.veicoli_ID = v.ID
)
You can check the results with your sample data in this db fiddle.
Or you can use a window function (this requires MySQL 8.0):
select
v.ID,
v.Modello,
v.Targa,
(DataScadenzaRevisione >= now()) RevisionPresent,
(DataScadenzaRevisione >= now() and EsitoPositivo = 1) RevisionStillActive,
(EsitoPositivo = 1) LastRevisionPassed
from (
select
v.*,
r.*,
row_number() over(partition by ID order by r.DataScadenzaRevisione desc) rn
from veicoli v
left join revisioni_veicolo r on r.veicoli_ID = v.ID
) where coaelesce(rn, 1) = 1

Combine 2 SELECT statements with filled gaps

I have this SELECT:
SELECT
m.`maschine-name` AS 'byMaschine',
q.`mname` AS 'byMName'
FROM
`qualitaet` q
INNER JOIN
maschinen m ON m.maschine = q.maschine
WHERE
q.`status`='1'
GROUP BY
concat(q.maschine, q.mname)
and get this result:
| maschine-name | mname |
| TYP 1 | 0 |
| TYP 2 | 3 |
| TYP 2 | 4 |
| TYP 3 | 0 |
| TYP 4 | 0 |
see SQL Fiddle here
Then i have nearly the same SELECT with additional COUNT and Datefilter:
SELECT
m.`maschine-name` AS 'byMaschine',
q.`mname` AS 'byMName',
COUNT(*) AS 'total'
FROM
`qualitaet` q
INNER JOIN
maschinen m ON m.maschine = q.maschine
WHERE
q.`created`>=NOW() - INTERVAL 2 DAY
AND
q.`status`='1'
GROUP BY
concat(q.maschine, q.mname)
and get this result:
| maschine-name | mname | total |
| TYP 2 | 3 | 1 |
| TYP 3 | 0 | 2 |
see SQL Fiddle here
The 2nd SELECT doesn't give me all information. I need a mix from both SELECTS
The 2nd SELECT should look like this result:
| maschine-name | mname | total |
| TYP 1 | 0 | 0 |
| TYP 2 | 3 | 1 |
| TYP 2 | 4 | 0 |
| TYP 3 | 0 | 2 |
| TYP 4 | 0 | 0 |
Is it possible to RIGHT JOIN with 2 SELECTS? Or is there another way to get the result?
Use conditional aggregation:
SELECT m.`maschine-name` AS byMaschine, q.`mname` AS byMName,
sum(q.created >= NOW() - INTERVAL 2 DAY) as Total
FROM qualitaet q INNER JOIN
maschinen m
ON m.maschine = q.maschine
WHERE q.status = '1'
GROUP BY q.maschine, q.mname;
Other suggestions:
There is no need to concatenate the grouping columns in the GROUP BY -- unless you really, really intend to do this (which I doubt).
If status is numeric, don't use single quotes for the constant.
Don't use single quotes for column aliases. In fact, your aliases don't need any quotes at all.

Select distinct from a list based on set of parameters

I'm trying to get a distinct list of results, distinct based on user, where the selected result would be based on a set of parameters. To break it down, I have users, logs, and files. Each user can be on multiple logs and can have multiple files. Files CAN be associated with logs or not, and can also have a 'billing' flag set to true. What I'm trying to do when someone selects a log is bring up the list of files most closely associated with both the 'billing' flag and the log.
If the user has a file that is associated with the log AND has the
'billing' flag set to true, that is the result for that user.
If that is not available, the next would be the file that only has the 'billing' flag set to true (associated with any highest log or none).
If that is not available, the highest log number.
Here is the generalization of the tables:
Test Table:
+----+------+-----+
| ID | user | log |
+----+------+-----+
| 1 | 1 | 2 |
| 2 | 1 | 2 |
| 3 | 2 | 2 |
| 4 | 3 | 2 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
+----+------+-----+
File Table:
+----+-------+-----+---------+------+
| ID | file | log | billing | user |
+----+-------+-----+---------+------+
| 1 | a.pdf | 2 | 0 | 1 |
| 2 | b.pdf | 3 | 1 | 1 |
| 3 | c.pdf | 1 | 0 | 2 |
| 4 | d.pdf | 2 | 1 | 2 |
| 5 | e.pdf | 1 | 0 | 3 |
| 6 | f.pdf | 3 | 0 | 3 |
| 7 | g.pdf | 0 | 1 | 4 |
| 8 | h.pdf | 1 | 0 | 4 |
| 9 | i.pdf | 2 | 1 | 4 |
| 10 | j.pdf | 3 | 0 | 4 |
+----+-------+-----+---------+------+
In this case I would want to get:
+------+-------+-----+---------+
| user | file | log | billing |
+------+-------+-----+---------+
| 1 | b.pdf | 3 | 1 |
| 2 | d.pdf | 2 | 1 |
| 3 | f.pdf | 3 | 0 |
| 4 | i.pdf | 2 | 1 |
+------+-------+-----+---------+
My simplified query so far returns all files for the users but I'm having trouble grouping based on the above parameters.
SELECT
user,
file,
log,
billing
FROM
files
WHERE
user IN (
SELECT
DISTINCT(user)
FROM
tests
WHERE
log = 2
)
ORDER BY
CASE
WHEN log = 2 AND billing = 1 THEN 1
WHEN billing = 1 THEN 2
ELSE -1
END
Any help would be greatly appreciated.
You can use a separate query to get the results based on each of the 3 criteria specified in the OP, then UNION the results from these queries and fetch result from first query if available, otherwise from second query, otherwise from third query:
SELECT user, file, log, billing
FROM (
SELECT #row_number:=CASE WHEN #user=user THEN #row_number+1
ELSE 1
END AS row_number,
#user:=user AS user,
file, log, billing
FROM (
-- 1st query: has biggest priority
SELECT 1 AS pri, t.user, f.file, f.log, f.billing
FROM (SELECT DISTINCT user, log
FROM tests
WHERE log = 2) AS t
INNER JOIN files AS f
ON (t.user = f.user AND t.log = f.log AND f.billing = 1)
UNION ALL
-- 2nd query: priority = 2
SELECT 2 AS pri, t.user, f.file, f.log, f.billing
FROM (SELECT DISTINCT user, log
FROM tests
WHERE log = 2) AS t
INNER JOIN files AS f
ON (t.user = f.user AND f.billing = 1)
WHERE f.log > t.log OR f.log = 0
UNION ALL
-- 3rd query: priority = 3
SELECT 3 AS pri, t.user, f.file, f.log, f.billing
FROM (SELECT DISTINCT user, log
FROM tests
WHERE log = 2) AS t
INNER JOIN files AS f ON (t.user = f.user)
ORDER BY user, pri, log DESC ) s ) r
WHERE r.row_number = 1
ORDER BY user
pri column is used so as to discern and prioritize results between the three separate queries. #row_number and #user variables are used in order to simulate ROW_NUMBER() OVER (PARTITION BY user ORDER BY pri) window function. Using #row_number in the outermost query we can select the required record, i.e. the record having the highest priority within each 'user' partition.
SQL Fiddle Demo

Selecting multiple unrelated data from two tables and insert into one table mysql

This is my scenario
I have a permissions table with the following fields.
id | module | permission
1 | client | add
2 | client | edit
3 | client | delete
4 | someth | edit
5 | someth | delete
employee table
id | status | somestatus
1 | act | 1
2 | den | 1
3 | act | 0
4 | den | 1
5 | act | 0
6 | act | 1
Now what i would need to do is select the employee who have status="act" and somestatus=1 and give them all permissions where module="client"
so the table employee_permissions should have these rows
id | empid | permid | permvalue
1 | 1 | 1 | 1
2 | 1 | 2 | 1
3 | 1 | 3 | 1
1 | 6 | 1 | 1
2 | 6 | 2 | 1
3 | 6 | 3 | 1
This is the query I tried and I'm stuck here
INSERT INTO at2_permission_employee (employee_id,permission_id)
SELECT at2_employee.employee_id as employee_id
, (SELECT at2_permission.permission_id as permission_id
FROM at2_permission
where at2_permission.permission_module='client'
)
from at2_employee
where at2_employee.employee_status='Active'
and at2_employee.employees_served_admin = 1;
I get the error sub query returns multiple rows which makes sense to me. But I'm not sure how to modify the query to account for iterating over the rows returned by sub query
If I'm not wrong, like this:
INSERT INTO at2_permission_employee (employee_id, permission_id, permvalue)
SELECT
at2_employee.employee_id,
at2_permission.permission_id,
1
FROM at2_permission cross join at2_employee
WHERE
at2_employee.employee_status='Active'
and at2_employee.employees_served_admin = 1
and at2_permission.permission_module='client';
It's a bit unclear where the value for permvalue should come from so I hard coded it and used the permission.id for both id and permid, but this query should give you an idea on how to accomplish what you want:
insert employee_permissions (id, empid, permid, permvalue)
select p.id, e.id, p.id, 1
from employee e, permissions p
where p.module = 'client' and e.status = 'act' and e.somestatus = 1;

Order MySQL data by row value

I have two tables that looks like this:
Table: items
id | itemId
---|------
0 | 1
1 | 2
2 | 3
Table: item_specs
id | itemId | key | values
---|--------|---------------
0 | 1 | itemreceived | 2012-06-01
1 | 1 | modelyear | 1992
2 | 1 | model | 2
3 | 2 | itemreceived | 2012-06-05
4 | 2 | modelyear | 2003
5 | 2 | model | 1
6 | 3 | itemreceived | 2012-07-05
7 | 3 | modelyear | 2000
8 | 3 | model | 3
My current query looks like this:
SELECT items.*, item_specs.* FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
WHERE itemId IN(1,2,3)
How can I order the result by a key value, for example: model?
The result I'm looking for is something like this: (if I order by model)
id | itemId | key | values
---|--------|---------------
3 | 2 | itemreceived | 2012-06-05
4 | 2 | modelyear | 2003
5 | 2 | model | 1
0 | 1 | itemreceived | 2012-06-01
1 | 1 | modelyear | 1992
2 | 1 | model | 2
6 | 3 | itemreceived | 2012-07-05
7 | 3 | modelyear | 2000
8 | 3 | model | 3
The content that is returned is ordered by the value that is that has the key model
You need the model number for every row. You can do that with a join:
SELECT items.*, item_specs.*
FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
INNER JOIN item_specs aux ON (aux.key = 'model' and aux.itemID = item_specs.itemId)
WHERE item_specs.itemId IN(1,2,3)
ORDER BY aux.values/*this is the model*/, item_specs.id;
or with a subselect:
SELECT items.*,
item_specs.*,
(select aux.values
from item_specs aux
where aux.key = 'model' and aux.itemID = item_specs.itemId
) as model
FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
WHERE item_specs.itemId IN(1,2,3)
ORDER BY model, item_specs.id;
SELECT * FROM `table` WHERE `key` = 'model' ORDER BY `values` ASC
You have to manually specify a table type/storage engine. That can't be seen in the structure you provided.
Read more here.
It seems you want to use an order by clause. This will order by the columns you need. You can also do sneaky things here, like insert a true/false value for what you order by first.
SELECT * FROM `table`
Order by (case When Key='model' then 0 else 1 end), values
See, for instance, http://blog.sqlauthority.com/2007/07/17/sql-server-case-statement-in-order-by-clause-order-by-using-variable/
SELECT * FROM `table`
WHERE `key` = 'model'
ORDER BY `values`;