combine two sqlite queries that work separately - sql

I am working on the basic chinook database, and am trying to write a sqlite query to create a view called v10BestSellingArtists for the 10 bestselling artists based on the total quantity of tracks sold (named as TotalTrackSales) order by TotalTrackSales in descending order. TotalAlbum is the number of albums with tracks sold for each artist.
I can write queries for both of them separately, but I can't figure out how to merge these two queries:
query for finding Totaltracksales:
Select
r.name as artist,
count (i.quantity) as TotalTrackSales
from albums a
left join tracks t on t.albumid == a.albumid
left join invoice_items i on i.trackid == t.trackid
left join artists r on a.artistid == r.artistid
group by r.artistid
order by 2 desc
limit 10;
and the second query for totalAlbum :
Select
r.name as artist,
count(a.artistId) from albums a
left join artists r where a.artistid == r.artistid
group by a.artistid
order by 2 desc
limit 10;
but I want one query with the columns: Artist, TotalAlbum TotalTrackSales.
Any help is appreciated.
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

Just to merge your 2 queries, you can do the following using CTE:
with total_track_sales as (
Select
r.name as artist,
count (i.quantity) as TotalTrackSales
from albums a
left join tracks t on t.albumid == a.albumid
left join invoice_items i on i.trackid == t.trackid
left join artists r on a.artistid == r.artistid
group by r.artistid
order by 2 desc
limit 10 ),
with total_album as (
Select
r.name as artist,
count(a.artistId) as TotalAlbums from albums a
left join artists r where a.artistid == r.artistid
group by a.artistid
order by 2 desc
limit 10 )
select artist, TotalTrackSales, TotalAlbums
from total_track_sales ts inner join total_album ta
on ts.artist = ta.artist

You can try a single query using DISTINCT and combining aggregates and window functions.
select *
from (
select
r.name as artist,
count (i.quantity) as TotalTrackSales,
row_number() over (order by count (i.quantity) desc) rnT,
count (distinct a.albumid) as totalAlbums,
row_number() over (order by count (distinct a.albumid) desc) rnA,
from albums a
left join tracks t on t.albumid == a.albumid
left join invoice_items i on i.trackid == t.trackid
left join artists r on a.artistid == r.artistid
group by r.artistid
)
where rnT <= 10 or rnA <= 10

Related

Join only when relation data exists

