SQL IN () is including NULL values - mysql

Employee table:
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| emp_id | fname | lname | start_date | end_date | superior_emp_id | dept_id | title | assigned_branch_id |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| 1 | Michael | Smith | 2005-06-22 | NULL | NULL | 3 | President | 1 |
| 2 | Susan | Barker | 2006-09-12 | NULL | 1 | 3 | Vice President | 1 |
| 3 | Robert | Tyler | 2005-02-09 | NULL | 1 | 3 | Treasurer | 1 |
| 4 | Susan | Hawthorne | 2006-04-24 | NULL | 3 | 1 | Operations Manager | 1 |
| 5 | John | Gooding | 2007-11-14 | NULL | 4 | 2 | Loan Manager | 1 |
| 6 | Helen | Fleming | 2008-03-17 | NULL | 4 | 1 | Head Teller | 1 |
| 7 | Chris | Tucker | 2008-09-15 | NULL | 6 | 1 | Teller | 1 |
| 8 | Sarah | Parker | 2006-12-02 | NULL | 6 | 1 | Teller | 1 |
| 9 | Jane | Grossman | 2006-05-03 | NULL | 6 | 1 | Teller | 1 |
| 10 | Paula | Roberts | 2006-07-27 | NULL | 4 | 1 | Head Teller | 2 |
| 11 | Thomas | Ziegler | 2004-10-23 | NULL | 10 | 1 | Teller | 2 |
| 12 | Samantha | Jameson | 2007-01-08 | NULL | 10 | 1 | Teller | 2 |
| 13 | John | Blake | 2004-05-11 | NULL | 4 | 1 | Head Teller | 3 |
| 14 | Cindy | Mason | 2006-08-09 | NULL | 13 | 1 | Teller | 3 |
| 15 | Frank | Portman | 2007-04-01 | NULL | 13 | 1 | Teller | 3 |
| 16 | Theresa | Markham | 2005-03-15 | NULL | 4 | 1 | Head Teller | 4 |
| 17 | Beth | Fowler | 2006-06-29 | NULL | 16 | 1 | Teller | 4 |
| 18 | Rick | Tulman | 2006-12-12 | NULL | 16 | 1 | Teller | 4 |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
Query:
SELECT emp_id, fname, lname, title
FROM employee
WHERE emp_id IN (SELECT superior_emp_id FROM employee);
Query Result:
+--------+---------+-----------+--------------------+
| emp_id | fname | lname | title |
+--------+---------+-----------+--------------------+
| 1 | Michael | Smith | President |
| 3 | Robert | Tyler | Treasurer |
| 4 | Susan | Hawthorne | Operations Manager |
| 6 | Helen | Fleming | Head Teller |
| 10 | Paula | Roberts | Head Teller |
| 13 | John | Blake | Head Teller |
| 16 | Theresa | Markham | Head Teller |
+--------+---------+-----------+--------------------+
Subquery result:
+-----------------+
| superior_emp_id |
+-----------------+
| NULL |
| 1 |
| 1 |
| 3 |
| 4 |
| 4 |
| 4 |
| 4 |
| 4 |
| 6 |
| 6 |
| 6 |
| 10 |
| 10 |
| 13 |
| 13 |
| 16 |
| 16 |
+-----------------+
If the subquery SELECT superior_emp_id FROM employee returns NULL for Michael Smith how is it that the IN() operator returns it in the final result set? I thought nothing was equal to null.

If the subquery SELECT superior_emp_id FROM employee returns NULL for Michael Smith how is it that the IN() operator returns it in the final result set?
Short answer, it doesn't.
The subquery effectively returns the whole set of superior_emp_ids [NULL, 1, 1, 3, 3, 4, 4, 6, 6, 6, 4, 10, 10, 4, 13, 13, 4, 16, 16] for each row.
Your WHERE clause tests each emp_id to see if it is IN this set. And IN is basically a series of equals comparisons OR'd together.
Michael's emp_id is 1 and his row is returned because 1 = NULL OR 1 = 1 .... which can be written as FALSE OR TRUE .... returns TRUE.
You are correct in assuming that NULL doesn't equal anything, including NULL, so WHERE NULL IN (NULL, 1, FALSE, ... anything you like ...) will return FALSE. But that is not what's happening in your example.
N.B. To avoid any confusion it is much better to avoid NULL records on either side of an IN clause where possible as referenced by #Donal

