2

I have a search box that returns values from multiple tables using the LIKE operator. However I now want to make sure that any matches where x = y are not returned. Is this possible to do in the same query?

My attempt:

$search_sql = "SELECT * FROM User 
    INNER JOIN UserSkills ON User.UserId = UserSkills.UserId 
    INNER JOIN Skills ON UserSkills.SkillId = Skills.SkillId 
    INNER JOIN UserTasks on User.UserId = UserTasks.UserId
    WHERE Description LIKE '%".$value."%' 
    OR FName LIKE '%".$value."%' 
    OR LName LIKE '%".$value."%' 
    OR JobRole LIKE '%".$value."%' 
    OR UserId NOT IN (
        SELECT UserID from UserTasks WHERE TaskID = $Task) 
    GROUP BY Description" or die(mysqli_error($con));

    $search_query = mysqli_query($con, $search_sql);
            if(mysqli_num_rows($search_query) !=0) {
                $search_rs = mysqli_fetch_assoc($search_query);
                }

This attempt returns false (no results). There are results to be found.

This is the previous version that successfully returns results, before adding the new table (UserTasks), and NOT IN:

$search_sql = "SELECT * FROM User 
    INNER JOIN UserSkills ON User.UserId = UserSkills.UserId 
    INNER JOIN Skills ON UserSkills.SkillId = Skills.SkillId 
    WHERE Description LIKE '%".$value."%' 
    OR FName LIKE '%".$value."%' 
    OR LName LIKE '%".$value."%' 
    OR JobRole LIKE '%".$value."%' 
    GROUP BY Description" or die(mysqli_error($con));
