xylar
xylar

Reputation: 7673

SELECT users that match 1 to many fields and remove duplicates

I am making a list of users. I want to return users (and items) that match specific fields (matches email, matches email & type, matches name & type, or matches all of them). I would like to reduce duplicate users so don't return email if it matches email & type.

Example data:

+----+---------------------+--------+---------+
| id | email               | type   | name    |
+----+---------------------+--------+---------+
| 1  | [email protected]     | user   | Rod     |
| 2  | [email protected]    | user   | Jane    |
| 3  | [email protected]    | user   | Rod     |
| 4  | [email protected]    | admin  | Rod     |
| 5  | [email protected]   | admin  | helen   |
+----+---------------------+--------+---------+

So if $email = '[email protected]', $type = 'user', $name = 'Rod' I want to return the following results. I would like to return the following:

+----------------+----+
| matches        | id |
+----------------+----+
| 3 (all)        | 1  |
| 2 (name, type) | 3  |
| 1 (name)       | 4  |
+----------------+----+

I was thinking of separate sql queries for each match set, 1 for email, another for email & type, another for name & type, and 1 for all. And then filter the results at the end using php.

$users = array();

// match email
$sql = 'SELECT u.*, GROUP_CONCAT(i.name) as items
        FROM users u
        INNER JOIN items i
            ON u.id = i.user_id
        WHERE u.email = '.$email.'
        GROUP BY u.id';
$users[] = mysqli_query($sql);

// match email & type
$sql = 'SELECT u.*, GROUP_CONCAT(i.name) as items
        FROM users u
        INNER JOIN items i
            ON u.id = i.user_id
        WHERE u.email = '.$email.' AND u.type = '.$type.'
        GROUP BY u.id';
$users[] = mysqli_query($sql);

// match name & type
$sql = 'SELECT u.*, GROUP_CONCAT(i.name) as items
        FROM users u
        INNER JOIN items i
            ON u.id = i.user_id
        WHERE u.name = '.$name.' AND u.type = '.$type.'
        GROUP BY u.id';
$users[] = mysqli_query($sql);

// match email, name & type
$sql = 'SELECT u.*, GROUP_CONCAT(i.name) as items
        FROM users u
        INNER JOIN items i
            ON u.id = i.user_id
        WHERE u.email = '.$email.' AND u.name = '.$name.' AND u.type = '.$type.'
        GROUP BY u.id';
$users[] = mysqli_query($sql);

I feel there must be a way to create one query to replace them and avoid any filtering using php. Can anyone offer any suggestions?

Upvotes: 2

Views: 151

Answers (3)

user1502952
user1502952

Reputation: 1420

This could be the possible query:

select (CASE 

WHEN CONCAT(users.name, '@')=LEFT(users.email, LENGTH(users.name)+1)
and users.type='user' THEN "3 (all)"

WHEN users.type='user' and CONCAT(users.name, '@')!=LEFT(users.email, 
LENGTH(users.name)+1) THEN "2 (name, type)"

ELSE "1 (name)" END)matches, users.id from users 

inner join items on users.id=items.user_id and 

users.name in (select users.name from users 

group by users.name having count(users.name)>1);

OUTPUT:

+----------------+----+
| matches        | id |
+----------------+----+
| 3 (all)        | 1  |
| 2 (name, type) | 3  |
| 1 (name)       | 4  |
+----------------+----+

Upvotes: 0

Mosty Mostacho
Mosty Mostacho

Reputation: 43444

I'd go for this:

SELECT id,
  email = '[email protected]' EmailMatch,
  type = 'user' TypeMatch,
  name = 'Rod' NameMatch
FROM t
HAVING EmailMatch + TypeMatch + NameMatch > 0
ORDER BY EmailMatch + TypeMatch + NameMatch DESC

Output:

| ID | EMAILMATCH | TYPEMATCH | NAMEMATCH |
|----|------------|-----------|-----------|
|  1 |          1 |         1 |         1 |
|  3 |          0 |         1 |         1 |
|  2 |          0 |         1 |         0 |
|  4 |          0 |         0 |         1 |

Fiddle here.

You can also change id for the actual fields in the table and avoid a later join as you would be retrieving the data directly from that very same query.

If you do need to actually see the amount of total matches per row then you can add another row in which you duplicate all the conditions and add them or wrap your query in another select statement. Either way is not easy to read or inefficient. So I'd recommend that if you do need to see the match count per row, then add the match columns in PHP.

Upvotes: 1

Miquel
Miquel

Reputation: 858

I would perform that with a CASE WHEN (sorry if the syntax is not 100% correct):

$sql = 'SELECT u.id, 
  CASE WHEN (u.name = ' .$name. ' AND u.type = '.$type.' AND u.email = '.$email.') THEN "3 (all)"
    ELSE WHEN (u.name = ' .$name. ' AND u.type = '.$type.') THEN "2 (name, type)"
    ELSE WHEN (u.name = ' .$name. ' AND u.emal = '.$email.') THEN "2 (name, email)"
    ELSE WHEN (u.type = ' .$type. ' AND u.email = '.$email.') THEN "2 (email,type)"
    ELSE WHEN (u.name = ' .$name. ') THEN "1 (name)"
    ELSE WHEN (u.type = ' .$type. ') THEN "1 (type)"
    ELSE WHEN (u.email = ' .$email. ') THEN "1 (email)"
    ELSE "0" END AS matches
  FROM users u
  HAVING matches != "0"';

Upvotes: 0

Related Questions