Mysql8 join and count unique real appearances - mysql

I have the following talbes:
CREATE TABLE topics (
id INT,
text VARCHAR(100),
parent VARCHAR(1)
);
CREATE TABLE sentiment (
id INT,
grade INT,
parent VARCHAR(1)
);
And the following data:
INSERT INTO topics (id, text, parent) VALUES (1, 'Cryptocurrency', 'A');
INSERT INTO topics (id, text, parent) VALUES (2, 'Cryptocurrency', 'B');
INSERT INTO topics (id, text, parent) VALUES (2, 'ETH', 'B');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 0 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 0 , 'B');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'B');
I want to select count of each topics.text and shared parent sum of sentiment.grade.
So I came up with the following query:
SELECT
count(topics.text),
topics.text,
sum(sentiment.grade)
FROM topics
inner join sentiment on (sentiment.parent = topics.parent)
group by text
The result:
| count(topics.text) | sum(sentiment.grade) | text |
| ------------------ | -------------------- | -------------- |
| 6 | 4 | Cryptocurrency |
| 2 | 1 | ETH |
---
I only have a problem with the first column, the real count of Cryptocurrency is 2 and the real count of ETH is 1.
Can you fix this query?
(I'm using mysql8, would be glad to have 5.7 compliant if possible)
View on DB Fiddle

SELECT
count(distinct t.id),
t.text,
sum(s.grade)
FROM topics t
JOIN sentiment s on s.parent = t.parent
GROUP BY t.text

As you have two rows with text=cryptocurrency in topics, one with parent=A and the other with parent=B, when you join you should expect to see 6 rows for crpytocurrency(the first row of topics matches the first four of sentiment, and the second row of topics matches the last two of sentiment). You can see that if you change your original query to this one:
SELECT
*
FROM topics
inner join sentiment on (sentiment.parent = topics.parent)
I guess you want to see the number of topics with the same text and the total grades their parents have (for cryptocurrency, the sum of A and B). This could help you:
SELECT
topics_count.n_text,
topics.text,
SUM(sentiment.grade)
FROM topics
INNER JOIN (SELECT text, count(*) 'n_text' FROM topics GROUP BY text) topics_count ON topics.text = topics_count.text
INNER JOIN sentiment ON (sentiment.parent = topics.parent)
GROUP BY text

Related

select parent row where ALL children rows meet criterias

I have two table, rates and criterias. parent_id in criterias refers to id in rates.
I need to select the rates where ALL children rows in table criterias WHERE criteria_1 AND criteria_2 equal to NULL.
In the example below, only flat rate should be selected
rates
id | name
--------------------
1 | summer rate
2 | flat rate
3 | student rate
conditions
id | parent_id | criteria_1 | criteria_2
------------------------------------------------------
1 | 1 | 523 | 563
2 | 1 | null | null
3 | 2 | null | null
4 | 2 | null | null
5 | 3 | 777 | null
I tried NOT EXIST, but it return it return any rate where one children have two null criteria
try using this subquery with inner join.
select * from
(select * from rates where name = 'flat rate') t1
inner join
(select * from criterias where coalesce(criteria_1, 0) = 0 and coalesce(criteria_2, 0) = 0) t2
on t2.parent_id = t1.id
Please see the following query it should work. You need to compare 2 result set to find rate with ALL null childrens.
SELECT
a.parent_id
FROM(
SELECT
parent_id,
COUNT(*) AS total_count
FROM criterias c
WHERE c. criteria_1 IS NULL AND c.criteria_2 IS NULL
GROUP BY 1
) a
INNER JOIN (
SELECT
parent_id,
COUNT(*) AS total_count
FROM criterias c
GROUP BY 1
)b ON a.parent_id = b.parent_id AND a.total_count = b.total_count
I would use some aggregate function with an having clause grouped by parent_id.
Using a min or max would return a numerical value if there is at least one non-null value per parent_id but will be null if there are only null. So just need to use an having min(<field>) is null to find a parent_id with only null value.
select *
from rates r
where id in(
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
);
or With an inner join (if you prefer)
select *
from rates r
inner join (
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
) c ON c.parent_id = r.id;
Validated with :
create table rates(
id int,
name varchar(20)
);
create table criterias (
id int,
parent_id int,
criteria_1 int null,
criteria_2 int null
);
insert into rates values (1, 'summer rate');
insert into rates values (2, 'flate rate');
insert into rates values (3, 'student rate');
insert into rates values (4, 'old rate');
insert into rates values (5, 'any rate');
insert into criterias values (1, 1, 523, 563);
insert into criterias values (2, 1, null, null);
insert into criterias values (3, 2, null, null);
insert into criterias values (4, 2, null, null);
insert into criterias values (5, 1, 777, null);
insert into criterias values (6, 4, null, null);
insert into criterias values (7, 5, null, null);
insert into criterias values (8, 5, null, null);
/*insert into criterias values (9, 5, 1, null);*/
select *
from rates r
where id in(
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
);
Result:
id name
2 flate rate
4 old rate
5 any rate

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()

SQL limit for LEFT JOINed table

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

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.

MySQL multiple table query with average for each row

This is my setup:
Table "files": id (PK), filename, user_id, date, filesize
Table "scores": id(PK), file_id, user_id, score
Table "files" contains a list of files with details; table "scores" keeps track of 1-5 points scored per file. I need to get entries from the "files" table and in each row I need all the info for the file, as well as the average score. I can do another query for teh current file_id while I'm looping through the rows, but obviousely that's not very optimized. I tried something like below, but no success.
SELECT files.*, (SUM(scores.score)/(COUNT(scores.score))) AS total FROM files INNER JOIN scores ON files.id=scores.file_id;
Please point me in the right direction - thanks!
You may want to try the following:
SELECT f.id, f.filename, f.user_id, f.date, f.filesize,
(
SELECT AVG(s.score)
FROM scores s
WHERE s.file_id = f.id
) average_score
FROM files f;
Note that you can use the AVG() aggregate function. There is no need to divide the SUM() by the COUNT().
Test case:
CREATE TABLE files (id int, filename varchar(10));
CREATE TABLE scores (id int, file_id int, score int);
INSERT INTO files VALUES (1, 'f1.txt');
INSERT INTO files VALUES (2, 'f2.txt');
INSERT INTO files VALUES (3, 'f3.txt');
INSERT INTO files VALUES (4, 'f4.txt');
INSERT INTO scores VALUES (1, 1, 10);
INSERT INTO scores VALUES (2, 1, 15);
INSERT INTO scores VALUES (3, 1, 20);
INSERT INTO scores VALUES (4, 2, 5);
INSERT INTO scores VALUES (5, 2, 10);
INSERT INTO scores VALUES (6, 3, 20);
INSERT INTO scores VALUES (7, 3, 15);
INSERT INTO scores VALUES (8, 3, 15);
INSERT INTO scores VALUES (9, 4, 12);
Result:
SELECT f.id, f.filename,
(
SELECT AVG(s.score)
FROM scores s
WHERE s.file_id = f.id
) average_score
FROM files f;
+------+----------+---------------+
| id | filename | average_score |
+------+----------+---------------+
| 1 | f1.txt | 15.0000 |
| 2 | f2.txt | 7.5000 |
| 3 | f3.txt | 16.6667 |
| 4 | f4.txt | 12.0000 |
+------+----------+---------------+
4 rows in set (0.06 sec)
Note that #Ignacio's solution produces the same result, and is therefore another option.
Aggregate functions are not usually useful without aggregation.
SELECT f.*, AVG(s.score) AS total
FROM files AS f
INNER JOIN scores AS s
ON f.id=s.file_id
GROUP BY f.id