mysql advanced group query - mysql

I need some help figuring out a query
I have 3 tables
sources
id, name, rank
origin
id, source_id (FK to sources id), name
One source can have many origins
product
id, origin_id (FK to origin id), name, time_added
One origin can have many products
Now, what I want is to select the most recent products per source, ordered by rank descending
Any suggestions?

This should do as you have requested, though without sample output it's hard to be 100% certain. Inner query selects products linked to the source id ordered by the date added from newest to oldest, and in turn that's joined to sources and grouped.
SELECT
*
FROM sources AS s
INNER JOIN (
SELECT
origins.source_id,
product.*
FROM origin
INNER JOIN product
ON product.origin_id = origin.origin_id
ORDER BY time_added DESC
) AS productsOrdered
ON productsOrdered.source_id = sources.source_id
ORDER BY s.rank DESC, productsOrdered.time_added DESC
This avoids having to do potentially expensive opreations as the inner select should be pretty fast and can be limited as required

A typical way of doing this is to
Find the MAX(time_added) for each origin
Get the product's id for each of these origins
Join with the sources and origin tables to retrieve all columns
Note that this fails if there are origins with multiple records with the exact same time_added.
SQL Statement
SELECT *
FROM sources s
INNER JOIN origin o ON o.source_id = s.id
INNER JOIN product p ON p.origin_id = o.id
INNER JOIN (
SELECT id
FROM product p
INNER JOIN (
SELECT origin_id
, MAX(time_added) AS time_addded
FROM product p
GROUP BY
origin_id
) pmax ON pmax.origin_id = p.origin_id
AND pmax.time_added = p.time_added
) pmax ON pmax.id = p.id

SELECT o.id,count(o.id) as numOfProdFromOrig p.id, p.name, p.time_added, s.rank
FROM product as p NATURAL JOIN sources as s NATURAL JOIN origin as o
GROUP BY (numOfProdFromOrig)
ORDER BY s.rank DESC

select b.id,(select p.name from origin o inner join product p
on p.origin_id = o.id where o.source_id = b.id order by time_added desc limit 1)a as product_name
from source b ;
Try this:

Related

Print every max value using three tables (mysql)

I need to find the author who has written the most volumes in my database, I have three tables "VOLUMES", "AUTHOR", "WRITTEN BY". Table VOLUMES has columns like title, volume_id(primary key), year, edition_year, etc. Table AUTHOR has columns for generic infos like name, surname, id,etc and table WRITTEN BY is used to connect AUTHOR and VOLUMES, it contains the columns volume_id and author_id.
My data can contain many copies of the same volumes so I guess I need to group every copy of the same volume.
The query I have written is:
SELECT A.NAME, A.SURNAME, COUNT(V.ID) no_of_volumes
FROM VOLUMES AS V, AUTHOR AS A JOIN WRITTEN_BY W
WHERE (V.ID = W.VOLUME_ID AND A.ID = W.A_ID)
GROUP BY A.NAME, A.SURNAME
ORDER BY no_of_volumes DESC
LIMIT 1;
Now, this should print just one author, but I want it to print EVERY author that has written the same number of volumes... How do I do that?
If you are allowed to change the scheme slightly (use unique ID-names) than this could do the trick
select AUTHOR.NAME
, count(*) VolumeCount
from AUTHOR
inner join WRITTEN_BY using(AUTHOR_ID)
inner join VOLUME using(VOLUME_ID)
group by a.AUTHOR_ID
order by VolumeCount desc
limit 1;
x
If not than this should do the trick:
select AUTHOR.NAME
, count(*) VolumeCount
from AUTHOR
inner join WRITTEN_BY on AUTHOR_ID = AUTHOR.ID
inner join VOLUME on VOLUME_ID = VOLUME.ID
group by a.AUTHOR_ID
order by VolumeCount desc
limit 1;
Please reserve where for filtering only. Using it for foreign-key makes messy code.
(And if your table and field names are uppercase it makes sense to use lowercase keywords)
You must aggregate twice inside written_by: once to get the max number of volumes written and then to get the author ids who wrote the max number of volumes.
Then join to authors to get the authors details:
SELECT a.*, t.no_of_volumes
FROM author a INNER JOIN (
SELECT author_id, COUNT(*) no_of_volumes
FROM written_by
GROUP BY author_id
HAVING COUNT(*) = (
SELECT COUNT(*) no_of_volumes
FROM written_by
GROUP BY author_id
ORDER BY no_of_volumes DESC LIMIT 1
)
) t ON t.author_id = a.id
The table volumes is not needed.
For MySql 8.0+ the code can be simplified with the use of the window function RANK():
SELECT a.*, t.no_of_volumes
FROM author a INNER JOIN (
SELECT r.*
FROM (
SELECT author_id, COUNT(*) no_of_volumes,
RANK() OVER (ORDER BY COUNT(*) DESC) rnk
FROM written_by
GROUP BY author_id
) r
WHERE r.rnk = 1
) t ON t.author_id = a.id