SELECT superior_emp_id FROM employee
returns [NULL, 1, 3, 4, 6, 10, 13, 16]. I do not see the problem here.

Have a look at the ANSI_NULLS setting in SQL Server.
Transact-SQL supports an extension that allows for the comparison
operators to return TRUE or FALSE when comparing against null values.
This option is activated by setting ANSI_NULLS OFF. When ANSI_NULLS is
OFF, comparisons such as ColumnA = NULL return TRUE when ColumnA
contains a null value and FALSE when ColumnA contains some value
besides NULL.
Taken from here.
If you don't want the NULL value you will need to add a WHERE clause to the sub query. For example:
SELECT emp_id, fname, lname, title
FROM employee
WHERE emp_id IN (SELECT superior_emp_id FROM employee WHERE superior_emp_id IS NOT NULL);

Related

Query that removes a duplicate entry using mysql

My target is to create a query that removes a duplicate entry in a row using MySQL 5.7. My current function matches entries with the same weight however, it has a duplicate. (Please, see the 2nd table)
Here is my entry table.
entryID| entryName | weight |
-------------------------------
1 | lamok2 | 1970 |
2 | lamok2 | 1955 |
3 | lamok3 | 1965 |
4 | lamok3 | 1975 |
5 | lamok3 | 1985 |
6 | lamok4 | 1955 |
7 | lamok4 | 1960 |
8 | lamok4 | 1975 |
9 | lamok5 | 1955 |
10 | MA1 | 2000 |
11 | MA2 | 2010 |
After applying my query which is:
SELECT t1.entryName AS mname, t1.weight AS weight,
MIN(t2.entryName) AS wname,MIN(t2.weight) AS weight
FROM entry t1
LEFT JOIN entry t2 ON t1.weight = t2.weight AND t1.entryName != t2.entryName
GROUP BY t1.entryID, t1.entryName, t1.weight
it produces this:
mname| weight | wname | weight|
--------------------------------------------
lamok2 | 1970 | NULL | NULL |
lamok2 | 1955 | lamok4 | 1955 |
lamok3 | 1965 | NULL | NULL |
lamok3 | 1975 | lamok4 | 1975 |
lamok3 | 1985 | NULL | NULL |
lamok4 | 1955 | lamok2 | 1955 |
lamok4 | 1960 | NULL | NULL |
lamok4 | 1975 | lamok3 | 1975 |
lamok5 | 1955 | lamok2 | 1955 |
MA1 | 2000 | NULL | NULL |
MA2 | 2010 | NULL | NULL |
As we can see on the result, Lamok 2 and Lamok 4 had a 2 matches Lamok 3 and Lamok 4 had a 2 matches too... It shouldn't be possible... This is counted as the duplicate fight. How can I avoid this kind of duplication in my query?
This is my target output: (No duplicate fights)
mname| weight | wname | weight|
--------------------------------------------
lamok2 | 1970 | NULL | NULL |
lamok2 | 1955 | lamok4 | 1955 |
lamok3 | 1965 | NULL | NULL |
lamok3 | 1975 | lamok4 | 1975 |
lamok3 | 1985 | NULL | NULL |
lamok4 | 1960 | NULL | NULL |
lamok5 | 1955 | lamok2 | 1955 |
MA1 | 2000 | NULL | NULL |
MA2 | 2010 | NULL | NULL |
I think that you need group by two columns, by entryName and weight.
So this query will return your duplicates:
SELECT *
FROM entry
GROUP BY entryName, weight
HAVING COUNT(*) > 1
When you find duplicated then you can delete them. If you want to delete them with query, then you can use something like this:
DELETE FROM entry
WHERE entryID IN (
SELECT e.entryID
FROM entry AS e
GROUP BY e.entryName, e.weight
HAVING COUNT(*) > 1
)

