Converting nested select into join - mysql

My query is
select COUNT(*) from result
where test_id in (select test_id
from test_schedule
where scheduler_id in (select user_id
from users
where user_type=1))

Try this:
SELECT COUNT(r.*)
FROM result r
INNER JOIN test_schedule s ON r.test_id = s.test_id
INNER JOIN users u ON s.scheduler_id = u.user_id
WHERE u.user_type = 1

SELECT COUNT(*)
FROM result r
JOIN (SELECT DISTINCT test_id
FROM test_schedule s
JOIN users u ON s.scheduler_id = u.user_id
WHERE u.user_type = 1) s
USING (test_id)
The DISTINCT is necessary to keep rows from being multiplied by all the rows in the other tables that match.

SELECT COUNT(r.*)
FROM result r
RIGHT JOIN test_schedule s USING(test_id)
RIGHT JOIN users u ON s.scheduler_id = u.user_id
WHERE u.user_type = 1

Related

JOIN when one table has no rows

I have the following query:
SELECT u.id, u.name, u.date_registered, p.time_created
FROM users u JOIN
prospect_notes p
ON u.id=p.subject_id
WHERE u.allocated_instructors = 668
AND p.time_created = (SELECT MAX(p2.time_created) FROM prospect_notes p2 WHERE p2.subject_id = p.subject_id)
ORDER BY p.time_created;
My problem is that when there are no rows in the prospect_notes table which match the following:
AND p.time_created = (SELECT MAX(p2.time_created) FROM prospect_notes p2 WHERE p2.subject_id = p.subject_id)
I get no result.
Instead, I want all the rows in the users table to return (presumably p.time_created would be NULL in such cases).
You need to be careful because of the JOIN clause. Presumably, if there are no matches for the correlated subquery, there are no matches in the JOIN either.
So, use LEFT JOIN and move the logic to the FROM clause:
SELECT u.id, u.name, u.date_registered, p.time_created
FROM users u LEFT JOIN
prospect_notes p
ON u.id = p.subject_id LEFT JOIN
(SELECT p2.subject_id, MAX(p2.time_created) as max_time_created
FROM prospect_notes p2
GROUP BY p2.subject_id
) p2
ON p2.subject_id = p.subject_id AND p2.time_created = p.time_created
WHERE u.allocated_instructors = 668
ORDER BY p.time_created;
That said, if you only want time_created from prospect_notes, then use a simpler query:
SELECT u.id, u.name, u.date_registered, MAX(p.time_created)
FROM users u LEFT JOIN
prospect_notes p
ON u.id = p.subject_id
WHERE u.allocated_instructors = 668
GROUP BY u.id -- okay, assuming id is unique or a primary key
ORDER BY MAX(p.time_created);
You need LEFT JOIN this clause permit you to have NULL values in the right table.
Try :
SELECT u.id, u.name, u.date_registered, p.time_created
FROM users u
LEFT JOIN prospect_notes p
ON u.id=p.subject_id
WHERE u.allocated_instructors = 668
AND p.time_created = (SELECT MAX(p2.time_created) FROM prospect_notes p2 WHERE p2.subject_id = p.subject_id)
ORDER BY p.time_created;

Merge query with two different WHERE clauses/conditions in one

I have three tables: users, accounts and scores. Each query is actually giving me the desired results:
-- This will return all user ids with a count of "calculated" scores
SELECT u.id AS user_id, count(1) AS total FROM scores s
INNER JOIN accounts a ON s.account_id = a.id
INNER JOIN user u ON a.user_id = u.id
WHERE s.status = 'CALCULATED'
GROUP BY user_id;
-- This will return all user ids with a count of non-calculated scores
SELECT u.id AS user_id, count(1) AS failures FROM scores s
INNER JOIN accounts a ON s.account_id = a.id
INNER JOIN user u ON a.user_id = u.id
WHERE s.status <> 'CALCULATED'
GROUP BY user_id;
But I would like to return something like this: user id, total, failures...all in one query!
This can be done with conditional aggregation. Conditions in SUM return 1 or 0 depending on the condition being satisfied.
SELECT u.id AS user_id,
SUM(s.status='CALCULATED'),
SUM(s.status<>'CALCULATED') AS total
FROM scores s
INNER JOIN accounts a ON s.account_id = a.id
INNER JOIN user u ON a.user_id = u.id
GROUP BY u.id;
As a note, you can simplify your query, because the user table is (presumably) not needed:
SELECT a.user_id,
SUM(s.status = 'CALCULATED') as num_calc,
SUM(s.status <> 'CALCULATED') AS num_notcalc
FROM scores s INNER JOIN
accounts a
ON s.account_id = a.id
GROUP BY a.user_id;
Your queries are giving the right answer, but you might also need to be careful about NULL values. If that's a concern, use <=> the NULL-safe equality operator:
SELECT a.user_id,
SUM(s.status = 'CALCULATED') as num_calc,
SUM(NOT s.status <=> 'CALCULATED') AS num_notcalc
FROM scores s INNER JOIN
accounts a
ON s.account_id = a.id
GROUP BY a.user_id;

