How to SELECT name, max(value from a table joined on name)? - mysql

Summary: I have a forum with a forums table and a posts table. Every post has a unique ID (an auto-incrementing integer), and every post references a forums.id.
I'm trying to issue a SELECT query that retrieves all the forum names, all the forum IDs, and then the highest posts.id associated with that forum.
It's possible that there are no posts in a forum and in that case I want the max-posts-id to be 0.
Forums table:
| ID | Name |
|----|------|
| 1 | Dogs |
| 2 | Food |
| 3 | Work |
Posts table:
| ID | Forum_ID | Author | Text |
|----|----------|--------|------|
| 42 | 1 | Mr. S | foo |
| 43 | 3 | Mr. Y | bar |
| 44 | 1 | Ms. X | baz |
| 45 | 2 | Ms. A | foo |
| 46 | 1 | Mr. M | foo |
| 47 | 3 | Ms. A | bar |
| 48 | 2 | Mr. L | baz |
Desired result:
| Forum_ID | Name | Max_Posts_ID |
|----------|------|--------------|
| 1 | Dogs | 46 |
| 2 | Food | 48 |
| 3 | Work | 47 |
My attempt
SELECT
forums.id AS id,
forums.name AS name,
COALESCE(MAX(SELECT id FROM posts WHERE forums.id = ?), 0)
JOIN
posts ON forums.id = posts.forum_id;
But I don't think I can pass a parameter to my nested SELECT query, I don't think that's the right approach. What should I do instead?

You could use LEFT JOIN and aggregation:
SELECT f.id AS id,
f.name AS name,
COALESCE(MAX(p.id),0) AS Max_Posts_ID
FROM Forums f
LEFT JOIN Posts p
ON f.Id = p.forum_id
GROUP BY f.id, f.name
ORDER BY f.id;

Solution to your problem:
MySQL
SELECT fs.id AS Forum_ID ,
fs.name AS Name,
IFNULL(MAX(ps.ID),0) AS Max_Posts_ID
FROM forums fs
LEFT JOIN posts ps
ON fs.id = ps.forum_id
GROUP BY fs.id,fs.name;
Link To the MySQL Demo:
http://sqlfiddle.com/#!9/a18ab2/1
MSSQL
SELECT fs.id AS Forum_ID ,
fs.name AS Name,
ISNULL(MAX(ps.ID),0) AS Max_Posts_ID
FROM forums fs
LEFT JOIN posts ps
ON fs.id = ps.forum_id
GROUP BY fs.id,fs.name;
Link To the MSSQL Demo:
http://sqlfiddle.com/#!18/a18ab/2
OUTPUT:
Forum_ID Name Max_Posts_ID
1 Dogs 46
2 Food 48
3 Work 47

Let me correct your current attempt with correlation subquery
SELECT
id AS id,
name AS name,
(SELECT COALESCE(MAX(ID), 0) FROM Posts where forum_id = f.Id) AS Max_Posts_ID
FROM Forums f
Corrections :
Your outer query wasn't have from clause
Subquery wasn't referenced the outer query column id with Posts forum_id

Link to Code : http://tpcg.io/pI2HO5
BEGIN TRANSACTION;
/* Create a table called NAMES */
CREATE TABLE Forums (Id integer PRIMARY KEY, Name text);
/* Create few records in this table */
INSERT INTO Forums VALUES(1,'Dogs');
INSERT INTO Forums VALUES(2,'Food');
INSERT INTO Forums VALUES(3,'Work');
/* Create a table called NAMES */
CREATE TABLE Posts (Id integer PRIMARY KEY, forId integer);
/* Create few records in this table */
INSERT INTO Posts VALUES(42,1);
INSERT INTO Posts VALUES(43,3);
INSERT INTO Posts VALUES(64,1);
INSERT INTO Posts VALUES(45,2);
INSERT INTO Posts VALUES(46,1);
INSERT INTO Posts VALUES(47,3);
INSERT INTO Posts VALUES(48,2);
INSERT INTO Posts VALUES(51,2);
COMMIT;
/* Display all the records from the table */
SELECT Distinct forId as Forum_Id, Posts.id, (Select Name from Forums where forId == Forums.id) FROM Posts,Forums GROUP BY Posts.forId;
Output :
1|64|Dogs
2|51|Food
3|47|Work

Related

mysql: How to exclude rows from table which exist in table_alias with good perfomanse?