Is there a mySQL procedure that can merge duplicate rows of data into one, then allow me to manipulate that data as if it were one row?

I'm trying to come up with a stored procedure that takes multiple rows that are exactly identical, and combines them into one row while summing one column, which can then be run through more stored procedures based on the sum of that one column.
I've tried a GROUP BY statement, but that doesn't actually group the rows together, because if I run the table through another procedure it performs actions as if each row were not combined. Performing a SELECT * FROM mytable query shows that each row was not actually combined into one.
Is there any way to permanently combine multiple rows into one singular row?
To start, I've got a table like this:
+-------+-----+--------+---------+------+-----+-----------+
| RowID | pID | Name | Date | Code | QTY | Purchased |
+-------+-----+--------+---------+------+-----+-----------+
| 1 | 1 | bob | 9/29/20 | 123 | 1 | |
| 2 | 1 | bob | 8/10/20 | 456 | 1 | |
| 3 | 2 | rob | 9/15/20 | 123 | 1 | |
| 4 | 2 | rob | 9/15/20 | 123 | 1 | |
| 5 | 2 | rob | 9/15/20 | 123 | 1 | |
| 6 | 2 | rob | 9/15/20 | 123 | 1 | |
| 7 | 2 | rob | 9/15/20 | 123 | 1 | |
| 8 | 3 | john | 7/12/20 | 987 | 1 | |
| 9 | 3 | john | 7/12/20 | 987 | 1 | |
| 10 | 4 | george | 9/12/20 | 684 | 1 | |
| 11 | 5 | paul | 2/2/20 | 454 | 1 | |
| 12 | 6 | amy | 1/12/20 | 252 | 1 | |
| 13 | 7 | susan | 5/30/20 | 131 | 1 | |
| 14 | 7 | susan | 6/6/20 | 252 | 1 | |
| 15 | 7 | susan | 5/30/20 | 131 | 1 | |
+-------+-----+--------+---------+------+-----+-----------+
By the end, i'd like to have a table like this:
+-------+-----+--------+---------+------+-----+-----------+
| RowID | pID | Name | Date | Code | QTY | Purchased |
+-------+-----+--------+---------+------+-----+-----------+
| 1 | 1 | bob | 9/29/20 | 123 | 1 | |
| 2 | 1 | bob | 8/10/20 | 456 | 1 | |
| 3 | 2 | rob | 9/15/20 | 123 | 5 | |
| 4 | 3 | john | 7/12/20 | 987 | 2 | |
| 5 | 4 | george | 9/12/20 | 684 | 1 | |
| 6 | 5 | paul | 2/2/20 | 454 | 1 | |
| 7 | 6 | amy | 1/12/20 | 252 | 1 | |
| 8 | 7 | susan | 5/30/20 | 131 | 2 | |
| 9 | 7 | susan | 6/6/20 | 252 | 1 | |
+-------+-----+--------+---------+------+-----+-----------+
Where exactly identical rows are combined into one row, and the QTY field is summed, that I can then add purchases to, or make deductions from the quantity as a total. Using GROUP BY statements can achieve this, but when I go to alter the quantity or add purchases to each person, it treats it like the first table, as if nothing was actually grouped.
So you have this table:
| RowID | pID | Name | Date | Code | QTY | Purchased |
+-------+-----+--------+---------+------+-----+-----------+
| 1 | 1 | bob | 9/29/20 | 123 | 1 | |
| 2 | 1 | bob | 8/10/20 | 456 | 1 | |
| 3 | 2 | rob | 9/15/20 | 123 | 1 | |
| 4 | 2 | rob | 9/15/20 | 123 | 1 | |
| 5 | 2 | rob | 9/15/20 | 123 | 1 | |
| 6 | 2 | rob | 9/15/20 | 123 | 1 | |
| 7 | 2 | rob | 9/15/20 | 123 | 1 | |
| 8 | 3 | john | 7/12/20 | 987 | 1 | |
| 9 | 3 | john | 7/12/20 | 987 | 1 | |
| 10 | 4 | george | 9/12/20 | 684 | 1 | |
| 11 | 5 | paul | 2/2/20 | 454 | 1 | |
| 12 | 6 | amy | 1/12/20 | 252 | 1 | |
| 13 | 7 | susan | 5/30/20 | 131 | 1 | |
| 14 | 7 | susan | 6/6/20 | 252 | 1 | |
| 15 | 7 | susan | 5/30/20 | 131 | 1 | |
The best way, as has been suggested, is to create a new table with the content of your query, then to rename the old table, and the new table to the original table's name, to check if everything is all right, and to drop the original table if yes.
CREATE TABLE indata_new AS
WITH grp AS (
SELECT
MIN(rowid) AS orowid
, pid
, name
, MAX(date) AS date
, code
, SUM(qty) AS qty
FROM indata
GROUP BY
pid
, name
, code
)
SELECT
ROW_NUMBER() OVER(ORDER BY orowid ASC) AS rowid
, *
FROM grp;
ALTER TABLE indata RENAME TO indata_old;
ALTER TABLE indata_new RENAME TO indata;
-- if "indata" now contains the data you want ...
SELECT * FROM indata;
-- out rowid | orowid | pid | name | date | code | qty
-- out -------+--------+-----+--------+------------+------+-----
-- out 1 | 1 | 1 | bob | 2020-09-29 | 123 | 1
-- out 2 | 2 | 1 | bob | 2020-08-10 | 456 | 1
-- out 3 | 3 | 2 | rob | 2020-09-15 | 123 | 5
-- out 4 | 8 | 3 | john | 2020-07-12 | 987 | 2
-- out 5 | 10 | 4 | george | 2020-09-12 | 684 | 1
-- out 6 | 11 | 5 | paul | 2020-02-02 | 454 | 1
-- out 7 | 12 | 6 | amy | 2020-01-12 | 252 | 1
-- out 8 | 13 | 7 | susan | 2020-05-30 | 131 | 2
-- out 9 | 14 | 7 | susan | 2020-06-06 | 252 | 1
-- you can ...
DROP TABLE indata_old;

