SQL top 10 results in nested query - sql

hope someone can help me produce a nested query. Does not have to be efficient, just simple to follow.
I am looking to "Find the top 10 albums that have the greatest number of played tracks"
I have the following tables:
Album which has GRid and Title
AlbumTrack which has GRid and ISRC
Track which has ISRC and PlayCount
Ive currently got:
SELECT TOP 10 Album.Title
FROM Album
WHERE Grid IN
(SELECT AlbumTrack.GRid
FROM AlbumTrack
WHERE ISRC IN
(SELECT Track,ISRC
FROM TRACK
WHERE Track.ISRC = SUM(Track.PlayCount)
ORDER BY Track.PlayCount));
Any thoughts?

Using Join
SELECT Title
FROM (
SELECT Album.Title,
DENSE_RANK() OVER (ORDER BY PlayCount) AS RN
FROM Album
INNER JOIN AlbumTrack
ON Album.Grid = AlbumTrack.Grid
INNER JOIN
( SELECT ISRC, SUM(PlayCount) AS PlayCount
FROM TRACK
GROUP BY ISRC
)AS TRACK
ON AlbumTrack.ISRC=TRACK.ISRC
) AS T
WHERE RN<=10

try this query :
SELECT TOP 10 Album.Title FROM Album
JOIN AlbumTrack ON Album.Grid = AlbumTrack.GRid
JOIN (SELECT Sum(PlayCount) as NumberOfPlay , ISRC TRACK
GROUP BY AlbumTrack.ISRC
) as Track ON AlbumTrack.ISRC = Track.ISRC
ORDER BY Track.NumberOfPlay

Related

Getting single row from JOIN given an additional condition