These are my tables.
CREATE TABLE IF NOT EXISTS categories (
category_id serial primary key,
category text not null,
user_id int not null
);
CREATE TABLE IF NOT EXISTS activities (
activity_id serial primary key,
activity text not null,
user_id int not null
);
CREATE TABLE IF NOT EXISTS categories_to_activities (
category_id int not null REFERENCES categories (category_id) ON UPDATE CASCADE ON DELETE CASCADE,
activity_id int not null REFERENCES activities (activity_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT category_activity_pkey PRIMARY KEY (category_id, activity_id)
);
This is the query I'm using to get all activities with their categories.
SELECT a.*, ARRAY_AGG ( ROW_TO_JSON (c) ) categories
FROM activities a
JOIN categories_to_activities ca ON a.activity_id = ca.activity_id
JOIN categories c ON ca.category_id = c.category_id
WHERE a.user_id = ${userId}
GROUP BY a.activity_id
The issue is that if an activity does not have a category assigned, it won't get returned.
I'm having trouble combining JOIN with CASE, which I suppose is what I need. Essentially I want to JOIN only when there is some record in categories_to_activities?
How do I do that?
You could use a left join, and return nulls for activities without categories:
SELECT a.*,
CASE WHEN ca.activity_id IS NOT NULL THEN ARRAY_AGG(ROW_TO_JSON(c))
ELSE ARRAY[]::JSON[]
END as categories
FROM activities a
LEFT JOIN categories_to_activities ca ON a.activity_id = ca.activity_id
LEFT JOIN categories c ON ca.category_id = c.category_id
WHERE a.user_id = ${userId}
GROUP BY a.activity_id
What I ended up with, thanks to #Murenik
SELECT a.*,
CASE WHEN ca.activity_id IS NOT NULL
THEN ARRAY_AGG ( ROW_TO_JSON (c) )
ELSE ARRAY[]::JSON[]
END as categories
FROM activities a
LEFT JOIN categories_to_activities ca ON a.activity_id = ca.activity_id
LEFT JOIN categories c ON ca.category_id = c.category_id
WHERE a.user_id = ${userId}
GROUP BY a.activity_id, ca.activity_id

How can I select only the max food_order_id?

How can I select only the max food_order_id?
SELECT
o.food_order_id,
fo.user_id,
f.name,
f.price,
o.quantity,
o.subtotal
FROM food_order fo
INNER JOIN order_item o ON fo.food_order_id = o.food_order_id
INNER JOIN food_item f ON o.food_id = f.food_id
WHERE fo.user_id = 12;
if I want to update or delete the information selected from table with how will I do it?
tables:
CREATE TABLE food_item (
food_id SERIAL PRIMARY KEY,
name TEXT,
image TEXT,
description TEXT,
price INT
);
CREATE TABLE food_order (
food_order_id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(user_id)
);
CREATE TABLE order_item (
order_item_id SERIAL PRIMARY KEY,
food_id INT REFERENCES food_item(food_id),
food_order_id INT REFERENCES food_order(food_order_id),
quantity INT,
subtotal INT
);
In Postgres 13, you can use FETCH FIRST WITH TIES:
SELECT o.food_order_id, fo.user_id, f.name, f.price, o.quantity, o.subtotal
FROM food_order fo JOIN
order_item o
ON fo.food_order_id = o.food_order_id JOIN
food_item f
ON o.food_id = f.food_id
WHERE fo.user_id = 12
ORDER BY o.food_order_id DESC
FETCH FIRST 1 ROW WITH TIES;
In older versions, I would recommend:
SELECT *
FROM (SELECT o.food_order_id, fo.user_id, f.name, f.price, o.quantity, o.subtotal,
RANK() OVER (ORDER BY o.food_order_id DESC) as seqnum
FROM food_order fo JOIN
order_item o
ON fo.food_order_id = o.food_order_id JOIN
food_item f
ON o.food_id = f.food_id
WHERE fo.user_id = 12
) x
WHERE seqnum = 1;

How can I create a view connecting two tables show which entities in one table are not connected to entities from the other table?

I need to create a view of persons and contracts that they have not yet subscribed to. So far I've come up with a nested select to collect the foreign keys in my Subscription table, but I'm stuck with how to use this information to get contracts a person doesn't have.
SELECT s.PersonId as pId, s.ContractID as cId
FROM dbo.Subscription AS s
FULL OUTER JOIN dbo.Person as p ON s.PersonId = p.Id
FULL OUTER JOIN dbo.Contract as c ON s.ContractID = c.Id
WHERE p.Id IN (SELECT PersonId FROM dbo.Subscription)
Pseudocode of what I want to do:
Get Persons that have Contracts
For each Person, get contract they don't have
Display Persons and each missing contract for Person
Schema (edited to remove business info):
CREATE TABLE [dbo].[Contract]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[ContractNumber] NUMERIC(16) NULL
)
CREATE TABLE [dbo].[Person]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
Name nvarchar(200) NOT NULL
)
CREATE TABLE [dbo].[Subscription]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[PersonID] UNIQUEIDENTIFIER NOT NULL,
[ContractID] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [FK_Subscription_Person] FOREIGN KEY ([PersonID]) REFERENCES [Person]([Id]),
CONSTRAINT [FK_Subscription_Contract] FOREIGN KEY ([ContractID]) REFERENCES [Contract]([Id])
)
Here's a cross join and not exists solution:
SELECT p.Id as pId, c.ID as cId
from dbo.Person as p
cross join dbo.Contract as c
WHERE p.Id IN (SELECT PersonId FROM dbo.Subscription as s1)
and not exists(select 1 from dbo.Subscription as s2 where s2.PersonId = p.Id and s2.ContractID = c.Id)
Get Persons that have Contracts
You already did this correctly with WHERE p.Id IN (SELECT PersonId FROM dbo.Subscription as s1)
For each Person, get contract they don't have
First we take all combinations with cross join, then we filter out the ones you don't want with not exists
For (3.) We just select what we want
use left join
SELECT p.*,s.*,c.*
FROM dbo.Person as p
left OUTER JOIN dbo.Subscription AS s ON s.PersonId = p.Id
left join Contract c on s.ContractID=c.Id

Oracle query for customers who buy popular products