I've sql with NOT EXIST and it works very slowly in big db:
SELECT *
FROM
(
SELECT * FROM profiles ORDER BY id DESC
/* I need this order HERE! More info: https://stackoverflow.com/q/43516402/2051938 */
) AS users
WHERE
NOT EXISTS (
SELECT *
FROM request_for_friendship
WHERE
(
request_for_friendship.from_id = 1
AND
request_for_friendship.to_id = users.id
)
OR
(
request_for_friendship.from_id = users.id
AND
request_for_friendship.to_id = 1
)
)
LIMIT 0 , 1;
And I think I need to get request_for_friendship with some WHERE and after that check NOT EXIST, like this:
SELECT users.*
FROM
(
SELECT * FROM profiles ORDER BY id DESC
) AS users,
(
SELECT *
FROM request_for_friendship
WHERE
request_for_friendship.from_id = 1
OR
request_for_friendship.to_id = 1
) AS exclude_table
WHERE
NOT EXISTS
(
SELECT *
FROM exclude_table /* #1146 - Table 'join_test.exclude_table' doesn't exist */
WHERE
request_for_friendship.from_id = users.id
OR
request_for_friendship.to_id = users.id
)
LIMIT 0 , 1;
But it doesn't work: #1146 - Table 'join_test.exclude_table' doesn't exist
My tables:
1) profiles
+----+---------+
| id | name |
+----+---------+
| 1 | WILLIAM |
| 2 | JOHN |
| 3 | ROBERT |
| 4 | MICHAEL |
| 5 | JAMES |
| 6 | DAVID |
| 7 | RICHARD |
| 8 | CHARLES |
| 9 | JOSEPH |
| 10 | THOMAS |
+----+---------+
2) request_for_friendship
+----+---------+-------+
| id | from_id | to_id |
+----+---------+-------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 8 |
| 5 | 4 | 1 |
| 6 | 9 | 1 |
+----+---------+-------+
How to do some like this or better for perfomance?
p.s. I need to get only 1 row from table
Demo: http://rextester.com/DTA64368
I've already tried LEFT JOIN, but I've problem with order with him. mysql: how to save ORDER BY after LEFT JOIN without reorder?
First, do not use subqueries unnecessarily. Second, split the NOT EXISTS into two conditions:
SELECT p.*
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.from_id = 1 AND
rff.to_id = p.id
) AND
NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.to_id = 1 AND
rff.from_id = p.id
)
ORDER BY id DESC;
This can now make use of two indexes: request_for_friendship(to_id, from_id) and request_for_friendship(from_id, to_id). Each index is needed for one of the NOT EXISTS conditions.
I still think there's ways to optimize this as 'in' is generally slower.
SELECT *
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship
WHERE (request_for_friendship.from_id,
request_for_friendship.to_id)
in ((1,p.id),
(p.id,1))
)
Get rid of the id in request_for_friendship. It wastes space and performance. The table has a "natural" PRIMARY KEY, which I will get to in a moment.
Since it seems that the relationship seems to commutative, let's make use of that by sorting the from and to -- put the smaller id in from and the larger is to. See LEAST() and GREATEST() functions.
Then you need only one EXISTS(), not two. And have
PRIMARY KEY(from_id, to_id)
Now to rethink the purpose of the query... You are looking for the highest id that is not "related" to id #1, correct? That sounds like a LEFT JOIN.
SELECT
FROM profiles AS p
LEFT JOIN request_for_friendship AS r ON r.to = p.id AND r.from = 1
WHERE r.to IS NULL
ORDER BY id DESC
LIMIT 1;
This may run about the same speed as the EXISTS -- Both walk through profiles from the highest id, reaching into the other table to see if a row is there.
If there is no such id, then the entire profiles table will be scanned, plus a the same number of probes into the other table.

Inserting rows into a MySQL DB based on what is missing