MYSQL: Handling Multiple LEFT JOINS

I have a query with one LEFT JOIN that works fine. When I add a second LEFT JOIN to a table with multiple records per field in the first table, however, I am getting the product of the results in the two tables ie books x publishers returned. How can I prevent this from happening?
SELECT a.*,b.*,p.*, group_concat(b.id as `bids`)
FROM authors `a`
LEFT JOIN books `b`
ON b.authorid = a.id
LEFT JOIN publishers `p`
on p.authorid = a.id
GROUP by a.id
EDIT:
Figured it out. The way to do this is to use subqueries as in this answer:
SELECT u.id
, u.account_balance
, g.grocery_visits
, f.fishmarket_visits
FROM users u
LEFT JOIN (
SELECT user_id, count(*) AS grocery_visits
FROM grocery
GROUP BY user_id
) g ON g.user_id = u.id
LEFT JOIN (
SELECT user_id, count(*) AS fishmarket_visits
FROM fishmarket
GROUP BY user_id
) f ON f.user_id = u.id
ORDER BY u.id;
If you do multiple LEFT Joins, your query will return a cartesian product of the results. To avoid this and get only one copy of fields you desire, do a subquery for each table you wish to join as below. Hope this helps someone in the future.
SELECT u.id
, u.account_balance
, g.grocery_visits
, f.fishmarket_visits
FROM users u
LEFT JOIN (
SELECT user_id, count(*) AS grocery_visits
FROM grocery
GROUP BY user_id
) g ON g.user_id = u.id
LEFT JOIN (
SELECT user_id, count(*) AS fishmarket_visits
FROM fishmarket
GROUP BY user_id
) f ON f.user_id = u.id
ORDER BY u.id;

sql select distinc where max date

I have 3 tables "maintenances", "cars", "users" . I want to select all data from table maintenance with a distinct car_id and the last record for each distinct (based on max maintenance_date)
SELECT
m. * , u.username, c.Model, c.Make, c.License, c.Milage, COUNT( m.process_id ) AS count_nr
FROM
maintenances AS m
LEFT JOIN users AS u ON u.id = m.user_id
LEFT JOIN cars AS c ON c.id = m.car_id
WHERE
maintenance_date = (SELECT MAX(maintenance_date) FROM maintenances WHERE car_id = m.car_id)
The problem is that this query returns only one record which has the max date from all records. I want all records (distinct car_id and from records with the same car_id to display only values for max(maintenance_date))
This is your query:
SELECT m. * , u.username, c.Model, c.Make, c.License, c.Milage, COUNT( m.process_id ) AS count_nr
----------------------------------------------------------------^
FROM maintenances AS m LEFT JOIN
users AS u
ON u.id = m.user_id LEFT JOIN
cars AS c
ON c.id = m.car_id
WHERE maintenance_date = (SELECT MAX(maintenance_date) FROM maintenances WHERE car_id = m.car_id);
It is an aggregation query. Without a group by, only one row is returned (all the rows are in one group). So, add the group by:
SELECT m. * , u.username, c.Model, c.Make, c.License, c.Milage, COUNT( m.process_id ) AS count_nr
FROM maintenances AS m LEFT JOIN
users AS u
ON u.id = m.user_id LEFT JOIN
cars AS c
ON c.id = m.car_id
WHERE maintenance_date = (SELECT MAX(m2.maintenance_date) FROM maintenances m2 WHERE m2.car_id = m.car_id);
GROUP BY c.id
I also fixed the correlation statement, to be clear that it is correlated to the outer query.
add GROUP BY u.username .
WHERE
maintenance_date = (SELECT MAX(maintenance_date) FROM maintenances WHERE car_id = m.car_id)
GROUP BY u.username

