MySQL select only unique values - mysql

I have two tables.
Clients:
+--------------+-------------+
| CLIENT_ID | LABEL |
+--------------+-------------+
| 123 | label1 |
+--------------+-------------+
| 123 | label3 |
+--------------+-------------+
| 456 | label1 |
+--------------+-------------+
| 789 | label2 |
+--------------+-------------+
| 987 | label2 |
+--------------+-------------+
| 987 | label4 |
+--------------+-------------+
Managers:
+----+--------------+
| ID | CLIENT_ID |
+----+--------------+
| 1 | 123 |
+----+--------------+
| 1 | 456 |
+----+--------------+
| 2 | 456 |
+----+--------------+
| 3 | 789 |
+----+--------------+
| 3 | 987 |
+----+--------------+
| 4 | 789 |
+----+--------------+
I need to select ID from Managers which have only clients with labels "label1" or "label2" and do not have clients with other labels.
The resulting output should be like 2 and 4.
I tried to do as
select m.id
from managers m
join clients c on m.client_id = c.client_id
where c.label in ('label1', 'label2');
but it returns all ids.

use group by as follows:
select m.id
from managers m
join clients c on m.client_id = c.client_id
where c.label in ('label1', 'label2')
group by m.id
having count(*) = 1;

Please try group by and having statement here to get unique records
SELECT ID FROM `Clients` c
left join Managers m on (c.client_id = m.CLIENT_ID)
where LABEL in ('label1', 'label2')
group by ID having count(*) = 1
Thanks

Close. Just add aggregation:
select m.id
from managers s join
clients c
on m.client_id = c.client_id
where c.label in ('label1', 'label2')
group by m.id
having count(distinct c.label) = 2;
If you want only clients with these two labels, then use:
select m.id
from managers s join
clients c
on m.client_id = c.client_id
group by m.id
having count(distinct c.label) = 2;
select m.id
from managers s join
clients c
on m.client_id = c.client_id
group by m.id
having count(distinct case when c.label in ('label1', 'label2') then c.label end) = 2 and
count(distinct c.label) = 2;
Or, more efficiently:
having sum(c.label = 'label1') > 0 and
sum(c.label = 'label2') > 0 and
sum(c.label not in ('label1', 'label2')) = 0;

You can use a CTE to get the number of occurrences where client labels associated with a particular manager are valid:
with r as (select m.id id, sum(c.label in ('label1', 'label2')) c1, count(*) c2
from managers m join clients c on m.client_id = c.client_id group by m.id)
select id from r where c1 = c2;
Output:
id
2
4

Join the tables, group by manager and use conditional aggregation for your conditions in the HAVING clause:
SELECT m.id
FROM managers m INNER JOIN clients c
ON m.client_id = c.client_id
GROUP BY m.id
HAVING MAX(c.label IN ('label1', 'label2')) = 1
AND MAX(c.label NOT IN ('label1', 'label2')) = 0
This will also work if a client has both labels 'label1' and 'label2' and may be extended to more labels.
See the demo.
Results:
id
2
4

Related

MySQL: Join with 4 tables and common field: row_number()

I have 4 tables -
Tab: d
Name | ID
----------
A | 1
B | 2
C | 3
Tab: p
Name | ID
----------
AX | 1
B | 2
X | 3
Y | 4
Z | 5
Tab: s
Name | ID
----------
A | 1
BL | 2
V | 3
M | 4
Tab: a
Name | ID
----------
K | 1
J | 2
H | 3
N | 4
Now I am using MySQL and today I found out that MySQL does not support FULL join. So, I am using left join with all 4tables and then using "union" and right join to merge all the 4 tables' records.
The query I am using is like -
(select d.Name, p.Name, s.Name, a.Name from doc d
left join
prof p
on d.id = p.id
left join
sing s
on d.id = s.id
left join
act a
on d.id = a.id)
union
(select d.Name, p.Name, s.Name, a.Name from doc d
right join
prof p
on d.id = p.id
right join
sing s
on d.id = s.id
right join
act a
on d.id = a.id)
But this is not giving the intended output. It is giving something like -
D | P | S | A
---------------------------------
A | AX | A | K
B | B | BL | J
C | X | V | H
NULL | NULL | NULL | N
Actual output should be -
D | P | S | A
---------------------------------
A | AX | A | K
B | B | BL | J
C | X | V | H
NULL | Y | M | N
NULL | Z | NULL | NULL
Please help me to figure out what I am missing! And also to help me to get the result...
The behavior you really want here is a full outer join, but MySQL does not directly support that (and the workaround is fairly ugly). One approach I can suggest here would be to maintain a fifth table containing all ID values which you expect in the result set. Consider:
SELECT c.ID, d.Name, p.Name, s.Name, a.Name
FROM
(SELECT 1 AS ID UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5) c
LEFT JOIN d ON d.ID = c.ID
LEFT JOIN p ON p.ID = c.ID
LEFT JOIN s ON s.ID = c.ID
LEFT JOIN a ON a.ID = c.ID
ORDER BY c.ID;
Assuming that you're using MySQL v8, you might be able to do it this way:
WITH RECURSIVE cte AS (
SELECT 1 AS rn, MAX(cnt) AS mxcnt
FROM
( SELECT COUNT(ID) cnt FROM doc UNION
SELECT COUNT(ID) FROM prof UNION
SELECT COUNT(ID) FROM sing UNION
SELECT COUNT(ID) FROM act ) v UNION ALL
SELECT rn+1, mxcnt FROM cte WHERE rn+1 <= mxcnt)
SELECT c.rn, d.Name, p.Name, s.Name, a.Name
FROM cte c
LEFT JOIN doc d ON d.ID = c.rn
LEFT JOIN prof p ON p.ID = c.rn
LEFT JOIN sing s ON s.ID = c.rn
LEFT JOIN act a ON a.ID = c.rn
ORDER BY c.rn;
Using WITH RECURSIVE to generate numbering sequence based on the largest count result from all related table then use it as the reference in LEFT JOIN. I agree with Tim about having a master table for all of the ids.

