Subqueries in Access
A subquery is essentially a query within a query. A subquery is a SELECT within a select (also called nested, sub, and inner). When the SELECT is executed, the inner SELECT is executed and the results are used to resolve the outer SELECT. Subqueries can be based on multiple tables where the result of the query on one table are used to extract data from another query. Subqueries can also be based on the same table when you want to select data in the table based on an initial conditional check of the table. The subquery can be located in a FROM, WHERE or HAVING clause and can use the tests of >, <, = , !=, >=, <=, ANY, ALL, IN. The structure of the subquery calls for the inner query which is resolved first to be enclosed in parenthesis. It is also important to note that the value returned from the inner select is the value that the outer select is testing on - the code must reflect this fact. The examples below show a variety of uses of the select within a select.
In the first example, the inner select will return the DyrFirst for John Doe. Note that the way this subquery is constructred it would not work if there were multiple people in the table with the last name of Doe - more about that in the next example.
The inner select finds the person with the name of John Doe and returns the 1995 which is the date of the first contribution (DYRFirst). Then the outer select shows the fields asked for where the DyrFirst on the record is greater than the 1995 that was returned from the inner query.
SELECT DIdno, DName, DYrFirst, Dcontact FROM newdonor WHERE DYrFirst > (SELECT DYrFirst FROM newdonor WHERE DName LIKE '*Doe');
DIdno |
DName |
DYrFirst |
DContact |
22222 |
Mary Wilson |
1996 |
David Costa |
44444 |
Robert Brooks |
1996 |
Roger Brown |
The table below shows the entire newdonor table for comparison purposes.
DIdno |
DName |
DStAdr |
DCity |
DState |
DZip |
DYrFirst |
DContact |
11111 |
John Doe |
123 Elm St |
Braintree |
MA |
02184 |
1995 |
Ann Smith |
22222 |
Mary Wilson |
14 Main St |
Fall River |
MA |
02770 |
1996 |
David Costa |
33333 |
Nancy Taylor |
1 Heritage Rd |
New Bedford |
MA |
02775 |
1994 |
Ann Smith |
44444 |
Robert Brooks |
45 East St |
Weymouth |
MA |
02176 |
1996 |
Roger Brown |
For the following query, I used the newdonor table after I had added a record to it. The table as it now appears is shown below:
DIdno |
DName |
DStAdr |
DCity |
DState |
DZip |
DYrFirst |
DContact |
11111 |
John Doe |
123 Elm St |
Braintree |
MA |
02184 |
1995 |
Ann Smith |
22222 |
Mary Wilson |
14 Main St |
Fall River |
MA |
02770 |
1996 |
David Costa |
33333 |
Nancy Taylor |
1 Heritage Rd |
New Bedford |
MA |
02775 |
1994 |
Ann Smith |
44444 |
Robert Brooks |
45 East St |
Weymouth |
MA |
02176 |
1996 |
Roger Brown |
55555 |
Susan Ash |
45 Elsbree St |
Fall River |
MA |
02720 |
1995 |
David Costa |
This select statement resolves the inner select first by coming back with the DyrFirst for all donors that have a contact person of Ann Smith. Because multiple years will be returned, I need to use the IN when I code the WHERE clause as opposed to the greater than sign that I used in the previous code.
The inner select will return 1995 and 1994.
The outer select then returns the records where the date on the record matches one of these two numbers. The results are shown below.
SELECT DIdno, DName, DYrFirst, DContact FROM newdonor WHERE DYrFirst IN (SELECT DYrFirst FROM newdonor WHERE DContact = 'Ann Smith');
DIdno |
DName |
DYrFirst |
DContact |
11111 |
John Doe |
1995 |
Ann Smith |
33333 |
Nancy Taylor |
1994 |
Ann Smith |
55555 |
Susan Ash |
1995 |
David Costa |
In this example, the inner query returns the average contribution amount. The outer query than shows all of the donations that were greater than the average. The average is 665 so the results will show all donations above that amount.
SELECT DIdno, DDriveNo, DAmtCont FROM newdonation WHERE DAmtCont >= (SELECT AVG(DAmtCont) FROM newdonation);
DIdno |
DDriveNo |
DAmtCont |
11111 |
123 |
1000 |
11111 |
123 |
1200 |
33333 |
124 |
1000 |
44444 |
124 |
1000 |
In the next example, I am going to use two tables. One in the inner select or subquery and one in the outer select. In this example, the inner select returns the identification numbers (Didno) from the newdonation table for all donors who gave a contribution greater than or equal to 1000. Then the outer select showed the identification number, name and state for the donors with that identification number.
SELECT DIdno, DName, DState FROM newdonor WHERE DIdno IN (SELECT DIdno FROM newdonation WHERE DAmtCont >= 1000);
DIdno |
DName |
DState |
11111 |
John Doe |
MA |
33333 |
Nancy Taylor |
MA |
44444 |
Robert Brooks |
MA |
To prove that this worked, I will show the donation table below. The donor table appears in the example above.
DIdno |
DDriveNo |
DDateCont |
DAmtCont |
11111 |
123 |
1/5/00 |
1000 |
11111 |
123 |
7/5/00 |
1200 |
11111 |
124 |
5/14/00 |
500 |
22222 |
123 |
5/15/00 |
500 |
33333 |
123 |
2/20/00 |
600 |
33333 |
124 |
1/12/00 |
1000 |
33333 |
125 |
5/5/00 |
100 |
44444 |
123 |
1/1/00 |
500 |
44444 |
124 |
5/1/00 |
1000 |
44444 |
125 |
8/1/00 |
250 |
Looking at this table we can see that the donations greater than or equal to 1000 were given by donor 11111, 33333, and 44444. Donors 22222 and 55555 did not meet the criteria. The answer shows the requested information about these donors.
In this example, the inner select returns the average donation (which I calculated at 665).
I am now asking to see the driveno and the sum of the contributions for all drive groups where the sum is greater than the average. The sum for 125 is 350 so it does not return. The sum of drives 123 and 124 are both greater than the average, so they are listed in the results.
SELECT DDriveNo, SUM(DAmtCont) AS [Sum Contr] FROM newdonation GROUP BY DDriveNo HAVING SUM(DAmtCont) > (SELECT AVG(DAmtCont) FROM newdonation);
DDriveNo |
Sum Contr |
123 |
3800 |
124 |
2500 |
As an aside, note that the name that I group by is listed in the outer select along with the results of a group function. If I had grouped by two things then both of those things could have been listed. If you group by two things, the first thing is the major grouping and the second thing is the minor grouping.
For the next examples I decided that I need more data in the new donation table. The results of these additions is the table shown below.
DIdno |
DName |
DStAdr |
DCity |
DState |
DZip |
DYrFirst |
DContact |
11111 |
John Doe |
123 Elm St |
Braintree |
MA |
02184 |
1995 |
Ann Smith |
22222 |
Mary Wilson |
14 Main St |
Fall River |
MA |
02770 |
1996 |
David Costa |
33333 |
Nancy Taylor |
1 Heritage Rd |
New Bedford |
MA |
02775 |
1994 |
Ann Smith |
44444 |
Robert Brooks |
45 East St |
Weymouth |
MA |
02176 |
1996 |
Roger Brown |
55555 |
Susan Ash |
45 Elsbree St |
Fall River |
MA |
02720 |
1995 |
David Costa |
66666 |
James Souza |
23 East St |
Providence |
RI |
03456 |
1996 |
Jill Ryan |
77777 |
Linda Hall |
126 Oak St |
Braintree |
MA |
02184 |
1994 |
Roger Brown |
88888 |
Stephen York |
45 Pine St |
Providence |
RI |
03456 |
1996 |
Jill Ryan |
The next example has an AND in the WHERE clause so there are two inner queries or subqueries. In analizing the results, I will show the results of each of the inner queries below.
SELECT DIDno, DName, DCity, DState, DYrFirst, DContact FROM newdonor WHERE DYrFirst IN (SELECT DYrFirst FROM newdonor WHERE DContact = "David Costa") AND DState IN (SELECT DState FROM newdonor WHERE DCity = "Providence" OR DCity = "Fall River");
DIDno |
DName |
DCity |
DState |
DYrFirst |
DContact |
11111 |
John Doe |
Braintree |
MA |
1995 |
Ann Smith |
22222 |
Mary Wilson |
Fall River |
MA |
1996 |
David Costa |
44444 |
Robert Brooks |
Weymouth |
MA |
1996 |
Roger Brown |
55555 |
Susan Ash |
Fall River |
MA |
1995 |
David Costa |
66666 |
James Souza |
Providence |
RI |
1996 |
Jill Ryan |
88888 |
Stephen York |
Providence |
RI |
1996 |
Jill Ryan |
The first inner query returns 1996 or 1995 and the second select returns MA and RI. This means the outer query is return information where the year is 1995 or 1995 and the state is MA or RI. This would have been a better test if there was a state other than MA or RI in the table.
SELECT DYrFirst FROM newdonor WHERE DContact = "David Costa";
DYrFirst |
1996 |
1995 |
SELECT DState FROM newdonor WHERE DCity = "Providence" OR DCity = "Fall River");
DState |
MA |
MA |
RI |
RI |
The next example uses a compound subquery with an OR.
SELECT DIDno, DName, DCity, DState, DYrFirst, DContact FROM newdonor WHERE DYrFirst IN (SELECT DYrFirst FROM newdonor WHERE DContact = "Ann Smith") OR DState IN (SELECT DState FROM newdonor WHERE DCity = "Providence");
DIDno |
DName |
DCity |
DState |
DYrFirst |
DContact |
11111 |
John Doe |
Braintree |
MA |
1995 |
Ann Smith |
33333 |
Nancy Taylor |
New Bedford |
MA |
1994 |
Ann Smith |
55555 |
Susan Ash |
Fall River |
MA |
1995 |
David Costa |
66666 |
James Souza |
Providence |
RI |
1996 |
Jill Ryan |
77777 |
Linda Hall |
Braintree |
MA |
1994 |
Roger Brown |
88888 |
Stephen York |
Providence |
RI |
1996 |
Jill Ryan |
To analyze this, I will look at each of the inner selects separately.
SELECT DYrFirst FROM newdonor WHERE DContact = "Ann Smith";
DYrFirst |
1995 |
1994 |
SELECT DState FROM newdonor WHERE DCity = "Providence";
DState |
RI |
RI |
The results of the query are therefore all records that have a DYrFirst of 1994 or 1995 OR a DState of RI. Looking at the output, the only records that are 1996 are from RI so it worked!
This example uses ALL before the inner select.
SELECT DIdno, DDriveNo, DDateCont, DAmtCont FROM newdonation WHERE DAmtCont > ALL (SELECT MIN(DAmtCont) FROM newdonation GROUP BY DDriveNo);
DIdno |
DDriveNo |
DDateCont |
DAmtCont |
11111 |
123 |
1/5/00 |
1000 |
11111 |
123 |
7/5/00 |
1200 |
33333 |
123 |
2/20/00 |
600 |
33333 |
124 |
1/12/00 |
1000 |
44444 |
124 |
5/1/00 |
1000 |
In this example, the inner select comes up with the minimum contribution by driveno. This is shown as a separate query below.
SELECT MIN(DAmtCont) FROM newdonation GROUP BY DDriveNo;
Expr1000 |
500 |
500 |
100 |
Note that the minimums are 500, 500 and 100. The outer select then shows contributions that are greater than all of those minimums. Anything 500 or below does not show.
The next example uses the ANY instead of the ALL in the select.
SELECT DIdno, DDriveNo, DDateCont, DAmtCont FROM newdonation WHERE DAmtCont > ANY (SELECT MIN(DAmtCont) FROM newdonation GROUP BY DDriveNo);
DIdno |
DDriveNo |
DDateCont |
DAmtCont |
11111 |
123 |
1/5/00 |
1000 |
11111 |
123 |
7/5/00 |
1200 |
11111 |
124 |
5/14/00 |
500 |
22222 |
123 |
5/15/00 |
500 |
33333 |
123 |
2/20/00 |
600 |
33333 |
124 |
1/12/00 |
1000 |
44444 |
123 |
1/1/00 |
500 |
44444 |
124 |
5/1/00 |
1000 |
44444 |
125 |
8/1/00 |
250 |
Again the inner select returned 500, 500 and 100. The only contribution that was not greater than these was the 100 contribution.
In the example below, I modified it slight to remove the 100 from the list. After the groups were formed I excluded any group that had a minimum that wasn't greater than 250 before the numbers were returned to the outer select.
SELECT DIdno, DDriveNo, DDateCont, DAmtCont FROM newdonation WHERE DAmtCont > ANY (SELECT MIN(DAmtCont) FROM newdonation GROUP BY DDriveNo HAVING MIN(DAmtCont) > 250);
DIdno |
DDriveNo |
DDateCont |
DAmtCont |
11111 |
123 |
1/5/00 |
1000 |
11111 |
123 |
7/5/00 |
1200 |
33333 |
123 |
2/20/00 |
600 |
33333 |
124 |
1/12/00 |
1000 |
44444 |
124 |
5/1/00 |
1000 |
The results of this query show only contributions greater than the 500 that was returned.