Add if statement to Oracle query - sql

I have a query that needs to return a single row if there are more than 2 rows with a certain ID. I figured the best way to do this was to add a counter in my query to count the instances of x and then return the results if x >=2 but I'm not sure how in Oracle.
My query now returns all of the instances of x in all playlists. I need it to return the playlist only if it has more than 2 instances of x
Select * from PLAYLIST p
left join PLAYLIST_SONGS ps on p.PLAYLIST_ID = ps.PLAYLIST_ID
join SONG s on ps.SONG_ID = s.SONG_ID
join Artists art on s.ARTIST_ID = art.ARTIST_ID
where art.BAND='x'
and p.NUM_SONGS >=2;

If you want to get the playlists with 2 or more songs of a certain artist, something like this might help you out:
SELECT *
FROM playlist p
WHERE (SELECT COUNT(1)
FROM playlist_songs ps JOIN song s ON ps.song_id = s.song_id
JOIN artists art ON s.artist_id = art.artist_id
WHERE ps.playlist_id = p.playlist_id
AND art.band = 'X') >= 2

Here's the concept of having and a group by to get songs with count > 1. Note field 1,2,3,4 in this case must not cause the records you want to combine to be unique. Otherwise, pablomatico's approach could work
Select Field1, Field2, Field3, Field4
from PLAYLIST p
left join PLAYLIST_SONGS ps on p.PLAYLIST_ID = ps.PLAYLIST_ID
join SONG s on ps.SONG_ID = s.SONG_ID
join Artists art on s.ARTIST_ID = art.ARTIST_ID
where art.BAND='x'
GROUP BY Field1, Field2, Field3, Field4
HAVING count(PS.Song_ID) >=2
You could also use an analytic function (window function) such as over partition by assign a row number to each song. something like row_number() over (partition by song_Id)

One other solution would be to use an inline correlated view to also get the number of songs:
select
p.*,
(
select
count(distinct s.song_id)
from
playlist_songs ps
join songs s on s.song_id = ps.song_id
join artists art on s.artist_id = art.artist_id
where p.playlist_id = ps.playlist_id
) number_of_songs
from
playlist p
where
number_of_songs > 1;

Related

Subqueries - Finding the average number of tracks per album

I'm trying to Write a subquery to join 'album' and 'track' tables.
Eventually I need to figure out how many songs on average are on albums with the word "Rock" in the title. The chosen albums must have at least eight songs on them.
ER diagram
SELECT AVG(tr.track_id)
FROM(SELECT al.album_id AS album,
tr.name,
COUNT(tr.track_id)
FROM track as tr
LEFT OUTER JOIN album as al ON al.album_id = tr.album_id
WHERE tr.name LIKE '%Rock%'
GROUP tr.name )AS ag
HAVING COUNT(al.album_id) >= 8;
You were close. The HAVING clause should be inside the inner query, filtering only those with more than 8 songs in it. Also you filtering by track name instead of album name, so I changed it as well.
SELECT AVG(ag.cnt)
FROM(SELECT al.album_id AS id,
COUNT(*) as cnt
FROM track as tr
JOIN album as al ON al.album_id = tr.album_id
WHERE al.name LIKE '%Rock%'
GROUP al.album_id
HAVING COUNT(al.album_id) >= 8 ) AS ag

Get Average in SQL Through Join

