SQL limit for LEFT JOINed table - mysql

I have the following tables.
Industry(id, name)
Movie(id, name, industry_id) [Industry has many movies]
Trailer(id, name, movie_id) [Movie has many trailers]
I need to find 6 latest trailers for each Industry. Every movie does not need to have a trailer or can have multiple[0-n].
CREATE TABLE industry(id int, name char(10), PRIMARY KEY (id));
CREATE TABLE movie(id int, name char(10), industry_id int, PRIMARY KEY (id),
FOREIGN KEY (industry_id) REFERENCES industry(id));
CREATE TABLE trailer(id int, name char(10), movie_id int, PRIMARY KEY (id),
FOREIGN KEY (movie_id) REFERENCES movie(id));
INSERT INTO industry VALUES (1, "sandalwood");
INSERT INTO industry VALUES (2, "kollywood");
INSERT INTO movie VALUES (1, "lakshmi", 1);
INSERT INTO movie VALUES (2, "saarathi", 2);
INSERT INTO trailer VALUES (1, "lakshmi1", 1);
INSERT INTO trailer VALUES (2, "lakshmi2", 1);
INSERT INTO trailer VALUES (3, "lakshmi3", 1);
INSERT INTO trailer VALUES (4, "lakshmi4", 1);
INSERT INTO trailer VALUES (5, "lakshmi5", 1);
INSERT INTO trailer VALUES (6, "lakshmi6", 1);
INSERT INTO trailer VALUES (7, "saarathi4", 2);
INSERT INTO trailer VALUES (8, "saarathi5", 2);
INSERT INTO trailer VALUES (9, "saarathi6", 2);
SELECT c.*
FROM industry a
LEFT JOIN movie b
ON a.id = b.industry_id
LEFT JOIN trailer c
ON b.id = c.movie_id
LIMIT 0, 6
| ID | NAME | MOVIE_ID |
----------------------------
| 1 | lakshmi1 | 1 |
| 2 | lakshmi2 | 1 |
| 3 | lakshmi3 | 1 |
| 4 | lakshmi4 | 1 |
| 5 | lakshmi5 | 1 |
| 6 | lakshmi6 | 1 |
I need to fetch only one recent trailer from each movie. But I am getting all trailers for each movie. Please suggest me to get the SQL statement.

I'm not sure if this works in MySql or not because I can't remember if you can have subqueries inside of an in clause, but you might try:
select * from trailer
where id in (select max(id) from trailer group by movie_id)
Whether it works or not, it looks like you're not using the industry table in your query so there's not much point in joining to it (unless you are actually trying to exclude movies that don't have any industry assigned to them... but based on your sample I it doesn't look like that was your intention).
If the above query doesn't work in MySql, then try this one
select t.*
from trailer t join
(select max(id) id from trailer group by movie_id) t2 on t1.id = t2.id

To get recent trailor you should include date field column from which we can fetch it

If you must do this all in SQL (and not in whatever backend or code you are using, which I would actually recommend) then you are probably going to have to rely on some variable magic.
Essentially, you need to "rank" each trailer by the date and then "partition" it by the movie that the trailer belongs to. These words have actual meaning in some other flavors of SQL (such as PL/SQL) but unfortunately don't have native functionality in MySQL.
You're going to want do to something similar to what is mentioned in this SO post. Once you get the "ranks" in there partitioned by movie_id, you just select WHERE rank < 6. The query could get pretty messy and there is some risk in using variables in that way but from what I can tell this is the best way to do it strictly with a MySQL query

Try this query
SELECT * FROM industry
LEFT JOIN movie on movie.industry_id = industry.id
LEFT JOIN (
SELECT
id as T_ID,
name as T_Name,
movie_id
FROM trailer
INNER JOIN ( SELECT
MAX(id) as TID
FROM trailer
GROUP BY movie_id
) as t on t.TID = trailer.id
) as c on c.movie_id = movie.id;
Here is the Demo

SELECT i.name, m.name, MAX(t.id) AS 'Latest Trailer ID', MAX(t.name) AS 'Latest Trailer'
FROM industry i
INNER JOIN movie m ON(i.id = m.industry_id)
INNER JOIN trailer t ON(m.id = movie_id)
GROUP BY m.id

