SQL query to exclude one to many that have a specific value? - mysql

Using MySQL, I'd like to list all users that don't have the document "liaison". It could means Users that does not have any document at all, or users that have documents, but not "liaison" in these ones.
How can I do using MySQL Query ? I can't make it work!
Here's the (simple) model
Users (id, name)
Documents (id, user_id, name, path)

The NOT EXISTS is a workable solution. As an alternative, sometimes, with large sets, an "anti JOIN" operation can give better performance:
SELECT u.*
FROM Users u
LEFT
JOIN (SELECT d.user_id
FROM Documents d
WHERE d.name = 'liaison'
) l
ON l.user_id = u.id
WHERE l.user_id IS NULL
The inline view aliased as l returns us a list of user_id that have document named 'liaison'; that result set gets outer joined to the Users table, and then we exclude any rows where we found a match (the test of l.user_id IS NULL).
This returns a resultset equivalent to your query with the NOT EXISTS predicate.
Another alternative is to use a query with a NOT IN predicate. Note that we need to guarantee that the subquery does not return a NULL, so the general approach is to include an IS NOT NULL predicate on the column being returned by the subquery.
SELECT u.*
FROM Users u
WHERE u.id NOT IN
( SELECT d.user_id
FROM Documents d
WHERE d.user_id IS NOT NULL
AND d.name = 'liaison'
)
I'd write the NOT EXISTS query like this:
SELECT u.*
FROM Users u
WHERE NOT EXISTS
( SELECT 1
FROM Documents d
WHERE d.name = 'liaison'
AND d.user_id = u.id
)
My personal preference is to use a literal 1 in the SELECT list of that correlated subquery; it reminds me that the query is just looking for the existence of 1 row.)
Again, I usually find that the "anti-join" pattern gives the best performance with large sets. (You'd need to look at the EXPLAIN output for each statement, and measure the performance of each to determine which will work best in your situation.)

The correct query you are looking for is:
SELECT
*
FROM
Users
WHERE
id NOT IN (
SELECT
user_id
FROM
Documents
WHERE
name = "liaison"
)
This will achieve the exact result you are looking for. If a specific user has no documents, it will be listed. If it has many documents, and one of those is 'liaison', it won't be listed.
If you want to search for 'liaison' in your document's name, replace name = "liaison" for name LIKE "%liaison%".
It basically says: Select all users such as there are no documents with name "liaison" pointing to it.

So, I finally came up with this solution that seems to work good :
SELECT * FROM users u WHERE id NOT IN (SELECT DISTINCT user_id FROM user_documents WHERE name = 'LIAISON') ORDER BY c.lastname, c.firstname

SELECT users.*
FROM users left join Documents
on users.id = Documents.user_id
and documents.name='LIAISON'
WHERE documents.user_id is null

select * from Users where not exists (select id from Documents where Users.id = Documents.id and Documents.name = 'liaison')

Try :
SELECT DISTINCT u.*
FROM users u LEFT JOIN documents d ON d.user_id = u.id
WHERE d.id IS NULL OR d.name NOT LIKE '%liaison%'
Remove percent signs if "liaison" is the exact name of the document.

Related

SQL - Renaming fields after query

Im Running into walls with this one but im sure somebody here knows a way around
I have 2 tables for example USERS and ISSUES. In USERS they have we say 2 columns needed
id
firstname
lastname
in 'ISSUES` they have 2 columns needed
id
assigned_to_id
Im trying to compare and replace a field on output.
for example if users.id = issues.assigned_to_id
then print 'users.firstnameinstead ofusers.id`
Any help would be fantastic. cheers guys.
EDIT; i can do two queries to what i need, the first one is this;
SELECT assigned_to_id AS Name, COUNT(*) AS Issues FROM issues
WHERE `status_id`=1
OR `status_id`=2
OR `status_id`=4
OR `status_id`=7
OR `status_id`=8
OR `status_id`=9
OR `status_id`=10
GROUP BY `assigned_to_id`
and this one;
SELECT id, firstname FROM users
now if users.id = issues.assigned_to_id basically use the corresponding users firstname instead of the id
I don't see an issues.firstName column in your example so I assume you mean users.firstname
So to put into plain English what your after...
You want to return all issues and when an issue matches a user return the user name instead of the issueID?
If so then this should do it (Changed left to right as you wanted all issues I believe)
SELECT coalesce(u.FirstName, to_char(I.ID)) as UserNameOrIssueID
FROM Users U
RIGHT JOIN issues I
on U.ID = I.assigned_to_ID
The tricky part here is that firstname and ID are likely of different data types, so you have to cast the user.id to a character field in the DB appropriate syntax...
Using explicit cast (I'm also assuming Issue.ID is a numeric field if it's character then the second one below will work fine as it doesn't even need to do the implicit conversion.
SELECT coalesce(u.FirstName, cast(I.ID as char(30)) as UserNameOrIssueID
FROM Users U
RIGHT JOIN issues I
on U.ID = I.assigned_to_ID
Hoping implicit works:
SELECT coalesce(u.FirstName, I.ID) as UserNameOrIssueID
FROM Users U
RIGHT JOIN issues I
on U.ID = I.assigned_to_ID
I do not know, if this is what you need:
select
u.firstname
from
users as u,
issues as i
where
u.id = i.assigned_to_id;
You can not do that ... Only if you Alter table issues...
all you can do is this !
SELECT U.firstname FROM USERS U
LEFT JOIN ISSUES I ON U.id = I.assigned_to_id
I'll pus an update later ... with the alter table ! Stay tune !
UPDATE
IF OBJECT_ID ('tempdb..#TempTable') IS NOT NULL
DROP TABLE #TempTable
SELECT U.firstname INTO #TempTable FROM USERS U
LEFT JOIN ISSUES I ON U.id = I.assigned_to_id
SELECT * FROM #TempTable
-- ALTER TABLE ISSUES ADD firstname varchar(100) -- RUN ONLY ONCE !
UPDATE I
SET firstname = (SELECT TOP 1 U.firstname FROM USERS U
LEFT JOIN ISSUES I ON U.id = I.assigned_to_id)
FROM ISSUES I
SELECT * FROM ISSUES
I had a look over the weekend, this is what worked and did what i needed it to do. thanks for your help guys.
SELECT COUNT(*) as Issues, assigned_to_id as UserID , users.firstname as Name FROM issues
JOIN users on users.id = issues.assigned_to_id
WHERE `status_id`=1
OR `status_id`=2
OR `status_id`=4
OR `status_id`=7
OR `status_id`=8
OR `status_id`=9
OR `status_id`=10
GROUP BY `assigned_to_id`
ORDER BY Issues DESC

MySQL JOIN ON the one of two columns that doesn't match variable

Let's jump right into it: I've got two simple tables set up in my MySQL database, a users table and a matches table. The users table holds, well, users. The matches table is meant to establish many-to-many connections between users and contains just two userID's.
What is want to query is a list of names of all matched users for the user with userID 1 but I can't wrap my head around it. The problem is that the userID (in this case 1) could be in either one field and I don't have a clue in which one.
Just to clarify; I mean something like this (please don't mind the weird pseudo-code):
SELECT users.name
FROM matches
INNER JOIN users
ON userId = (userId1 OR userId2 DEPENDS ON WHERE)
WHERE userId1 = '1'
OR userId2 = '1';
Could you please tell me if this is possible with MySQL and if so, what I should look for/if you would be so kind, give a simple example.
Thanks a lot.
The user of or in a join condition often prevents MySQL from using an index. The use of union or union all makes the query rather cumbersome. You can do what you want with left outer join:
SELECT coalesce(u1.name, u2.name) as name
FROM matches m LEFT JOIN
users u1
ON u.userId = m.userId1 AND m.userId2 = '1' LEFT JOIN
users u2
ON u.userId = m.userId2 AND m.userId1 = '1'
WHERE '1' in (m.userId1, m.userId2);
This should take advantage of indexes on users for looking up the values. If you want distinct names, then add the distinct keyword.
Try:
SELECT DISTINCT u.name
FROM matches m
INNER JOIN users u
ON (u.userId = m.userId1 AND m.userId2 = '1')
OR (u.userId = m.userId2 AND m.userId1 = '1')
Added DISTINCT to avoid duplicate rows.
See this fiddle.
Here's one way to do it that avoids excessive JOIN logic (to make sure SQL can use indexes on users.userId, matches.userId1, matches.userId2)
SELECT u.`name`
FROM
matches AS m
JOIN users AS u
ON m.userId1=u.userId
AND m.userId2='1'
UNION
SELECT u.`name`
FROM
matches AS m
JOIN users AS u
ON m.userId2=u.userId
AND m.userId1='1'
Something like this:
Select UserId1, UserId2
From Matches
Where UserId1 = 1
Union
Select UserId2, UserId1
From Matches
Where UserId2 = 1
Notice the order of the UserIds have been changed in the Select clause. This will give you a single list of matches with you searched user '1' in a single column and all their matches in the other.
This approach will require you then link in your users table as follows:
Select searchmatches.UserId1, searchmatches.UserId2, leftuser.Name, rightuser.name
From (
Select UserId1, UserId2
From Matches
Where UserId1 = 1
Union
Select UserId2, UserId1
From Matches
Where UserId2 = 1
) searchmatches
inner join users leftuser userMatches.UserId1 = leftuser.UserId
inner join users rightuser userMatches.UserId2 = rightuser.UserId
Hope that Helps! If you want you can remove one of the inner joins to the users table as you know who the left user is as you searched on them!

MySQL query optimization: Multiple SELECT IN to LEFT JOIN

I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.

MYSQL JOIN syntax How to Join Three Tables

The following query does what I want. It returns all the resuls in the users table and then if there is a match in the details tble, returns the relevant data
users
id|username
details
id|userid|firstname|lastname
$sql = "SELECT u.*, d.*
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
ORDER BY $strorder";
However, when I try to join an additonal table where I want to do the same thing--return all the results of the users table and if there is a match in the third table, return the relevant data (total followers of this user)--it only returns one record.
3rd table
follow
id|followerid|followedid
$sql = "SELECT u.*, d.*, COUNT(f.id)
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
LEFT JOIN `follow` f on
u.id = f.followedid
ORDER BY $strorder";
Can anyone see what I am doing wrong? Thanks in advance for any suggestions.
Many thanks.
Try to avoid * to select fields, it will be clearer to group your datas (even if mysql is quite permissive with groupings).
When you have an aggregate function (like COUNT, SUM), the other "non aggregated" requested fields should be in a GROUP BY clause.
Mysql don't force you to GROUP BY all the fields, but... I think it's quite a good habit to be "as ANSI as possible" (usefull when you use another DBMS)
SELECT u.id, u.username, d.firstname, d.lastname, count(*) as numberfollowers
FROM user u
LEFT JOIN details d on u.id = d.userid
LEFT JOIN follow f on u.id = f.followedid
GROUP BY u.id, u.username, d.firstname, d.lastname --or just GROUP BY u.id with Mysql
ORDER BY count(*) desc
COUNT being an aggregate function, when selected with other columns, requires you to group your results by those other columns in the select list.
You should rewrite your query with columns that you want to select from users and details and group by those columns.

MySQL select rows that do not have matching column in other table

I can't seem to figure this out so far. I am trying to join two tables and only select the rows in table A that do not have a matching column in table B. For example, lets assume we have a users table and a sent table.
users table has the following columns: id, username
sent table has the following columns: id, username
I want to select all rows from users where username does not exist in sent table. So, if tom is in users and in sent he will not be selected. If he is in users but not in sent he will be selected. I tried this but it didn't work at all:
SELECT pooltest.name,senttest.sentname
FROM pooltest,senttest
WHERE pooltest.name != senttest.sentname
Typically, you would use NOT EXISTS for this type of query
SELECT p.Name
FROM pooltest p
WHERE NOT EXISTS (SELECT s.Name
FROM senttest s
WHERE s.Name = p.Name)
An alternative would be to use a LEFT OUTER JOIN and check for NULL
SELECT p.Name
FROM pooltest p
LEFT OUTER JOIN senttest s ON s.Name = p.Name
WHERE s.Name IS NULL
Note that the implicit join syntax you are using is considered obsolete and should be replaced with an explicit join.
Try this SQL:
SELECT users.username
FROM users
LEFT JOIN sent ON sent.username = users.username
WHERE sent.username IS NULL;
The better way in my opinion would be:
SELECT users.username
FROM users
LEFT JOIN sent ON sent.id = users.id
WHERE sent.id IS NULL;
As both the id fields, would be indexed (primary key I would have thought) so this query would be better optimised than the first one I suggested.
However you may find my first suggestion better for you, it depends on what your requirements are for your application.
May be this one can help you ....
I had also the same problem but Solved using this this query
INSERT INTO tbl1 (id,name) SELECT id,name from tbl2 where (name) not in(select name from tbl1);
hope this one will solve your problem