sqlite query for the best selling album in each genre - sql

I am using the basic chinook database, but i can't figure out how to write a SQLite query to create a view called BestSeller for the bestselling album in each genre with sales (based on the quantity of tracks sold, named as Sales) with the columns: Genre, Album, Artist, Sales.
The schema for the album table:
[Title] NVARCHAR(160) NOT NULL,
[ArtistId] INTEGER NOT NULL,
FOREIGN KEY ([ArtistId]) REFERENCES "artists" ([ArtistId])
artists table :
[ArtistId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[Name] NVARCHAR(120)
tracks table schema:
[TrackId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[Name] NVARCHAR(200) NOT NULL,
[AlbumId] INTEGER,
[MediaTypeId] INTEGER NOT NULL,
[GenreId] INTEGER,
[Composer] NVARCHAR(220),
[Milliseconds] INTEGER NOT NULL,
[Bytes] INTEGER,
[UnitPrice] NUMERIC(10,2) NOT NULL,
FOREIGN KEY ([AlbumId]) REFERENCES "albums" ([AlbumId])
ON DELETE NO ACTION ON UPDATE NO ACTION,
FOREIGN KEY ([GenreId]) REFERENCES "genres" ([GenreId])
ON DELETE NO ACTION ON UPDATE NO ACTION,
FOREIGN KEY ([MediaTypeId]) REFERENCES "media_types" ([MediaTypeId])
ON DELETE NO ACTION ON UPDATE NO ACTION
table invoice_items:
[InvoiceLineId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[InvoiceId] INTEGER NOT NULL,
[TrackId] INTEGER NOT NULL,
[UnitPrice] NUMERIC(10,2) NOT NULL,
[Quantity] INTEGER NOT NULL,
FOREIGN KEY ([InvoiceId]) REFERENCES "invoices" ([InvoiceId])
ON DELETE NO ACTION ON UPDATE NO ACTION,
FOREIGN KEY ([TrackId]) REFERENCES "tracks" ([TrackId])
ON DELETE NO ACTION ON UPDATE NO ACTION
I have all these other tables as well.
albums employees invoices playlists
artists genres media_types tracks
customers invoice_items playlist_track
Any help is appreciated.

Join the relevant tables and group by genre and album to get the quantity of tracks sold of each album.
Then use FIRST_VALUE() and MAX() window functions to get the album with the most sales in each genre and the the number of sales:
CREATE VIEW BestSeller AS
SELECT DISTINCT
g.Name Genre,
FIRST_VALUE(a.Title) OVER (PARTITION BY g.GenreId ORDER BY COUNT(*) DESC) Album,
FIRST_VALUE(r.Name) OVER (PARTITION BY g.GenreId ORDER BY COUNT(*) DESC) Artist,
MAX(COUNT(*)) OVER (PARTITION BY g.GenreId) Sales
FROM genres g
INNER JOIN tracks t ON t.GenreId = g.GenreId
INNER JOIN albums a ON a.AlbumId = t.AlbumId
INNER JOIN artists r ON r.ArtistId = a.ArtistId
INNER JOIN invoice_items i ON i.TrackId = t.TrackId
GROUP BY g.GenreId, a.AlbumId;
See the demo

Related

Shorter way to write this SQL query with a 3-way join and a condition

I'm currently learning SQL in the Harvard CS50 online course. The assignment is to write various SQL queries for a database. Here is a link to the assignment. I'm talking about the 12th query there.
The schema of the database looks like this:
CREATE TABLE movies (
id INTEGER,
title TEXT NOT NULL,
year NUMERIC,
PRIMARY KEY(id)
);
CREATE TABLE stars (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE directors (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE ratings (
movie_id INTEGER NOT NULL,
rating REAL NOT NULL,
votes INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id)
);
CREATE TABLE people (
id INTEGER,
name TEXT NOT NULL,
birth NUMERIC,
PRIMARY KEY(id)
);
The goal is to write a SQL query that returns the titles of all movies in which both Johnny Depp and Helena Bonham Carter starred. The query I came up with returns the list of movies for each actor and then uses INTERSECT on both of these lists. This is the query:
SELECT
movies.title
FROM
movies
JOIN stars ON movies.id = stars .movie_id
JOIN people ON stars .person_id = people.id
WHERE
people.name = "Johnny Depp"
INTERSECT
SELECT
movies.title
FROM
movies
JOIN stars ON movies.id = stars .movie_id
JOIN people ON stars .person_id = people.id
WHERE
people.name = "Helena Bonham Carter";
The query returns the correct results, however I feel it isn't very elegant or fast. Is there a shorter, more elegant and/or faster way to write this?
You could do something like this:
with cte as (
select
movie_id,count(p.id) as cnt
from stars s
join people p on p.id = s.person_id
where p.name in (
"Johnny Depp","Helena Bonham Carter"
)
group by movie_id
)
select title
from movies
where id in (
select movie_id from cte where cnt = 2
)
Basically the cte gets the movie ids where the two people are present in the stars table. Then we count that to see where both people are present in the stars table and join it to the movies table to get the actual name. You could probably use 'having' instead also.

SQL join query to library

I have these tables,
CREATE TABLE Book
(
ISBN CHAR(05)NOT NULL,
BKName VARCHAR(20)NOT NULL,
Author VARCHAR(20)NOT NULL,
Price NUMERIC(02)NOT NULL,
CONSTRAINT Book_PK PRIMARY KEY (ISBN)
);
CREATE TABLE Location
(
LoID CHAR(05)NOT NULL,
CityName VARCHAR(15)NOT NULL,
Stoke CHAR(05)NOT NULL,
CONSTRAINT Location_PK PRIMARY KEY (LoID)
);
CREATE TABLE Customer
(
CuID CHAR(05)NOT NULL,
CusName VARCHAR(20)NOT NULL,
RegiDate DATE NOT NULL,
Gender VARCHAR(06)NOT NULL,
TeleNum CHAR(12)NOT NULL,
Address VARCHAR(30)NOT NULL,
CONSTRAINT Customer_PK PRIMARY KEY (CuID)
);
CREATE TABLE BookCopy
(
CopyID CHAR(05)NOT NULL,
ISBN CHAR(05)NOT NULL,
LoID CHAR(05)NOT NULL,
CONSTRAINT pk_BookCopy PRIMARY KEY (CopyID),
CONSTRAINT fk_BookCopy_ISBN_FK FOREIGN KEY (ISBN) REFERENCES Book(ISBN),
CONSTRAINT fk_BookCopy_LoID_FK FOREIGN KEY (LoID) REFERENCES Location(LoID)
);
CREATE TABLE BorrowRecord
(
BrrDate DATE NOT NULL,
RetDate DATE NOT NULL,
BrFee NUMBER(05) NOT NULL,
Cus_Review NUMERIC(02)NOT NULL,
CopyID CHAR(05)NOT NULL,
CuID CHAR(05) NOT NULL,
CONSTRAINT pk_BorrowRecord PRIMARY KEY (CopyID, CuID),
CONSTRAINT fk_BorrowRecord_CopyID_FK FOREIGN KEY (CopyID) REFERENCES BookCopy(CopyID) ,
CONSTRAINT fk_BorrowRecord_CuID_FK FOREIGN KEY (CuID) REFERENCES Customer(CuID) );
This is the task: "write and test a query to list the customer ID and name of every Customer along with the books that they have hired within the past 200 days. Include starting date, ending date, and location name for those hirings. All customer details (ID and name) should be included in the output, whether or not they have actually borrowed any books."
I write a query to list the customer ID and name of every Customer along with the books that they have borrowed within the past 200 days. Include starting date, ending date, and location name for those hirings. This the query for it.
SELECT
BorrowRecord.CuID, Customer.CusName, Book.ISBN, Book.BkName,
BorrowRecord.BrrDate, BorrowRecord.RetDate, Location.CityName
FROM
Book
LEFT JOIN
BookCopy ON Book.ISBN = BookCopy.ISBN
LEFT JOIN
BorrowRecord ON BookCopy.CopyID = BorrowRecord.CopyID
LEFT JOIN
Customer ON BorrowRecord.CuID = Customer.CuID
LEFT JOIN
Location ON Location.LoID = BookCopy.LoID
WHERE
BorrowRecord.BrrDate >= sysdate - 200 ;
But I also need to get all customer details (ID and name) that should be included in the output, whether or not they have actually borrowed any books. How can I do it?
If You want to see all customers independently whether they have borrowed a book or not, it's important that the first LEFT JOIN is based on customers table. In Your script, You start with Book, then join to BorrowRecord and only then have The Customer on the right side of the join. Another point is the WHERE -Condition that excludes all entries of Customer that borrowed a book outside of the 200-days-range. This meand that all records of BorrowRecord are shown (that match to the previous join) but only the customers that are found in a link to BorrowRecords from the past 200 days.
Try the following:
SELECT BorrowRecord.CuID, Customer.CusName, Book.ISBN, Book.BkName,
BorrowRecord.BrrDate,BorrowRecord.RetDate, Location.CityName
FROM Customer
LEFT JOIN BorrowRecord ON BorrowRecord.CuID = Customer.CuID
LEFT JOIN BookCopy ON BorrowRecord.CopyID = BookCopy.CopyID
LEFT JOIN Book ON BookCopy.ISBN= Book.ISBN
LEFT JOIN Location ON Location.LoID = BookCopy.LoID
WHERE ISNULL(BorrowRecord.BrrDate,'') >=sysdate-200 ;

is it possible in SQL to call two difrent table with difrent data at the same time

so basicly what i need is to get two tables at the same time and then use a condition that reside in the first table and i want to apply it to the second one
if it not possible do i need to call SELECT Twice ?
is this code right ?
to determine the average rating of all movies released in 2012
SELECT AVG(rating),year FROM ratings , movies
WHERE year = 2012;
//these are the tables that i have
CREATE TABLE movies (
id INTEGER,
title TEXT NOT NULL,
year NUMERIC,
PRIMARY KEY(id)
);
CREATE TABLE stars (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE directors (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE ratings (
movie_id INTEGER NOT NULL,
rating REAL NOT NULL,
votes INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id)
);
CREATE TABLE people (
id INTEGER,
name TEXT NOT NULL,
birth NUMERIC,
PRIMARY KEY(id)
);
I think you want a JOIN and an aggregate function:
SELECT AVG(r.rating) avg_rating_2012
FROM ratings r
INNER JOIN movies m on m.id = r.movie_id
WHERE m.year = 2012;
If you want this for all years at once, then use GROUP BY:
SELECT m.year, AVG(r.rating) avg_rating
FROM ratings r
INNER JOIN movies m on m.id = r.movie_id
GROUP BY m.year;

Is there a way to implement count in left join

I'm setting up a chat-Group application using a simple database. I want to know if there is a way for me to see the people that liked/disliked a certain post, which can have a picture or a video attached to it. The important information I want to know is the post ID, message of the post, date of the post, url of the picture I want to post, url of the video, and the id of the user posting said post. I'm using left joins to see all this information, but I'm missing if the posts have likes or dislikes.
I already tried using the aggregate count to see each like and dislike of the posts, but I don't know how to mix this query with the previous information.
This is my query to see the information listed above:
select P.post_ID, P.post_msg, P.post_date, F.photo_url,V.video_url, P.user_ID
from ((Post as P left join Photo as F on P.post_ID = F.post_ID ) left join Video as V
on P.post_ID = V.post_ID)
where P.chat_ID = 1
Here is the output of the Query:
Here is an example of the output I want:
The query above displays everything I'm looking for except each like and dislike of each post. Is there anyway to see this information mixed with what I want?
Here is the structure of the DataBase:
create table Login(login_ID serial primary key, login_Date DATE NOT NULL DEFAULT CURRENT_DATE, user_name varchar(20) NOT NULL);
create table Users(user_ID serial primary key, user_name varchar(20) NOT NULL, user_password varchar(20) NOT NULL);
create table Contact_List(contactlist_ID serial primary key, user_name varchar(20) NOT NULL,user_ID integer references Users(user_ID) on delete cascade);
create table Person(person_ID serial primary key, person_name varchar(20) NOT NULL, person_lastname varchar(30) NOT NULL, person_phone varchar(12), person_email varchar(50) NOT NULL, user_ID INTEGER references Users(user_ID) on delete cascade);
create table Admin(admin_ID serial primary key, user_ID INTEGER references Users(user_ID) on delete cascade);
create table Chat_Group(chat_ID serial primary key, chat_name varchar(50) NOT NULL, admin_Id INTEGER references Admin(admin_ID) on delete cascade);
create table Group_List(user_ID INTEGER references Users(user_ID) on delete cascade, chat_ID integer references Chatgroup(chat_id) on delete cascade, primary key(user_id, chat_id));
create table Post(post_ID serial primary key, post_msg varchar(280), post_date DATE NOT NULL DEFAULT CURRENT_DATE, user_ID INTEGER references Users(user_ID) on delete cascade, chat_ID INTEGER references Chatgroup(chat_ID) on delete cascade);
create table Video(video_ID serial primary key, video_url varchar(280) NOT NULL, post_ID INTEGER references Post(post_ID) on delete cascade);
create table Photo(photo_ID serial primary key, photo_url varchar(280) NOT NULL, post_ID INTEGER references Post(post_ID) on delete cascade);
create table Reply(reply_ID serial primary key, reply_msg varchar(280) NOT NULL, reply_Date DATE NOT NULL DEFAULT CURRENT_DATE, post_ID INTEGER references Post(post_ID) on delete cascade, user_ID INTEGER references Users(user_ID) on delete cascade);
create table HashTag(hashtag_ID serial primary key, hashtag_msg varchar(200) NOT NULL,post_ID INTEGER references Post(post_ID) on delete cascade NOT NULL, user_ID INTEGER references Users(user_ID) on delete cascade);
create table Reaction(reaction_ID serial primary key,reaction_date DATE NOT NULL DEFAULT CURRENT_DATE, reaction_like BOOLEAN, reaction_dislike BOOLEAN, post_ID INTEGER references Post(post_ID) on delete cascade, user_ID INTEGER references users(user_ID) on delete cascade);
create table React_to(reaction_ID INTEGER references Reaction(reaction_ID) on delete cascade, reply_ID INTEGER references Reply(reply_ID) on delete cascade, primary key(reaction_ID,reply_ID));
Excuse me for my bad english, english is not my main language.
One way of doing this would use correlated subquery.
SELECT p.post_id,
p.post_msg,
p.post_date,
f.photo_url,
v.video_url,
p.user_id,
(SELECT count(*)
FROM reaction r
WHERE r.post_id = p.post_id
AND r.reaction_like) likes,
(SELECT count(*)
FROM reaction r
WHERE r.post_id = p.post_id
AND r.reaction_dislike) dislikes
FROM post p
LEFT JOIN photo f
ON p.post_id = f.post_id
LEFT JOIN video v
ON p.post_id = v.post_id
WHERE p.chat_id = 1;
However I wonder if a reaction can be both a like and a dislike at once. At least your data model allowes such a thing...
I don't like the idea of going to the reactionss table twice. One way to avoid this is to use a lateral join:
select P.post_ID, P.post_msg, P.post_date, F.photo_url, V.video_url, P.user_ID,
r.num_likes, r.num_dislikes
from Post P left join
Photo F
on P.post_ID = F.post_ID left join
Video V
on P.post_ID = V.post_ID left join lateral
(select sum(r.reaction_like) as num_likes,
sum(r.reaction_dislike) as num_dislikes
from reactions r
where r.post_id = p.post_id
) r
on 1=1
where P.chat_ID = 1

Multiple Columns in one column

I have a Database which manages my music library, where I store all the id3 Tags in DB tables. The data model looks as followed: http://abload.de/img/modelq5sx3.png
CREATE TABLE Tracks (
Track_ID INTEGER NOT NULL AUTO_INCREMENT,
Title VARCHAR(255),
Track_Year YEAR,
Filename VARCHAR(255),
Track_Length CHAR(5),
Folder_ID INTEGER,
Album_ID INTEGER,
CONSTRAINT PK_Tracks PRIMARY KEY (Track_ID),
UNIQUE Unique_Path (Folder_ID, Filename)
);
CREATE TABLE Artists (
Artist_ID INTEGER NOT NULL AUTO_INCREMENT,
Artist VARCHAR(50),
CONSTRAINT PK_Artists PRIMARY KEY (Artist_ID),
UNIQUE (Artist)
);
CREATE TABLE Albums (
Album_ID INTEGER NOT NULL AUTO_INCREMENT,
Album VARCHAR(100),
AlbumArtist INTEGER,
AlbumCover VARCHAR(100),
CONSTRAINT PK_Albums PRIMARY KEY (Album_ID),
UNIQUE (Album)
);
CREATE TABLE Tracks_Artists (
Artist_ID INTEGER NOT NULL,
Track_ID INTEGER NOT NULL,
CONSTRAINT PK_Tracks_Artists PRIMARY KEY (Artist_ID, Track_ID)
);
So I have tracks,albums and artists in seperate entities with a n:m relation between tracks and artists as a track can be performed by more than one artist and a artist can perform in more than one track. I have done that to search for tracks by artist.
But to make a nice track view I want to have all these artists in one line per track, which I did not manage so far. All I can do is to join over Album, which has one album artist per album:
create or replace view v_c_tracks as SELECT t.traCK_ID,t.title,ar.arTIST,a.album
FROM TRACKS t
join albums a on t.album_id=a.album_id
join artists ar on a.albumartist=ar.artist_id
order by t.title;
You need to join to the track_artists table. Then to get the results on a single row, you need to aggregate by track. The key to bringing the artists together in a list is group_concat():
SELECT t.traCK_ID, t.title, group_concat(ar.artist) as artists, a.album
FROM tracks t join
albums a
on t.album_id = a.album_id join
track_artists ta
on ta.track_id = track.track_id join
artists ar
on ta.artist_id = ar.artist_id
group by t.traCK_ID, t.title, a.album
order by t.title;