MySQL INNER JOIN select only one row from second table

I have a users table and a payments table, for each user, those of which have payments, may have multiple associated payments in the payments table. I would like to select all users who have payments, but only select their latest payment. I'm trying this SQL but i've never tried nested SQL statements before so I want to know what i'm doing wrong. Appreciate the help
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*
FROM payments AS p
ORDER BY date DESC
LIMIT 1
)
ON p.user_id = u.id
WHERE u.package = 1
You need to have a subquery to get their latest date per user ID.
SELECT u.*, p.*
FROM users u
INNER JOIN payments p
ON u.id = p.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON p.user_ID = b.user_ID AND
p.date = b.maxDate
WHERE u.package = 1
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.id = (
SELECT id
FROM payments AS p2
WHERE p2.user_id = u.id
ORDER BY date DESC
LIMIT 1
)
Or
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
WHERE NOT EXISTS (
SELECT 1
FROM payments AS p2
WHERE
p2.user_id = p.user_id AND
(p2.date > p.date OR (p2.date = p.date AND p2.id > p.id))
)
These solutions are better than the accepted answer because they work correctly when there are multiple payments with same user and date. You can try on SQL Fiddle.
SELECT u.*, p.*, max(p.date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
ORDER BY p.date DESC
Check out this sqlfiddle
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM payments AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
ORDER BY p.user_id ASC, date DESC)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
You can try this:
SELECT u.*, p.*
FROM users AS u LEFT JOIN (
SELECT *, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY [Date] DESC) AS RowNo
FROM payments
) AS p ON u.userid = p.userid AND p.RowNo=1
There are two problems with your query:
Every table and subquery needs a name, so you have to name the subquery INNER JOIN (SELECT ...) AS p ON ....
The subquery as you have it only returns one row period, but you actually want one row for each user. For that you need one query to get the max date and then self-join back to get the whole row.
Assuming there are no ties for payments.date, try:
SELECT u.*, p.*
FROM (
SELECT MAX(p.date) AS date, p.user_id
FROM payments AS p
GROUP BY p.user_id
) AS latestP
INNER JOIN users AS u ON latestP.user_id = u.id
INNER JOIN payments AS p ON p.user_id = u.id AND p.date = latestP.date
WHERE u.package = 1
#John Woo's answer helped me solve a similar problem. I've improved upon his answer by setting the correct ordering as well. This has worked for me:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN (
SELECT user_ID, MAX(date) as maxDate FROM
(
SELECT user_ID, date
FROM payments
ORDER BY date DESC
) d
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
I'm not sure how efficient this is, though.
SELECT U.*, V.* FROM users AS U
INNER JOIN (SELECT *
FROM payments
WHERE id IN (
SELECT MAX(id)
FROM payments
GROUP BY user_id
)) AS V ON U.id = V.user_id
This will get it working
Matei Mihai given a simple and efficient solution but it will not work until put a MAX(date) in SELECT part so this query will become:
SELECT u.*, p.*, max(date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
And order by will not make any difference in grouping but it can order the final result provided by group by. I tried it and it worked for me.
My answer directly inspired from #valex very usefull, if you need several cols in the ORDER BY clause.
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM (SELECT * FROM payments ORDER BY p.user_id ASC, date DESC) AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
This is quite simple do The inner join and then group by user_id and use max aggregate function in payment_id assuming your table being user and payment query can be
SELECT user.id, max(payment.id)
FROM user INNER JOIN payment ON (user.id = payment.user_id)
GROUP BY user.id
If you do not have to return the payment from the query you can do this with distinct, like:
SELECT DISTINCT u.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
This will return only users which have at least one record associated in payment table (because of inner join), and if user have multiple payments, will be returned only once (because of distinct), but the payment itself won't be returned, if you need the payment to be returned from the query, you can use for example subquery as other proposed.