I've this table called Runks (a Runk is basically like a challenge in this game that I'm making).
Every game can hold 4 users. Thus per round 4 Runks will be created. 1 round will last 24 hours.
At the end of the round the status of these Runks changes.
However I am running into a problem. If one or more of the users neglected to upload Runk in the meantime I need to create an empty Runk for them in the database.
This query:
SELECT runk_group_id, COUNT(runk_id)
FROM runks
WHERE runk_status = 'ACTIVE'
GROUP BY runk_group_id
Would output this:
This should then result in a next query creating 5 Runks.
1 Runk needs to be created for group_id 32
1 Runk needs to be created for group_id 35
3 Runks need to be created for group_id 44
Also one thing that needs to be taken into is the fact that I need new Runks created with the player ids that have not yet uploaded a Runk.
So if for group 32 player 1, 2 & 3 have already uploaded a Runk... the Runk that will need to be created needs to belong to player 4.
This is what my table looks like:
For the sake of an answer, here is a simplified example (apologies for the terrible naming...):
CREATE TABLE users (
user_id int,
);
INSERT INTO users (1), (2), (3);
CREATE TABLE users_list (
user_id int
);
INSERT INTO users_list values (1), (1), (1), (3);
-- SELECT as shown
SELECT user_id, count(user_id)
FROM users_list
GROUP BY user_id;
+---------+----------------+
| user_id | count(user_id) |
+---------+----------------+
| 1 | 3 |
| 3 | 1 |
+---------+----------------+
-- Incorrect, count includes all an entry even if the left join has nulls
SELECT u.user_id, count(u.user_id)
FROM users u
LEFT JOIN users_list ul ON u.user_id = ul.user_id
GROUP BY u.user_id;
# Gives - WRONG
+---------+------------------+
| user_id | count(u.user_id) |
+---------+------------------+
| 1 | 3 |
| 2 | 1 |
| 3 | 1 |
+---------+------------------+
-- Doesn't include the nulls in the count so we ge the correct answer
SELECT u.user_id, count(ul.user_id)
FROM users u
LEFT JOIN users_list ul ON u.user_id = ul.user_id
GROUP BY u.user_id;
+---------+-------------------+
| user_id | count(ul.user_id) |
+---------+-------------------+
| 2 | 0 |
| 1 | 3 |
| 3 | 1 |
+---------+-------------------+

Multi join one to many

Trades
id |Trade_name |
1 | trade1 |
2 | trade2 |
3 | trade3 |
4 | trade4 |
Users
Name | Primary_id(FK to trade.id) | secondary_id (FK to trade.id) |
John | 1 | 2 |
Alex | 3 | 4 |
This is my current SQL which joins trades.t1 to primary & secondary.id:
select
`users`.`name` ,
`t1`.`trade_name` AS `Primary_trade`,
`t2`.`trade_name` AS `Secondary_trade`,
FROM `users`
right JOIN `trades` `t1` On (`t1`.`trade_id` = `users`.`primary_trade_id`)
right JOIN `trades` `t2` on (`t2`.`trade_id` = `users`.`secondary_trade_id`)
My question is, how do I identify which trades are not used for users both as primary or secondary. I want to see record where a trade does not exist in both primary or secondary column so I can perform housekeeping.
Thanking you all in advance for your help.
If you need only the trades rows
SELECT t.*
FROM trades t
WHERE NOT EXISTS ( SELECT 'u'
FROM Users u
WHERE u.Primary_id = t.id
OR u.Secondary_id = t.id
)
I think this should work for you:
SELECT * FROM trades WHERE id NOT IN (SELECT Primary_id FROM Users) AND id NOT IN (SELECT Secondary_id FROM Users)
It selects the rows which are not in either primary_id nor secondary_id

nested select to fetch date in SQL

