Trouble with an aggregate SQL statement - sql

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

Related

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

Order by join column but use distinct on another

I'm building a system in which there are the following tables:
Song
Broadcast
Station
Follow
User
A user follows stations, which have songs on them through broadcasts.
I'm building a "feed" of songs for a user based on the stations they follow.
Here's the query:
SELECT DISTINCT ON ("broadcasts"."created_at", "songs"."id") songs.*
FROM "songs"
INNER JOIN "broadcasts" ON "songs"."shared_id" = "broadcasts"."song_id"
INNER JOIN "stations" ON "broadcasts"."station_id" = "stations"."id"
INNER JOIN "follows" ON "stations"."id" = "follows"."station_id"
WHERE "follows"."user_id" = 2
ORDER BY broadcasts.created_at desc
LIMIT 18
Note: shared_id is the same as id.
As you can see I'm getting duplicate results, which I don't want. I found out from a previous question that this was due to selecting distinct on broadcasts.created_at.
My question is: How do I modify this query so it will return only unique songs based on their id but still order by broadcasts.created_at?
Try this solution:
SELECT a.maxcreated, b.*
FROM
(
SELECT bb.song_id, MAX(bb.created_at) AS maxcreated
FROM follows aa
INNER JOIN broadcasts bb ON aa.station_id = bb.station_id
WHERE aa.user_id = 2
GROUP BY bb.song_id
) a
INNER JOIN songs b ON a.song_id = b.id
ORDER BY a.maxcreated DESC
LIMIT 18
The FROM subselect retrieves distinct song_ids that are broadcasted by all stations the user follows; it also gets the latest broadcast date associated with each song. We have to encase this in a subquery because we have to GROUP BY on the columns we're selecting from, and we only want the unique song_id and the maxdate regardless of the station.
We then join that result in the outer query to the songs table to get the song information associated with each unique song_id
You can use Common Table Expressions (CTE) if you want a cleaner query (nested queries make things harder to read)
I would look like this:
WITH a as (
SELECT bb.song_id, MAX(bb.created_at) AS maxcreated
FROM follows aa
INNER JOIN broadcasts bb ON aa.station_id = bb.station_id
INNER JOIN songs cc ON bb.song_id = cc.shared_id
WHERE aa.user_id = 2
GROUP BY bb.song_id
)
SELECT
a.maxcreated,
b.*
FROM a INNER JOIN
songs b ON a.song_id = b.id
ORDER BY
a.maxcreated DESC
LIMIT 18
Using a CTE offers the advantages of improved readability and ease in maintenance of complex queries. The query can be divided into separate, simple, logical building blocks. These simple blocks can then be used to build more complex, interim CTEs until the final result set is generated.
Try by adding GROUP BY Songs.id
I had a very similar query I was doing between listens, tracks and albums and it took me a long while to figure it out (hours).
If you use a GROUP_BY songs.id, you can get it to work by ordering by MAX(broadcasts.created_at) DESC.
Here's what the full SQL looks like:
SELECT songs.* FROM "songs"
INNER JOIN "broadcasts" ON "songs"."shared_id" = "broadcasts"."song_id"
INNER JOIN "stations" ON "broadcasts"."station_id" = "stations"."id"
INNER JOIN "follows" ON "stations"."id" = "follows"."station_id"
WHERE "follows"."user_id" = 2
GROUP BY songs.id
ORDER BY MAX(broadcasts.created_at) desc
LIMIT 18;

i want to modify this SQL statement to return only distinct rows of a column

select
picks.`fbid`,
picks.`time`,
categories.`name` as cname,
options.`name` as oname,
users.`name`
from
picks
left join categories
on (categories.`id` = picks.`cid`)
left join options
on (options.`id` = picks.oid)
left join users
on (users.fbid = picks.`fbid`)
order by
time desc
that query returns a result that like:
my question is.... I would like to modify the query to select only DISTINCT fbid's. (perhaps the first row only sorted by time)
can someone help with this?
select
p2.fbid,
p2.time,
c.`name` as cname,
o.`name` as oname,
u.`name`
from
( select p1.fbid,
min( p1.time ) FirstTimePerID
from picks p1
group by p1.fbid ) as FirstPerID
JOIN Picks p2
on FirstPerID.fbid = p2.fbid
AND FirstPerID.FirstTimePerID = p2.time
LEFT JOIN Categories c
on p2.cid = c.id
LEFT JOIN Options o
on p2.oid = o.id
LEFT JOIN Users u
on p2.fbid = u.fbid
order by
time desc
I don't know why you originally had LEFT JOINs, as it appears that all picks must be associated with a valid category, option and user... I would then remove the left, and change them to INNER joins instead.
The first inner query grabs for each fbid, the FIRST entry time which will result in a single entity for the FBID. From that, it re-joins to the picks table for the same ID and timeslot... then continues for the rest of the category, options, users join criteria of that single entry.
2 options, you could write a group by clause.
Or you could write a nested query joined back to itself to get pertinent info.
Nested aliased table:
SELECT
n.fBids
FROM
MyTable t
INNER JOIN
(SELECT DISTINCT fBids
FROM MyTable) n
ON n.ID = t.ID
Or group by option
SELECT fBId from MyTable
GROUP BY fBID
select picks.`fbid`, picks.`time`, categories.`name` as cname,
options.`name` as oname, users.`name` from picks left join categories
on (categories.`id` = picks.`cid`) left join options on (options.`id` = picks.oid)
left join users on (users.fbid = picks.`fbid`)
order by time desc GROUP BY picks.`fbid`
select
picks.fbid,
MIN(picks.time) as first_time,
MAX(picks.time) as last_time
from
picks
group by
picks.fbid
order by
MIN(picks.time) desc
However, if you want only distinct fbid's you cannot display cname and other columns at the same time.