I have three tables: customer, order and line items. They are set up as follows:
CREATE TABLE cust_account(
cust_id DECIMAL(10) NOT NULL,
first VARCHAR(30),
last VARCHAR(30),
address VARCHAR(50),
PRIMARY KEY (cust_id));
CREATE TABLE orders(
order_num DECIMAL(10) NOT NULL,
cust_id DECIMAL(10) NOT NULL,
order_date DATE,
PRIMARY KEY (order_num));
CREATE TABLE line_it(
order_id DECIMAL(10) NOT NULL,
line_id DECIMAL(10) NOT NULL,
item_num DECIMAL(10) NOT NULL,
PRIMARY KEY (order_id, line_id),
FOREIGN KEY (item_id) REFERENCES products);
I need to write a query that selects customers, their names and addresses who have purchased items that have been bought by 3 or more people. I have the following query:
SELECT cust_account.cust_id, cust_account.first, cust_account.last, cust_account.address
FROM cust_account
INNER JOIN orders ON cust_account.cust_id = orders.cust_id
INNER JOIN line_it ON orders.order_id = line_it.order_id
GROUP BY cust_account.cust_id, cust_account.last
HAVING COUNT(line_it.item_num) = (
SELECT COUNT (DISTINCT order_num > 3)
FROM line_it
);
Do I even need to make it a subquery? I am a bit lost. Appreciate any help, thanks.
Start with "items bought by 3 or more people". You can get these by doing:
select li.item_id
from line_item li join
order_info oi
on li.order_id = oi.order_id
group by li.item_id
having count(distinct oi.customer_id) >= 3;
Now you want customers in this set. Hmmmm:
select distinct ca.*
from customer_account ca join
orderinfo oi
on ca.customer_id = oi.customer_id join
line_item li
on li.order_id = oi.order_id
where li.item_id in (select li.item_id
from line_item li join
order_info oi
on li.order_id = oi.order_id
group by li.item_id
having count(distinct oi.customer_id) >= 3
);
You can also express this with window functions:
select distinct ca.*
from (select ca.*, count(distinct customer_id) over (partition by li.item_id) as num_customers_on_item
from customer_account ca join
orderinfo oi
on ca.customer_id = oi.customer_id join
line_item li
on li.order_id = oi.order_id
) ca
where num_customers_on_item >= 3;
You can use the following query
SELECT distinct customer_account.* FROM line_item, order_info ,customer_account where item_id in (
--Selecting only item purchased 3 or more
SELECT item_id FROM line_item group by item_id having count(1) >=3
)
and line_item.order_id = order_info.order_id
and customer_account.customer_id = order_info.customer_id
;

How to use an aggregate function in SQL WITH JOINS?

The following is a query that I built to find the cheapest price for a particular movie along with the distributor who sells it, how do I manipulate it so that all of the movies are listed with their respective highest movie price along with the distributor who sells it?
/*finds the cheapest price for the movie "American Beauty" and the distributor who sells it*/
SELECT movies.title, movie_distributors.distributor_name, MIN(distributed_movie_list.unit_price) AS lowest_price
FROM distributed_movie_list
INNER JOIN movies ON distributed_movie_list.movie_id = movies.movie_id
INNER JOIN movie_distributors ON movie_distributors.distributor_id = distributed_movie_list.distributor_id
WHERE movies.title = 'American Beauty'
AND distributed_movie_list.unit_price =
(SELECT MIN(unit_price)
FROM distributed_movie_list
INNER JOIN movies ON distributed_movie_list.movie_id = movies.movie_id
WHERE movies.title = 'American Beauty')
GROUP BY movies.title, distributed_movie_list.distributor_id, movie_distributors.distributor_name;
The following tables are used for this query:
create table movies (
movie_id number(5),
title varchar2(30) not null,
category_code char(3) not null,
description varchar2(500),
released_by number(3) not null,
released_on date not null
);
create table movie_distributors(
distributor_id number(3),
distributor_name varchar2(30) not null,
location varchar2(40),
contact varchar2(40)
);
create table distributed_movie_list(
distribution_id number(8),
movie_id number(5) not null,
distributor_id number(3) not null,
distribute_type varchar2(10),
inventory_quantity number(3) default 0,
unit_price number(8,2)
);
The end result I'm looking for is a query that list distributors and highest prices for each movie. Any help would be greatly appreciated ; )
The end result I'm looking for is a query that list distributors and
highest prices for each movie
Then why not just GROUP BY title, distributor_name with MAX like so:
SELECT
m.movie_id,
m.title,
md.distributor_name,
MAX(d.unit_price) AS Highest_price
FROM distributed_movie_list AS d
INNER JOIN movies AS m ON d.movie_id = m.movie_id
INNER JOIN movie_distributors AS md ON md.distributor_id = d.distributor_id
GROUP BY m.movie_id,m.title,
md.distributor_name
HAVING MAX(d.unit_price) = (SELECT MAX(d2.unit_price)
FROM distributed_movie_list d2
WHERE m.movie_id = d2.movie_id)
The following should work on most RDBMSs:
SELECT DISTINCT m.title, md.distributor_name, dml.unit_price AS highest_price
FROM distributed_movie_list dml
INNER JOIN movies m ON dml.movie_id = m.movie_id
INNER JOIN movie_distributors md ON md.distributor_id = dml.distributor_id
INNER JOIN (SELECT movie_id, MAX(unit_price)
FROM distributed_movie_list
GROUP BY movie_id) dmlh
ON dml.unit_price = dmlH.unit_price AND dml.movie_id = dmlh.movie_id
A more efficient solution should be possible for RDBMSs (such as Oracle or SQLServer) that support ranking functions.
If you are trying to get the highest price for each movie -- and associated information -- then use the row_number() function. The following query returns all the information about the highest price for each movie:
select dml.*
from (select dml.*,
ROW_NUMBER() over (partition by movie_id order by unit_price desc) as seqnum
from distributed_movie_list dml
) dml
where seqnum = 1
You can join in other information that you want about movies and distributors.