If you want latest trailer by id of trailer table then use below query:
SELECT * FROM trailer t
INNER JOIN (SELECT movie_id, MAX(id) id
FROM trailer GROUP BY movie_id) AS A ON t.id = A.id
OR If you want data latest by date then use this query:
SELECT * FROM trailer t
INNER JOIN (SELECT movie_id, MAX(latestbydate) latestbydate
FROM trailer GROUP BY movie_id
) AS A ON t.movie_id = A.movie_id AND t.latestbydate = A.latestbydate

Related

How do I remove results based on conditions to calculate an average and specific movie

I have the schema below. A quick explanation of it is:
bob rated the movie up, 5/5
james rated the movie up, 1/5
macy rated the movie up, 5/5
No one has rated the movie avengers.
The logic:
If I am personA, look up everyone I have blocked.
Look up all the movie reviews.
Anyone who has left a movie review, and personA has blocked, remove them from the calculation.
Calculate the average rating of the movies.
CREATE TABLE movies (
id integer AUTO_INCREMENT primary key,
name varchar(100) NOT NULL
);
CREATE TABLE customer (
id integer AUTO_INCREMENT primary key,
name varchar(100) NOT NULL
);
CREATE TABLE reviews (
id integer AUTO_INCREMENT primary key,
rating integer NOT NULL,
cus_id integer NOT NULL,
movie_id integer NOT NULL,
FOREIGN KEY (cus_id) REFERENCES customer(id),
FOREIGN KEY (movie_id) REFERENCES movies(id)
);
CREATE TABLE blocked(
id integer AUTO_INCREMENT primary key,
cus_id integer NOT NULL, -- This is the person blocking
blocked_cus_id integer NOT NULL, -- This is the person who is blocked
FOREIGN KEY (cus_id) REFERENCES customer(id),
FOREIGN KEY (blocked_cus_id) REFERENCES customer(id)
);
INSERT INTO movies (id, name) VALUES (1, 'up'), (2, 'avengers');
INSERT INTO customer (id, name) VALUES (1, 'bob'), (2, 'james'), (3, 'macy');
INSERT INTO reviews (id, rating, cus_id, movie_id) VALUES (1, 5, 1, 1), (2, 1, 2, 1), (3, 5, 3, 1);
INSERT INTO blocked (id, cus_id, blocked_cus_id) VALUES (1, 1, 2);
I received some help with this question here: How do I remove results based on conditions to calculate an average (and the statement was correct) but when I want to find the rating for a specific movie the statement it only shows the movie if it had a rating. I want it to show the movie regardless if it has a rating or not. If it doesn't have a rating it should just say 0. Below, the movie avengers has no ratings and no results are shown.
SELECT m.name, AVG(r.rating) AS avg_rating
FROM movies m
INNER JOIN reviews r ON m.id = r.movie_id
WHERE NOT EXISTS (SELECT 1 FROM blocked b
WHERE b.blocked_cus_id = r.cus_id AND b.cus_id = 1)
AND m.id = 2
GROUP BY m.name;
The above select statement should show:
+----------+------------+
| movie | avg_rating |
+----------+------------+
| avengers | 0 |
+----------+------------+
When I view the database as bob, I should get:
+-------+------------+
| movie | avg_rating |
+-------+------------+
| up | 5 |
+-------+------------+
When I view the database as macy, I should get:
+-------+------------+
| movie | avg_rating |
+-------+------------+
| up | 3.67 |
+-------+------------+
Do you want a left join? Starting from your current query, that would be:
SELECT m.name, AVG(COALESCE(r.rating, 0)) AS avg_rating
FROM movies m
LEFT JOIN reviews r
ON m.id = r.movie_id
AND NOT EXISTS (
SELECT 1
FROM blocked b
WHERE b.blocked_cus_id = r.cus_id AND b.cus_id = 1
)
WHERE m.id = 2
GROUP BY m.id, m.name;

MySQL - Join if no duplicate