SQL NOT IN JOIN equivalent

Table:
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| emp_id | fname | lname | start_date | end_date | superior_emp_id | dept_id | title | assigned_branch_id |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| 1 | Michael | Smith | 2005-06-22 | NULL | NULL | 3 | President | 1 |
| 2 | Susan | Barker | 2006-09-12 | NULL | 1 | 3 | Vice President | 1 |
| 3 | Robert | Tyler | 2005-02-09 | NULL | 1 | 3 | Treasurer | 1 |
| 4 | Susan | Hawthorne | 2006-04-24 | NULL | 3 | 1 | Operations Manager | 1 |
| 5 | John | Gooding | 2007-11-14 | NULL | 4 | 2 | Loan Manager | 1 |
| 6 | Helen | Fleming | 2008-03-17 | NULL | 4 | 1 | Head Teller | 1 |
| 7 | Chris | Tucker | 2008-09-15 | NULL | 6 | 1 | Teller | 1 |
| 8 | Sarah | Parker | 2006-12-02 | NULL | 6 | 1 | Teller | 1 |
| 9 | Jane | Grossman | 2006-05-03 | NULL | 6 | 1 | Teller | 1 |
| 10 | Paula | Roberts | 2006-07-27 | NULL | 4 | 1 | Head Teller | 2 |
| 11 | Thomas | Ziegler | 2004-10-23 | NULL | 10 | 1 | Teller | 2 |
| 12 | Samantha | Jameson | 2007-01-08 | NULL | 10 | 1 | Teller | 2 |
| 13 | John | Blake | 2004-05-11 | NULL | 4 | 1 | Head Teller | 3 |
| 14 | Cindy | Mason | 2006-08-09 | NULL | 13 | 1 | Teller | 3 |
| 15 | Frank | Portman | 2007-04-01 | NULL | 13 | 1 | Teller | 3 |
| 16 | Theresa | Markham | 2005-03-15 | NULL | 4 | 1 | Head Teller | 4 |
| 17 | Beth | Fowler | 2006-06-29 | NULL | 16 | 1 | Teller | 4 |
| 18 | Rick | Tulman | 2006-12-12 | NULL | 16 | 1 | Teller | 4 |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------
Query using subquery:
-- Select employees that do not manage others
SELECT
emp_id,
fname,
lname,
title
FROM
employee
WHERE
emp_id NOT IN (
SELECT
superior_emp_id
FROM
employee
WHERE
superior_emp_id IS NOT NUll
);
Result:
+--------+----------+----------+----------------+
| emp_id | fname | lname | title |
+--------+----------+----------+----------------+
| 2 | Susan | Barker | Vice President |
| 5 | John | Gooding | Loan Manager |
| 7 | Chris | Tucker | Teller |
| 8 | Sarah | Parker | Teller |
| 9 | Jane | Grossman | Teller |
| 11 | Thomas | Ziegler | Teller |
| 12 | Samantha | Jameson | Teller |
| 14 | Cindy | Mason | Teller |
| 15 | Frank | Portman | Teller |
| 17 | Beth | Fowler | Teller |
| 18 | Rick | Tulman | Teller |
+--------+----------+----------+----------------+
The above query works fine but I am curious as to how to accomplish the same result using a join.
So far here is what I have but does not return the same results:
SELECT
e1.emp_id,
e1.fname,
e1.lname,
e1.title
FROM
employee e1
JOIN
employee e2 ON e1.emp_id != e2.superior_emp_id
GROUP BY
e1.emp_id;
Results:
+--------+----------+-----------+--------------------+
| emp_id | fname | lname | title |
+--------+----------+-----------+--------------------+
| 1 | Michael | Smith | President |
| 2 | Susan | Barker | Vice President |
| 3 | Robert | Tyler | Treasurer |
| 4 | Susan | Hawthorne | Operations Manager |
| 5 | John | Gooding | Loan Manager |
| 6 | Helen | Fleming | Head Teller |
| 7 | Chris | Tucker | Teller |
| 8 | Sarah | Parker | Teller |
| 9 | Jane | Grossman | Teller |
| 10 | Paula | Roberts | Head Teller |
| 11 | Thomas | Ziegler | Teller |
| 12 | Samantha | Jameson | Teller |
| 13 | John | Blake | Head Teller |
| 14 | Cindy | Mason | Teller |
| 15 | Frank | Portman | Teller |
| 16 | Theresa | Markham | Head Teller |
| 17 | Beth | Fowler | Teller |
| 18 | Rick | Tulman | Teller |
+--------+----------+-----------+--------------------+
The equivalent query uses a left join and where:
SELECT e.*
FROM employee e LEFT JOIN
employee es
ON es.superior_emp_id = e.id
WHERE es.id IS NULL;
This looks for a match on the superior_emp_id. However, you specifically don't want a match -- hence the LEFT JOIN and the WHERE which filters out any matches.
This would normally have better performance than your version. A comparable version would use NOT EXISTS.

