SQL windows function in PostgreSQL - sql

I am new in SQL and I try to query a database using PostgreSQL (9.6).
When I write the following code I have and error syntax with the windows function:
/* Ranking total of rental movie by film category (I use the sakila database) */
SELECT category_name, rental_count
FROM
(SELECT c.name category_name, Count(r.rental_id) rental_count
FROM category c
JOIN film_category USING (category_id)
JOIN inventory USING (film_id)
JOIN rental r USING (inventory_id)
JOIN film f USING (film_id)
GROUP BY 1, 2
ORDER by 2, 1
) sub
RANK() OVER (PARTITION BY category_name ORDER BY rental_count DESC) AS total_rank

You don't need a subquery:
SELECT c.name as category_name, COUNT(*) as rental_count,
ROW_NUMBER() OVER (PARTITION BY c.name ORDER BY COUNT(*) DESC)
FROM category c JOIN
film_category USING (category_id) JOIN
inventory USING (film_id) JOIN
rental r USING (inventory_id) JOIN
film f USING (film_id)
GROUP BY 1
ORDER by 2, 1;
You also don't need the join to film, because you are using nothing from that table.
Your query fails because the column list goes in the SELECT clause. The FROM list follows the SELECT.

Related

Write a query to find the film which grossed the highest revenue for the video renting organisation

select title from film where film_id in ("highest revenue")
select film_id from inventory where inventory_id in (
select inventory_id from rental group by inventory_id
order by count(inventory_id) desc ))
limit (highest revenue);
where im wrong?
SELECT title
FROM film
WHERE film_id in (SELECT film_id
FROM inventory
WHERE inventory_id in (
SELECT inventory_id
FROM rental
GROUP BY inventory_id
ORDER BY count(inventory_id) DESC
)) limit 1;
even this code with joins will work
select Title
from film
inner join inventory
using (film_id)
inner join rental
using (inventory_id)
inner join payment
using (rental_id)
group by title
order by sum(amount) desc
limit 1;
SELECT title
FROM film
WHERE film_id IN ("highest revenue")
SELECT film_id
FROM inventory
WHERE inventory_id IN (
SELECT inventory_id
FROM rental
GROUP BY inventory_id
ORDER BY count(inventory_id) DESC
) limit(highest revenue);
Remove the ) after DESC
Write a query to find the film which grossed the highest revenue for the video renting organization.
select TITLE from FILM f
inner join INVENTORY i using (FILM_ID)
inner join RENTAL r using (INVENTORY_ID)
inner join PAYMENT p using (RENTAL_ID)
group by TITLE
order by sum(AMOUNT) desc
limit 1;

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

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.

WITH CTE query using postgresql

I am learning how to use SQL CTE and I would like to compare two query to have the same answer (using postgresql) but I fail can someone help plese?
I create this query and I have the total of each film title (Sakila database):
SELECT COUNT(r.rental_id) rental_count,
f.title as "Film"
FROM film f
JOIN inventory i
ON f.film_id = i.film_id
JOIN rental r USING (inventory_id)
GROUP BY f.title
ORDER BY rental_count DESC;
I would like to do the same using the WITH (CTE) and for that I create this code :
WITH table1 AS (
SELECT f.film_id,
f.title as "Film"
FROM film f),
table2 AS (
SELECT r.inventory_id,
COUNT(r.rental_id) rental_count,
i.film_id,
i.inventory_id
FROM inventory i
JOIN rental r USING (inventory_id)
GROUP BY r.inventory_id, i.film_id, i.inventory_id)
SELECT *
FROM table1
JOIN table2
ON table1.film_id = table2.film_id;
The problem is that the result did not show the total of each film title, but instead every film title separately.
The second CTE would need to be grouped by film to produce an equivalent end result.
WITH table1 AS (
SELECT
f.film_id
, f.title AS "Film"
FROM film f
)
, table2 AS (
SELECT
COUNT(r.rental_id) rental_count
, i.film_id
FROM inventory i
JOIN rental r ON i.inventory_id = r.inventory
GROUP BY i.film_id
)
SELECT
table2.rental_count
, table1.Film
FROM table1
JOIN table2 ON table1.film_id = table2.film_id
ORDER BY rental_count DESC;
Just a note; I would not recommend using both natural and non-natural join types in a single query, it can get quite confusing.
SELECT
COUNT(r.rental_id) rental_count
, f.title AS "Film"
FROM film f
JOIN inventory i ON f.film_id = i.film_id
JOIN rental r ON i.inventory_id = r.inventory_id -- change here
GROUP BY f.title
ORDER BY rental_count DESC;
To get the same result, you'd have to aggregate and group in the second query just like in the first:
WITH table1 AS (...),
table2 AS (...)
SELECT count(table2.rental_count) AS rental_count,
table1."Film"
FROM table1
JOIN table2 USING (film_id)
GROUP BY table1."Film"
ORDER BY rental_count DESC;
Basically you use the CTEs instead of the original tables.

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

Need hints on seemingly simple SQL query

I'm trying to do something like:
SELECT c.id, c.name, COUNT(orders.id)
FROM customers c
JOIN orders o ON o.customerId = c.id
However, SQL will not allow the COUNT function. The error given at execution is that c.Id is not valid in the select list because it isn't in the group by clause or isn't aggregated.
I think I know the problem, COUNT just counts all the rows in the orders table. How can I make a count for each customer?
EDIT
Full query, but it's in dutch... This is what I tried:
select k.ID,
Naam,
Voornaam,
Adres,
Postcode,
Gemeente,
Land,
Emailadres,
Telefoonnummer,
count(*) over (partition by k.id) as 'Aantal bestellingen',
Kredietbedrag,
Gebruikersnaam,
k.LeverAdres,
k.LeverPostnummer,
k.LeverGemeente,
k.LeverLand
from klanten k
join bestellingen on bestellingen.klantId = k.id
No errors but no results either..
When using an aggregate function like that, you need to group by any columns that aren't aggregates:
SELECT c.id, c.name, COUNT(orders.id)
FROM customers c
JOIN orders o ON o.customerId = c.id
GROUP BY c.id, c.name
If you really want to be able to select all of the columns in Customers without specifying the names (please read this blog post in full for reasons to avoid this, and easy workarounds), then you can do this lazy shorthand instead:
;WITH o AS
(
SELECT CustomerID, CustomerCount = COUNT(*)
FROM dbo.Orders GROUP BY CustomerID
)
SELECT c.*, o.OrderCount
FROM dbo.Customers AS c
INNER JOIN dbo.Orders AS o
ON c.id = o.CustomerID;
EDIT for your real query
SELECT
k.ID,
k.Naam,
k.Voornaam,
k.Adres,
k.Postcode,
k.Gemeente,
k.Land,
k.Emailadres,
k.Telefoonnummer,
[Aantal bestellingen] = o.klantCount,
k.Kredietbedrag,
k.Gebruikersnaam,
k.LeverAdres,
k.LeverPostnummer,
k.LeverGemeente,
k.LeverLand
FROM klanten AS k
INNER JOIN
(
SELECT klantId, klanCount = COUNT(*)
FROM dbo.bestellingen
GROUP BY klantId
) AS o
ON k.id = o.klantId;
I think this solution is much cleaner than grouping by all of the columns. Grouping on the orders table first and then joining once to each customer row is likely to be much more efficient than joining first and then grouping.
The following will count the orders per customer without the need to group the overall query by customer.id. But this also means that for customers with more than one order, that count will repeated for each order.
SELECT c.id, c.name, COUNT(orders.id) over (partition by c.id)
FROM customers c
JOIN orders ON o.customerId = c.id