Examples of recursive common table expressions
Recursive SQL is very useful in bill of materials (BOM) applications.
Consider a table of parts with associated subparts and the quantity of subparts required by each part. For more information about recursive SQL, refer to Creating recursive SQL by using common table expressions.
For the examples in this topic, create the following table:
CREATE TABLE PARTLIST
(PART VARCHAR(8),
SUBPART VARCHAR(8),
QUANTITY INTEGER);
Assume that the PARTLIST table is populated with the values that are in the following table:
PART | SUBPART | QUANTITY |
---|---|---|
00 | 01 | 5 |
00 | 05 | 3 |
01 | 02 | 2 |
01 | 03 | 3 |
01 | 04 | 4 |
01 | 06 | 3 |
02 | 05 | 7 |
02 | 06 | 6 |
03 | 07 | 6 |
04 | 08 | 10 |
04 | 09 | 11 |
05 | 10 | 10 |
05 | 11 | 10 |
06 | 12 | 10 |
06 | 13 | 10 |
07 | 14 | 8 |
07 | 12 | 8 |
Example 1: Single level explosion:
Single level explosion answers the question, "What parts are needed to build the part identified by '01'?". The list will include the direct subparts, subparts of the subparts and so on. However, if a part is used multiple times, its subparts are only listed once.
WITH RPL (PART, SUBPART, QUANTITY) AS
(SELECT ROOT.PART, ROOT.SUBPART, ROOT.QUANTITY
FROM PARTLIST ROOT
WHERE ROOT.PART = '01'
UNION ALL
SELECT CHILD.PART, CHILD.SUBPART, CHILD.QUANTITY
FROM RPL PARENT, PARTLIST CHILD
WHERE PARENT.SUBPART = CHILD.PART)
SELECT DISTINCT PART, SUBPART, QUANTITY
FROM RPL
ORDER BY PART, SUBPART, QUANTITY;
The preceding query includes a common table expression, identified by the name RPL, that expresses the recursive part of this query. It illustrates the basic elements of a recursive common table expression.
The first operand (fullselect) of the UNION, referred to as the initialization fullselect, gets the direct subparts of part '01'. The FROM clause of this fullselect refers to the source table and will never refer to itself (RPL in this case). The result of this first fullselect goes into the common table expression RPL. As in this example, the UNION must always be a UNION ALL.
The second operand (fullselect) of the UNION uses RPL to compute subparts of subparts by using the FROM clause to refer to the common table expression RPL and the source table PARTLIST with a join of a part from the source table (child) to a subpart of the current result contained in RPL (parent). The result goes then back to RPL again. The second operand of UNION is used repeatedly until no more subparts exist.
The SELECT DISTINCT in the main fullselect of this query ensures the same part/subpart is not listed more than once.
The result of the query is shown in the following table:
PART | SUBPART | QUANTITY |
---|---|---|
01 | 02 | 2 |
01 | 03 | 3 |
01 | 04 | 4 |
01 | 06 | 3 |
02 | 05 | 7 |
02 | 06 | 6 |
03 | 07 | 6 |
04 | 08 | 10 |
04 | 09 | 11 |
05 | 10 | 10 |
05 | 11 | 10 |
06 | 12 | 10 |
06 | 13 | 10 |
07 | 12 | 8 |
07 | 14 | 8 |
Observe in the result that part '01' contains subpart '02' which contains subpart '06' and so on. Further, notice that part '06' is reached twice, once through part '01' directly and another time through part '02'. In the output, however, the subparts of part '06' are listed only once (this is the result of using a SELECT DISTINCT).
Remember that with recursive common table expressions it is possible to introduce an infinite loop. In this example, an infinite loop would be created if the search condition of the second operand that joins the parent and child tables was coded as follows:
WHERE PARENT.SUBPART = CHILD.SUBPART
This infinite loop is created by not coding what is intended. You should carefully determining what to code so that there is a definite end of the recursion cycle.
The result produced by this example could be produced in an application program without using a recursive common table expression. However, such an application would require coding a different query for every level of recursion. Furthermore, the application would need to put all of the results back in the database to order the final result. This approach complicates the application logic and does not perform well. The application logic becomes more difficult and inefficient for other bill of material queries, such as summarized and indented explosion queries.
Example 2: Summarized explosion:
A summarized explosion answers the question, "What is the total quantity of each part required to build part '01'?" The main difference from a single level explosion is the need to aggregate the quantities. A single level explosion indicates the quantity of subparts required for the part whenever it is required. It does not indicate how many of each subpart is needed to build part '01'.
WITH RPL (PART, SUBPART, QUANTITY) AS
(
SELECT ROOT.PART, ROOT.SUBPART, ROOT.QUANTITY
FROM PARTLIST ROOT
WHERE ROOT.PART = '01'
UNION ALL
SELECT PARENT.PART, CHILD.SUBPART,
PARENT.QUANTITY*CHILD.QUANTITY
FROM RPL PARENT, PARTLIST CHILD
WHERE PARENT.SUBPART = CHILD.PART
)
SELECT PART, SUBPART, SUM(QUANTITY) AS "Total QTY Used"
FROM RPL
GROUP BY PART, SUBPART
ORDER BY PART, SUBPART;
In the preceding query, the select list of the second operand of the UNION in the recursive common table expression, identified by the name RPL, shows the aggregation of the quantity. To determine how many of each subpart is used, the quantity of the parent is multiplied by the quantity per parent of a child. If a part is used multiple times in different places, it requires another final aggregation. This is done by the grouping the parts and subparts in the common table expression RPL and using the SUM column function in the select list of the main fullselect.
The result of the query is shown in the following table:
PART | SUBPART | Total QTY Used |
---|---|---|
01 | 02 | 2 |
01 | 03 | 3 |
01 | 04 | 4 |
01 | 05 | 14 |
01 | 06 | 15 |
01 | 07 | 18 |
01 | 08 | 40 |
01 | 09 | 44 |
01 | 10 | 140 |
01 | 11 | 140 |
01 | 12 | 294 |
01 | 13 | 150 |
01 | 14 | 144 |
Consider the total quantity for subpart '06'. The value of 15 is derived from a quantity of 3 directly for part '01' and a quantity of 6 for part '02' which is needed two times by part '01'.
Example 3: Controlling depth:
You can control the depth of a recursive query to answer the question, "What are the first two levels of parts that are needed to build part '01'?" For the sake of clarity in this example, the level of each part is included in the result table.
WITH RPL (LEVEL, PART, SUBPART, QUANTITY) AS
(
SELECT 1, ROOT.PART, ROOT.SUBPART, ROOT.QUANTITY
FROM PARTLIST ROOT
WHERE ROOT.PART = '01'
UNION ALL
SELECT PARENT.LEVEL+1, CHILD.PART, CHILD.SUBPART, CHILD.QUANTITY
FROM RPL PARENT, PARTLIST CHILD
WHERE PARENT.SUBPART = CHILD.PART
AND PARENT.LEVEL < 2
)
SELECT PART, LEVEL, SUBPART, QUANTITY
FROM RPL;
This query is similar to the query in example 1. The column LEVEL is introduced to count the level each subpart is from the original part. In the initialization fullselect, the value for the LEVEL column is initialized to 1. In the subsequent fullselect, the level from the parent table increments by 1. To control the number of levels in the result, the second fullselect includes the condition that the level of the parent must be less than 2. This ensures that the second fullselect only processes children to the second level.
The result of the query is shown in the following table:
PART | LEVEL | SUBPART | QUANTITY |
---|---|---|---|
01 | 1 | 02 | 2 |
01 | 1 | 03 | 3 |
01 | 1 | 04 | 4 |
01 | 1 | 06 | 3 |
02 | 2 | 05 | 7 |
02 | 2 | 06 | 6 |
03 | 2 | 07 | 6 |
04 | 2 | 08 | 10 |
04 | 2 | 09 | 11 |
06 | 2 | 12 | 10 |
06 | 2 | 13 | 10 |