mysql left join selecting only highest from left table - mysql

I've tried adapting solutions from SELECT biggest row from a LEFT JOIN and mysql: How to INNER JOIN a table but limit join to 1 result with the highest vote or count? but can't figure it out.
I'm joining two tables, the one being joined has two duplicate rows, the only difference is field 'page_id' - I want the one with the highest page_id number
tagID page_id companyID teamID companyID
1510 289 16 9 16
1418 163 16 9 16
(there are other text fields I want but these aren't used in matching so excluded them here)
My original query was
SELECT * FROM `cms_company_tags`
LEFT JOIN `cms_companies` ON `cms_companies`.`companyID`=`cms_company_tags`.`companyID`
WHERE `cms_company_tags`.`teamID`='9'
ORDER BY `cms_companies`.`companyName` ASC
which selected both rows
tagID page_id companyID teamID newsID companyID
1510 289 16 9 0 16
1418 163 16 9 0 16
I've tried
Select cms_company_tags.*, cms_companies.*
From cms_company_tags
Left Join cms_companies
On cms_companies.companyID = cms_company_tags.companyID
And cms_companies.page_id = (
Select Max( t.page_id )
From cms_companies As t
Where t.page_id = cms_company_tags.page_id
GROUP BY cms_company_tags.tagID
ORDER BY cms_company_tags.tagID DESC
)
WHERE `cms_company_tags`.`teamID`='9'
ORDER BY `cms_companies`.`companyName` ASC
and
SELECT * FROM `cms_company_tags`
LEFT JOIN `cms_companies` ON `cms_companies`.`companyID`=`cms_company_tags`.`companyID`
AND `cms_companies`.`page_id` = (SELECT MAX(page_id) AS pageID from `cms_companies` where `cms_companies`.`page_id` = `cms_company_tags`.`page_id`)
WHERE `cms_company_tags`.`teamID`='9'
ORDER BY `cms_companies`.`companyName` ASC
Both of which return
tagID page_id companyID teamID newsID companyID
1510 289 16 9 0 NULL
1418 163 16 9 0 16
With all the text fields being NULL too
I want the only the highest page_id. I could live with a duplicate row with NULL for the text field if it were the highest one and not the lowest one as I'm getting how.
EDIT:
Although this solution from returns the LOWEST page_id it does filter out the duplicate, luckily for me the text parts I need weren't affected. Posting here in the hope this partial solution is useful to someone
SELECT *
FROM cms_company_tags
INNER JOIN (
SELECT companyID, companyName, page_path, MAX(page_id) AS MaxPageID
FROM cms_companies
GROUP BY cms_companies.page_id
) MaxPages ON
cms_company_tags.companyID = MaxPages.companyID AND
cms_company_tags.page_id = MaxPages.MaxPageID AND
cms_company_tags.teamID = 9
ORDER BY MaxPages.companyName ASC

First off: why are you joining? You're only selecting from the left table, so left joining to another table won't affect the results. An inner join would affect results (by omitting companies that don't exist in cms_companies), but a left join won't.
At any rate, to get the highest page ID by company ID and team ID, try this:
SELECT companyID, teamID, MAX(page_ID)
FROM cms_company_tags
GROUP BY companyID, teamID
Then to get the unique rows from cms_company_tags, just join to the "highest by ID" as a subquery:
SELECT tagID, page_id, companyID, teamID
FROM cms_company_tags
INNER JOIN (
SELECT companyID, teamID, MAX(page_ID) AS MaxPageID
FROM cms_company_tags
GROUP BY companyID, teamID
) MaxPages ON
cms_company_tags.companyID = MaxPages.companyID AND
cms_company_tags.teamID = MaxPages.teamID
cms_company_tags.page_id = MaxPages.MaxPageID
This query returns the max page and other information for all companies/teams. You can add WHERE teamID = 9 to limit results to team 9.

Have you used ROW_NUMBER windowed function in sql server.
May be what you are looking is this:
select * from(
select *,row_number()over(partition by companyid, teamid, newsid, companyid order by page_id desc) as num
from 'cms_company_tags'
left join 'cms_companies' on 'cms_companies'.'companyID'='cms_company_tags'.'companyID'
where 'cms_company_tags'.'teamID'='9'
)tbl
where tbl.num =1

Related

Select ID of a row with max value

How can I select the ID of a row with the max value of another column in a query that joins multiple tables?
For example, say I have three tables. tblAccount which stores a grouping of users, like a family. tblUser which stores the users, each tied to a record from tblAccount. And each user can be part of a plan, stored in tblPlans. Each plan has a Rank column that determines it's sorting when comparing the levels of plans. For example, Lite is lower than Premium. So the idea is that each user can have a separate plan, like Premium, Basic, Lite etc..., but the parent account does not have a plan.
How can I determine the highest plan in the account with a single query?
tblAccount
PKID
Name
1
Adams Family
2
Cool Family
tblUsers
PKID
Name
AccountID
PlanID
1
Bob
1
3
2
Phil
2
2
3
Suzie
2
1
tblPlans
PKID
Name
Rank
1
Premium
3
2
Basic
2
3
Elite
4
4
Lite
1
Here's the result I'm hoping to produce:
AccountID
Name
HighestPlanID
PlanName
2
Adams Family
1
Premium
I've tried:
SELECT U.AccountID, A.Name, MAX(P.Rank) AS Rank, P.PKID as HighestPlanID, P.Name as PlanName
FROM tblPlans P
INNER JOIN tblUsers U ON U.PlanID = P.PKID
INNER JOIN tblAccounts A ON U.AccountID = A.PKID
WHERE U.AccountID = 2
and the query will not always work, selecting the MAX of Rank does not select entire row's values from tblPlans.
I am looking for a solution that is compatible with mysql-5.6.10
You can join the tables and use ROW_NUMBER() to identify the row you want. Then filtering is ieasy.
For example:
select *
from (
select a.*, p.*,
row_number() over(partition by a.pkid order by p.rank desc) as rn
from tblaccount a
join tblusers u on u.accountid = a.pkid
join tblplans p on p.pkid = u.planid
) x
where rn = 1
Inside the subquery you can add where u.accountid = 2 to retrieve a single account of interest, instead of all of them.
With the help of #the-impaler, I massaged their answer a bit and came out with something very similar:
select *
from (
select a.*, p.*
from tblaccount a
join tblusers u on u.accountid = a.pkid
join tblplans p on p.pkid = u.planid
where u.accountid = 2
order by p.rank desc
) x limit 1
The subquery sorts each user by plan rank from top to bottom, and then the top level query selects the top most row with limit 1. It seems to work!

Avoid using a subquery in a table join

In a MySQL 5.7 database, I have the following User table:
Name
Id
David
1
Frank
2
And the following Order table:
Id
Price
UserId
1
55
1
2
68
1
3
50
1
4
10
2
For every user, I want to select the price of the order with the biggest ID.
I can use the following query which adds additional complexity due to the nested subquery :
SELECT
User.Name,
last_user_order.Price
FROM User
LEFT JOIN (
SELECT Price, UserId FROM Order
ORDER BY Id DESC LIMIT 1
) AS last_user_order ON last_user_order.UserId = User.Id
There exist many questions here where the column to be selected is the same than the one being ordered. Hence, it is possible to use MAX in the first SELECT statement to avoid a subquery. Is it possible to avoid a subquery in my case?
For every user, I want to select the price of the order with the biggest ID.
That looks like:
SELECT
u.*,
o.Price,
FROM
User u
INNER JOIN Order o ON u.ID = o.UserID
INNER JOIN
(
SELECT MAX(ID) as OrderID FROM Order GROUP BY UserId
) maxO ON o.Id = maxO.OrderId
SELECT User.Name,
( SELECT Order.Price
FROM Order
WHERE Order.UserId = User.Id
ORDER BY Order.Id DESC LIMIT 1 ) LastPrice
FROM User;

MYSQL: How to join two tables using Inner join and then calculatin the total number from the second table for the following examples

I am stuck with the following requirement and I am finding it difficult to crack the query for it.
Consider a table customer with the following fields
id signup_date first_payment_date
10 2015-03-20 null
11 2015-03-20 null
12 2015-03-20 null
13 2015-03-20 null
14 2015-05-23 null
15 2015-05-23 null
Consider another table transaction_history
id product_name
10 vod trial
10 vod trial
11 vod trial
12 vod trial
12 vod
13 vod trial
14 vod trial
15 vod trial
15 vod trial
I need to pick the idfrom customer table and look up in transaction_history table based on the signup_date and first_payment_date is null.
Now I need to check if this id is present in transaction_history and check if he has at least 1 entry with product_name = "vod trial". If he has then he is a row in the result I want.
At the end I need to calculate the total number of id's from transaction_history who has at least one row where product_name="vod_trial" and this should be on a date basis mentioned in signup_date in customer table.
I wrote a query in the following manner:
SELECT
ts.guid,
cs.signup_date,
(SELECT
COUNT(ts2.guid)
FROM
transaction_history ts2
WHERE
cs.guid = ts2.guid
AND ts2.product_name = "vod trial"
HAVING COUNT(ts2.guid) = 1) AS count_ts_guid
FROM
customer AS cs,
transaction_history AS ts
WHERE
cs.guid = ts.guid
AND cs.first_payment_date IS NULL;
But in the above query I am not able to calculate the total count signup_datewise.
Would be great if someone could help me out.
Sample result:
date new trials
2015-03-20 2
2015-05-23 1
I am not sure I fully understand. You want customers without first_payment_date that have a trial entry in the transaction table?
select *
from customer
where first_payment_date is null
and id in (select id from transaction_history where product_name = 'vod trial');
Okay, from your last comment it seems, you want customers that have no trial entry in the transaction table, too. And you want to display them with their trial transaction count. So:
select signup_date,
(
select count(*)
from transaction_history th
where th.product_name = 'vod trial'
and th.id = c.id
)
from customer c
where first_payment_date is null;
If you even want to group by date, then aggregate:
select signup_date,
sum((
select count(*)
from transaction_history th
where th.product_name = 'vod trial'
and th.id = c.id
))
from customer c
where first_payment_date is null
group by signup_date;
Next try: Join all customers and transactions, such as to only get customers present in the transactions table. Then aggregate.
select c.signup_date, count(*)
from customer c
join transaction_history th on th.id = c.id and th.product_name = 'vod trial'
where c.first_payment_date is null
group by c.signup_date;
Or do you want this:
select c.signup_date, count(case when th.product_name = 'vod trial' then 1 end)
from customer c
join transaction_history th on th.id = c.id
where c.first_payment_date is null
group by c.signup_date;
I'd better make this a separate answer. You want to find customers that have only one entry in transaction_history and that entry must be 'vod trial'. So read the transaction table, group by customer id and count. Check your criteria with HAVING. Then join the found IDs with the customer table and group by date.
select c.signup_date, count(*)
from customer c
join
(
select id
from transaction_history
group by id
having count(*) = 1
and min(product_name) = 'vod trial'
) t on t.id = c.id
group by c.signup_date;

Find the frequency of rows from multiple joint tables

I have this problem with SQL and I can't figure it out.
Imagine that I have 3 tables as follows
Names
Nameid name
1 Starbucks Coffee
2 Johns Restaurant
3 Davids Restaurant
user_likes
userid Nameid
1 1
2 1
2 3
user_visited
userid Nameid
1 2
I want to find the places with the most number of (likes+visited). I also want to select all places not just those who have been liked or visited
I do:
SELECT n.nameid, n.name , COUNT(f.nameid) AS freq
FROM names AS n
LEFT JOIN user_likes ON n.nameid=user_likes.nameid
LEFT JOIN user_visited ON n.nameid=user_visited.nameid
ORDER BY freq DESC
But it doesn't give me the total frequency. The problem is, if a place is both visited and liked, it is counted only once, while I want it to be counted twice.
Any suggestions?
I've made a quick test and although I prefer Serge's solution, this one seemed to perform faster as the amount of items to join will be less:
SELECT n.nameId, n.name, coalesce(sum(likesCount), 0) totalCount FROM NAMES n
LEFT JOIN (
SELECT nameId, count(*) likesCount FROM user_likes
GROUP BY nameId
UNION ALL
SELECT nameId, count(*) visitsCount FROM user_visited
GROUP BY nameId
) s ON n.nameId = s.nameId
GROUP BY n.nameId
ORDER BY totalCount DESC
I'm assuming the following indexes:
alter table names add index(nameid);
alter table user_likes add index(nameid);
alter table user_visited add index(nameid);
Probably the OP can compare the efficiency of both queries with actual data and provide feedback.
SELECT n.name, t.nameid, COUNT(t.nameid) AS freq
FROM Names n
JOIN (
SELECT nameid FROM user_likes
UNION ALL
SELECT nameid FROM user_visited
) t
ON n.nameid = t.nameid
GROUP BY t.nameid ORDER BY freq DESC
Mosty, your usage of coalesce() gave me an idea and I came up with this:
SELECT n.nameid, n.name ,
SUM((IFNULL(user_likes.userid,0)>0)+(IFNULL(user_visited.userid,0)>0) ) AS freq
FROM names AS n LEFT JOIN user_likes ON n.nameid=user_likes.nameid LEFT JOIN
user_visited ON n.nameid=user_visited.nameid ORDER BY freq DESC
Since my example here was a simplification of my problem (I have to join more than two tables to the main table) I'm reluctant to use SELECT inside SELECT, because I know it's not very efficient. Do you see any fundamental problem with my solution?

MySQL Query making rows for each joined table row

SELECT deals.id,
deals.partner_id
FROM deals
LEFT JOIN deals_partners
ON ( deals.id = deals_partners.deal_id )
WHERE 1
AND ( `deals`.`partner_id` = 222
OR CASE
WHEN deals.partner_count = 1 THEN
deals_partners.partner_id = 222
end )
ORDER BY deals.id ASC
I would like to grab the deals that are associated to the partner.
They can either be master of the deal, deals.partner_id = 222 or they can have a row in deals_partners where they get linked to the deal.
The above works out for me, but gives me multiple of the same deals, because of the count of deals_partners I have. I made it a left join, I dont understand why it still keeps grabbing rows from the deals_partners?
Update:
table: deals, columns: ID, title, name, partner_id
table deals_partners, columns: deal_id, partner_id
I would like to display the partner 222's deals. To find out which deals, that the partner has, his partner_id can either be in the deal row, in the column partner_id or he can have a row in deals_partners where his partner_id is linked to the deal_id.
When you join to a table and match on multiple records, you will return multiple records. You can use the IN clause to prevent this.
SELECT deals.id,
deals.partner_id
FROM deals
WHERE `deals`.`partner_id` = 22
OR id in
(SELECT deal_id
FROM deals_partners
WHERE partner_id = 222)
ORDER BY deals.id ASC
Try something like: (hope it works)
SELECT deals.id,
deals.partner_id
FROM deals
LEFT JOIN deals_partners
ON deals.id = deals_partners.deal_id AND
deals_partners.partner_id = 222
WHERE `deals`.`partner_id` = 222 OR deals_partners.partner_id = 222
ORDER BY deals.id ASC
This assumes there is only one match for deals.id = deals_partners.deal_id AND deals_partners.partner_id = 222. Otherwise you'll need to add GROUP BY deals.id, deals.partner_id, deals_partners.partner_id.