I want to Join to table. the condition is I want to only join those rows which have only one row to match. eg.
books:
id | name | price
1 | book1 | 19
2 | book2 | 19
3 | book3 | 30
price_offer:
id | offer | price
1 | offer1 | 19
2 | offer2 | 30
so now if I do select query on these table:
SELECT * FROM price_offer
JOIN books ON price_offer.price = books.price
I only want to join book with id 3 as it have only one match with price_offer table.
You could use a self join for books table to pick a book with only single match
select po.*, b1.*
from price_offer po
join books b1 on po.price = b1.price
join (
select price,max(id) id
from books
group by price
having count(*) = 1
) b2 on b1.id = b2.id
Demo
Try following query:
Sample data:
create table books(id int, name varchar(10), price int);
insert into books values
(1, 'book1', 19),
(2, 'book2', 19),
(3, 'book3', 30);
create table price_offer(id int, offer varchar(10), price int);
insert into price_offer values
(1, 'offer1', 19),
(2, 'offer2', 30);
Query:
select max(b.id)
from price_offer p
left join books b on b.price = p.price
where p.id is not null
group by b.price
having count(*) = 1;
If you want to avoid nesting queries where you have to use self-joins, you can use window-functions of MySQL 8.0.11, which are exactly for cases like this

Joining table to union of two tables?

I have two tables: orders and oldorders. Both are structured the same way. I want to union these two tables and then join them to another table: users. Previously I only had orders and users, I am trying to shoehorn oldorders into my current code.
SELECT u.username, COUNT(user) AS cnt
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user
This finds the number of nonzero total orders all users have made in table orders, but I want to this in the union of orders and oldorders. How can I accomplish this?
create table orders (
user int,
shipped int,
total decimal(4,2)
);
insert into orders values
(5, 1, 28.21),
(5, 1, 24.12),
(5, 1, 19.99),
(5, 1, 59.22);
create table users (
username varchar(100),
userident int
);
insert into users values
("Bob", 5);
Output for this is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 4 |
+----------+-----+
After creating the oldorders table:
create table oldorders (
user int,
shipped int,
total decimal(4,2)
);
insert into oldorders values
(5, 1, 62.94),
(5, 1, 53.21);
The expected output when run on the union of the two tables is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 6 |
+----------+-----+
Just not sure where or how to shoehorn a union into there. Instead of running the query on orders, it needs to be on orders union oldorders. It can be assumed there is no intersect between the two tables.
You just need to union this way:
SELECT u.username, COUNT(user) AS cnt
FROM
(
SELECT * FROM orders
UNION
SELECT * FROM oldorders
) o
LEFT JOIN users u ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user;
First get the combined orders using UNION between orders and oldorders table.
The rest of the work is exactly same what you did.
SEE DEMO
Note:
Left join doesn't make sense in this case. Orders for which the users don't exist then you will get NULL 0 as output. This doesn't hold any value.
If you want <user,total orders> for all users including users who might not have ordered yet then you need to change the order of the LEFT JOIN

how to do subquery with 3 tables and using where clause in multi values?