sql query combine two queries into one with empty rows

This is my sql table structure:
Table1: details
|--id--|--id_user--|--price--|
| 1 | 1 | 10 |
| 2 | 2 | 15 |
| 3 | 1 | 25 |
| 4 | 3 | 30 |
| 5 | 3 | 7 |
------------------------------
Table2: users
|--id--|--id_country--|
| 1 | 1 |
| 2 | 2 |
| 3 | 0 |
-----------------------
Table3: country
|--id--|--country--|
| 1 | France |
| 2 | Italy |
--------------------
What I need is to get the SUM of price by country:
SELECT c.country, SUM(d.price) AS price
FROM details d
INNER JOIN users u ON u.id = d.id_user
INNER JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
I get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
-----------------------
BUT I'd need to get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
| Undefined | 37 |
-----------------------
where undefined would be if id_country=0. (I can't add to country table the id=0 or id=undefined, it will messed up other things). Right now I'm achieving this by two separate queries, the second one is:
SELECT SUM(d.price) as price
FROM details d
INNER JOIN users u ON u.id = d.id_user AND u.id_country=0
GROUP BY u.id_country
I'm thinking if... is it possible to do this in one query?
You need to use left join in this case:
SELECT c.country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
If you use INNER JOIN, you will only get results that exists in both tables.
To replace NULL with Undefined use:
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
One way to sort to get Undefined last is to add a Sortfield
SELECT A.Country,A.Price FROM (
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price, IFNULL(c.Country,'ZZZZZZZZ') AS Sort
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
) A
ORDER BY A.Sort
Edit: ORDER BY suggested in comments
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country IS NULL, c.country
Try below query.
SELECT
CASE
WHEN c.country is NULL THEN 'Undefined'
ELSE c.country
END as country
, SUM(d.price) AS price
FROM users u
left JOIN details d ON u.id = d.id_user
left JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
For Demo :
SqlfiddlE Demo :
Please let us know if you have any que.

MySQL: SELECT from three tables?

I have problems with a MySQL query with three tables. I would like to search for a name and get all (even better only the first one) phonenumber and email. Here are my tables:
Table 1, contact
==========
id | name
==========
1 | stefan
2 | michael
3 | andy
4 | bob
Table 2, phone
==============================
id | contact_id | phonenumber
==============================
1 | 1 | +1 434 434232
2 | 1 | +1 434 24234
3 | 2 | +1 89234
4 | 4 | +1 345345
5 | 4 | +1 434 7567567
Table 3, email
===============================
id | contact_id | emailaddress
===============================
1 | 1 | stefan#home.com
2 | 1 | stefan#work.com
3 | 1 | stefan#mars.com
4 | 4 | bob#anywhere.com
5 | 2 | michael#nothing.com
And this is my query, which seams to send MySQL to nirvana:
SELECT c.name, p.phonenumber, e.emailaddress
FROM contact AS c
JOIN phonenumber AS p ON c.id = p.contact_id
JOIN email AS e ON c.id = e.contact_id
WHERE c.name = 'michael'
When I do only one join this works fine as:
SELECT c.name, p.phonenumber
FROM contact AS c
JOIN phonenumber AS p ON c.id = p.contact_id
WHERE c.name = 'michael'
Any ideas?
Thanks
Mike
Try this:
SELECT c.name, p.phonenumber, e.emailaddress
FROM name_of_your_schema.contact AS c
JOIN name_of_your_schema.phone AS p ON c.id = p.contact_id
JOIN name_of_your_schema.email AS e ON c.id = e.contact_id
WHERE c.name = 'stefan'
LIMIT 1;
Tom L.
Try
SELECT c.name, p.phonenumber, e.emailaddress
FROM contact c
INNER JOIN phone p ON c.id = p.contact_id
INNER JOIN email e ON p.contact_id = e.contact_id
WHERE c.name = 'michael'
To get just one result per contact, you might use aggregation in a bit unorthodox way. I modified #Emanuel Saringan's query:
SELECT c.name, min(p.phonenumber), min(e.emailaddress)
FROM contact c
left JOIN phone p ON c.id = p.contact_id
left JOIN emailaddress e ON c.id = e.contact_id
WHERE c.name = 'michael'
GROUP BY c.id
See it work here:
http://sqlfiddle.com/#!2/6a8700/2

MySQL show relations MANY-MANY by condition

There are two tables:
partner
id | name
--------------
1 | partner_1
2 | partner_2
3 | partner_3
4 | partner_4
contract
id | name | is_active
---------------------------
1 | contract_1 | 1
2 | contract_2 | 0
3 | contract_3 | 1
4 | contract_4 | 0
5 | contract_5 | 0
There is a third table that relates the previous two tables with many-to-many relationship
partner_contract
partner_id | contract_id
------------------------
1 | 1
1 | 2
2 | 3
2 | 2
2 | 4
3 | 5
Each partner can have several contracts, among which ONLY ONE can be active and some inactive .
Also the partner may not have contract at all.
I need a query that displays all the partners together with the active contract. If partner dont' have an active contract, display NULL.
partner_id | partner_name | contract_name
-----------------------------------------
1 | partner_1 | contract_1
2 | partner_2 | contract_3
3 | partner_3 | NULL
4 | partner_4 | NULL
I found a solution, but it seems to me that it is not perfect .
SELECT
p.id AS partner_id,
p.name AS partner_name,
active_contract.name AS contract_name
FROM partner p
LEFT JOIN (
SELECT *
FROM contract c
LEFT JOIN partner_contract pc on pc.contract_id = c.id
WHERE c.is_active = 1
) active_contract
ON active_contract.partner_id = p.id
Is there a more elegant solution?
Ray's (deleted) query is close to the right solution. The condition on the contract should go in the on clause, not the where clause:
SELECT p.id AS partner_id, p.name AS partner_name, c.name AS contract_name
FROM partner p LEFT JOIN
partner_contract pc
ON p.id = pc.partner_id LEFT JOIN
contract c
ON pc.contract_id = c.id AND c.is_active = 1;
EDIT:
Okay, the above is wrong. This can be fixed with a group by:
SELECT p.id AS partner_id, p.name AS partner_name, MAX(c.name) AS contract_name
FROM partner p LEFT JOIN
partner_contract pc
ON p.id = pc.partner_id LEFT JOIN
contract c
ON pc.contract_id = c.id AND c.is_active = 1
GROUP BY p.id, p.name;
The more elegant solution (in my opinion):
SELECT p.*,
(select name
from partner_contract pc join
contract c
on pc.contract_id = c.id AND c.is_active = 1
where p.id = pc.partner_id
) as contract_name
FROM partner p;
SQL Fiddle
This can take advantage of indexes and does not require aggregation.

LEFT JOIN 3 columns to get username

I have three columns I need to join which comes from 3 different tables,
Contributions table:
+-----------+---------------------+
| record_id | contributor_user_id |
+-----------+---------------------+
| 1 | 2 |
+-----------+---------------------+
| 1 | 5 |
+-----------+---------------------+
Members table:
+--------------+---------+
| username | user_id |
+--------------+---------+
| Test | 1 |
+--------------+---------+
| Test2 | 5 |
+--------------+---------+
| Test3 | 6 |
+--------------+---------+
Records table:
+---------+-----------+
| user_id | record_id |
+---------+-----------+
| 28 | 1 |
+---------+-----------+
For what I need to return is the username and user_id for displaying the record owner. Also, display the username and the user_id, but this can be multiple (more than 1+ user). I've tried this:
SELECT usr.username,
usr.user_id,
rec.record_id,
contrib.record_id,
contrib.contributor_user_id
FROM
(
records rec
INNER JOIN members usr ON rec.user_id = usr.user_id
# this returns records as NULL
LEFT OUTER JOIN contributions contrib ON rec.record_id = contrib.record_id AND contrib.contributor_user_id = usr.user_id
# this works, but I need the username to be displayed too
LEFT OUTER JOIN contributions contrib ON rec.record_id = contrib.record_id
)
WHERE rec.record_id = 1
Try nesting the join for contributing users inside of the left join to contributions.
SELECT u.username, u.user_id, r.record_id, u2.username as ContributorName, u2.user_id as ContributorId
FROM records r
INNER JOIN members u
ON r.user_id = u.user_id
LEFT JOIN contributions c
INNER JOIN members u2
ON c.contributor_user_id = u2.user_id
ON r.record_id = c.record_id
WHERE r.record_id = 1
SELECT
usr.username AS record_owner
, usr.user_id AS record_owner_id
, rec.record_id
, con.contributor_user_id AS contributor_id
, contributors.username AS contributor_name
FROM
records rec
INNER JOIN
members usr
ON rec.user_id = usr.user_id
LEFT OUTER JOIN
contributions con
ON rec.record_id = con.record_id
INNER JOIN
members contributors
ON con.contributor_user_id = contributors.user_id
WHERE
rec.record_id = 1