IN operator caluse having a NULL value seems to work

I have an employee table:
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| emp_id | fname | lname | start_date | end_date | superior_emp_id | dept_id | title | assigned_branch_id |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| 1 | Michael | Smith | 2005-06-22 | NULL | NULL | 3 | President | 1 |
| 2 | Susan | Barker | 2006-09-12 | NULL | 1 | 3 | Vice President | 1 |
| 3 | Robert | Tyler | 2005-02-09 | NULL | 1 | 3 | Treasurer | 1 |
| 4 | Susan | Hawthorne | 2006-04-24 | NULL | 3 | 1 | Operations Manager | 1 |
| 5 | John | Gooding | 2007-11-14 | NULL | 4 | 2 | Loan Manager | 1 |
| 6 | Helen | Fleming | 2008-03-17 | NULL | 4 | 1 | Head Teller | 1 |
| 7 | Chris | Tucker | 2008-09-15 | NULL | 6 | 1 | Teller | 1 |
| 8 | Sarah | Parker | 2006-12-02 | NULL | 6 | 1 | Teller | 1 |
| 9 | Jane | Grossman | 2006-05-03 | NULL | 6 | 1 | Teller | 1 |
| 10 | Paula | Roberts | 2006-07-27 | NULL | 4 | 1 | Head Teller | 2 |
| 11 | Thomas | Ziegler | 2004-10-23 | NULL | 10 | 1 | Teller | 2 |
| 12 | Samantha | Jameson | 2007-01-08 | NULL | 10 | 1 | Teller | 2 |
| 13 | John | Blake | 2004-05-11 | NULL | 4 | 1 | Head Teller | 3 |
| 14 | Cindy | Mason | 2006-08-09 | NULL | 13 | 1 | Teller | 3 |
| 15 | Frank | Portman | 2007-04-01 | NULL | 13 | 1 | Teller | 3 |
| 16 | Theresa | Markham | 2005-03-15 | NULL | 4 | 1 | Head Teller | 4 |
| 17 | Beth | Fowler | 2006-06-29 | NULL | 16 | 1 | Teller | 4 |
| 18 | Rick | Tulman | 2006-12-12 | NULL | 16 | 1 | Teller | 4 |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
If I do
SELECT emp_id, fname, lname, title
FROM employee
WHERE emp_id IN (
SELECT superior_emp_id
FROM employee
);
I get:
+--------+---------+-----------+--------------------+
| emp_id | fname | lname | title |
+--------+---------+-----------+--------------------+
| 1 | Michael | Smith | President |
| 3 | Robert | Tyler | Treasurer |
| 4 | Susan | Hawthorne | Operations Manager |
| 6 | Helen | Fleming | Head Teller |
| 10 | Paula | Roberts | Head Teller |
| 13 | John | Blake | Head Teller |
| 16 | Theresa | Markham | Head Teller |
+--------+---------+-----------+--------------------+
There is a NULL value in the superior_emp_id column.
If the IN operator is equivalent to field=val1 OR field=val2 OR field=val3 OR field=null why does this query not fail or give some error?
It works just fine. You may be confusing in and not in.
If you run not in, then you will get no results. Why? Well, consider 1 not in (2, 3). That evaluates to true. No-brainer.
Then 2 not in (2, 3). That evaluates to false. Just as it should.
But . . . what about these two:
2 not in (2, 3, NULL)
1 not in (2, 3, NULL)
The first is false, because "2" is indeed in the list. The second is . . . well, NULL is not a value. It means "unknown". So, it could be "1" or something else. Hence, it evaluates to NULL. And NULL is treated the same as false in a where.
You can work it out, but this does not occur with in. For instance, in your case, NULL or 1=1 evaluates to true.
For this reason, I strongly recommend always using not exists rather than not in with a subquery. A corollary is that I recommend exists rather than in, but just so the habit of using exists is engrained.
x=NULL predicate evaluates to UNKNOWN
With regards to the OR operator:
TRUE OR UNKNOWN = TRUE
FALSE OR UNKNOWN = UNKNOWN (which is treated as a FALSE in the WHERE clause)
see: https://en.wikipedia.org/wiki/Three-valued_logic