Return the company with most film in a genre

I am working on this project at my university, where I need to create a query to the database. I want the query to return the company with most movies in the given genre. At the moment I have this query, but this only return one company, but there can probably be more than one.
SELECT CompanyID, CategoryID, COUNT(*) as NumberOfMovies
FROM Movie
NATURAL JOIN CategoryFilm
NATURAL JOIN Category
NATUAL JOIN Comapny
GROUP BY CategoryID, CompanyID
Order by NumberOfMovies DESC LIMIT 1
I beleave I will need a "having" in here.
pls try this, it may because you added limit 1, which only show 1st retrieved record
SELECT CompanyID, CategoryID, COUNT(*) as NumberOfMovies
FROM Movie
NATURAL JOIN CategoryFilm
NATURAL JOIN Category
NATURAL JOIN Comapny
GROUP BY CategoryID, CompanyID
Order by NumberOfMovies DESC
I assume by "category" you mean "genre" -- or that they are the same thing.
Do not use NATURAL JOIN. It does not even use properly declared foreign key relationships, instead relying merely on name similarity between tables. It is dangerous because the columns used are not specified and can introduce hard-to-debug errors. I often refer to it as an "abomination" because it does not take table declarations into account.
If you have a given category, then I would expect a WHERE clause:
SELECT CompanyID, COUNT(*) as NumberOfMovies
FROM Movie m JOIN
CategoryFilm cf
ON cf.movie_id = m.movie_id JOIN
Company c
ON c.company_id = m.company_id
WHERE cf.category_id = ?
GROUP BY CategoryID
ORDER BY NumberOfMovies DESC
LIMIT 1;
If you want to allow ties, you can use window function rank():
select *
from (
select
co.companyID,
ca.categoryID,
count(*) NumberOfMovies,
rank() over(partition by c.categoryID order by count(*) desc) rn
from movie m
inner join categoryFilm cf on cf.movieID = m.movieID
inner join category ca on ca.categoryID = cf.categoryID
inner join company co on co.companyID = m.companyID
group by co.companyID, ca.categoryID
) t
where rn = 1
order by ca.categoryID
This gives you the top company for each and every category, ties included. If you want to filter on a given category, you can just add a where clause to the inner query.
Side note: do not use natural joins: they are error-prone. I rewrote the query to use inner joins instead (I made a few assumptions on the relations).

Joining Multiple MySQL Statements

I have multiple queries I perform that I would like to join together.
The first query converts a user supplied path into an id. Paths can have multiple ids associated with them so I want to ensure I get the most recently created id for the path:
SELECT id FROM paths WHERE path=$path ORDER BY created DESC LIMIT 1
Once I know the id number I can perform the rest of my queries.
I have the most recently created id for the path but I need to also verify I have the most recently created path for that id (sometimes they are not the same):
SELECT path FROM paths WHERE id={id} ORDER BY created DESC LIMIT 1
I'd like to include the id's rating and the count of votes though sometimes there are none:
SELECT AVG(vote) AS rating, COUNT(*) AS count FROM votes WHERE id={id} GROUP BY id
Finally I'd like to include everything from a third table about the id (the id is UNIQUE on this table so there cannot be multiple lines):
SELECT * FROM places WHERE id={id}
How can I best join these statements together?
You can join them together into one query like this:
select pl.*, avg(vote) as rating
from (SELECT path, id
FROM paths
WHERE path = {path}
ORDER BY created DESC
LIMIT 1
) p left outer join
votes v
on v.id = p.id left outer join
places pl
on pl.id = p.id
group by p.id;
EDIT:
The first two conditions do seem redundant. But, if you need them:
select pl.*, avg(vote) as rating
from (SELECT path, id
FROM paths
WHERE id = (select id from paths where path = {path} order by created desc limit 1)
ORDER BY created DESC
LIMIT 1
) p left outer join
votes v
on v.id = p.id left outer join
places pl
on pl.id = p.id
group by p.id;

SQL max of a count involving 3 tables

