Showing maximum - mysql

I want to show maximum of guarantee which specific user has. For example user has bought 3 items which have 1,2,5 years guarantee. So I want to show 5 years guarantee and name of this product.
I did subquery in case few products have this same guarantee.
SELECT t.`id-user`, name, guarantee FROM transactions t
JOIN user u ON `t`.`id-user` = `u`.`id-user`
JOIN products p ON `p`.`id-product = `t`.`id-product`
WHERE guarantee = (SELECT MAX(p2.guarantee)
FROM products p2
WHERE `p2`.`id-product` = `p`.`id-product`)
This query shows all products and their guarantees.

I think the simplest method is the substring_index()/group_concat() method for getting values associated with a maximum/minimum:
SELECT t.iduser, u.name,
MAX(p.guarantee) as guarantee,
SUBSTRING_INDEX(GROUP_CONCAT(p.name ORDER BY p.guarantee DESC), ',', 1)
FROM transactions t JOIN
user u
ON t.iduser = u.iduser JOIN
products p
ON p.idproduct = t.idproduct
GROUP BY t.iduser, u.name;
You can use your method too, but the correlated subquery is tricky:
SELECT t.iduser, u.name, p.guarantee, p.name
FROM transactions t JOIN
user u
ON t.iduser = u.iduser JOIN
products p
ON p.idproduct = t.idproduct
WHERE p.guarantee = (SELECT MAX(p2.guarantee)
FROM transactions t2 JOIN
products p2
ON p2.idproduct = t2.idproduct
WHERE t2.iduser = u.iduser
);

I think it work.
select [User].Name as [UserName],
Product.MaxGuarantee,
Product.Name as Product_Name
from [Users] [User]
left join Transactions [Transaction]
on [Transaction].[User] = [User].ID
cross apply(
select max(guarantee) MaxGuarantee, Name
from Products
where ID = [Transaction].Product
) Product
where [User].ID = ''

Related

Subquery left join refer to parent ID

I am trying to make a query to fetch the newest car for each user:
select * from users
left join
(select cars.* from cars
where cars.userid=users.userid
order by cars.year desc limit 1) as cars
on cars.userid=users.userid
It looks like it says Unknown column "users.userid" in where clause
I tried to remove cars.userid=users.userid part, but then it only fetches 1 newest car, and sticks it on to each user.
Is there any way to accomplish what I'm after? thanks!!
For this purpose, I usually use row_number():
select *
from users u left join
(select c.* , row_number() over (partition by c.userid order by c.year desc) as seqnum
from cars c
) c
on c.userid = u.userid and c.seqnum = 1;
One option is to filter the left join with a subquery:
select * -- better enumerate the columns here
from users u
left join cars c
on c.userid = u.userid
and c.year = (select max(c1.year) from cars c1 where c1.userid = c.userid)
For performance, consider an index on car(userid, year).
Note that this might return multiple cars per user if you have duplicate (userid, year) in cars. It would be better to have a real date rather than just the year.
Maybe there are better and more efficient way to query this. Here is my solution;
select users.userid, cars.*
from users
left join cars on cars.userid = users.userid
join (SELECT userid, MAX(year) AS maxDate
FROM cars
GROUP BY userid) as sub on cars.year = sub.maxDate;

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;

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!

Select Row for Max Sum Value In Multiple Tables MYSQL

I need to query for the users with highest amount of sales by all projects, where the users are in users table, sales in units table, projects in projects table.
Projects Top Agent Total Sales for Project
Project A User A 100000
Project B User B 20000
Project C User A 1000
Project D - 0
The Projects column is list all the projects regardless it has sales or not.
The Top Agent column is list the user with the highest sales in the project.
The Total Sales for Project is the total sales for a projects.
The agent column i got is incorrect because there is someone else has the highest sales, the query seems to return the first row of the result
SELECT projects, pid, CASE WHEN agent is null THEN '-' ELSE agent END as agent,
CASE WHEN FORMAT(topagent,0) > 0 THEN FORMAT(topagent,0) ELSE 0 END as salesvolume
FROM (
SELECT projects.name as projects, projects.id as pid,
concat(users.f_name, ' ', users.l_name) as agent,
SUM(units.price) AS topagent
FROM users inner join bookings on bookings.agent_id = users.id
inner join units on bookings.unit = units.id
inner join types on types.id = units.types_id
inner join projects on projects.id = types.project_id
WHERE units.status = 'Sold'
GROUP BY pid
union
select projects.name as projects, projects.id as pid,
concat(users.f_name, ' ', users.l_name) as agent,
SUM(units.price) AS topagent
from projects left outer join types on projects.id = types.project_id
left outer join units on types.id = units.types_id and units.status = 'Sold'
left outer join bookings on units.id = bookings.unit and units.status = 'Sold'
left outer join users on bookings.agent_id = users.id and units.status = 'Sold'
group by pid
) a
GROUP BY pid
order by topagent desc
Try it if helps you-
SELECT a.prjname, IFNULL(usr.name,'-') AS Top_Agent, SUM(a.sale) AS Total_Sales_for_Project
FROM
(
SELECT prj.id AS prjid,prj.name AS prjname,usr.id,usr.name AS usrname,IFNULL(SUM(unit.price),0) AS sale
FROM projects AS prj
LEFT JOIN `types` AS typ ON typ.project_id=prj.id
LEFT JOIN units AS unt ON unt.type_id=typ.id AND unt.status='sold'
LEFT JOIN bookings bkg ON bkg.unit=unt.id
LEFT JOIN users usr ON usr.id=bkg.agent_it
GROUP BY prj.id,usr.id
ORDER BY prj.id,usr.id,sale DESC
) a
GROUP BY a.prjid
Your column aliases are confusing to read. In English, it seems what you mean by topagent is "sum of sales by a human". But in SQL, your GROUP BY pid means that the SUM(units.price) really means "sum of sales in a project".
Then the UNION adds a list of projects to a list of users. The agent names are basically random at this point.
If I decipher the requirements as "a list of projects ranked by the sales values of each project's top sales agent", then you'd have SQL as below:
SELECT
pid,
projects.name as project_name,
IFNULL(a.top_agent_name,'-') as top_agent_name,
CASE WHEN FORMAT(top_agent_sales,0) > 0 THEN FORMAT(top_agent_sales,0) ELSE 0 END as top_agent_salesvolume
FROM
projects
JOIN
SELECT
a.pid,
a.agent_name as top_agent_name,
a.agent_sales as top_agent_sales
FROM
(SELECT
projects.id as pid,
concat(users.f_name, ' ', users.l_name) as agent_name,
SUM(units.price) AS agent_sales
FROM users
inner join bookings on bookings.agent_id = users.id
inner join units on bookings.unit = units.id
inner join types on types.id = units.types_id
inner join projects on projects.id = types.project_id
WHERE units.status = 'Sold'
GROUP BY pid, users.id
) a # get all agents for all projects
JOIN
(SELECT
MAX(agent_sales) as max_project_agent_sales
FROM
(SELECT
projects.id as pid,
SUM(units.price) AS agent_sales
FROM users
inner join bookings on bookings.agent_id = users.id
inner join units on bookings.unit = units.id
inner join types on types.id = units.types_id
inner join projects on projects.id = types.project_id
WHERE units.status = 'Sold'
GROUP BY pid, users.id
)
GROUP BY pid) b ON a.pid = b.pid
WHERE
a.agent_sales = b.max_project_agent_sales
ORDER BY a.agent_sales desc
Old answer below:
There are 2 topagents for each pid in the inner query since it's a union of 2 group bys. There isn't a reducing function in the outer group by pid so the topagent returned in the select is the first one that came up in the inner query.

Adding a Subquery to a Query in SQL

I have a query that works very well. Let me start with it:
Edit: The SQL has been updated. I get 0 in every row.
SELECT i.item, i.user_id, u.username,
(COALESCE(r.ratetotal, 0)) AS total,
(COALESCE(c.commtotal, 0)) AS comments,
(COALESCE(r.rateav, '50%')) AS rate,
(COALESCE(x.wasRated, '0')) AS wasRated
FROM items AS i
LEFT JOIN master_cat AS c
ON (c.cat_id = i.cat_id)
LEFT JOIN users AS u
ON u.user_id = i.user_id
LEFT JOIN
(SELECT item_id,
COUNT(item_id) AS ratetotal,
AVG(rating) AS rateav
FROM ratings GROUP BY item_id) AS r
ON r.item_id = i.item_id
LEFT JOIN
(SELECT item_id,
COUNT(item_id) AS commtotal
FROM reviews GROUP BY item_id) AS c
ON c.item_id = i.item_id
LEFT JOIN
(SELECT xu.user_id, ra.item_id, '1' AS wasRated
FROM users AS xu
LEFT JOIN ratings AS ra
ON ra.user_id = xu.user_id
WHERE xu.user_id = '1') AS x
ON x.user_id = u.user_id
AND x.item_id = r.item_id
WHERE c.category = 'Movies'
ORDER by i.item ASC;
I need to add one more function to it, where you see AS x
Basically, there are three tables here that are important. items, reviews and ratings. In the top portion you see there are subqueries that are taking statistics such as averages and totals for each item.
I need a final query that is tied to user_id, item_id and rate_id (in ratings). In the end result, where it list each item and the stats with it, I want one more column, a simple true or false if logged in user has rated it. So I need something like this:
SELECT ???
FROM ratings AS r
WHERE r.user_id = '{$user_id}'
(user_id of logged in user is passed in from PHP.`)
How can I make a subquery that gives me that last bit of info, but puts it in each row of items in the parent query?
Add this to the parent query.
, coalesce(x.WasRated, 'false') as WasRated
Your x subquery is:
(select users.user_id
, ratings.item_id
, 'true' WasRated
from users join ratings on user.user_id = ratings.user_id
where users.user_id = the one for the logged in user
) x on x.user_id = users.user_id
and x.item_id = ratings.item_id
or something like it.