MySQL multiple table query with average for each row - mysql

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

Related

Mysql8 join and count unique real appearances

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

How to join two mysql table and retrieve latest results from joined table?

I have two tables. I need to join these two tables and retrieve latest status from execution table. How can I retrieve?
My schema and data:
CREATE TABLE test
(`id` serial primary key, `ref_id` int, `ref_name` varchar(7))
;
INSERT INTO test
(`id`, `ref_id`, `ref_name`)
VALUES
(1, 1, 'trial'),
(2, 3, 'test'),
(3, 7, 'testing')
;
CREATE TABLE execution
(`id` serial primary key, `ref_id` int, `status` varchar(11))
;
INSERT INTO execution
(`id`, `ref_id`, `status`)
VALUES
(1, 1, 'Completed'),
(2, 2, 'Completed'),
(3, 1, 'Completed'),
(4, 3, 'In progress'),
(5, 3, 'To do'),
(6, 2, 'In progress'),
(7, 1, 'Completed'),
(7, 1, 'To do')
;
Expected result is here below.
ref_id | ref_name | status |
3 | testing | In progress |
2 | test | To do |
1 | trial | To do |
I have tried with below query:
SELECT
ref_id,
ref_name,
status
FROM
test
JOIN execution ON test.ref_id = execution.ref_id
GROUP BY `ref_id`
ORDER BY `ref_id` DESC;
This query retrieves the status, but the retrieved status is not a latest one. How can retrieve the latest status by joining these two tables.
you can use below query
select T2.ref_id,T2.ref_name,OE.status from
(
select t1.ref_id,t1.ref_name,e.id from test t1 inner join
(select max(id) as id,ref_id from execution group by ref_id) as e
on
t1.ref_id=e.ref_id
) as T2
inner join execution OE on T2.id=OE.id
https://www.db-fiddle.com/f/rvnm8APX27dmW9a84JkCsS/1
It seems you have given in-correct data as an example as ref_id 7 not found in
execution table. However this might help you
SELECT b.ref_id,
b.ref_name,
a.status
FROM execution a
JOIN (SELECT MAX(id) id ,ref_id
FROM execution
GROUP BY ref_id) a1
USING(id,ref_id)
JOIN test b ON a.ref_id = b.ref_id ORDER BY ref_id DESC;

Group Concat TWO columns but in GROUPS -- THREE separators involved

We have a mysql table
id name groupid
1 user1 0
2 user2 0
3 user3 1
4 user4 1
We want the GROUP CONCAT such that we get the output as
1,user1;2,user2---3,user3;4,user4
This does what you describe:
create table NoOneEverNamesTheTableInSqlQuestions (
id int,
name text,
groupid int
);
insert into NoOneEverNamesTheTableInSqlQuestions values
(1, 'user1', 0),
(2, 'user2', 0),
(3, 'user3', 1),
(4, 'user4', 1);
select group_concat(g separator '---') as output
from (
select group_concat(concat_ws(',',id,name) separator ';') as g
from NoOneEverNamesTheTableInSqlQuestions
group by groupid
) as g;
Output, tested with MySQL 8.0.0-dmr:
+-----------------------------------+
| output |
+-----------------------------------+
| 1,user1;2,user2---3,user3;4,user4 |
+-----------------------------------+
But I don't know why you would want to do this. It seems like something that would be easier to do in application code.

(mysql) Select 50 highest rated items, with at most one item coming from each user

I'm not sure how to go about doing this efficiently in MySQL and would appreciate any help.
The goal is to select 50 of the top-selling items, with at most one item from each user. I'm used to doing this with either CTE's or DISTINCT ON, but of course that's not an option in MySQL. I'm hoping for a single-query solution, and I'd like to avoid using stored procedures.
The basic schema is a table of items posted by users, and a table of sales with a field determining the score of that particular sale.
CREATE TABLE items (
item_id INT PRIMARY KEY,
user_id INT NOT NULL
)
CREATE TABLE sales (
item_id INT NOT NULL,
score INT NOT NULL
)
-- Create some sample data
INSERT INTO items VALUES (1, 1), (2, 1), (3, 1), (4, 2), (5, 2), (6, 3), (7, 3);
INSERT INTO sales VALUES (1, 1), (1, 1), (2, 1), (3, 2), (3, 1), (4, 3), (4, 2), (5, 2), (6, 1), (6, 1), (6, 1), (7, 2);
The result of the query against this sample data should be
+---------+---------+-------------+
| user_id | item_id | total_score |
+---------+---------+-------------+
| 2 | 4 | 5 |
| 1 | 3 | 3 |
| 3 | 6 | 3 |
+---------+---------+-------------+
Here's the PostgreSQL solution:
SELECT DISTIN ON (items.user_id)
items.user_id,
items.item_id,
SUM(sales.score) AS total_score
FROM items
JOIN sales ON (sales.item_id = items.item_id)
GROUP BY items.item_id
ORDER BY total_score DESC
LIMIT 50
Here's the MySQL solution I've come up with, but it's quite ugly. I tried doing essentially the same thing using a temporary table, but in the process learned that MySQL doesn't allow joining to a temporary table multiple times in the same query.
SELECT items_scores.user_id, items_scores.item_id, items_scores.total_score
FROM (
SELECT items.user_id, items.item_id, SUM(sales.score) as total_score
FROM items
JOIN sales ON
sales.item_id = items.item_id
GROUP BY items.item_id
) AS items_scores
WHERE items_scores.total_score =
(
SELECT MAX(t.total_score)
FROM (
SELECT items.user_id, items.item_id, SUM(sales.score) as total_score
FROM items
JOIN sales ON
sales.item_id = items.item_id
GROUP BY items.item_id
) AS t
WHERE t.user_id = items_scores.user_id
)
ORDER BY items_scores.total_score DESC
MySQL query for it:
select user, item, total_score
from (
select sum(sales.score) as total_score, items.user_id as user, items.item_id as item
from sales
inner join items on sales.item_id = items.item_id
group by item,user
order by total_score desc) as t
group by user limit 50;
Output:
+------+------+-------------+
| user | item | total_score |
+------+------+-------------+
| 1 | 3 | 3 |
| 2 | 4 | 5 |
| 3 | 6 | 3 |
+------+------+-------------+
3 rows in set (0.00 sec)
Some explanation
MySQL documentation says:
However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values within each group the server chooses.
In our subquery... the nonagregated columns are user_id and item_id , we expect them to be same for every group that we are doing the sum on. Also we are not doing any order by that can influence the agregation..we want all the values of the group to be summed up. Finally we are sorting the output and saving it as a derived table.
Finally we run a select query on this derived table where we do the Group By user .. and Limit the output to 50

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.