I have one table Customers with CustomerID and PhoneNumber, the second table is Orders that has CustomerId and OrderNumber and a third table OrderDetails that has OrderNumber, PriceOfOneUnit and UnitsOrdered. I need to find the PhoneNumber for the customer who placed the largest order (PriceOfOneUnit * UnitsOrdered). Doing a count(PriceOfOneUnit*UnitsOrdered) as A1 and then `Group By CustomerId Order By A1 DESC LIMIT 1 is evidently not working after joining the 3 tables. Can anyone help.
If we take you at your word, and what you want is the biggest single line-item rather than the largest order, you can find the largest line-item and then find the order to which it belongs and then the customer who placed that order. You can use a dummy aggregate function to pull back the order id from orderDetails.
EDIT:
OK, for someone just starting out, I think it can be clearer to think in terms of Venn diagrams and use what are called Inline Views and subqueries:
select customername, phone
from customer
inner join
(
select o.id, customerid from orders o
inner join
(
select od.orderid from orderdetail od
where (od.qty * od.itemprice) =
(
select max(od.qty * od.itemprice)
from orderdetail as od
)
) as biggestorder
on o.id = biggestorder.orderid
) as X
on customer.id = X.customerid
Each of the queries inside parentheses returns a set that can be joined/intersected with other sets.
Give this a try,
SELECT cus.CustomerId, cus.PhoneNumber
FROM Customers cus
INNER JOIN Orders a
ON cus.CustomerId = a.CustomerId
INNER JOIN OrderDetails b
On a.OrderNumber = b.OrderNumber
GROUP BY cus.CustomerId, cus.PhoneNumber
HAVING SUM(b.PriceOfOneUnit * b.UnitsOrdered) =
(
SELECT SUM(b.PriceOfOneUnit * b.UnitsOrdered) totalOrdersAmount
FROM Orders aa
INNER JOIN OrderDetails bb
On aa.OrderNumber = bb.OrderNumber
GROUP BY aa.CustomerId
ORDER BY totalOrdersAmount DESC
LIMIT 1
)

How can I use MySQL to COUNT with a LEFT JOIN?

How can I use MySQL to count with a LEFT JOIN?
I have two tables, sometimes the Ratings table does not have ratings for a photo so I thought LEFT JOIN is needed but I also have a COUNT statement..
Photos
id name src
1 car bmw.jpg
2 bike baracuda.jpg
Loves (picid is foreign key with photos id)
id picid ratersip
4 1 81.0.0.0
6 1 84.0.0.0
7 2 81.0.0.0
Here the user can only rate one image with their IP.
I want to combine the two tables in order of the highest rating. New table
Combined
id name src picid
1 car bmw.jpg 1
2 bike baracuda.jpg 2
(bmw is highest rated)
My MySQL code:
SELECT * FROM photos
LEFT JOIN ON photos.id=loves.picid
ORDER BY COUNT (picid);
My PHP Code: (UPDATED AND ADDED - Working Example...)
$sqlcount = "SELECT p . *
FROM `pics` p
LEFT JOIN (
SELECT `loves`.`picid`, count( 1 ) AS piccount
FROM `loves`
GROUP BY `loves`.`picid`
)l ON p.`id` = l.`picid`
ORDER BY coalesce( l.piccount, 0 ) DESC";
$pics = mysql_query($sqlcount);
MySQL allows you to group by just the id column:
select
p.*
from
photos p
left join loves l on
p.id = l.picid
group by
p.id
order by
count(l.picid)
That being said, I know MySQL is really bad at group by, so you can try putting the loves count in a subquery in your join to optimize it:
select
p.*
from
photos p
left join (select picid, count(1) as piccount from loves group by picid) l on
p.id = l.picid
order by
coalesce(l.piccount, 0)
I don't have a MySQL instance to test out which is faster, so test them both.
You need to use subqueries:
SELECT id, name, src FROM (
SELECT photos.id, photos.name, photos.src, count(*) as the_count
FROM photos
LEFT JOIN ON photos.id=loves.picid
GROUP BY photos.id
) t
ORDER BY the_count
select
p.ID,
p.name,
p.src,
PreSum.LoveCount
from
Photos p
left join ( select L.picid,
count(*) as LoveCount
from
Loves L
group by
L.PicID ) PreSum
on p.id = PreSum.PicID
order by
PreSum.LoveCount DESC
I believe you just need to join the data and do a count(*) in your select. Make sure you specify which table you want to use for ambigous columns. Also, don't forget to use a group by function when you do a count(*). Here is an example query that I run on MS SQL.
Select CmsAgentInfo.LOGID, LOGNAME, hCmsAgent.SOURCEID, count(*) as COUNT from hCmsAgent
LEFT JOIN CmsAgentInfo on hCmsAgent.logid=CmsAgentInfo.logid
where SPLIT = '990'
GROUP BY CmsAgentInfo.LOGID, LOGNAME, hCmsAgent.SOURCEID
The example results form this will be something like this.
77615 SMITH, JANE 1 36
29422 DOE, JOHN 1 648
Hope that helps. Good Luck.