MySQL left join with NULL values - mysql

I'm certain this has been asked and answered already but don't know how exactly the question should be.
I have two tables
ID | name
=========
1 | foo
2 | bar
3 | lou
4 | sue
and a meta table:
p_ID | key | value
===================
1 | poo | 1
2 | zoo | 'whatever'
3 | clu | 423
4 | poo | 1
I like to get all entries from the first table which doesn'T have a poo value assigned:
ID | name
=========
2 | bar
3 | lou
My approach was
SELECT *
FROM table AS p
LEFT JOIN meta AS m
ON m.p_id = p.ID
WHERE m.key = 'poo'
AND m.value IS NULL
but this returns an empty result

You have to move the m.key = 'poo' expression into the ON-clause. Everything that is in the WHERE-clause MUST be present, even in a LEFT JOIN.
SELECT *
FROM table AS p
LEFT
JOIN meta AS m
ON m.p_id = p.ID
AND m.key = 'poo'
WHERE m.value IS NULL

I think you should want this;)
SELECT *
FROM table AS p
LEFT JOIN meta AS m
ON m.p_id = p.ID
AND m.key = 'poo'
WHERE m.value IS NULL
When you use m.key = 'poo' in WHERE clause, this will be computed as INNER JOIN, so you only get records with m.key = 'poo', not all rows in table.

You can use INNER JOIN instead.
SELECT *
FROM `table` T
INNER JOIN meta M ON M.p_id = T.ID
WHERE M.key <> 'poo'

Related

How can I use IN() clause in the INNER JOIN?

Here is my query:
SELECT p.*
FROM posts p
INNER JOIN pivot pi on p.id = pi.post_id
INNER JOIN tags t on t.id = pi.tag_id and t.name = "mysql"
All I'm trying to do is getting posts that are tagged with a specific tag. In query above, I'm selecting all posts with mysql tag.
Now I need to make it working for multiple-tags. I mean, I want to select all post which are tagged with both mysql and php. I guess I have to use IN() clause in the query. But I don't know how exactly. Any idea?
EX: Here are my tables:
// posts
+----+---------+------------+
| id | subject | body |
+----+---------+------------+
| 1 | sub1 | content1 |
| 2 | sub2 | content2 |
+----+---------+------------+
// tags
+----+--------+
| id | name |
+----+--------+
| 1 | mysql |
| 2 | php |
+----+--------+
// pivot
+---------+--------+
| post_id | tag_id |
+---------+--------+
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
+---------+--------+
The expected result for [mysql]:
| 1 | sub1 | content1 |
| 2 | sub2 | content2 |
The expected result for [mysql][php]:
| 2 | sub2 | content2 |
I would use a WHERE clause for the conditions and then aggregation:
SELECT p.*
FROM posts p INNER JOIN
pivot pi
ON p.id = pi.post_id INNER JOIN
tags t
ON t.id = pi.tag_id
WHERE t.name IN ('mysql', 'php')
GROUP BY p.id
HAVING COUNT(*) = 2; -- this is the number of tags in the list
This assumes that tags are not duplicated.
A note about using GROUP BY p.id with SELECT p.*. This is actually valid and allowed by the ANSI standard, assuming that p.id is unique in posts. The specific rule is based on functional dependency.
I do think the most recent versions of MySQL disallow the construct by default. You can always do something like:
SELECT p.*
FROM posts p JOIN
(SELECT pi.post_id
FROM pivot pi INNER JOIN
tags t
ON t.id = pi.tag_id
WHERE t.name IN ('mysql', 'php')
GROUP BY p.id
HAVING COUNT(*) = 2 -- this is the number of tags in the list
) t
ON p.id = t.post_id ;
I want to select all post which are tagged with both mysql and php. I guess I have to use IN() clause in the query.
Nope. That would show you posts which are tagged one or the other. To find posts with both, I would join to the tables twice:
SELECT p.*
FROM posts p
INNER JOIN pivot pi1 on p.id = pi1.post_id
INNER JOIN tags t1 on t1.id = pi1.tag_id and t1.name = "mysql"
INNER JOIN pivot pi2 on p.id = pi2.post_id
INNER JOIN tags t2 on t2.id = pi2.tag_id and t2.name = "php"
...
I don't know why should we complicate this ..
Instade of in clause just use string input to query and compare it with string aggregate.
SELECT *
FROM posts P
INNER JOIN (
SELECT post_id, CONCAT ("(", Group_concat(t.name SEPARATOR ' ,'), ")") AS tgs
FROM pivote p
INNER JOIN tag t
ON (t.id = p.tag_id)
GROUP BY post_id
) AA
ON P.id = post_id
WHERE tgs LIKE "(mysql,php)

