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.