Is there a way to count and loop with plain SQL? - sql

I have the following tables:
album:
albumID, albumTitle, albumReleaseDate, albumLabel, albumDigitalImg, albumCoverStory
albumTrack:
trackID, albumID, recordingID, albumTrackNumber, cd
italic = primary key
bold = foreign key
The question is as follows:
List the total number of tracks on each album that has any. Give the
column containing the total number of tracks a sensible name and use
albumId to identify each total.
My only idea would be to iterate over each albumID and check how many tracks are assigned to it but obviously loops aren't a thing in plain SQL? Is there any way to do this using plain SQL?
This only checks how many tracks are assigned for albumID 1, not for all albums and I'm really lost as to how I could do this without a loop.
SELECT COUNT(albumID)
FROM albumTrack
WHERE albumID = 1;
I'm using Oracle.

Below query will give you the number of tracks against each albumID,if any.
SELECT albumID,count(trackID) as NumberOfTracks
FROM albumTrack
Group By albumID;

You can try:
Select ab.albumID,
Count(Distinct trackID) As trackCount
From album ab
Inner Join albumTrack at
on at.albumID = ab.albumID
Group By ab.albumID;
This will give the count of tracks for each album.

The question asks for the counts per albumID.
For just that, a select on albumTrack alone with a GROUP BY on the albumID should be sufficient.
SELECT albumID, COUNT(*) AS TotalTracks
FROM albumTrack
GROUP BY albumID
ORDER BY albumID
But if albumTrack.albumID can be NULL, or isn't a foreign key on album.albumID?
Then an INNER JOIN on the "album" table should still be used.
To make sure that it only counts for albumID's that actually exist in the "album" table.
SELECT
tracks.albumID,
COUNT(*) AS TotalTracks
FROM albumTrack AS tracks
JOIN album ON album.albumID = tracks.albumID
GROUP BY tracks.albumID
ORDER BY tracks.albumID
And if you'd like to show the counts per albumTitle:
SELECT
a.albumTitle,
COUNT(t.trackID) AS TotalTracks
FROM album a
JOIN albumTrack t ON t.albumID = a.albumID
-- WHERE a.albumID = 1
GROUP BY a.albumID, a.albumTitle
ORDER BY a.albumTitle

Related

SQL : query to retrieve data

I'm working on a SQL project where i have the tables Actor AlbumId TrackId InvoiceLine and Invoice and i want to retrieve the names of the actors whose tracks are the top 3 sold.
ex(from a table of 10 artists ) .
Drake (300 tracks sold )
Kendrick (233 tracks)
Cardi B (200 tracks)
I'm new to SQL and i have my diagram below . I know that i have to connect the tables of actor album track and invoice to get the 3 max(count(trackid)) from the invoiceline table to give me the names of the actors that have made the specific tracks
Thank you for your time
Below joins the relevant tables together and uses the aggregrate COUNT function to count the number of invoice lines associated with a song.
You may want to use TOP 3 WITH TIES in case there are two artists in third place with same number of sales
SELECT TOP 3
ar.Name
FROM InvoiceLine il
JOIN Track t
ON il.TrackId = t.TrackId
JOIN Album al
ON al.AlbumId = t.AlbumId
JOIN Artist ar
ON ar.ArtistId = al.ArtistId
GROUP BY ar.Name
ORDER BY COUNT(t.TrackId) DESC
If you're looking for the top 3 tracks sold then this will give you the top 3 trackids. Then join this to your Track, Album and Artist tables to get the artist.
SELECT TOP 3 trackid
FROM InvoiceLine
GROUP BY trackid
ORDER BY COUNT(trackid) DESC

MS Access SQL - Left Join a table that only has unique matches