MYSQL: select 1 record per product, prefer if client is set?

My goal is to remove duplicate productID's, making the query pick the record that has a clientID over the one that doesn't.
The idea is to link files (like manuals) to a product. However, when a file is also linked to a client, it should take priority over the globally linked file as that file will be for this specific client only.
I'm not sure if this is even possible, the tables look like this:
client_products
| client | product |
products
| ID | name |
product_specifications
| filepath | product | client |
Now, this is what I've got so far:
SELECT products.*, ps.specification, cp.client
FROM products
LEFT JOIN product_specifications ps on ps.product = products.ID
RIGHT JOIN client_products cp ON cp.product = products.ID
WHERE cp.client = 1
AND products.ID = cp.product
ORDER BY products.name
Obviously, I'm doing a basic select query with some joins. I'm grouping it by the products.ID to make sure I don't get the product back twice, but this does not guarantee that I will get the specific client's linked specification file, right? Any ideas would be greatly appreciated!
Example results:
What i'm getting (without the GROUP BY)
| ID | specification | client |
| 1 | 1 | 1 |
| 1 | 1 | NULL |
| 2 | 3 | NULL |
What I would like to get when the client column is not null:
| ID | specification | client |
| 1 | 1 | 1 |
| 2 | 3 | NULL |
See my sql fiddle here
I've solved a similar fallback problem here. The "trick" is to use a LEFT JOIN to look if a client specific file exists:
select p.*, ps.*
from client_products cp
join products p on p.ID = cp.product
left join product_specifications ps
on ps.product = p.ID
and (ps.client = 1 or ps.client IS NULL)
left join product_specifications ps1
on ps1.product = p.ID
and ps1.client = 1
and ps.client IS NULL
where cp.client = 1
and ps1.client IS NULL
http://sqlfiddle.com/#!9/d9d705/29
If you only need one column from the product_specifications table, you could also try this one:
select p.*, coalesce(ps.specification, ps1.specification) as specification
from client_products cp
join products p on p.ID = cp.product
left join product_specifications ps
on ps.product = p.ID
and ps.client = 1
left join product_specifications ps1
on ps1.product = p.ID
and ps1.client IS NULL
where cp.client = 1
http://sqlfiddle.com/#!9/d9d705/34
You join the product_specifications table twice. Once for client = 1 and once for client IS NULL. Using COALESCE() you select the second one only if the first one doesn't exit.
I think the kernel of your problem can be solved with the following (I may have amended column names slightly)...
SELECT x.*
FROM product_specifications x
JOIN
( SELECT product_id
, MAX(client_id) client_id
FROM product_specifications
GROUP
BY product_id
) y
ON y.product_id = x.product_id
AND (y.client_id = x.client_id OR y.client_id IS NULL);

Return results only if it doesn't contain a particular value?

I have 2 tables :-
Table T
ID | val
1 | abcd
2 | 1234
3 | asd
4 | lkj
And another table M
ID | T_ID | Type
1 | 1 | I
2 | 1 | S
3 | 2 | I
4 | 2 | I
5 | 3 | I
6 | 4 | S
I want to write a query that joins table T and M on m.T_ID = T.ID but it should not return T.ID if any M mapped to it has Type S i.e. the above set of data should return values T.ID = 2,3 and not 1,4 because M mapped to it has Type S
One way to do it would be to write a inner query. Something like :-
SELECT T.id
FROM table1 T
JOIN table2 M
ON M.t_id = T.id
WHERE T.id NOT IN (SELECT m2.t_id
FROM table2 m2
WHERE m2.type = 'S')
But inner query can be very expensive as my table M has millions of rows. Is there a better way to do this ?
Use a conditional COUNT
SELECT T.id
FROM table1 T
JOIN table2 M
ON M.t_id = T.id
GROUP BY T.id
HAVING COUNT( CASE WHEN M.Type = 'S' THEN 1 END ) = 0
Mean you dont have 'S' in that group.
Not the prettiest but it seems to work
select T.ID
from Table1 T
left join Table2 M on M.T_ID = T.ID
group by T.Id
having sum(case when M.Type = 'S' then 1 else 0 end) = 0
You should check if it is actually less expensive in the execution plan.
You should look into LEFT JOIN as opposed to INNER JOIN.

how to join 3 tables, topic, comment and user

