nested select to fetch date in SQL - mysql

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)

Related

MySQL Delete with Join and Count

I have two tables s_filter_values and s_filter_articles.
s_filter_values: s_filter_articles:
| id | value | | articleID | valueID |
|----|-------| |-----------|---------|
| 1 | one | | 1 | 2 |
| 2 | two | | 1 | 3 |
| 3 | three | | 2 | 2 |
With the following statement I count the the occurence of the values
respectively I get the values that are not linked to an article:
SELECT v.*, IFNULL(COUNT(a.articleID), 0) AS counter
FROM s_filter_values AS v
LEFT JOIN s_filter_articles AS a ON v.id = a.valueID
GROUP BY v.id
HAVING counter = 0
In this case, I got
| id | value | counter |
| 1 | one | 0 |
My questions is: How can I use this statement to delete all rows from s_filter_values that are not linked to an article?
I will suggest using NOT EXIST rather than NOT IN.
The NOT EXISTS should perform faster on a large dataset.
There is one key difference between the two constructs: if the subquery returns a NULL in its results then the NOT IN condition will fail, because null is neither equal-to nor not-equal-to any other value.
create table s_filter_values(
id int(10),
`value` varchar(10) );
insert into s_filter_values values ( 1,'one'),( 2,'two'),( 3,'three');
create table s_filter_articles(
articleID int(10),
valueID int(10) );
insert into s_filter_articles values ( 1,2),( 1,3),( 2,2);
DELETE FROM s_filter_values
WHERE NOT EXISTS (SELECT valueID FROM s_filter_articles a where a.valueID= s_filter_values.id);
Demo: https://www.db-fiddle.com/f/7yUJcuMJPncBBnrExKbzYz/84
I would use a subquery to get all IDs that are in the table. Then drop the rows from s_filter_values that are not present.
DELETE FROM s_filter_values WHERE id NOT IN (SELECT DISTINCT valueID FROM s_filter_articles);
A simple sub select should do it:
DELETE FROM `s_filter_values`
WHERE `id` NOT IN SELECT DISTINCT `valueID` FROM `s_filter_articles`

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

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

Preserve order of SQL WHERE IN() clause with nested SELECT

I need a list of item names ordered by count of items. Item names and corresponing id's are stored in tabletwo while tableone refers to items by id's:
tableone tabletwo
+--------+-----------+ +----+------+
| itemid | condition | | id | name |
+--------+-----------+ +----+------+
| 2 | satisfied | | 1 | foo |
+--------+-----------+ +----+------+
| 1 | satisfied | | 2 | bar |
+--------+-----------+ +----+------+
| 3 | satisfied | | 3 | hurr |
+--------+-----------+ +----+------+
| 3 | satisfied | | 4 | durr |
+--------+-----------+ +----+------+
| 3 | satisfied |
+--------+-----------+
| 4 | satisfied |
+--------+-----------+
| 4 | satisfied |
+--------+-----------+
| 3 | nope |
+--------+-----------+
| 1 | satisfied |
+--------+-----------+
SQL code:
SELECT `itemname` FROM `tabletwo` WHERE `id` IN (
SELECT `itemid` FROM (
SELECT count(`itemid`), `itemid`
FROM `tableone`
WHERE `some_codition`="satisfied"
GROUP BY `itemid`
ORDER BY count(`itemid`) DESC
) alias
)
The nested SELECT returns a list of item id's in descendant order: 3, 4, 1, 2. This list is then used as an argument of an IN() clause. The expected result of the whole query is: hurr, durr, foo, bar (in this exact order). But the order is not preserved. I know it can be done like this: ORDER BY FIELD(id, 3, 4, 1, 2) but I don't know how to do this trick when the ordered list is fetched dynamically like in my case. Do I need to SELECT it again? Or temporary table maybe? Or is it better to build another query outside SQL?
Try using JOIN instead:
SELECT t2.`itemname`
FROM `tabletwo` AS t2
JOIN (
SELECT count(`itemid`) AS cnt, `itemid`
FROM `tableone`
WHERE `some_codition`="satisfied"
GROUP BY `itemid`
) AS t1 ON t1.`itemid` = t2.`id`
ORDER BY t1.cnt DESC
You can create a derived table using the subquery of the IN operator and perform a JOIN to this table, so that you are able to use the COUNT in the ORDER BY clause of the main query.
Use JOIN instead of IN:
SELECT
t2.name
FROM tabletwo t2
LEFT JOIN tableone t1
ON t1.itemid = t2.id
AND t1.`condition` = 'satisfied'
GROUP BY
t2.id, t2.name
ORDER BY COUNT(*) DESC
If you want to exclude rows from tabletwo that do not have a match on tableone, use INNER JOIN instead of LEFT JOIN.
ONLINE DEMO

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

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.'"';