I'm using an example database I got through DBeaver. It contains a table called Artists and a table called Albums. Some artists have no albums, some have one and some have many.
What I'm trying to achieve is a left join to show all artists but only display the album name of the artists who only have one album.
The following works using DBeaver but doesn't work if executed directly from MS Access (which i need to be able to do):
SELECT
ar.AName,
al.title
FROM
Artist ar
LEFT JOIN (
SELECT
al.*
FROM
album al
INNER JOIN (
SELECT
artistid,
COUNT( artistid ) artistCount
FROM
album
GROUP BY
artistid ) alc ON
alc.artistid = al.artistid
AND alc.artistcount = 1 ) al ON
al.artistid = ar.artistid
ORDER BY
ar.aname
Tables have the following columns:
Artist
ArtistID (Key)
Name
Album
AlbumID (Key)
Title
ArtistID
Many thanks in advance. I can't find an answer to this anywhere. I understand the version of SQL used in MS Access isn't the same as found in various other variants.
Cheers
Patrick
I would go with NOT EXISTS :
SELECT a.ArtistID, a.Name, al.Title
FROM Artist a LEFT JOIN
Album al
ON al.ArtistID = a.ArtistID AND
NOT EXISTS (SELECT 1
FROM Album al1
WHERE al1.ArtistID = al.ArtistID AND a1l.AlbumID <> al.AlbumID
);
Thank you for your help. I have managed to produce the results I was expecting using SQL in MS Access (will need to test to ensure its 100%).
I used the following:
SELECT
ar.AName,
al.title
FROM
Artist ar
LEFT JOIN (
SELECT
al.*
FROM
album al,
(
SELECT
al1.artistid,
COUNT( al1.artistid ) AS al1Count
FROM
album al1
GROUP BY
al1.artistid ) al1
WHERE
al.artistID = al1.artistid
AND al1.al1count = 1 ) al ON
al.artistid = ar.artistid
ORDER BY
ar.aname
Thanks again!
You don't want a left join. If an artist has exactly one album, then a regular join will work. The key is aggregation:
select ar.aname, max(al.title) as title
from artist as ar inner join
album as al
on ar.artistid = al.artistid
group by ar.artistid, ar.aname -- I supposed two artists could have the same name
having count(*) = 1;
The key idea is that if there is only one album, then max(al.title) returns that album's name.
EDIT: (based on comment)
select ar.aname,
switch(al.artistid is null, "No album"
count(*) = 1, max(al.title),
1=1, "> 1"
) as album_title
from artist as ar left join
album as al
on ar.artistid = al.artistid
group by ar.artistid, ar.aname;

Matching similar entities based on many to many relationship

I have two entities in my database that are connected with a many to many relationship. I was wondering what would be the best way to list which entities have the most similarities based on it?
I tried doing a count(*) with intersect, but the query takes too long to run on every entry in my database (there are about 20k records). When running the query I wrote, CPU usage jumps to 100% and the database has locking issues.
Here is some code showing what I've tried:
My tables look something along these lines:
/* 20k records */
create table Movie(
Id INT PRIMARY KEY,
Title varchar(255)
);
/* 200-300 records */
create table Tags(
Id INT PRIMARY KEY,
Desc varchar(255)
);
/* 200,000-300,000 records */
create table TagMovies(
Movie_Id INT,
Tag_Id INT,
PRIMARY KEY (Movie_Id, Tag_Id),
FOREIGN KEY (Movie_Id) REFERENCES Movie(Id),
FOREIGN KEY (Tag_Id) REFERENCES Tags(Id),
);
(This works, but it is terribly slow)
This is the query that I wrote to try and list them:
Usually I also filter with top 1 & add a where clause to get a specific set of related data.
SELECT
bk.Id,
rh.Id
FROM
Movies bk
CROSS APPLY (
SELECT TOP 15
b.Id,
/* Tags Score */
(
SELECT COUNT(*) FROM (
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = bk.Id
INTERSECT
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = b.Id
) Q1
)
as Amount
FROM
Movies b
WHERE
b.Id <> bk.Id
ORDER BY Amount DESC
) rh
Explanation:
Movies have tags and the user can get try to find movies similar to the one that they selected based on other movies that have similar tags.
Hmm ... just an idea, but maybe I didnt understand ...
This query should return best matched movies by tags for a given movie ID:
SELECT m.id, m.title, GROUP_CONCAT(DISTINCT t.Descr SEPARATOR ', ') as tags, count(*) as matches
FROM stack.Movie m
LEFT JOIN stack.TagMovies tm ON m.Id = tm.Movie_Id
LEFT JOIN stack.Tags t ON tm.Tag_Id = t.Id
WHERE m.id != 1
AND tm.Tag_Id IN (SELECT Tag_Id FROM stack.TagMovies tm WHERE tm.Movie_Id = 1)
GROUP BY m.id
ORDER BY matches DESC
LIMIT 15;
EDIT:
I just realized that it's for M$ SQL ... but maybe something similar can be done...
You should probably decide on a naming convention and stick with it. Are tables singular or plural nouns? I don't want to get into that debate, but pick one or the other.
Without access to your database I don't know how this will perform. It's just off the top of my head. You could also limit this by the M.id value to find the best matches for a single movie, which I think would improve performance by quite a bit.
Also, TOP x should let you get the x closest matches.
SELECT
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title,
COUNT(*) AS matched_tags
FROM
Movie M
INNER JOIN TagsMovie TM1 ON TM1.movie_id = M.movie_id
INNER JOIN TagsMovie TM2 ON
TM2.tag_id = TM1.tag_id AND
TM2.movie_id <> TM1.movie_id
INNER JOIN Movie SM ON SM.movie_id = TM2.movie_id
GROUP BY
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title
ORDER BY
COUNT(*) DESC