I have 3 tables, tbl_topic, tbl_comment, tbl_user.
I want to select all the topics created by the user and also the topics that he commented on even if he is not the creator. Here is my database:
tbl_topic
----------
topic_id
topic_title
user_id
tbl_comment
----------
comment_id
comment_message
user_id
topic_id
tbl_user
----------
user_id
user_name
Need it so badly. Thanks!
So far i got this
select * from tbl_topic T inner join tbl_comment C on T.topic_id = C.topic_id inner join tbl_user U on T.user_id = U.user_id GROUP BY T.topic_id
My problem is it only returns the topics that has comments on it. I want to include the topics created by the user even if it has 0 comments.
I want the result to be like this:
+-----------+-----------+----------+-------------+----------------+----------+-------
| topic_id | topic_title | user_id | comment_id | comment_message | user_id | topic_id |
+-----------+-----------+----------+-------------+----------------+----------+--------
| 1 | my topic | 1 | 1 | comment me | 1 | 1
| 2 | others | 2 | 2 | comment me | 1 | 2
| 3 | my nocoment| 1 | NULL | NULL | NULL | NULL
+-----------+---------+--------+-------------+----------+----------+---------+--------
----------+-----------+
user_id | user_name |
-----------+-----------
1 | me |
2 | someone |
1 | me
-----------+---------+--
I messed up with my fields in my tables, the user_id beside comment_message should be comment_user_id but i already created my database that way. Can you help make this possible?
The query below uses UNION in the subquery. The first SELECT gets all topics created by user. The second SELECT statement gets all comments of the user and joins it to table tbl_topic so we can get the topic_title.
SELECT topic_ID, topic_title
FROM
(
SELECT a.user_ID, b.topic_ID, b.topic_title
FROM tbl_user a
INNER JOIN tbl_topic b
ON a.user_ID = b.user_ID
UNION
SELECT a.user_ID, c.topic_ID, c.topic_title
FROM tbl_user a
INNER JOIN tbl_comment b
ON a.user_ID = b.user_ID
INNER JOIN tbl_topic c
ON b.topic_ID = c.topic_ID
) x
WHERE x.user_ID = ?
Try the query below. This will show all the fields.
SELECT tt.*, tc.*, tbl_user.*
FROM tbl_topic AS tt INNER JOIN tbl_comment AS tc ON tt.topic_id = tc.topic_id INNER JOIN tbl_user as tc ON tc.user_id = tu.user_id;
WHERE tu.user_id = x
If you have to filter add to the WHERE clause to the query.
Go with a left join. But there is still a Problem left, you will only get ONE table-comment and ONE user. To get more, you can use the GROUP_CONCAT function, like here: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
select * from tbl_topic T
LEFT JOIN tbl_comment C on T.topic_id = C.topic_id
LEFT join tbl_user U on T.user_id = U.user_id
WHERE T.user_id = x
GROUP BY T.topic_id
EDIT: the where clause was missing

How would I accomplish this using SQL?

I have 2 tables in my database that look like so:
clients
+-------------+
| id | sms |
|------+------|
| 1 | 0 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
+------+------+
clients_lists_relationships
+----------------------+
| listid | clientid |
|----------+-----------|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
+----------+-----------+
Now what I'm trying to do is get a list of clients who are in a bunch of lists. I do that like so:
SELECT c.id,
l.*
FROM clients AS c,
clients_lists_relationships AS l
WHERE c.id = l.clientid
AND c.sms = '1'
AND ( l.listid = '1'
OR l.listid = '2' );
This does give me a list of the clients that I need. But because a client can be in more than one list I get the same client more than once. How would I limit this to only one row for each client no matter how many lists they are in?
If you just need any client that is in a list, you can just query the relationship table:
SELECT DSITINCT(clientid) FROM clients_lists_relationships
You can also use that distinct on your combined query, but be aware that the "listid" you'll get is just one.
Use GROUP BY:
SELECT c.id,
l.listid
FROM clients c
INNER JOIN clients_lists_relationships l
ON c.id = l.clientid
WHERE c.sms = 1
AND l.listid IN (1,2)
GROUP BY c.id
Note that by doing this you lose information on which lists the client was a member of. This means that you should probably not select anything from client_lists_relationships as this information is either redundant (clientid) or incomplete (listid).
First of all take a look at MySQL:: JOIN It's much better than the WHERE statements you use now.
I think you are looking for GROUP BY.
In total, the query look like:
SELECT
c.id,
l.*
FROM
clients AS c
INNER JOIN
clients_lists_relationships AS l
ON
l.clientid = c.id
AND
c.sms = '1'
AND
( l.listid = '1'
OR l.listid = '2' );
GROUP BY
c.id
To return just the clients participating in more than 1 list you may want to consider using the HAVING clause:
SELECT c.id
FROM Clients c
INNER JOIN Client_Lists_Relationships l
ON l.clientid = c.id
WHERE c.sms = 1
HAVING COUNT(L.listid) > 1
GROUP BY c.id