I'm making a select in which I give a year (hardcoded as 1981 below) and I expect to get one row per qualifying band. The main problem is to get the oldest living member for each band:
SELECT b.id_band,
COUNT(DISTINCT a.id_album),
COUNT(DISTINCT s.id_song),
COUNT(DISTINCT m.id_musician),
(SELECT name FROM MUSICIAN WHERE year_death IS NULL ORDER BY(birth)LIMIT 1)
FROM BAND b
LEFT JOIN ALBUM a ON(b.id_band = a.id_band)
LEFT JOIN SONG s ON(a.id_album = s.id_album)
JOIN MEMBER m ON(b.id_band= m.id_band)
JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)
/*LEFT JOIN(SELECT name FROM MUSICIAN WHERE year_death IS NULL
ORDER BY(birth) LIMIT 1) AS alive FROM mu*/ -- ??
WHERE b.year_formed = 1981
GROUP BY b.id_band;
I would like to obtain the oldest living member from mu for each band. But I just get the oldest musician overall from the relation MUSICIAN.
Here is screenshot showing output for my current query:
Well, I think you can follow the structure that you have, but you need JOINs in in the subquery.
SELECT b.id_band,
COUNT(DISTINCT a.id_album),
COUNT(DISTINCT s.id_song),
COUNT(DISTINCT mem.id_musician),
(SELECT m.name
FROM MUSICIAN m JOIN
MEMBER mem
ON mem.id_musician = m.id_musician
WHERE m.year_death IS NULL AND mem.id_band = b.id_band
ORDER BY m.birth
LIMIT 1
) as oldest_member
FROM BAND b LEFT JOIN
ALBUM a
ON b.id_band = a.id_band LEFT JOIN
SONG s
ON a.id_album = s.id_album LEFT JOIN
MEMBER mem
ON mem.id_band = b.id_band
WHERE b.year_formed = 1981
GROUP BY b.id_band
Following query will give you oldest member of each band group. You can put filter by year_formed = 1981 if you need.
SELECT
b.id_band,
total_albums,
total_songs,
total_musicians
FROM
(
SELECT b.id_band,
COUNT(DISTINCT a.id_album) as total_albums,
COUNT(DISTINCT s.id_song) as total_songs,
COUNT(DISTINCT m.id_musician) as total_musicians,
dense_rank() over (partition by b.id_band order by mu.year_death desc) as rnk
FROM BAND b
LEFT JOIN ALBUM a ON(b.id_band = a.id_band)
LEFT JOIN SONG s ON(a.id_album = s.id_album)
JOIN MEMBER m ON(b.id_band= m.id_band)
JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)
WHERE mu.year_death is NULL
)
where rnk = 1
You can reference a table that is out of this nested select, like so
SELECT b.id_band,
COUNT(DISTINCT a.id_album),
COUNT(DISTINCT s.id_song),
COUNT(DISTINCT m.id_musician),
(SELECT name FROM MUSICIAN WHERE year_death IS NULL ORDER BY(birth) AND
MUSICIAN.id_BAND = b.id_band LIMIT 1)
FROM BAND b
LEFT JOIN ALBUM a ON(b.id_band = a.id_band)
LEFT JOIN SONG s ON(a.id_album = s.id_album)
JOIN MEMBER m ON(b.id_band= m.id_band)
JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)
/*LEFT JOIN(SELECT name FROM MUSICIAN WHERE year_death IS NULL ORDER
BY(birth)LIMIT 1) AS alive FROM mu*/
WHERE b.year_formed= 1981
GROUP BY b.id_band
For queries where you want to find the "max person by age" you can use ROW_NUMBER() grouped by the band
SELECT b.id_band,
COUNT(DISTINCT a.id_album),
COUNT(DISTINCT s.id_song),
COUNT(DISTINCT m.id_musician),
oldest_living_members.*
FROM
band b
LEFT JOIN album a ON(b.id_band = a.id_band)
LEFT JOIN song s ON(a.id_album = s.id_album)
LEFT JOIN
(
SELECT
m.id_band
mu.*,
ROW_NUMBER() OVER(PARTITION BY m.id_band ORDER BY mu.birthdate ASC) rown
FROM
MEMBER m
JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)
WHERE year_death IS NULL
) oldest_living_members
ON
b.id_band = oldest_living_members.id_band AND
oldest_living_members.rown = 1
WHERE b.year_formed= 1981
GROUP BY b.id_band
If you run just the subquery you'll see how it's working = artists are joined to member to get the band id, and this forms a partition. Rownumber will start numbering from 1 according to the order of birthdates (I didn't know what your column name for birthday was; you'll have to edit it) so the oldest person (earliest birthday) gets a 1.. Every time the band id changes the numbering will restart from 1 with the oldest person in that band. Then when we join it we just pick the 1s
I think this should be considerably faster (while also solving your problem):
SELECT b.id_band, a.*, m.*
FROM band b
LEFT JOIN LATERAL (
SELECT count(*) AS ct_albums, sum(ct_songs) AS ct_songs
FROM (
SELECT id_album, count(*) AS ct_songs
FROM album a
LEFT JOIN song s USING (id_album)
WHERE a.id_band = b.id_band
GROUP BY 1
) ab
) a ON true
LEFT JOIN LATERAL (
SELECT count(*) OVER () AS ct_musicians
, name AS senior_member -- any other columns you need?
FROM member m
JOIN musician mu USING (id_musician)
WHERE m.id_band = b.id_band
ORDER BY year_death IS NOT NULL -- sorts the living first
, birth
, name -- as tiebreaker (my optional addition)
LIMIT 1
) m ON true
WHERE b.year_formed = 1981;
Getting the senior band member is solved in the LATERAL subquery m - without multiplying the cost for the base query. It works because the window function count(*) OVER () is computed before ORDER BY and LIMIT are applied. Since bands naturally only have few members, this should be the fastest possible way. See:
Best way to get result count before LIMIT was applied
What is the difference between LATERAL and a subquery in PostgreSQL?
Prevent duplicate values in LEFT JOIN
The other optimization for counting albums and songs builds on the assumption that the same id_song is never included in multiple albums of the same band. Else, those are counted multiple times. (Easily fixed, and uncorrelated to the task of getting the senior band member.)
The point is to eliminate the need for DISTINCT at the top level after multiplying rows at the N-side repeatedly (I like to call that "proxy cross join"). That would produce a possibly huge number of rows in the derived table without need.
Plus, it's much more convenient to retrieve additional column (like more columns for the senior band member) than with some other query styles.

SQL Query not getting the corresponding column