user2258597
  • 113
  • 1
  • 10
  • Maybe left joins are what you need? are you sure you have rows in User that have matching rows in all other tables?(even if there is 1 that doesn't it will filter the result since you are using an inner join) – sagi Feb 16 '16 at 15:26

1 Answers1

1

If you want to exclude records where x = y, you would need to add something like AND x <> y (x does not equal y) to your WHERE. Keep in mind that you would need to parenthesize the WHERE components based on your intention when combining ANDs and ORs.

Without seeing the data in your tables I'm not sure why you currently are not getting results. My recommendation would be to start taking pieces out until you get the result you expect, and then adding them back testing your expectations until something doesn't match.

A few other notes:

  • The or die(..) here I don't think does anything, since the left side is simply string concatenation which I don't think could fail. You are not actually doing any MySQL related work when assigning to $search_sql.
  • I would recommend looking into parameterized queries rather than using string concatenation and variable interpolation to generate your query. It will be a bit more work initially, but tends to be easier to maintain and reason about and can easily be more secure.
  • I think INNER JOIN and JOIN have no functional difference, so I would pick one or the other and be consistent.
  • Using SELECT * when grouping by something has the potential to give unexpected/inconsistent results. Any column that is not in either the GROUP BY clause or contained in an aggregate will return the value on the first row of the group, but first is not guaranteed to be consistent (unless you ORDER). Other DBMS's (Oracle and SQL Server, at least, I think) actually forbid this behavior.

Update

SELECT Description
FROM User u
    JOIN UserSkills us ON u.UserId = us.UserId 
    JOIN Skills s ON us.SkillId = s.SkillId 
WHERE
    (
        Description LIKE '%".$value."%' 
        OR FName LIKE '%".$value."%' 
        OR LName LIKE '%".$value."%' 
        OR JobRole LIKE '%".$value."%' 
    )
    AND UserId NOT IN (
        SELECT UserID
        FROM User u
            JOIN UserTasks ut ON u.UserId = ut.UserId
        WHERE TaskID = $Task
    )
GROUP BY Description

Update with schema and data

CREATE TABLE users (
  id INT NOT NULL,
  first VARCHAR(16),
  last VARCHAR(16),

  PRIMARY KEY (id)
);

CREATE TABLE skills (
  id INT NOT NULL,
  description VARCHAR(64) NOT NULL,
  job_role VARCHAR(64) NOT NULL,

  PRIMARY KEY (id)
);

CREATE TABLE user_skills (
  user_id INT NOT NULL,
  skill_id INT NOT NULL,

  FOREIGN KEY user_skills_user (user_id) REFERENCES users (id),
  FOREIGN KEY user_skills_skill (skill_id) REFERENCES skills (id)
);

CREATE TABLE user_tasks (
  user_id INT NOT NULL,
  task_id INT NOT NULL,

  PRIMARY KEY (user_id,task_id),
  FOREIGN KEY user_tasks_user (user_id) REFERENCES users (id)
);

INSERT INTO users VALUES (0,'FA','LA');
INSERT INTO users VALUES (1,'FB','LB');
INSERT INTO users VALUES (2,'FC','LC');

INSERT INTO skills VALUES (0,'Skill Description A','Job Role A');
INSERT INTO skills VALUES (1,'Skill Description B','Job Role B');
INSERT INTO skills VALUES (2,'Skill Description C','Job Role C');

INSERT INTO user_skills VALUES (0,0);
INSERT INTO user_skills VALUES (0,1);
INSERT INTO user_skills VALUES (0,2);
INSERT INTO user_skills VALUES (1,0);
INSERT INTO user_skills VALUES (1,1);
INSERT INTO user_skills VALUES (2,1);

INSERT INTO user_tasks VALUES (0,1);
INSERT INTO user_tasks VALUES (1,2);
INSERT INTO user_tasks VALUES (2,1);

SELECT *
FROM users u
    JOIN user_skills us ON u.id = us.user_id
    JOIN skills s ON us.skill_id = s.id
WHERE
    (
        s.description LIKE ''
        OR u.first LIKE '%F%'
        OR u.last LIKE ''
        OR s.job_role LIKE ''
    )
    AND u.id NOT IN (
        SELECT user_id
        FROM user_tasks
        WHERE task_id = 2
    )
;

This should return 4 records, 3 for FA LA by 3 skills, and one for FC LC by 1 skill. FB LB is not returned because it is assigned task 2. Changing to task_id = 1 should return 2 records, FB LB by 2 skills.

ryachza
  • 4,460
  • 18
  • 28
  • I have included the previous working version in my OP. Also do you have any info or resources to explain "parenthesize the WHERE components"? I have not come across this yet at uni. – user2258597 Feb 16 '16 at 16:02
  • @user2258597 An `OR` constraint couldn't *reduce* the number of results returned, but the `INNER JOIN UserTasks` added certainly can - that join will limit the result to only users that have tasks. You would need to use `LEFT JOIN` instead if you want "everything that exists on the left". The `WHERE` is evaluated after joins, so you can't "undo" the loss. What I mean by parenthesization is just something like `WHERE (x OR y OR z) AND a` because `AND` "binds tighter" (higher precedence) than `OR`. Perhaps this will be helpful: http://stackoverflow.com/questions/12345569/mysql-or-and-precedence – ryachza Feb 16 '16 at 16:24
  • @user2258597 If I understand correctly now and you're looking to search all users who aren't associated with a specific task, please see my update. Moving the join into the `IN (SELECT ...)` and changing the `OR` to `AND` and parenthesizing appropriately. I also added another note regarding the `SELECT` clause and grouping. – ryachza Feb 16 '16 at 16:47
  • Yes you understand correctly. Thank you for the update I will test it soon and get back to you. Amazing effort and education, thank you. – user2258597 Feb 16 '16 at 16:55
  • I have tried implementing this suggestion and it generates zero results. There are definitely results to be found. Do you have any idea why this might be? – user2258597 Feb 19 '16 at 15:10
  • @user2258597 It's just a matter of debugging. I updated the answer with a schema and data and a query that seems to work that may help. If I were you I would start by echoing your query and pasting it into an IDE (for example, MySQL Workbench) and ripping stuff out/tweaking it until you get results you expect, then incrementally adding things back comparing your result sets to your expectations of the data. The process is a skill that will take practice but assume the result returned is correct and that you are simply asking the wrong question. – ryachza Feb 19 '16 at 15:47