What is the most efficient way of selecting data from relational database? - sql

I just started working with databases and
I have this data sample from PostgreSQL tutorial
https://www.postgresqltutorial.com/postgresql-sample-database/
Which diagram looks like this:
I want to find all film categories rented in for example Canada. Is there a way of doing it without using SELECT within SELECT.. statement like this:
SELECT * FROM category WHERE category_id IN (
SELECT category_id FROM film_category WHERE film_id IN (
SELECT film_id FROM film WHERE film_id IN (
SELECT film_id FROM inventory WHERE inventory_id IN (
SELECT inventory_id FROM rental WHERE staff_id IN (
SELECT staff_id FROM staff WHERE store_id IN (
SELECT store_id FROM store WHERE address_id IN (
SELECT address_id FROM address WHERE city_id IN (
SELECT city_id FROM city WHERE country_id IN (
SELECT country_id FROM country WHERE country IN ('Canada')
)
)
)
)
)
)
)
)
)
I'm sure there must be something that i'm missing.

The proper way is to use joins instead of all these nested subqueries:
select distinct c.category_id, c.name
from category c
inner join film_category fc on fc.category_id = c.category_id
inner join inventory i on i.film_id = fc.film_id
inner join rental r on r.inventory_id = i.inventory_id
inner join staff s on s.staff_id = r.staff_id
inner join store sr on sr.store_id = s.store_id
inner join address a on a.address_id = sr.address_id
inner join city ct on ct.city_id = a.city_id
inner join country cr on cr.country_id = ct.country_id
where cr.country = 'Canada'
For your requirement you must join 9 tables (1 less than your code because the table film is not really needed as the column film_id can link the tables film_category and inventory directly).
Notice the aliases for each table which shortens the code and makes it more readable and the ON clauses which are used to link each pair of tables.
Also the keyword DISTINCT is used so you don't get duplicates in the results because all these joins will return many rows for each category.

Related

Left outer joins aggregate first

I have the following tables
CREATE TABLE categories(
id SERIAL,
);
CREATE TABLE category_translations(
id SERIAL,
name varchar not null,
locale varchar not null,
category_id integer not null
);
CREATE TABLE products(
id SERIAL,
category_id integer not null
);
CREATE TABLE line_items(
id SERIAL,
total_cents integer
product_id integer not null
);
What I'm trying to do is output a map of each category name to the sum of total of its associated line_items total_cents. Something like:
name
sum_total_cents
Fresh foods
100000
Dry products
532000
There is a uniqueness constraint that only one name for each locale will be stored. So a category will have one row for each locale stored in the category_translations table
What I currently have is
SELECT SUM(line_items.total_cents) AS sum_total_cents, ???
FROM line_items INNER JOIN products ON products.id = line_items.product_id
INNER JOIN categories ON categories.id = products.category_id
LEFT OUTER JOIN category_translations ON category_translations.category_id = categories.id
WHERE category_translations.locale ='en'
GROUP BY categories.id
I'm looking for an aggregate function to return the first name for the category. The only piece missing is that what to be written instead of the ??? as I've been facing a lot of must appear in the GROUP BY clause or be used in an aggregate function errors. In pseudo-code I'm looking for a FIRST() aggregate method in PostgreSQL that I can use
Assuming you want one random name from any locale, you can do:
select
c.id,
(select name from category_translations t
where t.category_id = c.id limit 1) as name,
sum(i.total_cents) as sum_total_cents
from categories c
left join products p on p.category_id = c.id
left join line_items i on i.product_id = p.id
group by c.id, name
Alternatively, if you want the category name for the locale 'en' then you can do:
select
c.id,
(select t.name from category_translations t
where t.category_id = c.id and t.locale ='en') as name,
sum(i.total_cents) as sum_total_cents
from categories c
left join products p on p.category_id = c.id
left join line_items i on i.product_id = p.id
group by c.id, name

Avoid a SQL subquery double join on the right element in PostgreSQL?

I am trying to optimize a sql query and want to see how i can avoid subqueries when doing a second join on the resulting table. I have the following query from the dvd rental database provided by postgresql and have joined three tables with the purpose of getting the category of the film. I know that I can use a CTE or temp table but I was wondering if there was a shorter route to accomplish what is below:
--------get the category of a film
--------link film table to category id table with film id
--------then link resulting table to the category name table with category_id
SELECT
t1.title,
t1.film_id,
t1.category_id,
c.name
FROM
(
SELECT
f.title,
f.film_id,
fc.category_id
FROM
film as f
left join film_category as fc on f.film_id = fc.film_id
) as T1 left join category as c on t1.category_id = c.category_id
ORDER by title
I don't see why you have any subqueries at all:
SELECT f.title, f.film_id, fc.category_id, c.name
FROM film f LEFT JOIN
film_category fc
ON f.film_id = fc.film_id LEFT JOIN
category c
ON fc.category_id = c.category_id
ORDER by f.title

A query by using count with multiple columns in SQL