There is 3 table.
items_table:
iid
ititle
itext
tag_list_table
tid
tname;
tag_ref_table
iid
tid //foreign key for tag_list_table
per record in tag_list_table have one or more tags.
for example:
tag_list_table:
iid=1,itltle="this is title";itext="this is full text";
iid=2,itltle="this is title2";itext="this is full text2";
tag_list_table
tid=1 tname="red"
tid=2 tname="green"
tid=3 tname="yellow"
tid=4 tname="orange"
tag_ref_table
iid=1 tid=1
iid=1 tid=2
iid=1 tid=3
iid=2 tid=4
I want to have result like this:
row1:
1-
this is title
this is full text
red,green,yellow
row2:
2-
this is title2
this is full text2
orange
I tried these:
SELECT i.ititle,i.itext,t.tname
FROM tag_ref_table as i
LEFT JOIN tag_ref_table t WHERE tid=iid
LEFT JOIN tag_list_table r WHERE tid=??????....
If the "red,green,yellow" values are concatenated into a single string, in MySQL you could use GROUP_CONCAT function like this:
SELECT i.*, group_concat(l.tname SEPARATOR ',') as names
FROM items_table as i
JOIN tag_ref_table r ON r.iid=i.iid
JOIN tag_list_table l ON r.tid= l.tid
GROUP BY i.iid
Documentation: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat
Further examples: http://www.giombetti.com/2013/06/06/mysql-group_concat/
You are looking for GROUP_CONCAT
SELECT it.ititle, it.itext, GROUP_CONCAT(tlt.tname)
FROM tag_ref_table as trt
INNER JOIN items_table it ON trt.iid = it.iid
INNER JOIN tag_list_table tlt ON trt.tid = tlt.tid
GROUP BY it.ititle, it.itext;
SqlFiddle here
Unless your tag_ref_table Junction table allows for NULLS (which would be unusual in such a table), I would recommend switching to an INNER JOIN.
You can use CONCAT() and GROUP_CONCAT() in your select:
SELECT CONCAT(t1.iid,'- ',t1.ititle,' ',t1.itext,' ',t1.tname) FROM
(SELECT i.iid,i.ititle,i.itext,GROUP_CONCAT(r.tname) tname
FROM items_table i
INNER JOIN tag_ref_table t ON i.iid = t.iid
INNER JOIN tag_list_table r ON t.tid = r.tid
GROUP BY i.ititle,i.itext,i.iid)t1;
you can see complete answer in:
SQL Fiddle
DROP TABLE items;
CREATE TABLE items
(item_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,item_title VARCHAR(20)
,item_text VARCHAR(20) NOT NULL
);
DROP TABLE tags;
CREATE TABLE tags
(tag_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,tag_name VARCHAR(12) NOT NULL
);
DROP TABLE items_tags;
CREATE TABLE items_tags
(item_id INT NOT NULL
,tag_id INT NOT NULL
,PRIMARY KEY(item_id,tag_id)
);
INSERT INTO items VALUES
(1,"this is title1","this is full text1"),
(2,"this is title2","this is full text2");
INSERT INTO tags VALUES
(1,"red"),
(2,"green"),
(3,"yellow"),
(4,"orange");
INSERT INTO items_tags VALUES
(1,1),
(1,2),
(1,3),
(2,4);
mysql> SELECT * FROM items;
+---------+----------------+--------------------+
| item_id | item_title | item_text |
+---------+----------------+--------------------+
| 1 | this is title1 | this is full text1 |
| 2 | this is title2 | this is full text2 |
+---------+----------------+--------------------+
2 rows in set (0.00 sec)
SELECT * FROM tags;
+--------+----------+
| tag_id | tag_name |
+--------+----------+
| 1 | red |
| 2 | green |
| 3 | yellow |
| 4 | orange |
+--------+----------+
SELECT * FROM items_tags;
+---------+--------+
| item_id | tag_id |
+---------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
+---------+--------+
SELECT i.*
, GROUP_CONCAT(t.tag_name ORDER BY t.tag_id) tags
FROM items i
JOIN items_tags it
ON it.item_id = i.item_id
JOIN tags t
ON t.tag_id = it.tag_id
GROUP
BY i.item_id;
+---------+----------------+--------------------+------------------+
| item_id | item_title | item_text | tags |
+---------+----------------+--------------------+------------------+
| 1 | this is title1 | this is full text1 | red,green,yellow |
| 2 | this is title2 | this is full text2 | orange |
+---------+----------------+--------------------+------------------+
Note that the GROUP/GROUP_CONCAT part can be performed just as well (if not better) at the application level (e.g. a simple PHP loop acting upon an ordered array)

Selecting data from four tables at once

+----+-------+---------+------+--------+--------+
|q_id|q_title|q_content|q_date|q_status|q_author|
+----+-------+---------+------+--------+--------+
| 1 |varchar| text | int | int | int |
+----+-------+---------+------+--------+--------+
This is the first table: questions.
Tags table has this structure:
+------+--------+---------------+
|tag_id|tag_name|tag_description|
+------+--------+---------------+
| int |varchar | text |
+------+--------+---------------+
And the third table (question_tags) has this structure:
+----+--------+------+
| id | tag_id | q_id |
+----+--------+------+
|int | int | int |
+----+--------+------+
The last table (users) has this structure:
+----+----------+--------+
| id | username |password|
+----+----------+--------+
|int | varchar |varchar |
+----+----------+--------+
I used to select the data with this query:
SELECT * , GROUP_CONCAT( tags.tag_name )
FROM questions
LEFT JOIN users
ON q_author = users.id
LEFT JOIN question_tags
ON questions.q_id = question_tags.q_id
LEFT JOIN tags ON tags.tag_id = question_tags.tag_id
GROUP BY questions.q_id
But it doesn't satisfy my needs anymore. Also please notice that in the question_tags table you can have more than one tag per question and I want to get all tags and their IDs.
The query is correct. If you want the IDs of tags:
SELECT * , GROUP_CONCAT( CONCAT(tags.tag_id,'=',tags.tag_name))
How do you want the output to look?
Try this code it should work as expected
$tblQry = 'SELECT *,GROUP_CONCAT( tags.tag_name ) FROM questions
LEFT JOIN (tags, question_tags, users)
ON (tags.tag_id=question_tags.tag_id
AND questions.q_id=question_tags.q_id)
WHERE users.id = "'.$user_id.'"';