I'm just playing around with SQL and I'm trying to do the following.
I have 2 tables and here is their structure:
Movies_metadata Movies
ratings table:
Ratings
As there are many ratings for one movie, what I'd like to do is get the avg rating per movie and have it display next to the title which is only available in the Metadata table.
This is as far as I got but obviously the issue with my SELECT statement is that it'll return the average of all movies and display it for each record:
SELECT
(SELECT
AVG(rating)
FROM
`movies-dataset.movies_data.ratings`) AS rating_avg,
metadata.title,
metadata.budget,
metadata.revenue,
metadata.genres,
metadata.original_language,
metadata.release_date
FROM
`movies-dataset.movies_data.Movies_metadata` AS metadata
INNER JOIN `movies-dataset.movies_data.ratings` AS ratings
ON metadata.id = ratings.movieId
LIMIT 10
Here is an example of the result:
Result
I'm thinking I can potentially use a GROUP BY but when I try, I get an error
Appreciate the help!
The following should work:
SELECT movies_metadata.title, AVG(ratings.rating)
FROM movies_metadata
LEFT JOIN ratings ON movies_metadata.id = ratings.movieID
GROUP BY movies_metadata.title
You can swap movies_metadata.title by movies_metadata.id if not unique.
The LIMIT function and GROUP function might conflict with each other. Try getting the average rating as part of the inner join like this:
SELECT
ratings.averagerating,
metadata.title,
metadata.budget,
metadata.revenue,
metadata.genres,
metadata.original_language,
metadata.release_date
FROM `movies-dataset.movies_data.Movies_metadata` AS metadata
INNER JOIN (SELECT movieId, AVG(rating) averagerating FROM `movies-dataset.movies_data.ratings` GROUP by movieId) AS ratings
ON metadata.id = ratings.movieId
ORDER BY ratings.averagerating
LIMIT 5
Maybe try something like:
Select m.movieID, (r.rate_sum / r.num_rate) as avg_rating
From your_movies_table m
Left Join (select movie_id, sum(rating) as ‘rate_sum’, count(rating) as ‘num_rate’
From your_ratings_table
Group by movie_id) r
On m.movie_id = r.movie_id
I'm using a left join because I'm not sure if all movies have been rated at least once.

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.

Trouble with an aggregate SQL statement

This link will show you my schema and the contents of all tables involved.
My goal is to, using a single Select statement, display the name of every artist and sort by the number of "Rock" songs they have even for the artists that do not have any, in order of fewest to most. Here is what I tried, and it obviously did not work.
SELECT
Musical_genre.musical_genre_id,
COUNT(Musical_genre.musical_genre_id) AS nr_rocksongs
FROM
Musical_genre
JOIN
Album ON Album.musical_genre_id = Musical_genre.musical_genre_id
JOIN
Recording_artist ON Album.recording_artist_id = Recording_artist.recording_artist_id
GROUP BY
Album.recording_artist_id, Recording_artist.artist_name, Musical_genre.musical_genre_id
ORDER BY
nr_rocksongs ASC
Any ideas what I missed to get the results I am looking for? All help is greatly appreciated.
Remove the Musical_genre.musical_genre_id from group by and add the artist_name in select
SELECT Musical_genre.musical_genre_id, Recording_artist.artist_name,
COUNT(Musical_genre.musical_genre_id) AS nr_rocksongs
FROM Musical_genre
JOIN Album ON Album.musical_genre_id = Musical_genre.musical_genre_id
JOIN Recording_artist ON Album.recording_artist_id = Recording_artist.recording_artist_id
GROUP BY Musical_genre.musical_genre_id, Recording_artist.artist_name
ORDER BY nr_rocksongs ASC
You need to start with the artist and left join the album and genre.
SELECT
ra.artist_name,
mg.musical_genre_id,
mg.musical_genre,
COUNT(mg.musical_genre_id) AS nr_rocksongs
FROM
Recording_artist ra
LEFT JOIN Album a ON
a.recording_artist_id = ra.recording_artist_id
LEFT JOIN Musical_genre mg ON
a.musical_genre_id = mg.musical_genre_id
AND mg.musical_genre = 'Rock'
--use musical_genre_id = 201 if you like, but then you could remove the last left join entirely.
LEFT JOIN Song s ON
a.album_id = s.album_id
GROUP BY
mg.musical_genre_id,
mg.musical_genre,
ra.artist_name
ORDER BY
nr_rocksongs ASC

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