Optimizing simple query that takes 1 minute to execute

I have this rather simple SQL query, but it takes almost a minute to execute:
SELECT
i.id,
...,
a.id AS albums_id,
...,
u.id AS users_id,
...
FROM
images i
LEFT JOIN albums a ON i.albums_id = a.id
LEFT JOIN users u ON a.users_id = u.id
WHERE
a.access = 'public'
AND i.num_of_views > 0
ORDER BY
i.num_of_views DESC
LIMIT
0, 60
Result of EXPLAIN for the above query:
Tables involved:
images (~4,822,000 rows), albums (~149,000 rows), users (~43,000 rows)
Relevant indexes:
albums: access(access,num_of_images,album_time), access_2(access,num_of_images,num_of_all_comments,album_time), users_id(users_id,album_time)
images: browser_2(num_of_views), albums_id(albums_id,image_order)
All tabels are InnoDB, running on MySql v5.1.47
So how do I bring this down to under a second?
Please leave a comment if you need any additional info.
edit: users table can be joined either with albums or images does not matter to me.
edit2: moving a.access = 'public' from WHERE to JOIN does indeed solve my problem, but the results returned are not correct (I get images from albums that are not public), putting the a.access ... in both WHERE and JOIN slows the query down even more than before.
Add an index on albums.users_id. I also agree with the comments regarding a.access = 'public'. But the index should help either way.
UPDATE
Since the key above exists. Try adjusting the order of your JOIN, i.e. move users above albums or make a different table the primary. In rare cases this can help. Also to better join albums try:
LEFT JOIN albums a ON (i.albums_id = a.id AND a.access = 'public')
UPDATE
Based on the comments, I would remove as many of the LEFT JOIN as possible. As I am not sure what you require in your results, I will only show it for albums. This will not only decrease the result set, but solve the problem for applying the filter.
JOIN albums a ON (i.albums_id = a.id AND a.access = 'public')
I believe there's a little confusion going on here w/r/t the impact that a filter can have on a LEFT JOIN vs an INNER JOIN.
Jan, if what you are trying to ask in your query is "Get all images for all albums that are public, and get the users of those albums as well" then you do not want a left join, you want an inner join. A left join will return all images for all albums, but it will also return all images that have no matching album. You can add "and a.id IS NOT NULL" but that's the same as an INNER JOIN.
I believe what you want is the following:
SELECT
i.id,
...,
a.id AS albums_id,
...,
u.id AS users_id,
...
FROM images i
INNER JOIN albums a ON i.albums_id = a.id AND a.access = 'public'
INNER JOIN users u ON a.users_id = u.id
WHERE i.num_of_views > 0
ORDER BY i.num_of_views DESC
LIMIT 0, 60
If you left join albums to users you could return all albums that don't have users. Not sure which one you want.
Based on your most recent comments, you should use an INNER JOIN to albums instead of a LEFT JOIN.
SELECT
i.id,
...,
a.id AS albums_id,
...,
u.id AS users_id,
...
FROM images i
INNER JOIN albums a ON i.albums_id = a.id
LEFT JOIN users u ON a.users_id = u.id
WHERE a.access = 'public'
AND i.num_of_views > 0
ORDER BY i.num_of_views DESC
LIMIT 0, 60

Need help with a simple Join

Oi
Right to the problem.
SELECT *,t.id AS threadid FROM threads t
LEFT JOIN players p on p.id = t.last_poster
WHERE t.boardid = $boardid
I have two fields in threads called posterid and lastposterid. Which are the IDs of the thread starter / last poster. What I want to do is to get their names from players table.
But how?
You just need to join to your players table twice, like this.
SELECT
threads.*,
starterPlayer.*,
lastPosterPlayer.*
FROM
threads
LEFT OUTER JOIN
players starterPlayer
ON
starterPlayer.id = threads.posterid
LEFT OUTER JOIN
players lastPosterPlayer
ON
lastPosterPlayer.id = threads.lastposterid
You can join to the same table twice and give the table a different alias.
This presumes that there always will be a first and last poster, if this is the case then you want an INNER JOIN rather than a LEFT JOIN, you will need to change the select statement to get the relevant fields.
SELECT t.id AS threadid, playerFirst.name AS FirstPoster, playerLast.name as LastPoster
FROM threads t
INNER JOIN
players playerFirst ON playerFirst.id = t.posterid
INNER JOIN
players playerLast ON playerLast.id = t.lastposterid
How about...
SELECT *,
(SELECT name
FROM players
WHERE players.id = threads.posterid) AS poster,
(SELECT name
FROM players
WHERE players.id = threads.lastposterid) AS last_poster
FROM threads;