SQL using two subqueries

I am learning about SQL subqueries. Here is the subquery I am using from the book:
SELECT account_id, product_cd, cust_id
FROM account
WHERE open_branch_id = (
SELECT branch_id
FROM branch
WHERE name = 'Woburn Branch'
) AND open_emp_id IN (
SELECT emp_id
FROM employee
WHERE title = 'Teller' OR title = 'Head Teller'
);
Result:
+------------+------------+---------+
| account_id | product_cd | cust_id |
+------------+------------+---------+
| 1 | CHK | 1 |
| 2 | SAV | 1 |
| 3 | CD | 1 |
| 4 | CHK | 2 |
| 5 | SAV | 2 |
| 17 | CD | 7 |
| 27 | BUS | 11 |
+------------+------------+---------+
I have looked over this query trying to interpret it and understand it as well as the reasoning behind it's clauses but, I fail to understand the reason for the last AND clause AND open_emp_id IN...
I noticed that with just
SELECT account_id, product_cd, cust_id
FROM account
WHERE open_branch_id = (
SELECT branch_id
FROM branch
WHERE name = 'Woburn Branch'
)
You get the same result as above. Can anyone explain to me the reasoning behind the last AND open_emp_id IN clause and how omitting it would affect the final result?
Tables used in subquery:
Account table
+------------+------------+---------+------------+------------+--------------------+--------+----------------+-------------+---------------+-----------------+
| account_id | product_cd | cust_id | open_date | close_date | last_activity_date | status | open_branch_id | open_emp_id | avail_balance | pending_balance |
+------------+------------+---------+------------+------------+--------------------+--------+----------------+-------------+---------------+-----------------+
| 1 | CHK | 1 | 2000-01-15 | NULL | 2005-01-04 | ACTIVE | 2 | 10 | 1057.75 | 1057.75 |
| 2 | SAV | 1 | 2000-01-15 | NULL | 2004-12-19 | ACTIVE | 2 | 10 | 500.00 | 500.00 |
| 3 | CD | 1 | 2004-06-30 | NULL | 2004-06-30 | ACTIVE | 2 | 10 | 3000.00 | 3000.00 |
| 4 | CHK | 2 | 2001-03-12 | NULL | 2004-12-27 | ACTIVE | 2 | 10 | 2258.02 | 2258.02 |
| 5 | SAV | 2 | 2001-03-12 | NULL | 2004-12-11 | ACTIVE | 2 | 10 | 200.00 | 200.00 |
| 7 | CHK | 3 | 2002-11-23 | NULL | 2004-11-30 | ACTIVE | 3 | 13 | 1057.75 | 1057.75 |
| 8 | MM | 3 | 2002-12-15 | NULL | 2004-12-05 | ACTIVE | 3 | 13 | 2212.50 | 2212.50 |
| 10 | CHK | 4 | 2003-09-12 | NULL | 2005-01-03 | ACTIVE | 1 | 1 | 534.12 | 534.12 |
| 11 | SAV | 4 | 2000-01-15 | NULL | 2004-10-24 | ACTIVE | 1 | 1 | 767.77 | 767.77 |
| 12 | MM | 4 | 2004-09-30 | NULL | 2004-11-11 | ACTIVE | 1 | 1 | 5487.09 | 5487.09 |
| 13 | CHK | 5 | 2004-01-27 | NULL | 2005-01-05 | ACTIVE | 4 | 16 | 2237.97 | 2897.97 |
| 14 | CHK | 6 | 2002-08-24 | NULL | 2004-11-29 | ACTIVE | 1 | 1 | 122.37 | 122.37 |
| 15 | CD | 6 | 2004-12-28 | NULL | 2004-12-28 | ACTIVE | 1 | 1 | 10000.00 | 10000.00 |
| 17 | CD | 7 | 2004-01-12 | NULL | 2004-01-12 | ACTIVE | 2 | 10 | 5000.00 | 5000.00 |
| 18 | CHK | 8 | 2001-05-23 | NULL | 2005-01-03 | ACTIVE | 4 | 16 | 3487.19 | 3487.19 |
| 19 | SAV | 8 | 2001-05-23 | NULL | 2004-10-12 | ACTIVE | 4 | 16 | 387.99 | 387.99 |
| 21 | CHK | 9 | 2003-07-30 | NULL | 2004-12-15 | ACTIVE | 1 | 1 | 125.67 | 125.67 |
| 22 | MM | 9 | 2004-10-28 | NULL | 2004-10-28 | ACTIVE | 1 | 1 | 9345.55 | 9845.55 |
| 23 | CD | 9 | 2004-06-30 | NULL | 2004-06-30 | ACTIVE | 1 | 1 | 1500.00 | 1500.00 |
| 24 | CHK | 10 | 2002-09-30 | NULL | 2004-12-15 | ACTIVE | 4 | 16 | 23575.12 | 23575.12 |
| 25 | BUS | 10 | 2002-10-01 | NULL | 2004-08-28 | ACTIVE | 4 | 16 | 0.00 | 0.00 |
| 27 | BUS | 11 | 2004-03-22 | NULL | 2004-11-14 | ACTIVE | 2 | 10 | 9345.55 | 9345.55 |
| 28 | CHK | 12 | 2003-07-30 | NULL | 2004-12-15 | ACTIVE | 4 | 16 | 38552.05 | 38552.05 |
| 29 | SBL | 13 | 2004-02-22 | NULL | 2004-12-17 | ACTIVE | 3 | 13 | 50000.00 | 50000.00 |
+------------+------------+---------+------------+------------+--------------------+--------+----------------+-------------+---------------+-----------------+
Branch table:
+-----------+---------------+----------------------+---------+-------+-------+
| branch_id | name | address | city | state | zip |
+-----------+---------------+----------------------+---------+-------+-------+
| 1 | Headquarters | 3882 Main St. | Waltham | MA | 02451 |
| 2 | Woburn Branch | 422 Maple St. | Woburn | MA | 01801 |
| 3 | Quincy Branch | 125 Presidential Way | Quincy | MA | 02169 |
| 4 | So. NH Branch | 378 Maynard Ln. | Salem | NH | 03079 |
+-----------+---------------+----------------------+---------+-------+-------+
Employee table:
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| emp_id | fname | lname | start_date | end_date | superior_emp_id | dept_id | title | assigned_branch_id |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
| 1 | Michael | Smith | 2005-06-22 | NULL | NULL | 3 | President | 1 |
| 2 | Susan | Barker | 2006-09-12 | NULL | 1 | 3 | Vice President | 1 |
| 3 | Robert | Tyler | 2005-02-09 | NULL | 1 | 3 | Treasurer | 1 |
| 4 | Susan | Hawthorne | 2006-04-24 | NULL | 3 | 1 | Operations Manager | 1 |
| 5 | John | Gooding | 2007-11-14 | NULL | 4 | 2 | Loan Manager | 1 |
| 6 | Helen | Fleming | 2008-03-17 | NULL | 4 | 1 | Head Teller | 1 |
| 7 | Chris | Tucker | 2008-09-15 | NULL | 6 | 1 | Teller | 1 |
| 8 | Sarah | Parker | 2006-12-02 | NULL | 6 | 1 | Teller | 1 |
| 9 | Jane | Grossman | 2006-05-03 | NULL | 6 | 1 | Teller | 1 |
| 10 | Paula | Roberts | 2006-07-27 | NULL | 4 | 1 | Head Teller | 2 |
| 11 | Thomas | Ziegler | 2004-10-23 | NULL | 10 | 1 | Teller | 2 |
| 12 | Samantha | Jameson | 2007-01-08 | NULL | 10 | 1 | Teller | 2 |
| 13 | John | Blake | 2004-05-11 | NULL | 4 | 1 | Head Teller | 3 |
| 14 | Cindy | Mason | 2006-08-09 | NULL | 13 | 1 | Teller | 3 |
| 15 | Frank | Portman | 2007-04-01 | NULL | 13 | 1 | Teller | 3 |
| 16 | Theresa | Markham | 2005-03-15 | NULL | 4 | 1 | Head Teller | 4 |
| 17 | Beth | Fowler | 2006-06-29 | NULL | 16 | 1 | Teller | 4 |
| 18 | Rick | Tulman | 2006-12-12 | NULL | 16 | 1 | Teller | 4 |
+--------+----------+-----------+------------+----------+-----------------+---------+--------------------+--------------------+
The AND open_emp_id IN clause is selecting only the employee ids whose title is 'Teller' or 'Head Teller'. In your case the result is not different because you are looking for branch with the name 'Woburn Branch', and it is just a coincidence that all the employees there are of title 'Teller' or 'Head Teller' only.
| 10 | Paula | Roberts | 2006-07-27 | NULL | 4 | 1 | Head Teller | 2 |
| 11 | Thomas | Ziegler | 2004-10-23 | NULL | 10 | 1 | Teller | 2 |
| 12 | Samantha | Jameson | 2007-01-08 | NULL | 10 | 1 | Teller | 2 |
Change the branch name to 'Headquarters' in the first query, and you will see the difference that the subquery makes. Any employees at the 'Headquarters' who do not have title 'Teller' or 'Head Teller' will get excluded.
The last AND narrows the selection criteria to include only accounts that were open by a Teller or Head Teller. If the Loan Manager or Operations Manager had opened the account it would be excluded.