SQL: Counting number of games for team from results page

Completely noob to SQL.
I have created the following table, which stores data on matches between two opponents and the points the winner got.
CREATE TABLE matches ( winner INT references players,
loser INT references players,
gamepoints INT);
I created the below VIEW to show standings:
CREATE VIEW standings as
select
players.id,
players.name,
count(matches.winner) as number_of_wins,
coalesce(sum(matches.gamepoints),0) as points
from players left join matches
on players.id = matches.winner
group by players.name, players.id
order by number_of_wins desc, points desc;
I wish to add a column that will show how many games a player played. My problem is that games appear in both matches.winner and matches.loser columns, and I'm not sure how to aggregate them in the standings view.
Also, would you say that the matches table is normalized?
Any help would be greatly appreciated.
EDIT: changed matches content.
With the help of #Jorge Campos, this is the solution:
CREATE VIEW games_won as
select p.id, p.name, coalesce(sum(m.gamepoints),0) gp, count(m.winner) ng
from players p left join matches m
on p.id=m.winner
group by p.id, p.name;
CREATE VIEW games_lost as
select p.id, p.name, count(m.loser) as ng
from players p left join matches m
on p.id=m.loser
group by p.id, p.name;
CREATE VIEW standings as
select w.id, w.name, w.ng as wins, w.ng+l.ng as matches, w.gp as gamepoints
from games_won w INNER JOIN games_lost l
on w.id=l.id
order by wins desc, gamepoints;
For the simple case you show there are only a few things that you should fix to be ok. Again for the problem you show.
First: Change the columns types of the table matches it shouldn't be SERIAL as it is an autoincrement type column (not a real type). Both columns are foreign keys and it should be integer, int or bigint
as
create table matches (
winner bigint,
loser bigint,
gamepoints int,
constraint fk_player_winner foreign key (winner)
references players(id),
constraint fk_player_loser foreign key (loser)
references players(id)
);
Second: to know how many games a player did with the number of points you can create two subqueries one with the winners and one with the losers and join the two summing the values. The catch is that you have to decrease the gamepoints from the two:
select w.id, w.name, w.gp-l.gp as gamepoints, w.ng+l.ng
from (select p.id, p.name, sum(m.gamepoints) gp, count(m.winner) as ng
from players p inner join matches m
on p.id=m.winner
group by p.id, p.name ) w
INNER JOIN
(select p.id, p.name, sum(m.gamepoints) gp, count(m.loser) as ng
from players p inner join matches m
on p.id=m.loser
group by p.id, p.name) l on w.id=l.id;
From it you create your view.
Note: maybe I'm being overkill with this two subqueries. It is possible to work out with a join between two players tables and a matches
See how it goes here on fiddle: http://sqlfiddle.com/#!15/5b6a4/4

sort sql query by values in another table

I am querying a table named artists, but I would like to sort the response based on a table named paintings (an artist has_many paintings - the painting table has an artist_id column).
To be more specific, I want to sort the artists by their most recent painting (paintings have a column named date_created). Does anyone know how this could be done?
Ideally this should be done using ANSI joins:
SELECT DISTINCT a.artist
FROM artists a
INNER JOIN paintings p
ON a.artistID = p.artistID
ORDER BY p.date_created desc
Perhaps something like this, depending on the specifics of your schema?
SELECT DISTINCT artists.* FROM
artists, paintings
WHERE artists.id = paintings.artist_id
ORDER BY paintings.painting_date DESC;
This will join the two tables on the artist id, and then order by their painting dates. DISTINCT ensures you only get one row per artist.
This will only return each artist once, with the latest date_created value for that artist.
SELECT artists.name, paintings.date_created
FROM artists JOIN (
SELECT artist_id, MAX(date_created) as date_created FROM paintings GROUP BY artist_id
) paintings ON artists.id = paintings.artist_id
ORDER BY paintings.date_created DESC
If I understand your requirement correctly:
1) Write an aggregation query that returns each artist and his/her latest painting;
2) Use it as a sub-query, joining it to the artists table;
3) SELECT columns from the join, ordering by the date of latest painting.
You can create a query:
select artistid, max(paintingdate)
from paintings
group by artistid
and then join to that as an inline-view:
select artistname from artist
inner join
(
select artistid, max(paintingdate) as latestdate
from paintings
group by artistid
) as Foo
on artist.artistid = Foo.artistid
order by latestdate desc