Selecting data from four tables at once - mysql

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

Related

Show only products the user did not order

I'm getting data about products from 3 different tables and I want to show only products the user didn't order.
Table 1:
Supplier
__________________
| id | name | .. |
|____|______|____|
| 1 | john | .. |
|____|______|____|
Table 2:
Product
___________________________
| id | p_name| supplier_id |
|____|_______|_____________|
| 1 | phone | 1 |
|____|_______|_____________|
| 2 | watch | 1 |
|____|_______|_____________|
Table 3:
Order
___________________________
| id | p_id | buyer_id |
|____|_______|_____________|
| 1 | 1 | 10 |
|____|_______|_____________|
So in this case when the user visit the products page, I want to show the products he didn't order which is watch in this example.
My SQL query:
SELECT supplier.name, products.p_name FROM products
INNER JOIN supplier ON supplier.id = product.supplier_id
INNER JOIN order ON product.id = order.p_id
I tried LEFT JOIN order ON product.id != order.p_id and WHERE order.p_id IS NULL, But no success.
So how to check if the user didn't order this product? Then show the rest of the products?
You can use a WHERE product NOT IN to exclude specific products, shown below.
SELECT supplier.name, products.p_name
FROM products
INNER JOIN supplier ON supplier.id = products.supplier_id
WHERE products.id NOT IN (
SELECT p_id FROM order WHERE buyer_id = supplier.id
)
Within the WHERE statement, you select the product id's of all the orders from a specific user. By applying NOT IN all of these products will be excluded in your list.
You can use WHERE NOT EXISTS statement for you case:
SELECT suppliers.name, products.name
FROM products
INNER JOIN suppliers ON suppliers.id = products.supplier_id
WHERE NOT EXISTS (
SELECT product_id FROM orders WHERE orders.product_id = products.id
);
SQL fiddle here
It should be something like this:
SELECT supplier.name, products.p_name
FROM product
INNER JOIN supplier ON supplier.id = product.supplier_id
LEFT JOIN order ON product.id = order.p_id
WHERE order.id IS NULL
You issue a LEFT JOIN against order to also get products without a match and you discard rows without matches with order.id IS NULL. There's also no need to discard duplicate rows because products that haven't been ordered will only appear once.
+---------+
| |
| |
| |
| product +--------+
| | |
| | order |
| | |
+---------+--------+
This is an unusual schema; A supplier would not normally be an attribute of a 'products' table, and the details of the order would normally be held in a separate table from the orders - otherwise an order can only comprise one item, but anyway...
DROP TABLE IF EXISTS suppliers;
CREATE TABLE suppliers
(id INT AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) UNIQUE
);
INSERT INTO suppliers VALUES
(1,'john');
DROP TABLE IF EXISTS products;
CREATE TABLE products
(id INT AUTO_INCREMENT PRIMARY KEY
,product_name VARCHAR(12) UNIQUE
,supplier_id INT NOT NULL
);
INSERT INTO products VALUES
(1,'phone',1),
(2,'watch',1);
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(id INT AUTO_INCREMENT PRIMARY KEY
,product_id INT NOT NULL
,buyer_id INT NOT NULL
);
INSERT INTO orders VALUES
(1,1,10);
...
SELECT p.*
FROM products p
LEFT
JOIN orders o
ON o.product_id = p.id
WHERE o.id IS NULL;
+----+--------------+-------------+
| id | product_name | supplier_id |
+----+--------------+-------------+
| 2 | watch | 1 |
+----+--------------+-------------+

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

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.

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)