I want to do subquery with 3 tables and using where in multi values but I always get syntax error. I have to do reporting in Report Builder 3.0
Table A: record_id, Surname, Given Name
Table C: row_id, competency_code, competency_name
Table PC: link_id, record_id, row_id, attainment_date
I would like to join the tables into 1 table. One person will have some completion of competency_code and different with other person. the completion of competency_code based on the attainment_date. I also think to use iff function for attainment_date in competency_code value as complete/yes.
The table that I would like to create is:
Record_Id | Surname | GivenName | Code 1 | Code 2 | Code 3 | Code 4 | Code 5
01 | AA | AA | Complete | Complete | Complete | | Complete
02 | BB | BB | Complete | Complete | | Complete |
03 | CC | CC | | Complete | Complete | | Complete
here is the query that I tried to do.
select distinct a.id, a.surname, a.given_name
from all a
join
(
select pc.attainment_date
from personnel_competency pc
join
(
select c.code, c.name
from competency c)
competency c on (c.row_no = pc.linkid)
)
personnel_competency pc on (pc.id = a.id)
where c.code in ('ABC', 'BCD', 'ABE', 'DEA', 'DEF', 'POS', 'SAQ', 'LOP')
and pc.attainment_date < now()
order by a.record_id
My skill in SQL is very basic. Whether other ways to make the table like that?
Are you looking for a SQL to get your result. If so I think this is what you are looking for ..
It would help if you posted some sample data.
You can test it at
SQLFiddle
Here is the script ..
-- Generate schema and data
create table tableA (id int, surname varchar(30), given_name varchar(30));
create table tablePC (link_id int, id int, attainment_date datetime);
create table tableC (row_id int, competency_code varchar(20), Competency_name varchar(30));
insert into tableA (id, surname, given_name)
values (1, 'AA', 'AAgn')
, (2, 'BB', 'BBgn')
insert into tablePC (link_id, id, attainment_date)
values (1, 1, '2014-09-11')
, (2, 1, '2014-09-10')
, (3, 2, '2014-09-11')
insert into tableC (row_id, competency_code, Competency_name)
values (1, 'ABC', 'completed\Yes')
, (1, 'BCD', 'completed')
, (1, 'ABE', 'completed')
, (2, 'ABC', 'completed')
, (2, 'BCD', 'completed')
, (3, 'ABC', 'completed')
, (3, 'ABE', 'completed')
-- ===============
select *
from tableA TA
inner join tablePC PC
on TA.id = PC.id
inner join
(
select row_id, [ABC] as ABC, [BCD] as BCD, [ABE] as ABE
from tableC TC
pivot
(
max(Competency_name)
for Competency_code in ([ABC], [BCD], [ABE])
) as TCPVT
) TC
on PC.link_id = TC.row_id
where PC.attainment_date < GETDATE()

MySQL self join question

Take a look at the following mySQL query:
SELECT fname,lname FROM users WHERE users.id IN (SELECT sub FROM friends WHERE friends.dom = 1 )
The above query first creates a set of ALL the friends.sub's via the inner query, and then the outer query selects a list of users where user ids are contained within the set created by the inner query (ie the union of the two sets).
And this works fine. But if you needed the inner set to contain not only the subs where dom = 1, but also the doms where sub = 1, like so:
Outer query remains same as above, pure pseudocode:
(SELECT sub FROM friends WHERE friends.dom = 1 )
***AND***
(SELECT dom FROM friends WHERE friends.sub = 1 )
Is it possible to make this sort of functionality with the inner query??
Any help or assistance appreciated guys;-D
Thanks a lot guys, my headache is gone now!
Try this:
SELECT u.fname, u.lname
FROM users u
INNER JOIN friends f
ON (u.id = f.sub AND f.dom = 1)
OR (u.id = f.dom AND f.sub = 1)
I'm not sure if I correctly understand what sub and dom represent, but it looks like you can use a UNION in there:
SELECT fname, lname
FROM users
WHERE users.id IN
(
SELECT sub FROM friends WHERE friends.dom = 1
UNION
SELECT dom FROM friends WHERE friends.sub = 1
);
Test case:
CREATE TABLE users (id int, fname varchar(10), lname varchar(10));
CREATE TABLE friends (dom int, sub int);
INSERT INTO users VALUES (1, 'Bob', 'Smith');
INSERT INTO users VALUES (2, 'Peter', 'Brown');
INSERT INTO users VALUES (3, 'Jack', 'Green');
INSERT INTO users VALUES (4, 'Kevin', 'Jackson');
INSERT INTO users VALUES (5, 'Steven', 'Black');
INSERT INTO friends VALUES (1, 2);
INSERT INTO friends VALUES (1, 3);
INSERT INTO friends VALUES (4, 1);
INSERT INTO friends VALUES (3, 4);
INSERT INTO friends VALUES (5, 2);
Result:
+-------+---------+
| fname | lname |
+-------+---------+
| Peter | Brown |
| Jack | Green |
| Kevin | Jackson |
+-------+---------+
3 rows in set (0.00 sec)
That said, #Alec's solution is probably more efficient.