SELECT t.title, Max(t.st)
FROM (SELECT title,
Avg(stars) AS st
FROM movie
JOIN rating USING(mid)
GROUP BY title)t;
This is my query which I am writing to get max values of AVG(stars) and it's corresponding title. Max value is coming fine but facing trouble in title. I am not getting corresponding title.
Output of subquery is:
Avatar 4.0
E.T. 2.5
Gone with the Wind 3.0
Raiders of the Lost Ark 3.33333333333
Snow White 4.5
The Sound of Music 2.5
Output of whole query is wrong .
The Sound of Music 4.5
Expected /Correct output is
SnowWhite 4.5
Try this
select t.title,
t.st
from
(select title,
avg(stars) as st,rank() over(order by avg(stars) desc) as rSt
from movie
join rating using(mID)
group by title)t where t.rSt=1 ;
It is ranking movies in descending order first, then in outer query's where condition, movie with highest rank is being selected. Hope this helps :-)
You can try using correlated subquery
select *
from
(select title,
avg(stars) as st
from movie a
join rating b on a.mID=b.mID
group by title
)t where st in
(select max(st) from (select title,
avg(stars) as st
from movie a
join rating b on a.mID=b.mID
group by title
)t1 on t.title=t1.title)
You can use WHERE condition in your query. You can experiment with joins or just use subquery.
Select title, max(stars)
from table
where stars = (select max(stars) from table)
or
SELECT top 1 title
FROM table
ORDER BY Stars DESC

How to find the single highest value? How to show everything if there is a tie?

I want to have the output of the movie(s) that have the most awards. The problem I'm having is how do I show a single movie? I tried to make a PIVOT function, and use the MAX() function rather than the COUNT() function; however I am would only get the output of 1 with almost all of the rows. I would however like to use the MAX() function to do this. I also want to know how can I show all "Movies" if there would be a tie? From my information there isn't going to be any tie, however if there were to be one, I would like it if all of the information would be shown.
Expected output:
MOVIE Awards Won
----------------------------------- ----------
Saving Private Ryan 6
1 rows selected
Output with my query:
MOVIE Awards Won
----------------------------------- ----------
A Lonely Place to Die 5
Act of Valor 0
Captain America: The First Avenger 2
Date Night 1
Drive Angry 0
Saving Private Ryan 6
Taken 1
7 rows selected
Here is my query:
SELECT * FROM
(
SELECT MovieTitle AS "MOVIE",
TBLAWARDRESULT.AWARDRESULTDESC AS "Result Type",
TBLAWARDRESULT.AWARDRESULTID AS "Rating"
FROM TBLMOVIE
INNER JOIN TBLAWARDDETAIL
ON TBLMOVIE.MOVIEID = TBLAWARDDETAIL.MOVIEID
INNER JOIN TBLAWARDRESULT
ON TBLAWARDDETAIL.AWARDRESULTID = TBLAWARDRESULT.AWARDRESULTID
ORDER BY Movietitle
)
PIVOT
(
COUNT("Rating") FOR "Result Type"
IN ('Won' AS "Awards Won")
)
ORDER BY Movie;
Tables:
File1 (PasteBin)
File2 (PasteBin)
Use RANK function to order the results by award count descending, which would get you multiple rows in case of ties as well.
SELECT MOVIE,Awards_Won
FROM (
SELECT
MovieTitle AS "MOVIE",
COUNT(TBLAWARDRESULT.AWARDRESULTID) AS Awards_Won,
RANK() OVER(ORDER BY COUNT(TBLAWARDRESULT.AWARDRESULTID) DESC) RNK
FROM TBLMOVIE
INNER JOIN TBLAWARDDETAIL ON TBLMOVIE.MOVIEID = TBLAWARDDETAIL.MOVIEID
INNER JOIN TBLAWARDRESULT ON TBLAWARDDETAIL.AWARDRESULTID = TBLAWARDRESULT.AWARDRESULTID
WHERE TBLAWARDRESULT.AWARDRESULTDESC = 'Won'
) t
WHERE RNK = 1
Don't use pivot. Use window functions:
SELECT "MOVIE", AWARDS_WON
FROM (SELECT m.MovieTitle AS "MOVIE", COUNT(*) as AWARDS_WON,
RANK() OVER (PARTITION BY m.MovieTitle ORDER BY COUNT(*) DESC) as seqnum
FROM TBLMOVIE m INNER JOIN
TBLAWARDDETAIL ad
ON m.MOVIEID = ad.MOVIEID INNER JOIN
TBLAWARDRESULT ar
ON ad.AWARDRESULTID = ar.AWARDRESULTID
WHERE ar.AWARDTYPE = 'Won'
GROUP BY m.MovieTitle
) m
WHERE seqnum = 1;
If your on Oracle 12c there is the slightly simpler option to ROW_NUMBER of using FETCH.
SELECT m.MovieTitle MOVIE, COUNT(1) AS "Awards Won"
FROM
TBLMOVIE m
INNER JOIN
TBLAWARDDETAIL ad ON m.MovieID = ad.MovieID
INNER JOIN
TBLAWARDRESULT ar ON ad.AwardResultID = ar.AwardResultID
WHERE ar.AwardResultDesc = 'Won'
GROUP BY m.MovieTitle
ORDER BY "Awards Won" DESC
FETCH FIRST ROW ONLY