I am pretty new to SQL.
I have a movie database. With the following tables with the following with their columns listed:
Category Table
columns - category_id, name, last_update
Film_Category Table
columns - film id, category id, last_update
Inventory Table
columns - inventory_id, film_id, store_id, last_update
Rental Table
columns - rental_id, rental_date, inventory_id, customer_id, return_date, staff_id, last_update
Film Table
columns - film_id, title
Question/ Issue
I wish to create a query that lists each movie, the film category it is classified in, and how often it is rented. I wish to use the data from the five tables as much as possible.
I want the table to output the film title column, the category name column and the count of how many times it is rented out. The output should be something like this:
title name rental_count
Alter Victory Animation 10
Goofy Movie Animation 20
Help would really be appreciated for this task!
use join and aggregate function count
select F.title,C.name,count(rental_id) as rental_count from Rental R
left join Inventory I on R.inventory_id=I.inventory_id
inner join Film_Category Fc on I.film_id=Fc.film_id
inner join Flim F on F.film_id=Fc.film_id
inner join Category C on Fc.category_id=C.category_id
group by F.title,C.name
WITH film_rents AS
(
SELECT I.film_id, COUNT(1) AS rental_count
FROM Inventory AS I
INNER JOIN Rental AS R ON R.inventory_id = I.inventory_id
GROUP BY I.film_id)
SELECT F.title, ISNULL(rental_count, 0 ) AS rental_count, C.name
FROM Film AS F
LEFT JOIN film_rents AS FR ON F.film_id = FR.film_id
INNER JOIN Film_Category AS FC ON FC.film_id = F.film_id
INNER JOIN Category AS C ON C.category_id = FC.category_id
this does what you asked, however I think what you really wants is more than this. I am saying this because you have a junction table Film_Category which means for one film there is one or more categories. in that case the query you asked for ( and above query) does not do the job for you. Asuming you are using SQL‌ Server 2017 you can use this:
WITH film_rents AS
(
SELECT I.film_id, COUNT(1) AS rental_count
FROM Inventory AS I
INNER JOIN Rental AS R ON R.inventory_id = I.inventory_id
GROUP BY I.film_id),
film_categories AS
(
SELECT FC.film_id, STRING_AGG(C.name, ',') AS categories
FROM Film_Category AS FC
INNER JOIN Category AS C ON C.category_id = FC.category_id
GROUP BY FC.film_id
)
SELECT F.title, ISNULL(rental_count, 0 ) AS rental_count, FC.categories AS [name]
FROM Film AS F
LEFT JOIN film_rents AS FR ON F.film_id = FR.film_id
INNER JOIN film_categories AS FC ON FC.film_id = F.film_id

creating possible combinations list from Many to Many relation

I have a Set of tables
Hotels
Countries
Regions
Cities
Hotel_Types
and a many to many relations table named Mappings which contains all the relations/mappings which contains info like
id, hotel_id, reference_type, reference_id, ...
where reference_type can be a Country, Region, City, Hotel_Type etc
and reference_id is the id of said entity like country_id or city_id etc.
I need to create a list of all possible combinations of
Country_Name+Hotel_Type_Name
Region_Name+Hotel_Type_Name
City_Name+Hotel_Type_Name
Where the hotels exist. Any help how may I access the names from different tables and how to combine them
I am implying few things here but you could do inner joins in this way:
select name, hotel_type_name
from (select c.country_name as name, h.hotel_type_name Mappings m inner join Countries c on m.reference_type='Country' and m.reference_id=c.country_id inner join hotel_Types h on m.reference_type='Hotel_type' and m.reference_id=h.hotel_type_id) union all
(select c.region_name as name, h.hotel_type_name Mappings m inner join Regions r on m.reference_type='Region' and m.reference_id=r.region_id inner join Hotel_Types h on m.reference_type='Hotel_type' and m.reference_id=h.hotel_type_id) union all
(select c.city_name as name, h.hotel_type_name Mappings m inner join Cities ci on m.reference_type='City' and m.reference_id=ci.city_id inner join Hotel_Types h on m.reference_type='Hotel_type' and m.reference_id=h.hotel_type_id)
This will list unique combinations of Country_Name+Hotel_Type_Name
--link hotels to hotel_type
with Hotel_Hotel_Types as (
select h.hotel_id
,ht.reference_id as hotel_types_id
from Hotels as h
inner join Mappings ht on ht.reference_type = 'Hotel_Type' and h.hotel_id = ht.hotel_id
)
--link hotels to Country_Name
,Hotel_Country_Name as (
select h.hotel_id
,c.reference_id as countries_id
from Hotels as h
inner join Mappings c on c.reference_type = 'Country' and h.hotel_id = c.hotel_id
)
select distinct ht.*, c.*
from Hotel_Hotel_Types hht
inner join Hotel_Types ht on ht.hotel_types_id = hht.hotel_types_id
inner join Hotel_Country_Name hc on hc.hotel_id = hht.hotel_id
inner join Countries c on с.countries_id = hc.countries_id
Region_Name+Hotel_Type_Name and City_Name+Hotel_Type_Name can be queried using similar sqls.

Retrive counts of two columns from two diffrent tables with third table using join query in SQL

I have 3 tables: COUNTRY, STATE, CITY
This is my Country table with two columns:
CountryID, Name
This is my State table:
This is my City table:
I want to retrieve the count of states and cities according to the country table using join query.
Skipping the fact that your question is not asked well - try this query, it should work for you:
WITH
tab_a AS (
SELECT c.countryid, COUNT (s.stateid) AS state_num
FROM country c
LEFT JOIN state s ON c.countryid = s.countryid
GROUP BY c.countryid
),
tab_b AS (
SELECT c.countryid, COUNT (cc.cityid) city_num
FROM country c
LEFT JOIN state s ON c.countryid = s.countryid
LEFT JOIN city cc ON s.stateid = cc.stateid
GROUP BY c.countryid
)
SELECT a.countryid,
a.state_num,
b.city_num
FROM tab_a a JOIN tab_b b ON a.countryid=b.countryid