find only one row from table in foreign with another tables

i have 2 tables gallery(Gallery_ID,Name) and Gallery_Image(ID,Gallery_ID,Image). My question is i want to select only one image of each gallery
i have tried this query
SELECT distinct top 1 Gallery.Gallery_ID, Gallery.Gallery_Name,Gallery.Gallery_Name as Gallery_Image
FROM Gallery
union
select distinct Gallery_Image.Gallery_ID,Gallery_Image.Gallery_Images as Gallery_Name,Gallery_Image.Gallery_Images
from Gallery_Image inner join Gallery on Gallery.Gallery_ID=Gallery_Image.Gallery_ID
where Gallery_Image.Gallery_ID in(select Gallery_ID from Gallery)
You say: "i want to select only one image of each gallery." This makes sense. I have no idea how your query relates to this question.
You don't specify which database you are using. A good approach is to use row_number() to assign a sequential order to the images within a gallery. The key is to do a random sort. The last piece depends on the database. Here is the SQL Server syntax:
select gi.*
from (select gi.*, row_number() over (partition by Gallery_Id order by newid()) as seqnum
from Gallery_Image gi
) gi
where seqnum = 1;
EDIT:
To get information from the Gallery table, join it in:
select gi.*
from (select gi.*, row_number() over (partition by Gallery_Id order by newid()) as seqnum
from Gallery_Image gi
) gi join
Gallery g
on gi.Gallery_Id = g.Gallery_Id and
seqnum = 1;
I also moved the logic to take the "first" image to the on clause from the where clause.

Getting content and count from SQL at same time

I've got 2 tables, albums and pictures...
pictures has relation to albums via fk_albumID.
Now want im trying is to select all from albums, and at the same time count how many pictures that has relation to albums...
I tried with:
SELECT *, (SELECT COUNT(*) FROM pictures WHERE pictures.fk_albumID = albums.albumID) AS albumCount FROM pictures, albums
But this first of all dont return any results if theres no pictures at all...
And then it repeats results according to count.
So if albums has 3 pictures, then i will get the album 3 times in my list, when i bind it to a Repeater.
And i tried:
SELECT COUNT(albums.albumID) AS albumCount, albums.albumName, albums.albumID FROM albums INNER JOIN pictures ON pictures.fk_albumID = albums.albumID GROUP BY albums.albumID, albums.albuName
But this only shows albums that has pictures...
You were close though. All you need is go from an INNER JOIN to an OUTER JOIN
SELECT COUNT(billeder.album_id) AS AlbumSize,
albums.album_name,
albums.album_id
FROM albums
LEFT OUTER JOIN billeder
ON billeder.album_home = albums.album_id
GROUP BY albums.album_id, albums.album_name
You can use a sub-query:
SELECT p.PicCount AS AlbumSize,
albums.album_name,
albums.album_id
FROM albums a
INNER JOIN billeder b
ON b.album_home = a.album_id
LEFT JOIN
(
SELECT count(*) PicCount, fk_albumid
FROM pictures
GROUY BY fk_albumid
) p
on a.album_id = p.fk_albumid
SELECT t1.album_title, isnull(sum(flag),0) AS PicCount
FROM albums AS t1
LEFT JOIN
(
SELECT *, 1 AS Flag
FROM pictures) AS t2
ON t1.fk_albumID = t2.fk_albumID
GROUP BY t1.album_title
Link to SQL Fiddle
SELECT (SELECT COUNT(1)
FROM billeder
WHERE billeder.album_home = albums.album_id) albumCount,
albums.album_name,
albums.album_id
FROM albums
select
AL.album_name,
AL.albumID,
(select count(*) from
ALBUMS AL1,
PICTURES PIC1
where AL1.albumID = PIC1.albumID) as CNT_ALBUMID
from
ALBUMS AL
Have a try