# Recursive SQL for Network Analysis, and Duality

In March 2013 I wrote an article on the use of SQL to group network-structured records into their distinct connected subnetworks, SQL for Network Grouping. I looked at two solution approaches commonly put forward on Oracle forums for these types of problem, using Oracle's Connect By recursion, and the more recent recursive subquery factoring, and also put forward a new solution of my own using the Model clause. I noted however that SQL solutions are generally very inefficent compared with a good PL/SQL solution, such as I posted here, PL/SQL Pipelined Function for Network Analysis. For the first two methods, I noted:

1. Non-hierarchical networks have no root nodes, so the traversal needs to be repeated from every node in the network set
2. Hierarchical queries retrieve all possible routes through a network

I also noted that Connect By is more inefficient than recursive subquery factoring, but did not say why, promising a more detailed explanation at a later date. In this article I illustrate the behaviour of both recursive SQL methods through a series of five elementary networks, followed by a simple combination of the five. I then use the foreign key network from Oracle's HR demo (v12 version, with OE and PM schemas included) as a final example.

In this article I consider traversal of a single connected network from a given root node (or several if each root node is specified).

It is shown that the behaviour of Connect By can be understood best by considering it to traverse all paths through a network that is dual to the original network.

Dual Networks

Dual network definition

The dual network consists of a set of nodes and links (d-nodes and d-links say) defined thus:

• the d-nodes correspond to each link in the original network that is adjacent (via a node) to at least one other link, including itself if its start and end nodes are the same

Dual network SQL

The d-node identifiers are just the link identifiers, while the d-link identifiers use the adjacency-defining node identifiers with a sequential number (partitioned by node) attached.

```WITH dist_links AS (
SELECT	DISTINCT CASE WHEN lin_2.node_fr IN (lin_1.node_fr, lin_1.node_to) THEN lin_2.node_fr ELSE lin_2.node_to END link_node,
lin_1.id node_fr_d,
lin_2.id node_to_d
ON lin_2.node_fr IN (lin_1.node_fr, lin_1.node_to)
OR lin_2.node_to IN (lin_1.node_fr, lin_1.node_to)
WHERE lin_2.id >= lin_1.id
AND (lin_2.id != lin_1.id OR lin_2.node_fr = lin_1.node_to)
)
ORDER BY node_fr_d, node_to_d) || '-' || Substr (link_node, -1),
node_fr_d,
node_to_d
```

Dual network characteristics

Dual networks defined as above are generally larger than the original networks and are usually more heavily looped, which explains the inferior performance of Connect by compared with recursive subquery factor solutions. The PL/SQL solution mentioned above, while traversing the entire network, does not traverse all possible routes through it and its performance is thus not adversely affected by the degree of looping.

SQL Queries

The recursive SQL queries return all routes through the network from the roots supplied. In my attached script I also have versions that filter out repeated links. The pipelined function query returns a single, exhaustive route through the network, distinguishing a set of tree links from loop-closing links; it also returns all subnetworks without requiring input roots.

Pipelined Function Query (PLF)

See PL/SQL Pipelined Function for Network Analysis for the Pl/SQL function.

```SELECT root_node_id             "Network",
Count (DISTINCT node_id) OVER (PARTITION BY root_node_id) "#Nodes",
LPad (dirn || ' ', 2*node_level, ' ') || node_id || loop_flag "Node",
node_level               "Lev"
FROM TABLE (Net_Pipe.All_Nets)
ORDER BY line_no
```

Recursive Subquery Factor Query (RSF)

```WITH rsf (node_id, prefix, id, lev) AS (
SELECT node_id, '', 'ROOT_' || Substr (node_id, 4, 1), 0
FROM nodes_v
WHERE Substr (node_id, 2, 1) = '1'
UNION ALL
SELECT CASE WHEN l.node_id_to = r.node_id THEN l.node_id_fr ELSE l.node_id_to END,
CASE WHEN l.node_id_fr = l.node_id_to THEN '= ' WHEN l.node_id_fr = r.node_id THEN '> ' ELSE '< ' END,
FROM rsf r
ON (l.node_id_fr = r.node_id OR l.node_id_to = r.node_id)
AND l.link_id != Nvl (r.id, '0')
) SEARCH DEPTH FIRST BY node_id SET line_no
CYCLE node_id SET is_cycle TO '*' DEFAULT ' '
SELECT LPad (r.prefix || ' ', 2*r.lev) || r.node_id || is_cycle "Node",
line_no
FROM rsf r
ORDER BY line_no
```

Connect By Query (CBY)

```SELECT node_id_fr || ' > ' || node_id_to  "Nodes",
LPad (' ', 2 * (LEVEL-1)) || link_id || CASE WHEN CONNECT_BY_ISCYCLE = 1 THEN '*' ELSE ' ' END "Link Path"
CONNECT BY NOCYCLE ((node_id_fr = PRIOR node_id_to OR node_id_to = PRIOR node_id_fr OR
node_id_fr = PRIOR node_id_fr OR node_id_to = PRIOR node_id_to) /*AND link_id != PRIOR link_id*/)
START WITH Substr (node_id_fr, 2, 1) = '1' AND Substr (node_id_to, 2, 1) = '2'
ORDER SIBLINGS BY node_id_to
```

Five Elementary Networks

Oracle's two forms of SQL recursion treat cycles differently

Connect By Cycles

The CONNECT_BY_ISCYCLE pseudocolumn returns 1 if the current row has a child which is also its ancestor. Otherwise it returns 0

Connect By queries do not return loop-closing nodes, and the prior node is marked as the cycle node.

Recursive Subquery Factor Cycles

A row is considered to form a cycle if one of its ancestor rows has the same values for the cycle columns.

Recursive Subquery Factor queries do return loop-closing nodes, and these nodes are marked as the cycle nodes.

We will see this differing behaviour clearly in the following examples. We will also see that the Connect By output on the original network has exactly the same structure as recursive subquery factor output on the dual network if the loop-closing rows are disregarded. Cycle nodes on both definitions are marked with a '*' in the outputs below.

Network 1: 3 nodes in line

Network 2: Simple fork

Network 3: 2-node loop

Network 4: 3-node loop

Network 5: 2 nodes with a self-loop

Combination of Elementary Networks

Combination Network 6

This network has 10 links with 3 loops.

Combination Network 6: PLF Output

```Node              Link
----------------- ----------
N1-6
> N2-6            L12-6
= N2-6*         L22-6
> N3-6          L23-6
> N4-6        L34-6
> N6-6      L46-6
> N4-6*   L64-6
> N5-6        L35-6
> N7-6      L57-6
> N8-6    L78-6
< N5-6* L58-6
```

Combination Network 6: RSF Output

```Node              Link
----------------- ----------
N1-6
> N2-6            L12-6
=  N2-6*         L22-6
>  N3-6          L23-6
>  N4-6        L34-6
<  N6-6      L64-6
<  N4-6*   L46-6
>  N6-6      L46-6
>  N4-6*   L64-6
>  N5-6        L35-6
>  N7-6      L57-6
>  N8-6    L78-6
<  N5-6* L58-6
>  N8-6      L58-6
<  N7-6    L78-6
<  N5-6* L57-6
```

Combination Network 6: CBY Output

```Nodes           Link Path
--------------- --------------------
N1-6 > N2-6     L12-6*
N2-6 > N2-6       L22-6*
N2-6 > N3-6         L23-6*
N3-6 > N4-6           L34-6*
N6-6 > N4-6             L64-6*
N4-6 > N6-6               L46-6*
N3-6 > N5-6             L35-6*
N5-6 > N7-6               L57-6*
N5-6 > N8-6                 L58-6*
N7-6 > N8-6                   L78-6*
N7-6 > N8-6                 L78-6*
N5-6 > N8-6                   L58-6*
N5-6 > N8-6               L58-6*
N5-6 > N7-6                 L57-6*
N7-6 > N8-6                   L78-6*
N7-6 > N8-6                 L78-6*
N5-6 > N7-6                   L57-6*
N4-6 > N6-6             L46-6*
N6-6 > N4-6               L64-6*
N3-6 > N5-6           L35-6*
N3-6 > N4-6             L34-6*
N6-6 > N4-6               L64-6*
N4-6 > N6-6                 L46-6*
N4-6 > N6-6               L46-6*
N6-6 > N4-6                 L64-6*
N5-6 > N7-6             L57-6*
N5-6 > N8-6               L58-6*
N7-6 > N8-6                 L78-6*
N7-6 > N8-6               L78-6*
N5-6 > N8-6                 L58-6*
N5-6 > N8-6             L58-6*
N5-6 > N7-6               L57-6*
N7-6 > N8-6                 L78-6*
N7-6 > N8-6               L78-6*
N5-6 > N7-6                 L57-6*
N2-6 > N3-6       L23-6*
N2-6 > N2-6         L22-6*
N3-6 > N4-6         L34-6*
N6-6 > N4-6           L64-6*
N4-6 > N6-6             L46-6*
N3-6 > N5-6           L35-6*
N5-6 > N7-6             L57-6*
N5-6 > N8-6               L58-6*
N7-6 > N8-6                 L78-6*
N7-6 > N8-6               L78-6*
N5-6 > N8-6                 L58-6*
N5-6 > N8-6             L58-6*
N5-6 > N7-6               L57-6*
N7-6 > N8-6                 L78-6*
N7-6 > N8-6               L78-6*
N5-6 > N7-6                 L57-6*
N4-6 > N6-6           L46-6*
N6-6 > N4-6             L64-6*
N3-6 > N5-6         L35-6*
N3-6 > N4-6           L34-6*
N6-6 > N4-6             L64-6*
N4-6 > N6-6               L46-6*
N4-6 > N6-6             L46-6*
N6-6 > N4-6               L64-6*
N5-6 > N7-6           L57-6*
N5-6 > N8-6             L58-6*
N7-6 > N8-6               L78-6*
N7-6 > N8-6             L78-6*
N5-6 > N8-6               L58-6*
N5-6 > N8-6           L58-6*
N5-6 > N7-6             L57-6*
N7-6 > N8-6               L78-6*
N7-6 > N8-6             L78-6*
N5-6 > N7-6               L57-6*
```

Dual Combination Network 6

This network has 15 links with 6 loops, whereas the original had 10 links with 3 loops.

Dual Combination Network 6: PLF Output

```Node                      Link
------------------------- ------
L12-6
> L22-6                   N2-1-6
= L22-6*                N2-3-6
> L23-6                 N2-4-6
< L12-6*              N2-2-6
> L34-6               N3-1-6
> L35-6             N3-3-6
< L23-6*          N3-2-6
> L57-6           N5-1-6
> L58-6         N5-3-6
< L35-6*      N5-2-6
> L78-6       N8-1-6
< L57-6*    N7-1-6
> L46-6             N4-1-6
> L64-6           N6-1-6
< L34-6*        N4-2-6
```

Dual Combination Network 6: RSF Output

```Node                      Link
------------------------- ------
L12-6
> L22-6                   N2-1-6
=  L22-6*                N2-3-6
>  L23-6                 N2-4-6
<  L12-6*              N2-2-6
>  L34-6               N3-1-6
>  L35-6             N3-3-6
<  L23-6*          N3-2-6
>  L57-6           N5-1-6
>  L58-6         N5-3-6
<  L35-6*      N5-2-6
>  L78-6       N8-1-6
<  L57-6*    N7-1-6
>  L78-6         N7-1-6
<  L58-6       N8-1-6
<  L35-6*    N5-2-6
<  L57-6*    N5-3-6
>  L58-6           N5-2-6
<  L57-6         N5-3-6
<  L35-6*      N5-1-6
>  L78-6       N7-1-6
<  L58-6*    N8-1-6
>  L78-6         N8-1-6
<  L57-6       N7-1-6
<  L35-6*    N5-1-6
>  L58-6*    N5-3-6
>  L46-6             N4-1-6
>  L64-6           N6-1-6
<  L34-6*        N4-2-6
>  L64-6             N4-2-6
<  L46-6           N6-1-6
<  L34-6*        N4-1-6
>  L35-6               N3-2-6
<  L34-6             N3-3-6
<  L23-6*          N3-1-6
>  L46-6           N4-1-6
>  L64-6         N6-1-6
<  L34-6*      N4-2-6
>  L64-6           N4-2-6
<  L46-6         N6-1-6
<  L34-6*      N4-1-6
>  L57-6             N5-1-6
>  L58-6           N5-3-6
<  L35-6*        N5-2-6
>  L78-6         N8-1-6
<  L57-6*      N7-1-6
>  L78-6           N7-1-6
<  L58-6         N8-1-6
<  L35-6*      N5-2-6
<  L57-6*      N5-3-6
>  L58-6             N5-2-6
<  L57-6           N5-3-6
<  L35-6*        N5-1-6
>  L78-6         N7-1-6
<  L58-6*      N8-1-6
>  L78-6           N8-1-6
<  L57-6         N7-1-6
<  L35-6*      N5-1-6
>  L58-6*      N5-3-6
> L23-6                   N2-2-6
<  L22-6                 N2-4-6
<  L12-6*              N2-1-6
=  L22-6*              N2-3-6
>  L34-6                 N3-1-6
>  L35-6               N3-3-6
<  L23-6*            N3-2-6
>  L57-6             N5-1-6
>  L58-6           N5-3-6
<  L35-6*        N5-2-6
>  L78-6         N8-1-6
<  L57-6*      N7-1-6
>  L78-6           N7-1-6
<  L58-6         N8-1-6
<  L35-6*      N5-2-6
<  L57-6*      N5-3-6
>  L58-6             N5-2-6
<  L57-6           N5-3-6
<  L35-6*        N5-1-6
>  L78-6         N7-1-6
<  L58-6*      N8-1-6
>  L78-6           N8-1-6
<  L57-6         N7-1-6
<  L35-6*      N5-1-6
>  L58-6*      N5-3-6
>  L46-6               N4-1-6
>  L64-6             N6-1-6
<  L34-6*          N4-2-6
>  L64-6               N4-2-6
<  L46-6             N6-1-6
<  L34-6*          N4-1-6
>  L35-6                 N3-2-6
<  L34-6               N3-3-6
<  L23-6*            N3-1-6
>  L46-6             N4-1-6
>  L64-6           N6-1-6
<  L34-6*        N4-2-6
>  L64-6             N4-2-6
<  L46-6           N6-1-6
<  L34-6*        N4-1-6
>  L57-6               N5-1-6
>  L58-6             N5-3-6
<  L35-6*          N5-2-6
>  L78-6           N8-1-6
<  L57-6*        N7-1-6
>  L78-6             N7-1-6
<  L58-6           N8-1-6
<  L35-6*        N5-2-6
<  L57-6*        N5-3-6
>  L58-6               N5-2-6
<  L57-6             N5-3-6
<  L35-6*          N5-1-6
>  L78-6           N7-1-6
<  L58-6*        N8-1-6
>  L78-6             N8-1-6
<  L57-6           N7-1-6
<  L35-6*        N5-1-6
>  L58-6*        N5-3-6
```

Combination Network 6: CBY Original with RSF Dual Output

In the output below I deleted all the loop rows from the RSF output for the dual network and placed the result beside the output for CBY for the original network, using a column-wise copy and paste. It's easy to see then their equivalent structure. Both have 69 rows.

```Network 6: CBY                         Dual Network 6: RSF with loop rows deleted
==============                         ==========================================
--------------- --------------------   ------------------------- ------
N1-6 > N2-6     L12-6*                 L12-6
N2-6 > N2-6       L22-6*	       > L22-6                   N2-1-6
N2-6 > N3-6         L23-6*	        >  L23-6                 N2-4-6
N3-6 > N4-6           L34-6*	          >  L34-6               N3-1-6
N6-6 > N4-6             L64-6*	            >  L35-6             N3-3-6
N4-6 > N6-6               L46-6*              >  L57-6           N5-1-6
N3-6 > N5-6             L35-6*	                >  L58-6         N5-3-6
N5-6 > N7-6               L57-6*                  >  L78-6       N8-1-6
N5-6 > N8-6                 L58-6*              >  L78-6         N7-1-6
N7-6 > N8-6                   L78-6*              <  L58-6       N8-1-6
N7-6 > N8-6                 L78-6*            >  L58-6           N5-2-6
N5-6 > N8-6                   L58-6*            <  L57-6         N5-3-6
N5-6 > N8-6               L58-6*                  >  L78-6       N7-1-6
N5-6 > N7-6                 L57-6*              >  L78-6         N8-1-6
N7-6 > N8-6                   L78-6*              <  L57-6       N7-1-6
N7-6 > N8-6                 L78-6*          >  L46-6             N4-1-6
N5-6 > N7-6                   L57-6*          >  L64-6           N6-1-6
N4-6 > N6-6             L46-6*	            >  L64-6             N4-2-6
N6-6 > N4-6               L64-6*              <  L46-6           N6-1-6
N3-6 > N5-6           L35-6*	          >  L35-6               N3-2-6
N3-6 > N4-6             L34-6*	            <  L34-6             N3-3-6
N6-6 > N4-6               L64-6*              >  L46-6           N4-1-6
N4-6 > N6-6                 L46-6*              >  L64-6         N6-1-6
N4-6 > N6-6               L46-6*              >  L64-6           N4-2-6
N6-6 > N4-6                 L64-6*              <  L46-6         N6-1-6
N5-6 > N7-6             L57-6*	            >  L57-6             N5-1-6
N5-6 > N8-6               L58-6*              >  L58-6           N5-3-6
N7-6 > N8-6                 L78-6*              >  L78-6         N8-1-6
N7-6 > N8-6               L78-6*              >  L78-6           N7-1-6
N5-6 > N8-6                 L58-6*              <  L58-6         N8-1-6
N5-6 > N8-6             L58-6*	            >  L58-6             N5-2-6
N5-6 > N7-6               L57-6*              <  L57-6           N5-3-6
N7-6 > N8-6                 L78-6*              >  L78-6         N7-1-6
N7-6 > N8-6               L78-6*              >  L78-6           N8-1-6
N5-6 > N7-6                 L57-6*              <  L57-6         N7-1-6
N2-6 > N3-6       L23-6*	       > L23-6                   N2-2-6
N2-6 > N2-6         L22-6*	        <  L22-6                 N2-4-6
N3-6 > N4-6         L34-6*	        >  L34-6                 N3-1-6
N6-6 > N4-6           L64-6*	          >  L35-6               N3-3-6
N4-6 > N6-6             L46-6*	            >  L57-6             N5-1-6
N3-6 > N5-6           L35-6*	              >  L58-6           N5-3-6
N5-6 > N7-6             L57-6*	                >  L78-6         N8-1-6
N5-6 > N8-6               L58-6*              >  L78-6           N7-1-6
N7-6 > N8-6                 L78-6*              <  L58-6         N8-1-6
N7-6 > N8-6               L78-6*            >  L58-6             N5-2-6
N5-6 > N8-6                 L58-6*            <  L57-6           N5-3-6
N5-6 > N8-6             L58-6*	                >  L78-6         N7-1-6
N5-6 > N7-6               L57-6*              >  L78-6           N8-1-6
N7-6 > N8-6                 L78-6*              <  L57-6         N7-1-6
N7-6 > N8-6               L78-6*          >  L46-6               N4-1-6
N5-6 > N7-6                 L57-6*          >  L64-6             N6-1-6
N4-6 > N6-6           L46-6*	          >  L64-6               N4-2-6
N6-6 > N4-6             L64-6*	            <  L46-6             N6-1-6
N3-6 > N5-6         L35-6*	        >  L35-6                 N3-2-6
N3-6 > N4-6           L34-6*	          <  L34-6               N3-3-6
N6-6 > N4-6             L64-6*	            >  L46-6             N4-1-6
N4-6 > N6-6               L46-6*              >  L64-6           N6-1-6
N4-6 > N6-6             L46-6*	            >  L64-6             N4-2-6
N6-6 > N4-6               L64-6*              <  L46-6           N6-1-6
N5-6 > N7-6           L57-6*	          >  L57-6               N5-1-6
N5-6 > N8-6             L58-6*	            >  L58-6             N5-3-6
N7-6 > N8-6               L78-6*              >  L78-6           N8-1-6
N7-6 > N8-6             L78-6*	            >  L78-6             N7-1-6
N5-6 > N8-6               L58-6*              <  L58-6           N8-1-6
N5-6 > N8-6           L58-6*	          >  L58-6               N5-2-6
N5-6 > N7-6             L57-6*	            <  L57-6             N5-3-6
N7-6 > N8-6               L78-6*              >  L78-6           N7-1-6
N7-6 > N8-6             L78-6*	            >  L78-6             N8-1-6
N5-6 > N7-6               L57-6*              <  L57-6           N7-1-6
```

Dual Combination Network 6: CBY Output

34547 rows selected.

[See attached file if interested in detail.]

Oracle's HR/OE/PM Demo Network

Original Demo Network

This network has 21 links with 6 loops.

Original Demo Network: PLF Output

```Node                                          Link                                 Lev
--------------------------------------------- ----------------------------------- ----
COUNTRIES|HR                                  ROOT                                   0
< LOCATIONS|HR                                loc_c_id_fk|HR                         1
< DEPARTMENTS|HR                            dept_loc_fk|HR                         2
> EMPLOYEES|HR                            dept_mgr_fk|HR                         3
< CUSTOMERS|OE                          customers_account_manager_fk|OE        4
< ORDERS|OE                           orders_customer_id_fk|OE               5
> EMPLOYEES|HR*                     orders_sales_rep_fk|OE                 6
< ORDER_ITEMS|OE                    order_items_order_id_fk|OE             6
> PRODUCT_INFORMATION|OE          order_items_product_id_fk|OE           7
< INVENTORIES|OE                inventories_product_id_fk|OE           8
> WAREHOUSES|OE               inventories_warehouses_fk|OE           9
> LOCATIONS|HR*             warehouses_location_fk|OE             10
< ONLINE_MEDIA|PM               loc_c_id_fk|PM                         8
< PRINT_MEDIA|PM                printmedia_fk|PM                       8
< PRODUCT_DESCRIPTIONS|OE       pd_product_id_fk|OE                    8
> DEPARTMENTS|HR*                       emp_dept_fk|HR                         4
= EMPLOYEES|HR*                         emp_manager_fk|HR                      4
> JOBS|HR                               emp_job_fk|HR                          4
< JOB_HISTORY|HR                      jhist_job_fk|HR                        5
> DEPARTMENTS|HR*                   jhist_dept_fk|HR                       6
> EMPLOYEES|HR*                     jhist_emp_fk|HR                        6
> REGIONS|HR                                  countr_reg_fk|HR                       1

22 rows selected.

Elapsed: 00:00:00.15
```

Original Demo Network: RSF Output

```Node                                          Link
--------------------------------------------- -----------------------------------
COUNTRIES|HR
< LOCATIONS|HR                                loc_c_id_fk|HR
< DEPARTMENTS|HR                            dept_loc_fk|HR
< EMPLOYEES|HR                            emp_dept_fk|HR
< CUSTOMERS|OE                          customers_account_manager_fk|OE
< ORDERS|OE                           orders_customer_id_fk|OE
> EMPLOYEES|HR*                     orders_sales_rep_fk|OE
< ORDER_ITEMS|OE                    order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE          order_items_product_id_fk|OE
< INVENTORIES|OE                inventories_product_id_fk|OE
> WAREHOUSES|OE               inventories_warehouses_fk|OE
> LOCATIONS|HR*             warehouses_location_fk|OE
< ONLINE_MEDIA|PM               loc_c_id_fk|PM
< PRINT_MEDIA|PM                printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE       pd_product_id_fk|OE
< DEPARTMENTS|HR*                       dept_mgr_fk|HR
= EMPLOYEES|HR*                         emp_manager_fk|HR
> JOBS|HR                               emp_job_fk|HR
< JOB_HISTORY|HR                      jhist_job_fk|HR
> DEPARTMENTS|HR*                   jhist_dept_fk|HR
> EMPLOYEES|HR*                     jhist_emp_fk|HR
< JOB_HISTORY|HR                        jhist_emp_fk|HR
> DEPARTMENTS|HR*                     jhist_dept_fk|HR
> JOBS|HR                             jhist_job_fk|HR
< EMPLOYEES|HR*                     emp_job_fk|HR
< ORDERS|OE                             orders_sales_rep_fk|OE
> CUSTOMERS|OE                        orders_customer_id_fk|OE
> EMPLOYEES|HR*                     customers_account_manager_fk|OE
< ORDER_ITEMS|OE                      order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE            order_items_product_id_fk|OE
< INVENTORIES|OE                  inventories_product_id_fk|OE
> WAREHOUSES|OE                 inventories_warehouses_fk|OE
> LOCATIONS|HR*               warehouses_location_fk|OE
< ONLINE_MEDIA|PM                 loc_c_id_fk|PM
< PRINT_MEDIA|PM                  printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE         pd_product_id_fk|OE
> EMPLOYEES|HR                            dept_mgr_fk|HR
< CUSTOMERS|OE                          customers_account_manager_fk|OE
< ORDERS|OE                           orders_customer_id_fk|OE
> EMPLOYEES|HR*                     orders_sales_rep_fk|OE
< ORDER_ITEMS|OE                    order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE          order_items_product_id_fk|OE
< INVENTORIES|OE                inventories_product_id_fk|OE
> WAREHOUSES|OE               inventories_warehouses_fk|OE
> LOCATIONS|HR*             warehouses_location_fk|OE
< ONLINE_MEDIA|PM               loc_c_id_fk|PM
< PRINT_MEDIA|PM                printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE       pd_product_id_fk|OE
> DEPARTMENTS|HR*                       emp_dept_fk|HR
= EMPLOYEES|HR*                         emp_manager_fk|HR
> JOBS|HR                               emp_job_fk|HR
< JOB_HISTORY|HR                      jhist_job_fk|HR
> DEPARTMENTS|HR*                   jhist_dept_fk|HR
> EMPLOYEES|HR*                     jhist_emp_fk|HR
< JOB_HISTORY|HR                        jhist_emp_fk|HR
> DEPARTMENTS|HR*                     jhist_dept_fk|HR
> JOBS|HR                             jhist_job_fk|HR
< EMPLOYEES|HR*                     emp_job_fk|HR
< ORDERS|OE                             orders_sales_rep_fk|OE
> CUSTOMERS|OE                        orders_customer_id_fk|OE
> EMPLOYEES|HR*                     customers_account_manager_fk|OE
< ORDER_ITEMS|OE                      order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE            order_items_product_id_fk|OE
< INVENTORIES|OE                  inventories_product_id_fk|OE
> WAREHOUSES|OE                 inventories_warehouses_fk|OE
> LOCATIONS|HR*               warehouses_location_fk|OE
< ONLINE_MEDIA|PM                 loc_c_id_fk|PM
< PRINT_MEDIA|PM                  printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE         pd_product_id_fk|OE
< JOB_HISTORY|HR                          jhist_dept_fk|HR
> EMPLOYEES|HR                          jhist_emp_fk|HR
< CUSTOMERS|OE                        customers_account_manager_fk|OE
< ORDERS|OE                         orders_customer_id_fk|OE
> EMPLOYEES|HR*                   orders_sales_rep_fk|OE
< ORDER_ITEMS|OE                  order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE        order_items_product_id_fk|OE
< INVENTORIES|OE              inventories_product_id_fk|OE
> WAREHOUSES|OE             inventories_warehouses_fk|OE
> LOCATIONS|HR*           warehouses_location_fk|OE
< ONLINE_MEDIA|PM             loc_c_id_fk|PM
< PRINT_MEDIA|PM              printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE     pd_product_id_fk|OE
< DEPARTMENTS|HR*                     dept_mgr_fk|HR
> DEPARTMENTS|HR*                     emp_dept_fk|HR
= EMPLOYEES|HR*                       emp_manager_fk|HR
> JOBS|HR                             emp_job_fk|HR
< JOB_HISTORY|HR*                   jhist_job_fk|HR
< ORDERS|OE                           orders_sales_rep_fk|OE
> CUSTOMERS|OE                      orders_customer_id_fk|OE
> EMPLOYEES|HR*                   customers_account_manager_fk|OE
< ORDER_ITEMS|OE                    order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE          order_items_product_id_fk|OE
< INVENTORIES|OE                inventories_product_id_fk|OE
> WAREHOUSES|OE               inventories_warehouses_fk|OE
> LOCATIONS|HR*             warehouses_location_fk|OE
< ONLINE_MEDIA|PM               loc_c_id_fk|PM
< PRINT_MEDIA|PM                printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE       pd_product_id_fk|OE
> JOBS|HR                               jhist_job_fk|HR
< EMPLOYEES|HR                        emp_job_fk|HR
< CUSTOMERS|OE                      customers_account_manager_fk|OE
< ORDERS|OE                       orders_customer_id_fk|OE
> EMPLOYEES|HR*                 orders_sales_rep_fk|OE
< ORDER_ITEMS|OE                order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE      order_items_product_id_fk|OE
< INVENTORIES|OE            inventories_product_id_fk|OE
> WAREHOUSES|OE           inventories_warehouses_fk|OE
> LOCATIONS|HR*         warehouses_location_fk|OE
< ONLINE_MEDIA|PM           loc_c_id_fk|PM
< PRINT_MEDIA|PM            printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE   pd_product_id_fk|OE
< DEPARTMENTS|HR*                   dept_mgr_fk|HR
> DEPARTMENTS|HR*                   emp_dept_fk|HR
= EMPLOYEES|HR*                     emp_manager_fk|HR
< JOB_HISTORY|HR*                   jhist_emp_fk|HR
< ORDERS|OE                         orders_sales_rep_fk|OE
> CUSTOMERS|OE                    orders_customer_id_fk|OE
> EMPLOYEES|HR*                 customers_account_manager_fk|OE
< ORDER_ITEMS|OE                  order_items_order_id_fk|OE
> PRODUCT_INFORMATION|OE        order_items_product_id_fk|OE
< INVENTORIES|OE              inventories_product_id_fk|OE
> WAREHOUSES|OE             inventories_warehouses_fk|OE
> LOCATIONS|HR*           warehouses_location_fk|OE
< ONLINE_MEDIA|PM             loc_c_id_fk|PM
< PRINT_MEDIA|PM              printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE     pd_product_id_fk|OE
< WAREHOUSES|OE                             warehouses_location_fk|OE
< INVENTORIES|OE                          inventories_warehouses_fk|OE
> PRODUCT_INFORMATION|OE                inventories_product_id_fk|OE
< ONLINE_MEDIA|PM                     loc_c_id_fk|PM
< ORDER_ITEMS|OE                      order_items_product_id_fk|OE
> ORDERS|OE                         order_items_order_id_fk|OE
> CUSTOMERS|OE                    orders_customer_id_fk|OE
> EMPLOYEES|HR                  customers_account_manager_fk|OE
< DEPARTMENTS|HR              dept_mgr_fk|HR
< EMPLOYEES|HR*             emp_dept_fk|HR
< JOB_HISTORY|HR            jhist_dept_fk|HR
> EMPLOYEES|HR*           jhist_emp_fk|HR
> JOBS|HR                 jhist_job_fk|HR
< EMPLOYEES|HR*         emp_job_fk|HR
> LOCATIONS|HR*             dept_loc_fk|HR
> DEPARTMENTS|HR              emp_dept_fk|HR
> EMPLOYEES|HR*             dept_mgr_fk|HR
< JOB_HISTORY|HR            jhist_dept_fk|HR
> EMPLOYEES|HR*           jhist_emp_fk|HR
> JOBS|HR                 jhist_job_fk|HR
< EMPLOYEES|HR*         emp_job_fk|HR
> LOCATIONS|HR*             dept_loc_fk|HR
= EMPLOYEES|HR*               emp_manager_fk|HR
> JOBS|HR                     emp_job_fk|HR
< JOB_HISTORY|HR            jhist_job_fk|HR
> DEPARTMENTS|HR          jhist_dept_fk|HR
< EMPLOYEES|HR*         emp_dept_fk|HR
> EMPLOYEES|HR*         dept_mgr_fk|HR
> LOCATIONS|HR*         dept_loc_fk|HR
> EMPLOYEES|HR*           jhist_emp_fk|HR
< JOB_HISTORY|HR              jhist_emp_fk|HR
> DEPARTMENTS|HR            jhist_dept_fk|HR
< EMPLOYEES|HR*           emp_dept_fk|HR
> EMPLOYEES|HR*           dept_mgr_fk|HR
> LOCATIONS|HR*           dept_loc_fk|HR
> JOBS|HR                   jhist_job_fk|HR
< EMPLOYEES|HR*           emp_job_fk|HR
< ORDERS|OE*                  orders_sales_rep_fk|OE
> EMPLOYEES|HR                    orders_sales_rep_fk|OE
< CUSTOMERS|OE                  customers_account_manager_fk|OE
< ORDERS|OE*                  orders_customer_id_fk|OE
< DEPARTMENTS|HR                dept_mgr_fk|HR
< EMPLOYEES|HR*               emp_dept_fk|HR
< JOB_HISTORY|HR              jhist_dept_fk|HR
> EMPLOYEES|HR*             jhist_emp_fk|HR
> JOBS|HR                   jhist_job_fk|HR
< EMPLOYEES|HR*           emp_job_fk|HR
> LOCATIONS|HR*               dept_loc_fk|HR
> DEPARTMENTS|HR                emp_dept_fk|HR
> EMPLOYEES|HR*               dept_mgr_fk|HR
< JOB_HISTORY|HR              jhist_dept_fk|HR
> EMPLOYEES|HR*             jhist_emp_fk|HR
> JOBS|HR                   jhist_job_fk|HR
< EMPLOYEES|HR*           emp_job_fk|HR
> LOCATIONS|HR*               dept_loc_fk|HR
= EMPLOYEES|HR*                 emp_manager_fk|HR
> JOBS|HR                       emp_job_fk|HR
< JOB_HISTORY|HR              jhist_job_fk|HR
> DEPARTMENTS|HR            jhist_dept_fk|HR
< EMPLOYEES|HR*           emp_dept_fk|HR
> EMPLOYEES|HR*           dept_mgr_fk|HR
> LOCATIONS|HR*           dept_loc_fk|HR
> EMPLOYEES|HR*             jhist_emp_fk|HR
< JOB_HISTORY|HR                jhist_emp_fk|HR
> DEPARTMENTS|HR              jhist_dept_fk|HR
< EMPLOYEES|HR*             emp_dept_fk|HR
> EMPLOYEES|HR*             dept_mgr_fk|HR
> LOCATIONS|HR*             dept_loc_fk|HR
> JOBS|HR                     jhist_job_fk|HR
< EMPLOYEES|HR*             emp_job_fk|HR
< PRINT_MEDIA|PM                      printmedia_fk|PM
< PRODUCT_DESCRIPTIONS|OE             pd_product_id_fk|OE
> REGIONS|HR                                  countr_reg_fk|HR

199 rows selected.

Elapsed: 00:00:00.30
```

The output above shows that RSF returned 199 rows unfiltered in 0.3s.

Original Demo Network: CBY Output

```One tree by Connect By

-------------------------------------------------- ----------------------------------------------------------------------
COUNTRIES|HR > REGIONS|HR                          countr_reg_fk|HR*
LOCATIONS|HR > COUNTRIES|HR                          loc_c_id_fk|HR*
DEPARTMENTS|HR > LOCATIONS|HR                          dept_loc_fk|HR*
EMPLOYEES|HR > DEPARTMENTS|HR                            emp_dept_fk|HR*
JOB_HISTORY|HR > DEPARTMENTS|HR                            jhist_dept_fk|HR*
DEPARTMENTS|HR > EMPLOYEES|HR                                dept_mgr_fk|HR*
EMPLOYEES|HR > EMPLOYEES|HR                                    emp_manager_fk|HR*
CUSTOMERS|OE > EMPLOYEES|HR                                      customers_account_manager_fk|OE*
ORDERS|OE > CUSTOMERS|OE                                           orders_customer_id_fk|OE*
ORDERS|OE > EMPLOYEES|HR                                             orders_sales_rep_fk|OE*
JOB_HISTORY|HR > EMPLOYEES|HR                                          jhist_emp_fk|HR*
EMPLOYEES|HR > JOBS|HR                                                   emp_job_fk|HR*
JOB_HISTORY|HR > JOBS|HR                                                   jhist_job_fk|HR*
JOB_HISTORY|HR > JOBS|HR                                                 jhist_job_fk|HR*
EMPLOYEES|HR > JOBS|HR                                                     emp_job_fk|HR*
EMPLOYEES|HR > JOBS|HR                                                 emp_job_fk|HR*
JOB_HISTORY|HR > EMPLOYEES|HR                                            jhist_emp_fk|HR*
JOB_HISTORY|HR > JOBS|HR                                                   jhist_job_fk|HR*
JOB_HISTORY|HR > JOBS|HR                                                 jhist_job_fk|HR*
JOB_HISTORY|HR > EMPLOYEES|HR                                              jhist_emp_fk|HR*
ORDER_ITEMS|OE > ORDERS|OE                                             order_items_order_id_fk|OE*
ORDER_ITEMS|OE > PRODUCT_INFORMATION|OE                                  order_items_product_id_fk|OE*
INVENTORIES|OE > PRODUCT_INFORMATION|OE                                    inventories_product_id_fk|OE*
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                                      printmedia_fk|PM*
.
.
.
ORDERS|OE > CUSTOMERS|OE                                                           orders_customer_id_fk|OE*
EMPLOYEES|HR > EMPLOYEES|HR                                                        emp_manager_fk|HR*
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                        printmedia_fk|PM*
ONLINE_MEDIA|PM > PRODUCT_INFORMATION|OE                         loc_c_id_fk|PM*
PRODUCT_DESCRIPTIONS|OE > PRODUCT_INFORMATION|OE                   pd_product_id_fk|OE*
PRODUCT_DESCRIPTIONS|OE > PRODUCT_INFORMATION|OE                 pd_product_id_fk|OE*
ONLINE_MEDIA|PM > PRODUCT_INFORMATION|OE                           loc_c_id_fk|PM*
ONLINE_MEDIA|PM > PRODUCT_INFORMATION|OE                       loc_c_id_fk|PM*
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                          printmedia_fk|PM*
PRODUCT_DESCRIPTIONS|OE > PRODUCT_INFORMATION|OE                   pd_product_id_fk|OE*
PRODUCT_DESCRIPTIONS|OE > PRODUCT_INFORMATION|OE                 pd_product_id_fk|OE*
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                            printmedia_fk|PM*
PRODUCT_DESCRIPTIONS|OE > PRODUCT_INFORMATION|OE               pd_product_id_fk|OE*
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                          printmedia_fk|PM*
ONLINE_MEDIA|PM > PRODUCT_INFORMATION|OE                           loc_c_id_fk|PM*
ONLINE_MEDIA|PM > PRODUCT_INFORMATION|OE                         loc_c_id_fk|PM*
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                            printmedia_fk|PM*

4414420 rows selected.

Elapsed: 00:29:33.41

One tree by Connect By filtered

-------------------------------------------------- ---------------------------------------------------------------------- ----------
COUNTRIES|HR > REGIONS|HR                          countr_reg_fk|HR*                                                               1
LOCATIONS|HR > COUNTRIES|HR                          loc_c_id_fk|HR*                                                               1
DEPARTMENTS|HR > LOCATIONS|HR                          dept_loc_fk|HR*                                                        214178
EMPLOYEES|HR > DEPARTMENTS|HR                            emp_dept_fk|HR*                                                      169932
JOB_HISTORY|HR > DEPARTMENTS|HR                            jhist_dept_fk|HR*                                                  272162
DEPARTMENTS|HR > EMPLOYEES|HR                                dept_mgr_fk|HR*                                                  169932
EMPLOYEES|HR > EMPLOYEES|HR                                    emp_manager_fk|HR*                                             207910
CUSTOMERS|OE > EMPLOYEES|HR                                      customers_account_manager_fk|OE*                             132490
ORDERS|OE > CUSTOMERS|OE                                           orders_customer_id_fk|OE*                                   85298
ORDERS|OE > EMPLOYEES|HR                                             orders_sales_rep_fk|OE*                                   72234
JOB_HISTORY|HR > EMPLOYEES|HR                                          jhist_emp_fk|HR*                                       164660
EMPLOYEES|HR > JOBS|HR                                                   emp_job_fk|HR*                                       182784
JOB_HISTORY|HR > JOBS|HR                                                   jhist_job_fk|HR*                                   333192
ORDER_ITEMS|OE > ORDERS|OE                                             order_items_order_id_fk|OE*                             26804
ORDER_ITEMS|OE > PRODUCT_INFORMATION|OE                                  order_items_product_id_fk|OE*                         26804
INVENTORIES|OE > PRODUCT_INFORMATION|OE                                    inventories_product_id_fk|OE*                      428354
PRINT_MEDIA|PM > PRODUCT_INFORMATION|OE                                      printmedia_fk|PM*                                428384
ONLINE_MEDIA|PM > PRODUCT_INFORMATION|OE                                       loc_c_id_fk|PM*                                428384
PRODUCT_DESCRIPTIONS|OE > PRODUCT_INFORMATION|OE                                 pd_product_id_fk|OE*                         428384
INVENTORIES|OE > WAREHOUSES|OE                                               inventories_warehouses_fk|OE*                    428354
WAREHOUSES|OE > LOCATIONS|HR                                                   warehouses_location_fk|OE*                     214178

21 rows selected.

Elapsed: 00:03:03.16
```

The output above shows that CBY returned 4,414,420 rows unfiltered in 29m33s. Adding filtering reduced the time to 3m03s.

Dual Demo Network

This network has 52 links with 32 loops, whereas the original had 21 links with 6 loops.

Dual Demo Network: PLF Output

```Node                                                           Link
-------------------------------------------------------------- -------------------------
countr_reg_fk|HR                                               ROOT
> loc_c_id_fk|HR                                               COUNTRIES|HR-1
< dept_loc_fk|HR                                             LOCATIONS|HR-1
> dept_mgr_fk|HR                                           DEPARTMENTS|HR-1
< customers_account_manager_fk|OE                        EMPLOYEES|HR-1
> emp_dept_fk|HR                                       EMPLOYEES|HR-2
< dept_loc_fk|HR*                                    DEPARTMENTS|HR-2
< dept_mgr_fk|HR*                                    EMPLOYEES|HR-7
> emp_job_fk|HR                                      EMPLOYEES|HR-12
< customers_account_manager_fk|OE*                 EMPLOYEES|HR-3
< dept_mgr_fk|HR*                                  EMPLOYEES|HR-8
> emp_manager_fk|HR                                EMPLOYEES|HR-16
< customers_account_manager_fk|OE*               EMPLOYEES|HR-4
< dept_mgr_fk|HR*                                EMPLOYEES|HR-9
< emp_dept_fk|HR*                                EMPLOYEES|HR-13
= emp_manager_fk|HR*                             EMPLOYEES|HR-19
> jhist_emp_fk|HR                                EMPLOYEES|HR-20
< customers_account_manager_fk|OE*             EMPLOYEES|HR-5
< dept_mgr_fk|HR*                              EMPLOYEES|HR-10
< emp_dept_fk|HR*                              EMPLOYEES|HR-14
< emp_job_fk|HR*                               EMPLOYEES|HR-17
< jhist_dept_fk|HR                             JOB_HISTORY|HR-1
< dept_loc_fk|HR*                            DEPARTMENTS|HR-3
< dept_mgr_fk|HR*                            DEPARTMENTS|HR-4
< emp_dept_fk|HR*                            DEPARTMENTS|HR-5
> jhist_job_fk|HR                            JOB_HISTORY|HR-2
< emp_job_fk|HR*                           JOBS|HR-1
< jhist_emp_fk|HR*                         JOB_HISTORY|HR-3
> orders_sales_rep_fk|OE                       EMPLOYEES|HR-22
< customers_account_manager_fk|OE*           EMPLOYEES|HR-6
< dept_mgr_fk|HR*                            EMPLOYEES|HR-11
< emp_dept_fk|HR*                            EMPLOYEES|HR-15
< emp_job_fk|HR*                             EMPLOYEES|HR-18
< emp_manager_fk|HR*                         EMPLOYEES|HR-21
< order_items_order_id_fk|OE                 ORDERS|OE-2
> order_items_product_id_fk|OE             ORDER_ITEMS|OE-1
< inventories_product_id_fk|OE           PRODUCT_INFORMATION|OE-2
> inventories_warehouses_fk|OE         INVENTORIES|OE-1
> warehouses_location_fk|OE          WAREHOUSES|OE-1
< dept_loc_fk|HR*                  LOCATIONS|HR-2
< loc_c_id_fk|HR*                  LOCATIONS|HR-3
> loc_c_id_fk|PM                       PRODUCT_INFORMATION|OE-1
> order_items_product_id_fk|OE*      PRODUCT_INFORMATION|OE-5
> pd_product_id_fk|OE                PRODUCT_INFORMATION|OE-6
< inventories_product_id_fk|OE*    PRODUCT_INFORMATION|OE-3
< order_items_product_id_fk|OE*    PRODUCT_INFORMATION|OE-8
> printmedia_fk|PM                 PRODUCT_INFORMATION|OE-10
< inventories_product_id_fk|OE*  PRODUCT_INFORMATION|OE-4
< loc_c_id_fk|PM*                PRODUCT_INFORMATION|OE-7
< order_items_product_id_fk|OE*  PRODUCT_INFORMATION|OE-9
> orders_customer_id_fk|OE                 ORDERS|OE-1
< customers_account_manager_fk|OE*       CUSTOMERS|OE-1
> orders_sales_rep_fk|OE*                ORDERS|OE-3

53 rows selected.

Elapsed: 00:00:00.27
```

Dual Demo Network: RSF and CBY Results

Neither of the two SQL recursion methods completed within a period of an hour and had to be terminated. The result for CBY on the original network suggests that RSF on the dual network should return somewhere above 4,414,420 rows.

Conclusions

• We have shown by examples how network traversal by the Connect By (CBY) approach in SQL corresponds to traversal of all routes in a type of dual version of the original network
• This dual version, which has forks converted to loops, tends to be larger and more heavily looped, resulting in worse performance compared with solution by recursive subquery factors (RSF)
• The examples illustrate the different treatment of loop-closing links between the two types of SQL recursion
• The RSF solutions on the dual network in the simpler examples where it completes is seen to be equivalent to the CBY solution on the original network, after allowing for the different treatment of loop-closing links
• On the foreign key network for Oracle's HR/OE/PM demo, which has 21 links, RSF returns 199 rows while CBY returns 4,414,420 rows
• On the dual version of the foreign key network for Oracle's HR/OE/PM demo, which has 52 links, RSF and CBY fail to complete in reasonable times
• The pipelined function method returns the solution on both original and dual in a small fraction of a second

SQL files: SQL for network duality
Output files: Output for network duality

Oracle version used: Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production

# SQL for Shortest Path Problems 2: A Branch and Bound Approach

I wrote an article a couple of weeks ago, SQL for Shortest Path Problems, in which analytic functions are used to truncate sub-optimal routes in SQL recursions for shortest paths through networks. The problem was posed by an OTN poster, How to use Recursive Subquery Factoring (RSF) to Implement Dijkstra’s shortest path algorithm?, who referenced a very simple test network, and included his own SQL to solve it, which turned out to be quite similar to my own effort. The solutions are guaranteed to be optimal if the algorithm terminates normally, which it does on the small test network, and will on any network unless resources such as memory or time are exhausted owing to problem size.

In that article I referenced two earlier articles that I had written (in June/July 2013) that used analytic functions for other combinatorial problems. The usage in those cases was similar syntactically, but pruned out routes that looked inferior to others at a given iteration, so that the final solutions were not guaranteed to be optimal. The motivation was to to be able to use the SQL for exact solutions on smaller problems and for good, maybe sub-optimal, solutions for problems too large to solve exactly.

I wondered how the SQL in the last article would perform on larger networks, and whether further tuning methods could be found, perhaps based on some form of search truncation, as in the earlier articles. The resulting solution methods can be considered as branch and bound algorithms in SQL.

Test Problems

I took two test problems from this site: Stanford Large Network Dataset Collection.

The larger second data set has 428,156 arcs.

Both data sets are of the non-physical type, where there are no differential costs associated with links, and the problem therefore reduces to determining the minimum number of links between a node and other reachable nodes. These non-physical networks tend to be heavily looped, owing to the essentially zero cost of adding a new link. For that reason, I change my problem definition in this article to that of finding a single best path to each reachable node, rather than all, which reduces the solution set size considerably.

It is well known that in non-physical networks, such as social media networks like Linked-In and Facebook, the minimum paths between members tends to remain relatively small as the network size goes up. This will influence the type of algorithm that will be more efficient.

Approximation Methods: Simple truncation

The most obvious approximative approach would be to simple truncate the search after a certain depth (or level). This actually works quite well and gives good results for highly looped networks, where the minimum paths tend to be much shorter than the number of nodes. However there is no guarantee of optimality, and it will be less effective for less looped networks with longer minimum paths.

SQL for SP_RSFONE (simple truncation)

```WITH paths (node, path, lev, rn) AS (
SELECT a.dst, To_Char(a.dst), 1, 1
FROM arcs_v a
WHERE a.src = &SRC
UNION ALL
SELECT  a.dst,
p.path || ',' || a.dst,
p.lev + 1,
Row_Number () OVER (PARTITION BY a.dst ORDER BY a.dst)
FROM paths p
JOIN arcs_v a
ON a.src = p.node
WHERE p.rn = 1
AND p.lev < &LEVMAX
)  SEARCH DEPTH FIRST BY node SET line_no
CYCLE node SET lp TO '*' DEFAULT ' '
SELECT Substr (LPad ('.', 1 + 2 * (Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) - 1), '.') || node, 2) node,
Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) lev,
Max (Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev)) OVER () maxlev,
Max (lev) intnod,
Max (Max (lev)) OVER () intmax,
Max (path) KEEP (DENSE_RANK FIRST ORDER BY lev) path,
Max (lp) KEEP (DENSE_RANK FIRST ORDER BY lev) lp
FROM paths
GROUP BY node
ORDER BY Max (line_no) KEEP (DENSE_RANK FIRST ORDER BY lev)
```

Notes on SQL for SP_RSFONE (simple truncation)

• Row_Number gives a single row with value 1 per partitioning node, so that we retain only one row per node for the previous iteration
• The global pruning can be done without an additional subquery by grouping with KEEP, since we only want one optimal row per node
• Note that in the double Max to get maxlev, the inner one is the grouping Max, while the outer is an analytic Max over the whole (grouped) result set
• intnod obtains the maximum intermdiate value of lev for a given node
• intmax obtains the maximum intermdiate value of lev over all nodes

Approximation Methods: Preliminary approximate subquery
A less obvious approach is based on the fact that during the recursion our path pruning can only take into account information available to the current iteration: Other than loops, we can prune out only paths to a given node that are longer than another at the same level. But what if we ran an approximate search in advance, in a prior subquery? Then we could outer-join the subquery by node and prune out any paths for which the subquery has found a better cost. This would potentially reduce the total searching without sacrificing guranteed optimality.

SQL for SP_RSFTWO (preliminary approximate subquery)

```WITH paths_0 (node, path, lev, rn) AS (
SELECT a.dst, To_Char(a.dst), 1, 1
FROM arcs_v a
WHERE a.src = &SRC
UNION ALL
SELECT a.dst,
p.path || ',' || a.dst,
p.lev + 1,
Row_Number () OVER (PARTITION BY a.dst ORDER BY a.dst)
FROM paths_0 p
JOIN arcs_v a
ON a.src = p.node
WHERE p.rn = 1
AND p.lev < &LEVMAX
) CYCLE node SET lp TO '*' DEFAULT ' '
, approx_best_paths AS (
SELECT node,
Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) lev
FROM paths_0
GROUP BY node
), paths (node, path, lev, rn) AS (
SELECT a.dst, To_Char(a.dst), 1, 1
FROM arcs_v a
WHERE a.src = &SRC
UNION ALL
SELECT a.dst,
p.path || ',' || a.dst,
p.lev + 1,
Row_Number () OVER (PARTITION BY a.dst ORDER BY a.dst)
FROM paths p
JOIN arcs_v a
ON a.src = p.node
LEFT JOIN approx_best_paths b
ON b.node = a.dst
WHERE p.rn = 1
AND p.lev < Nvl (b.lev, 1000000)
)  SEARCH DEPTH FIRST BY node SET line_no CYCLE node SET lp TO '*' DEFAULT ' '
SELECT Substr (LPad ('.', 1 + 2 * (Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) - 1), '.') || node, 2) node,
Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) lev,
Max (Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev)) OVER () maxlev,
Max (lev) intnod,
Max (Max (lev)) OVER () intmax,
Max (path) KEEP (DENSE_RANK FIRST ORDER BY lev) path,
Max (lp) KEEP (DENSE_RANK FIRST ORDER BY lev) lp
FROM paths
GROUP BY node
ORDER BY Max (line_no) KEEP (DENSE_RANK FIRST ORDER BY lev)
```

Notes on SQL for SP_RSFTWO (preliminary approximate subquery)

• This query has two recursive subquery factors
• path_0 truncates after an input level is reached
• approx_best_paths gets the global best paths found from the approximate recursion
• paths now has an outer join to path_0 that is used to prune paths that are inferior to any path to the same node found in the prior subquery
• the approximate recursion may not reach all reachable nodes, but the outer join ensures this does not cut off any such nodes incorrectly

Approximation Methods: Preliminary approximate subquery to GTT
We will see later that the second approach works quite well, but that the CBO does not process the preliminary query very efficiently. For this reason writing the query result instead to a temporary table may be more efficient overall. The table can be indexed, and dynamic sampling allows the CBO to estimate the cardinalities more accurately.

SQL for SP_GTTRSF_I and SP_GTTRSF_Q (preliminary approximate query to GTT)

```PROMPT SP_GTTRSF_I
INSERT INTO approx_min_levs
WITH paths_0 (node, path, lev, rn) /* SP_GTTRSF_I */ AS (
SELECT a.dst, To_Char(a.dst), 1, 1
FROM arcs_v a
WHERE a.src = &SRC
UNION ALL
SELECT  a.dst,
p.path || ',' || a.dst,
p.lev + 1,
Row_Number () OVER (PARTITION BY a.dst ORDER BY a.dst)
FROM paths_0 p
JOIN arcs_v a
ON a.src = p.node
WHERE p.rn = 1
AND p.lev < &LEVMAX
) CYCLE node SET lp TO '*' DEFAULT ' '
SELECT node,
Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) lev
FROM paths_0
GROUP BY node
/
PROMPT SP_GTTRSF_Q
WITH paths (node, path, lev, rn) AS (
SELECT a.dst, To_Char(a.dst), 1, 1
FROM arcs_v a
WHERE a.src = &SRC
UNION ALL
SELECT a.dst,
p.path || ',' || a.dst,
p.lev + 1,
Row_Number () OVER (PARTITION BY a.dst ORDER BY a.dst)
FROM paths p
JOIN arcs_v a
ON a.src = p.node
LEFT JOIN approx_min_levs b
ON b.node = a.dst
WHERE p.rn = 1
AND p.lev < Nvl (b.lev, 1000000)
)  SEARCH DEPTH FIRST BY node SET line_no CYCLE node SET lp TO '*' DEFAULT ' '
SELECT Substr (LPad ('.', 1 + 2 * (Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) - 1), '.') || node, 2) node,
Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev) lev,
Max (Max (lev) KEEP (DENSE_RANK FIRST ORDER BY lev)) OVER () maxlev,
Max (lev) intnod,
Max (Max (lev)) OVER () intmax,
Max (path) KEEP (DENSE_RANK FIRST ORDER BY lev) path,
Max (lp) KEEP (DENSE_RANK FIRST ORDER BY lev) lp
FROM paths
GROUP BY node
ORDER BY Max (line_no) KEEP (DENSE_RANK FIRST ORDER BY lev)
```

Notes on SQL for preliminary approximate subquery to GTT

• the SQL above consists of an insert of the approximate subquery into a temporary table, followed by the exact recursive query now referencing the table rather than a subquery factor

Results: Collaboration network of Arxiv General Relativity category
Collaboration network of Arxiv General Relativity category

Arxiv GR-QC (General Relativity and Quantum Cosmology) collaboration network is from the e-print arXiv and covers scientific collaborations between authors papers submitted to General Relativity and Quantum Cosmology category. If an author i co-authored a paper with author j, the graph contains a undirected edge from i to j. If the paper is co-authored by k authors this generates a completely connected (sub)graph on k nodes.

The data set comes with the reverse arcs already present, making a total of 28,980.

I took the first node in the first line in the data set file as the initial root node, 3466, and tested the three methods above for values of LEVMAX of 5, 10, 15, 20, 25 and 30. The complete results, including execution plans are in the attached file.

The exact solution has 4,158 nodes reached from the source node 3466, with a maximum level of 11. The listing below gives the output from SP_GTTRSF_Q for LEVMAX=5.

```NODE                            LEV  MAXLEV  INTNOD  INTMAX PATH                                                                             LP
------------------------------ ---- ------- ------- ------- -------------------------------------------------------------------------------- --
937                               1      11       1      45 937
5233                              1      11       1      45 5233
8579                              1      11       1      45 8579
..4135                            2      11       2      45 937,4135
....1860                          3      11       3      45 8579,4135,1860
....16498                         3      11       3      45 8579,4135,16498
......19442                       4      11       4      45 8579,4135,16498,19442
..........9890                    6      11       6      45 8579,4135,16498,19442,6264,9890
......22826                       4      11       4      45 8579,4135,16498,22826
..........12260                   6      11       8      45 8579,4135,16498,22826,6804,12260
..........25491                   6      11       8      45 8579,4135,16498,22826,6804,25491
..........25844                   6      11       6      45 8579,4135,16498,22826,6804,25844
..............8037                8      11       9      45 8579,4135,16498,22826,13520,122,4825,8037
..............14621               8      11      11      45 8579,4135,16498,22826,13520,122,4825,14621
..............19608               8      11      10      45 8579,4135,16498,22826,13520,122,4825,19608
..............4836                8      11       9      45 8579,4135,16498,22826,13520,122,9593,4836
............4641                  7      11       9      45 8579,4135,16498,22826,13520,22224,4641
............17937                 7      11       9      45 8579,4135,16498,22826,13520,22224,17937
............21660                 7      11       7      45 8579,4135,16498,22826,13520,22224,21660
............22088                 7      11       7      45 8579,4135,16498,22826,13520,22224,22088
..........22224                   6      11       9      45 8579,4135,16498,22826,13520,22224
........17599                     5      11       5      45 8579,4135,16498,22826,17599
..16258                           2      11       2      45 8579,16258
....1356                          3      11       3      45 8579,16258,1356
....1727                          3      11       3      45 8579,16258,1727
......4241                        4      11       4      45 8579,16258,1727,4241
........5227                      5      11       5      45 8579,16258,1727,4241,5227
........7015                      5      11       5      45 8579,16258,1727,4241,7015
........10476                     5      11       5      45 8579,16258,1727,4241,10476
..............7854                8      11      10      45 8579,16258,1727,4241,10476,4875,11844,7854
............11844                 7      11      10      45 8579,16258,1727,4241,10476,4875,11844
.
. extracted
.
..23429                           2      11       2      45 17038,23429
....4781                          3      11       3      45 17038,23429,4781
....19697                         3      11       3      45 17038,23429,19697
......5519                        4      11       4      45 17038,23429,19697,5519
........26167                     5      11       5      45 17038,23429,19697,5519,26167
......17818                       4      11       4      45 17038,23429,19697,17818
......20260                       4      11       4      45 17038,23429,19697,20260
......23809                       4      11       4      45 17038,23429,19697,23809
........23219                     5      11       5      45 17038,23429,19697,5519,23219
..24578                           2      11       2      45 17038,24578
....18297                         3      11       3      45 17038,24578,18297
......5402                        4      11       4      45 17038,24578,18297,5402
......15947                       4      11       4      45 17038,24578,18297,15947
18720                             1      11       1      45 18720
19607                             1      11       1      45 19607
..3466                            2      11       2      45 937,3466

4158 rows selected.

Elapsed: 00:00:09.78
```

The embedded Excel file below summarises the results with relevant statistics from the query runs and the execution plans.

Results summary - SP_RSFONE (simple truncation)

• SP_RSFONE ran in from 3 seconds to 60 seconds, and returned the exact solution from LEVMAX = 15 on
• It continued iterating up to the maximum level of LEVMAX in each case, although all optimal path lengths are found by level 11

Results summary - SP_RSFTWO (preliminary approximate subquery)
This is the execution plan for LEVMAX=5

```-----------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                          | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                   |                 |      1 |        |   4158 |00:00:27.47 |    2716K|       |       |          |
|   1 |  WINDOW SORT                                       |                 |      1 |     39 |   4158 |00:00:27.47 |    2716K|   372K|   372K|  330K (0)|
|   2 |   SORT GROUP BY                                    |                 |      1 |     39 |   4158 |00:00:27.45 |    2716K|    19M|  6662K|   17M (0)|
|   3 |    VIEW                                            |                 |      1 |     39 |  87755 |00:00:27.27 |    2716K|       |       |          |
|   4 |     UNION ALL (RECURSIVE WITH) DEPTH FIRST         |                 |      1 |        |  87755 |00:00:27.17 |    2716K|    16M|  1524K|   14M (0)|
|*  5 |      INDEX RANGE SCAN                              | ARCS_CA_GRQC_PK |      1 |      6 |      8 |00:00:00.01 |       2 |       |       |          |
|   6 |      WINDOW SORT                                   |                 |     45 |     33 |  87747 |00:00:21.78 |    1664K|   832K|   511K|  739K (0)|
|*  7 |       FILTER                                       |                 |     45 |        |  87747 |00:00:21.27 |    1664K|       |       |          |
|*  8 |        HASH JOIN RIGHT OUTER                       |                 |     45 |     33 |    110K|00:00:21.38 |    1664K|  1817K|  1817K| 1565K (0)|
|   9 |         VIEW                                       |                 |     45 |     39 |    114K|00:00:21.02 |    1655K|       |       |          |
|  10 |          SORT GROUP BY                             |                 |     45 |     39 |    114K|00:00:20.96 |    1655K|   337K|   337K|  299K (0)|
|  11 |           VIEW                                     |                 |     45 |     39 |    689K|00:00:20.23 |    1655K|       |       |          |
|  12 |            UNION ALL (RECURSIVE WITH) BREADTH FIRST|                 |     45 |        |    689K|00:00:19.32 |    1655K|   903K|   523K|  802K (0)|
|* 13 |             INDEX RANGE SCAN                       | ARCS_CA_GRQC_PK |     45 |      6 |    360 |00:00:00.01 |      50 |       |       |          |
|  14 |             WINDOW SORT                            |                 |    225 |     33 |    688K|00:00:04.00 |   44865 |  1116K|   557K|  991K (0)|
|  15 |              NESTED LOOPS                          |                 |    225 |     33 |    688K|00:00:01.27 |   44865 |       |       |          |
|  16 |               RECURSIVE WITH PUMP                  |                 |    225 |        |  67635 |00:00:00.27 |       0 |       |       |          |
|* 17 |               INDEX RANGE SCAN                     | ARCS_CA_GRQC_PK |  67635 |      6 |    688K|00:00:00.57 |   44865 |       |       |          |
|  18 |         NESTED LOOPS                               |                 |     45 |     33 |    110K|00:00:00.21 |    8594 |       |       |          |
|  19 |          RECURSIVE WITH PUMP                       |                 |     45 |        |  10787 |00:00:00.03 |       0 |       |       |          |
|* 20 |          INDEX RANGE SCAN                          | ARCS_CA_GRQC_PK |  10787 |      6 |    110K|00:00:00.11 |    8594 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - access("SRC"=3466)
7 - filter("P"."LEV"<NVL("B"."LEV",1000000))
8 - access("B"."NODE"="DST")
13 - access("SRC"=3466)
17 - access("SRC"="P"."NODE")
20 - access("SRC"="P"."NODE")

```
• SP_RSFTWO ran in from 27 seconds to 552 seconds, and returned the exact solution in all cases
• From the intmax value (Excel file) we can see that only in the case of LEVMAX=5 did the second recursion iterate beyond the optimum level of 11 (in fact to a level of 45). This would be due the preliminary approximate subquery allowing it to discard sub-optimal paths in the second recursion
• The execution plan above shows that Oracle CBO has chosen not to materialize the first recursive subquery, and essentially reruns it at each of the 45 outer iterations
• A better approach for the CBO to have taken would appear to be to form a hash table in memory (where possible) of the first subquery, and run each iteration of the outer recursion outer-joining to that unchanging hash table; or alternatively, to materialize it and outer-join in any other way deemed appropriate
• I tried hinting the subquery to get CBO to materialise or avoid the repetition of the first subquery, but without success, and so decided using a temproray table to materialise it myself would be a good idea

Results summary - SP_GTTRSF_I and SP_GTTRSF_Q (preliminary approximate query to GTT)

• SP_GTTRSF_I and SP_GTTRSF_Q ran in from 5 seconds to 54 seconds combined
• LEVMAX=10 gave slight the better result here: Evidently, the extra work in the insert compared with LEVMAX=5 was over-compensated by the benefit of a better approximate solution
• Once LEVMAX is sufficiently large to give a good approximate solution it is most efficient not to increase it further
• This method is almost as efficient as simply truncating at a given level, but guarantees optimality

Network analysis

I have my own network analysis program, implemented as a PL/SQL pipelined function. I thought this might be useful to help validate the results. The function returns all distinct subnetworks and is called three times to give different levels of detail. It runs against a view links_v that must be created for the data source containing the network links. Here is the output:

```links_v based on net_CA_GrQc

View dropped.

View created.

COUNT(*)
----------
14484

Network detail

---------- ------- ------- ------ ---------- ----------
1000         13422    4158      0   1000     ROOT
1 > 1149     770
2 > 12016    4681
3 < 1000*    778
3 > 18271    7628
4 < 13702    3675
.
. Extracted
.
11 < 5400     14039
8 < 5241     5959
7 > 18145    8551
8 > 21799    13889
9 < 11557*   8553
6 > 18271*   14193
6 > 19978    14194
7 < 13702*   3676
7 < 18271*   3671
4 < 18152    12071
2 > 16234    4682
3 > 18373    4686
4 < 1149*    4684
4 > 19871    4687
5 > 25526    5590
6 < 18373*   4688
2 > 17251    4683
2 > 20557    4685
2 > 4189     4679
3 > 5829     13222
1 > 20667    785
10002            1       2      0   10002    ROOT
1 < 7468     8487
10056           11       6      0   10056    ROOT
1 < 2754     13119
2 > 5511     13116
3 > 10056*   13122
3 > 7265     13120
4 > 10056*   13115
4 < 2754*    13117
4 > 7479     13114
5 > 10056*   12036
5 > 13390    12037
5 < 2754*    13118
5 < 5511*    13121
1010             1       2      0   1010     ROOT
1 > 2749     13221
10115            2       3      0   10115    ROOT
1 > 10134    50
1 > 23916    51
1013             2       3      0   1013     ROOT
1 > 19011    9224
2 < 2123     12933
10133            1       2      0   10133    ROOT
1 > 18218    6369
10148            1       2      0   10148    ROOT
1 > 10847    7955
10154            5       4      0   10154    ROOT
1 > 16830    13630
2 < 11410    14133
3 < 9224     12381
4 > 10154*   12380
4 > 16830*   12382
10163           24       8      0   10163    ROOT
1 > 12551    7434
2 < 1281     7440
3 > 10163*   7439
3 > 18621    7441
4 < 10163*   7435
4 < 12551*   5987
4 < 17588    5984
5 < 12551*   5986
5 > 24207    5985
6 < 10163*   7436
6 < 12551*   5988
6 < 1281*    7442
6 < 18621*   5982
6 > 25287    5981
7 < 10163*   7437
7 < 12551*   5989
7 < 1281*    7443
7 < 18621*   5983
7 < 3653     7448
8 > 10163*   7444
8 > 12551*   7445
8 < 1281*    7438
8 > 18621*   7446
8 > 24207*   7447
10178            3       3      0   10178    ROOT
1 > 24133    10756
2 < 3744     14126
3 > 10178*   14125
10180            3       3      0   10180    ROOT
1 > 17587    13060
2 < 2334     13059
3 > 10180*   13058
10252            3       3      0   10252    ROOT
1 > 16820    6964
2 < 9630     6963
3 > 10252*   6962
10317            2       3      0   10317    ROOT
1 > 11816    4265
1 < 2259     4264
10338           14       7      0   10338    ROOT
1 > 12458    5220
2 > 12460    8958
3 < 10338*   5221
3 > 16651    13302
4 < 10338*   5223
4 < 12458*   8959
4 > 19378    13976
5 < 10338*   5224
5 < 12458*   8960
5 < 12460*   13303
5 < 7523     12926
6 > 10338*   12924
6 > 12458*   12925
1 > 14844    5222
10341            6       4      0   10341    ROOT
1 < 1292     10693
2 > 14840    10694
3 < 10341*   10695
3 < 6648     10697
4 > 10341*   10696
4 < 1292*    10692
10355            5       6      0   10355    ROOT
1 < 2054     3240
2 > 13741    3241
2 < 1552     13484
2 > 8719     3238
2 > 9760     3239
10356            1       2      0   10356    ROOT
1 > 19062    7274
10382            1       2      0   10382    ROOT
1 > 25482    12047
10407            1       2      0   10407    ROOT
1 < 6701     13278
10432            2       3      0   10432    ROOT
1 > 16891    7463
1 < 5774     13628
10433            1       2      0   10433    ROOT
1 < 2593     5143
1050             1       2      0   1050     ROOT
1 > 17120    4284
1051             3       3      0   1051     ROOT
1 > 25664    13897
2 < 4245     13898
3 < 1051*    13896
10513            2       3      0   10513    ROOT
1 > 25059    9437
1 > 25595    9438
10517            1       2      0   10517    ROOT
1 < 5119     9154
10522            1       2      0   10522    ROOT
1 > 10561    11775
10559            1       2      0   10559    ROOT
1 > 18245    8169
10564            6       5      0   10564    ROOT
1 > 15187    6266
2 < 8970     9824
3 > 10564*   9823
1 < 2984     6265
2 > 8731     6264
3 > 10564*   6294
10602            1       2      0   10602    ROOT
1 > 20805    13140
10637            4       4      0   10637    ROOT
1 > 21340    4094
2 < 8428     4093
3 > 10637*   4091
3 > 18603    4092
10638            1       2      0   10638    ROOT
1 < 8717     12974
10672            6       4      0   10672    ROOT
1 > 12512    11111
2 < 4630     11110
3 > 10672*   11109
3 > 9921     11108
4 > 10672*   13681
4 > 12512*   13682
10676            1       2      0   10676    ROOT
1 > 24961    1798
10677           29      14      0   10677    ROOT
1 > 17015    9130
2 > 20560    7465
3 < 309      7253
4 > 10677*   7250
4 > 17015*   7251
4 > 17453    7252
5 < 11030    10700
6 > 23946    10701
7 < 17453*   4282
7 > 25611    10703
8 < 11030*   10702
8 < 17453*   4283
8 < 6012     8987
9 > 11030*   8984
9 > 17453*   8985
9 > 23946*   8986
9 > 9591     8983
10 > 10677*   4275
10 > 11030*   4276
10 > 13704    4277
10 > 17015*   4278
10 > 17453*   4279
10 < 2136     4273
11 > 17453*   4274
10 < 2186     14089
10 > 23946*   4280
10 > 25611*   4281
10 < 309*     7249
2 > 21314    7466
10679            4       4      0   10679    ROOT
1 < 115      9941
2 > 5268     9940
3 > 10679*   9939
1 < 8671     9015
10682            4       4      0   10682    ROOT
1 < 2506     6022
2 > 9962     6021
3 > 10682*   5990
3 > 22428    5991
10792           11       7      0   10792    ROOT
1 > 15695    9866
2 > 20379    9647
2 > 21309    9648
3 < 20422    9661
4 < 2222     9642
5 > 10792*   9640
5 > 15695*   9641
5 > 21309*   9643
5 < 2215     9644
6 > 15695*   9645
6 > 21309*   9646
10818            3       3      0   10818    ROOT
1 > 11016    11965
2 > 25596    11967
3 < 10818*   11966
10872            3       3      0   10872    ROOT
1 > 24250    6959
2 < 2666     6961
3 > 10872*   6960
10884           10       7      0   10884    ROOT
1 > 16353    12670
2 < 4958     12669
3 > 10884*   12668
3 > 9606     12667
4 > 10884*   9663
4 > 12122    9664
5 < 5973     13438
6 > 9606*    13437
4 > 16353*   9665
4 < 2247     9662
10897            1       2      0   10897    ROOT
1 < 5548     7565
10904            7       7      0   10904    ROOT
1 > 12707    9216
2 < 5156     4083
3 > 10904*   4082
3 > 14282    4084
4 < 11035    4183
3 > 17933    4085
3 > 7483     4081
10906            5       6      0   10906    ROOT
1 < 1289     10560
2 > 1560     10558
2 > 4365     10559
1 < 8530     5023
2 > 21144    5024
1092             2       3      0   1092     ROOT
1 > 23226    5795
1 > 3196     5794
10936            3       4      0   10936    ROOT
1 > 19509    10723
1 > 19510    10724
1 > 23687    10725
11009           12       8      0   11009    ROOT
1 > 20157    7971
2 > 23471    7975
3 < 11009*   7974
1 > 21344    7972
2 < 11721    9753
3 > 12151    9751
4 > 15221    9756
5 < 11721*   9752
5 > 21344*   9754
4 > 21344*   9757
2 > 22990    9755
3 < 11009*   7973
11028            6       4      0   11028    ROOT
1 > 21171    9834
2 > 22730    9836
3 < 11028*   9835
3 < 5211     9833
4 > 11028*   9831
4 > 21171*   9832
11054            3       3      0   11054    ROOT
1 < 8395     14002
2 > 8725     14001
3 > 11054*   14003
1107             1       2      0   1107     ROOT
1 > 6973     6133
11120            3       4      0   11120    ROOT
1 > 16340    13294
1 > 17851    13295
1 > 20233    13296
11196            3       4      0   11196    ROOT
1 < 13       9182
2 > 19170    9183
2 > 7596     9181
11197            3       3      0   11197    ROOT
1 < 844      6408
2 > 890      6407
3 > 11197*   6409
11215            1       2      0   11215    ROOT
1 > 23156    13072
11216            2       3      0   11216    ROOT
1 > 19598    5564
2 < 13815    5565
11279            5       5      0   11279    ROOT
1 > 17750    5108
2 > 22976    5109
2 > 25609    5110
2 < 9832     6048
3 > 11279*   6047
11280            1       2      0   11280    ROOT
1 < 831      11119
11285            3       3      0   11285    ROOT
1 > 12193    7864
2 < 9618     7863
3 > 11285*   7862
11411            1       2      0   11411    ROOT
1 > 24242    13714
11418            5       5      0   11418    ROOT
1 < 161      5233
2 > 19583    5234
3 < 97       12273
4 > 161*     12272
2 > 3105     5232
11427            4       4      0   11427    ROOT
1 > 20414    6376
2 < 14170    7561
3 < 7632     6737
4 > 20414*   6738
11459            1       2      0   11459    ROOT
1 < 6706     4089
11462            1       2      0   11462    ROOT
1 > 17141    5708
11465            3       3      0   11465    ROOT
1 > 11577    12723
2 < 3239     5601
3 > 11465*   5600
11467            1       2      0   11467    ROOT
1 > 19575    13141
11539            7       5      0   11539    ROOT
1 > 15227    13103
2 > 16655    9932
3 < 11539*   13104
3 < 782      9931
4 > 11539*   9929
4 > 15227*   9930
2 > 18151    9933
1154             6       5      0   1154     ROOT
1 > 12017    4312
2 > 12930    4314
3 < 1154*    4313
3 > 17591    4315
4 < 8704     7088
5 > 12930*   7087
11561            6       6      0   11561    ROOT
1 < 9579     11992
2 > 16932    11993
3 > 19451    11991
4 < 9579*    11994
2 > 22381    11995
2 < 6534     12067
11566            3       3      0   11566    ROOT
1 > 11808    3143
2 > 18560    3145
3 < 11566*   3144
11579            1       2      0   11579    ROOT
1 > 15073    12917
11593           10      10      0   11593    ROOT
1 > 18415    9430
2 < 16818    8830
2 > 23648    3437
2 < 4633     3436
2 < 7194     886
3 > 15322    885
3 > 20960    887
4 < 6355     883
5 > 7194*    882
3 > 7542     884
11616           12       9      0   11616    ROOT
1 > 14149    5047
2 > 14662    5042
3 < 11616*   5048
2 > 16648    5043
2 > 19560    5044
3 > 23530    9159
4 < 14149*   5046
4 < 3750     11990
5 > 14149*   11988
5 > 19560*   11989
2 > 22199    5045
2 < 8147     13240
11622            5       5      0   11622    ROOT
1 < 3852     12250
2 > 7877     12249
3 > 11622*   6262
3 < 1378     6261
3 > 15422    6263
11623            1       2      0   11623    ROOT
1 > 23621    9950
11641            1       2      0   11641    ROOT
1 > 24462    11178
11642            1       2      0   11642    ROOT
1 > 25230    13233
11662           10       5      0   11662    ROOT
1 > 14094    9983
2 > 19014    9985
3 < 11662*   9984
3 < 5960     9979
4 > 11662*   9977
4 > 14094*   9978
4 > 8705     9976
5 > 11662*   9980
5 > 14094*   9981
5 > 19014*   9982
11718            1       2      0   11718    ROOT
1 > 20766    14024
11823           19       9      0   11823    ROOT
1 > 13628    13948
2 > 15246    13945
3 > 20332    13377
4 < 13628*   13946
4 < 6350     9193
5 > 11823*   9189
5 > 13628*   9190
5 > 15246*   9191
5 > 16014    9192
5 > 23710    9194
6 < 11823*   13949
6 < 13628*   13947
6 < 8372     13944
7 > 11823*   13940
7 > 13628*   13941
7 > 15246*   13942
7 > 20332*   13943
7 < 6350*    9188
5 < 4495     9195
11828            1       2      0   11828    ROOT
1 < 7807     7330
11842            5       5      0   11842    ROOT
1 < 1656     5766
2 > 21531    5767
3 < 5828     5768
4 < 1656*    5765
2 > 4040     5764
1187             1       2      0   1187     ROOT
1 > 6290     9841
11879            7       6      0   11879    ROOT
1 > 26092    11947
2 < 21187    10770
3 < 4051     7891
4 > 21313    7892
5 < 21310    13661
5 > 26092*   7894
4 > 26092*   7893
11893            3       3      0   11893    ROOT
1 > 25667    13467
2 < 8891     13036
3 > 11893*   13035
11911            1       2      0   11911    ROOT
1 < 9039     12102
1194             1       2      0   1194     ROOT
1 > 21858    1756
11965            3       4      0   11965    ROOT
1 < 1556     6619
2 > 14096    6620
2 > 23154    6621
11969            3       3      0   11969    ROOT
1 > 18996    10664
2 < 1967     10666
3 > 11969*   10665
11971            1       2      0   11971    ROOT
1 > 25211    11733
11991            1       2      0   11991    ROOT
1 < 3186     12664
12034           15       6      0   12034    ROOT
1 > 17935    14102
2 < 2116     14098
3 > 12034*   14097
3 > 9189     14094
4 > 12034*   14092
4 > 17935*   14093
4 > 9765     14090
5 > 12034*   14100
5 > 17935*   14101
5 < 2116*    14095
5 > 9774     14099
6 > 12034*   14103
6 > 17935*   14104
6 < 2116*    14096
6 < 9189*    14091
12041            1       2      0   12041    ROOT
1 > 14384    8493
12042           20      12      0   12042    ROOT
1 < 5738     3826
2 > 17252    3827
3 > 18142    12724
4 < 5738*    3828
4 < 6538     10717
5 > 16040    10715
5 > 17252*   10716
5 < 5478     13900
6 > 5738*    13899
6 > 8349     13901
7 < 1549     9651
8 > 22766    9652
9 > 23168    9655
10 < 1549*    9653
10 < 8349*    9650
9 < 8349*    9649
8 > 24732    9654
7 < 5738*    3825
7 < 6538*    10714
5 < 5738*    3824
12046            7       8      0   12046    ROOT
1 > 25957    7833
2 < 17724    4986
3 > 25958    4987
2 < 19126    9486
3 < 14135    13923
2 < 8263     3572
3 > 17882    3571
12050            1       2      0   12050    ROOT
1 < 5364     13463
12113            4       4      0   12113    ROOT
1 > 16886    12069
2 < 142      12755
3 > 18626    12756
4 < 16886*   12070
12161            1       2      0   12161    ROOT
1 > 19162    4316
12192            3       3      0   12192    ROOT
1 < 5839     9611
2 > 7026     9610
3 > 12192*   9612
12213           21       7      0   12213    ROOT
1 > 13558    13457
2 > 21710    12987
3 < 12213*   13458
3 > 22318    13461
4 < 12213*   13459
4 < 13558*   12988
4 > 24888    13456
5 < 12213*   13460
5 < 13558*   12989
5 < 21710*   13462
5 < 8669     12995
6 > 12213*   12991
6 > 13558*   12992
6 > 21710*   12993
6 > 22318*   12994
6 > 9038     12990

---------- ------- ------- ------ ---------- ----------
12213           21       7      7 > 12213*   13451
7 > 13558*   13452
7 > 21710*   13453
7 > 22318*   13454
7 > 24888*   13455
12248            1       2      0   12248    ROOT
1 < 98       14149
12252            2       3      0   12252    ROOT
1 > 22404    10336
2 < 9026     13358
12256            2       3      0   12256    ROOT
1 > 17237    6040
1 < 5081     6039
12290            2       3      0   12290    ROOT
1 > 17307    8012
1 > 20031    8013
12307            3       3      0   12307    ROOT
1 > 14175    12694
2 > 22980    12693
3 < 12307*   12695
12320            3       3      0   12320    ROOT
1 > 12321    6226
2 > 19222    6228
3 < 12320*   6227
12338            3       3      0   12338    ROOT
1 > 15800    13147
2 < 1878     13146
3 > 12338*   13145
12387            7       6      0   12387    ROOT
1 > 19520    7612
2 > 23084    7614
3 < 12387*   7613
3 < 19931    14057
3 > 23553    7616
3 > 23554    7617
4 < 19520*   7615
12414           13       8      0   12414    ROOT
1 > 14431    5783
2 > 24439    14022
3 < 12414*   5785
3 < 18562    5786
4 < 12414*   5784
4 < 3102     5781
5 > 12414*   5780
5 > 24439*   5782
3 < 20537    13299
4 > 20543    13298
5 > 21339    10744
6 > 24439*   10746
5 > 24439*   10745
12473            1       2      0   12473    ROOT
1 < 9831     13760
12504            1       2      0   12504    ROOT
1 < 9053     9044
12554            1       2      0   12554    ROOT
1 < 7636     11747
12616            3       3      0   12616    ROOT
1 > 16266    4164
2 < 4485     4166
3 > 12616*   4165
12617            2       3      0   12617    ROOT
1 > 17388    7631
2 < 6037     8024
12676            2       3      0   12676    ROOT
1 > 14302    9722
2 < 5432     13653
12689            6       4      0   12689    ROOT
1 > 17560    4479
2 > 21697    4477
3 < 12689*   4480
3 > 25116    4476
4 < 12689*   4481
4 < 17560*   4478
12703            2       3      0   12703    ROOT
1 < 1669     4747
1 < 5260     6438
12704            2       3      0   12704    ROOT
1 < 7861     7495
2 < 2355     7496
12751            1       2      0   12751    ROOT
1 < 6307     11094
12798            2       3      0   12798    ROOT
1 > 19507    6123
2 < 3199     7089
12801            4       4      0   12801    ROOT
1 > 15641    13864
2 > 19548    9870
3 < 12801*   13865
2 < 5466     9871
12863            1       2      0   12863    ROOT
1 > 18390    11997
12940            2       3      0   12940    ROOT
1 < 787      9633
2 > 20883    9634
13012            3       3      0   13012    ROOT
1 < 1983     12737
2 > 20943    12738
3 < 13012*   12739
13022            1       2      0   13022    ROOT
1 < 8077     8653
13031            4       4      0   13031    ROOT
1 > 13334    12391
2 < 8738     12997
3 > 13031*   12996
1 > 13620    12392
13167            3       3      0   13167    ROOT
1 > 15485    13919
2 < 800      7678
3 > 13167*   7677
13202            3       4      0   13202    ROOT
1 > 16703    8062
2 > 22253    3823
3 < 4128     5098
13325           14       8      0   13325    ROOT
1 > 14502    9507
2 > 15899    9510
3 < 13325*   9508
2 > 20926    9511
3 < 13325*   9509
3 < 6735     9505
4 > 13325*   9503
4 > 14502*   9504
4 > 23167    9506
5 < 3388     13311
6 > 3818     13309
7 > 23167*   9127
7 > 6735*    9126
6 > 6735*    13310
13357            4       4      0   13357    ROOT
1 > 25115    9305
2 < 8739     9304
3 > 13357*   9303
1 > 25852    9306
13413            1       2      0   13413    ROOT
1 < 9211     13312
13415            3       3      0   13415    ROOT
1 > 15386    12935
2 < 7585     12980
3 > 13415*   12979
1344             1       2      0   1344     ROOT
1 > 18126    10351
1346             1       2      0   1346     ROOT
1 > 6448     4271
13485            2       3      0   13485    ROOT
1 > 21530    5692
2 < 5059     1701
13486            3       3      0   13486    ROOT
1 > 18688    11102
2 < 4259     11104
3 > 13486*   11103
13621            1       2      0   13621    ROOT
1 > 25388    560
13675            1       2      0   13675    ROOT
1 < 4186     4163
13677            3       3      0   13677    ROOT
1 > 18288    9861
2 > 23206    9863
3 < 13677*   9862
13717            1       2      0   13717    ROOT
1 < 8558     12265
1376             1       2      0   1376     ROOT
1 > 4267     10556
13802            6       4      0   13802    ROOT
1 > 16823    14183
2 > 19553    13907
3 < 13802*   14184
3 < 5571     14182
4 > 13802*   14180
4 > 16823*   14181
1383            11       6      0   1383     ROOT
1 > 3377     13218
2 < 1660     11777
3 > 23068    11778
4 > 25631    11781
5 < 1660*    11779
5 > 25678    11776
6 < 1660*    11780
6 < 23068*   11782
6 < 3377*    11785
5 < 3377*    11784
4 < 3377*    11783
13932            4       4      0   13932    ROOT
1 < 6075     1618
2 > 14499    1619
3 > 20345    1621
4 < 6075*    1620
14               1       2      0   14       ROOT
1 > 14171    13498
14130            1       2      0   14130    ROOT
1 < 3599     9719
14131            1       2      0   14131    ROOT
1 < 372      12066
14132            1       2      0   14132    ROOT
1 > 15154    11120
14159            4       4      0   14159    ROOT
1 > 24852    6980
2 < 5144     10685
3 > 14159*   10684
2 < 8869     8832
14182            1       2      0   14182    ROOT
1 > 17537    12666
14338            1       2      0   14338    ROOT
1 > 22729    11180
14339            2       3      0   14339    ROOT
1 > 18608    13026
2 > 22816    13025
14353            6       5      0   14353    ROOT
1 > 14886    4074
2 < 2043     9914
3 > 6315     9913
4 > 14886*   5819
2 < 2455     4073
3 > 14353*   4072
14358            1       2      0   14358    ROOT
1 < 5697     12262
14543            4       4      0   14543    ROOT
1 > 21221    7497
2 > 25130    7499
3 < 14543*   7498
3 < 7958     6801
14560            3       3      0   14560    ROOT
1 > 24251    3804
2 < 2656     3803
3 > 14560*   3802
14763            1       2      0   14763    ROOT
1 < 4432     14004
14770            9       7      0   14770    ROOT
1 > 21713    11906
2 > 25470    12922
3 < 14770*   11908
1 > 23461    11907
2 < 3297     11746
3 > 17936    11745
3 > 5667     11744
4 > 14770*   9772
4 > 23461*   9773
14771            1       2      0   14771    ROOT
1 > 24127    4463
14842            2       3      0   14842    ROOT
1 > 24575    9988
2 < 8255     13350
14845            3       3      0   14845    ROOT
1 > 19521    13500
2 < 5232     13502
3 > 14845*   13501
14868            3       3      0   14868    ROOT
1 > 16226    9830
2 < 2201     9829
3 > 14868*   9828
14894            2       3      0   14894    ROOT
1 < 1670     9778
1 > 22927    9777
1490             1       2      0   1490     ROOT
1 > 20813    3849
14990            1       2      0   14990    ROOT
1 > 25975    13930
15181            1       2      0   15181    ROOT
1 < 2331     8932
15182           10       5      0   15182    ROOT
1 > 16728    12023
2 > 22319    12026
3 < 15182*   12024
3 > 23424    12028
4 < 15182*   12025
4 < 16728*   12027
4 < 8153     12022
5 > 15182*   12019
5 > 16728*   12020
5 > 22319*   12021
15188            1       2      0   15188    ROOT
1 < 6354     5992
15208            1       2      0   15208    ROOT
1 > 23759    9082
15218            1       2      0   15218    ROOT
1 > 17585    8869
15248            5       5      0   15248    ROOT
1 > 17595    13446
2 > 19018    8520
3 < 17076    4176
2 < 8443     13445
3 > 15248*   13444
15249            1       2      0   15249    ROOT
1 > 15624    3148
15259            2       3      0   15259    ROOT
1 > 25136    13139
2 < 22000    10613
15358            3       3      0   15358    ROOT
1 > 18036    12671
2 > 18338    12673
3 < 15358*   12672
15374            1       2      0   15374    ROOT
1 > 15553    14037
15387            1       2      0   15387    ROOT
1 > 15388    4994
15396            7       5      0   15396    ROOT
1 > 18238    11870
2 > 26180    11865
3 < 15396*   11871
3 < 4777     11869
4 > 15396*   11866
4 > 18238*   11867
4 > 19064    11868
15401            1       2      0   15401    ROOT
1 < 4196     13893
15415            3       4      0   15415    ROOT
1 > 24640    2865
2 < 24152    5206
2 < 4685     11740
15423            1       2      0   15423    ROOT
1 > 18144    9875
1551             2       3      0   1551     ROOT
1 > 8058     9269
2 < 2328     13800
15572            1       2      0   15572    ROOT
1 < 7356     7467
15583            4       4      0   15583    ROOT
1 > 17785    12058
2 > 18628    10047
2 > 24272    10048
3 < 15583*   12059
15609            1       2      0   15609    ROOT
1 > 21809    8705
15642            3       3      0   15642    ROOT
1 > 16783    14117
2 > 17563    10323
3 < 15642*   14118
15668            6       4      0   15668    ROOT
1 > 17293    10740
2 > 25614    10738
3 < 15668*   10741
3 > 26006    10743
4 < 15668*   10742
4 < 17293*   10739
15681            2       3      0   15681    ROOT
1 > 20793    8829
1 < 7482     8828
15688            1       2      0   15688    ROOT
1 > 22692    9551
15706            2       3      0   15706    ROOT
1 > 20776    6419
1 < 374      12104
15712            1       2      0   15712    ROOT
1 > 19596    10679
15824            1       2      0   15824    ROOT
1 > 22283    4049
15847            3       3      0   15847    ROOT
1 < 348      10620
2 > 5660     10619
3 > 15847*   10621
1589             1       2      0   1589     ROOT
1 > 7603     9073
15962            1       2      0   15962    ROOT
1 > 25447    13908
16015            1       2      0   16015    ROOT
1 > 22938    13260
16109            9       6      0   16109    ROOT
1 > 21808    6661
2 < 2784     6660
3 > 16109*   6659
3 > 5425     6658
4 > 16109*   2533
4 > 16224    2534
5 > 24161    2537
6 < 5425*    2536
4 > 21808*   2535
16124            3       3      0   16124    ROOT
1 > 20807    8756
2 > 24334    8758
3 < 16124*   8757
16129            3       3      0   16129    ROOT
1 < 2298     12973
2 < 74       12971
3 > 16129*   12972
1621             3       3      0   1621     ROOT
1 > 6576     11063
2 > 7109     11062
3 < 1621*    11064
16281            1       2      0   16281    ROOT
1 > 24728    4740
16312            1       2      0   16312    ROOT
1 > 16314    12399
16338            1       2      0   16338    ROOT
1 > 21390    9527
16358            3       3      0   16358    ROOT
1 < 822      11830
2 > 9518     11829
3 > 16358*   11831
16470            1       2      0   16470    ROOT
1 > 17822    1078
16484            1       2      0   16484    ROOT
1 < 8302     290
16523            2       3      0   16523    ROOT
1 < 4110     9867
2 > 17696    9868
16609            2       3      0   16609    ROOT
1 < 6170     12054
2 > 8881     12053
16620            8       6      0   16620    ROOT
1 < 1908     8034
2 > 16621    8035
3 < 2611     6945
4 > 16620*   6944
4 < 1908*    8033
4 < 376      6942
5 > 16620*   6943
2 > 17173    8036
16622            1       2      0   16622    ROOT
1 > 18454    12390
16643            1       2      0   16643    ROOT
1 > 20035    12052
16802            3       3      0   16802    ROOT
1 > 23757    9016
2 < 830      9018
3 > 16802*   9017
16892            3       3      0   16892    ROOT
1 > 19572    13495
2 < 3062     8588
3 > 16892*   8587
16922            1       2      0   16922    ROOT
1 > 20925    12923
1695             1       2      0   1695     ROOT
1 > 19380    7838
16957            2       3      0   16957    ROOT
1 < 3362     9779
2 < 3347     9780
16971            3       3      0   16971    ROOT
1 > 20170    13022
2 < 5228     13024
3 > 16971*   13023
17178            8       7      0   17178    ROOT
1 > 21024    9774
2 < 19712    8585
2 > 23215    8586
3 < 17178*   9775
2 < 8071     12245
1 < 2329     13300
2 > 24846    13301
3 < 17178*   9776
17179            3       3      0   17179    ROOT
1 > 24816    5256
2 < 9871     5255
3 > 17179*   5254
17276            3       3      0   17276    ROOT
1 > 18383    4020
2 < 9994     4019
3 > 17276*   4018
17280            7       5      0   17280    ROOT
1 > 19956    7470
2 < 2785     13505
3 > 17280*   13504
3 > 3682     13503
4 > 17280*   13506
4 > 19956*   13507
1 > 24597    7471
17291            3       3      0   17291    ROOT
1 < 4017     4627
2 > 7888     4626
3 > 17291*   4628
17346            3       3      0   17346    ROOT
1 > 20319    9991
2 < 9149     9916
3 > 17346*   9915
17461            3       3      0   17461    ROOT
1 < 188      7261
2 > 22920    7262
3 < 17461*   8965
17468            3       3      0   17468    ROOT
1 < 3200     9325
2 > 4264     9324
3 > 17468*   9326
17470            1       2      0   17470    ROOT
1 > 19221    12233
17471            1       2      0   17471    ROOT
1 < 3989     10774
17503            1       2      0   17503    ROOT
1 < 3748     13137
17690            1       2      0   17690    ROOT
1 < 5814     5763
17782           10       5      0   17782    ROOT
1 > 18231    10034
2 < 1989     10031
3 > 17782*   10030
3 > 4106     10028
4 > 17782*   13352
4 > 18231*   13353
4 > 7515     13351
5 > 17782*   10032
5 > 18231*   10033
5 < 1989*    10029
17865            3       4      0   17865    ROOT
1 > 24159    9131
2 < 4276     10554
2 < 717      9713
17951            1       2      0   17951    ROOT
1 < 833      8539
1798             3       3      0   1798     ROOT
1 > 4828     11136
2 > 5404     11138
3 < 1798*    11137
17992            1       2      0   17992    ROOT
1 < 4181     14179
18003            2       3      0   18003    ROOT
1 < 5545     8014
1 < 6709     3077
18034           18       8      0   18034    ROOT
1 < 1981     9113
2 > 19879    9114
3 < 18034*   9120
3 < 5468     9119
4 > 18034*   9118
4 < 1981*    9109
4 > 5668     9116
5 > 18034*   11977
5 < 1981*    9110
5 > 19879*   11978
5 > 8187     11976
6 > 18034*   9514
6 < 1981*    9112
6 > 19879*   9515
6 < 5468*    9117
2 > 25444    9115
3 < 6908     11813
4 < 1981*    9111
18098            1       2      0   18098    ROOT
1 < 3100     7819
18156            1       2      0   18156    ROOT
1 < 5387     12715
18171            2       3      0   18171    ROOT
1 < 5413     1730
1 < 85       7675
18183            1       2      0   18183    ROOT
1 < 4278     11748
18194            1       2      0   18194    ROOT
1 > 21949    12248
1821             3       3      0   1821     ROOT
1 < 187      6296
2 > 21386    6297
3 < 1821*    6298
18279            3       3      0   18279    ROOT
1 < 6389     8481
2 > 6914     8480
3 > 18279*   8482
1836             1       2      0   1836     ROOT
1 > 8400     12006
18388            1       2      0   18388    ROOT
1 > 20654    13869
18417            1       2      0   18417    ROOT
1 > 19892    13920
18544            3       3      0   18544    ROOT
1 > 21596    10321
2 < 2332     10320
3 > 18544*   10319
18581            1       2      0   18581    ROOT
1 < 2938     9797
18596            3       3      0   18596    ROOT
1 > 23771    12385
2 < 4144     12384
3 > 18596*   12383
18669            4       4      0   18669    ROOT
1 > 19866    10338
2 > 25545    10340
3 < 18669*   10339
3 < 8538     12898
18721            1       2      0   18721    ROOT
1 < 3067     6377
18895            1       2      0   18895    ROOT
1 < 7957     8872
18908            1       2      0   18908    ROOT
1 > 25531    13413
18970           10       5      0   18970    ROOT
1 < 2409     9807
2 > 3522     9804
3 > 18970*   9810
3 > 4286     9808
4 > 18970*   11115
4 < 2409*    9805
4 > 4288     11114
5 > 18970*   11116
5 < 2409*    9806
5 < 3522*    9809
18984            1       2      0   18984    ROOT
1 < 2476     6293
19015           10       5      0   19015    ROOT
1 > 19444    6633
2 > 25139    6635
3 < 19015*   6634
3 < 5212     6629
4 > 19015*   6627
4 > 19444*   6628
4 > 6628     6626
5 > 19015*   6630
5 > 19444*   6631
5 > 25139*   6632
19052            1       2      0   19052    ROOT
1 < 7713     289
19061            1       2      0   19061    ROOT
1 < 9094     1873
19145            1       2      0   19145    ROOT
1 > 23760    12719
1916             3       3      0   1916     ROOT
1 > 20953    6416
2 > 24463    6418
3 < 1916*    6417
19252            1       2      0   19252    ROOT
1 < 3195     6456
19257            3       3      0   19257    ROOT
1 > 22961    9462
2 < 7477     9461
3 > 19257*   9460
19314           15       6      0   19314    ROOT
1 > 19880    13363
2 > 25496    13371
3 < 19314*   13364
3 > 25543    13360
4 < 19314*   13365
4 < 19880*   13372
4 > 26019    13368
5 < 19314*   13366
5 < 19880*   13373
5 < 25496*   13361
5 > 26048    13370
6 < 19314*   13367
6 < 19880*   13374
6 < 25496*   13362
6 < 25543*   13369
194              1       2      0   194      ROOT
1 > 8628     7522
19466            3       3      0   19466    ROOT
1 > 24128    13695
2 < 7543     13697
3 > 19466*   13696
19473            1       2      0   19473    ROOT
1 > 23812    13340
19502            3       3      0   19502    ROOT
1 > 23970    13101
2 < 7718     13100
3 > 19502*   13099
19554            3       3      0   19554    ROOT
1 > 24878    7606
2 < 6159     7605
3 > 19554*   7604
1969             3       3      0   1969     ROOT
1 > 24141    13017
2 < 8472     13018
3 < 1969*    13016
1976             1       2      0   1976     ROOT
1 > 23672    14065
19906            1       2      0   19906    ROOT
1 > 26102    9469
19911            1       2      0   19911    ROOT
1 > 25487    7946
19932           12       7      0   19932    ROOT
1 > 19936    3994
2 < 2118     4090
2 > 25864    3996
3 < 19932*   3995
3 < 4199     3990
4 > 19932*   3988
4 > 19936*   3989
4 > 6443     3987
5 > 19932*   3991
5 > 19936*   3992
5 > 25864*   3993
2 > 26132    3997
19941            1       2      0   19941    ROOT
1 < 6079     9479
2004             1       2      0   2004     ROOT
1 > 21191    900
20084            1       2      0   20084    ROOT
1 > 22311    11060
20150            3       3      0   20150    ROOT
1 > 22312    9946
2 > 25226    9948
3 < 20150*   9947
20216            3       3      0   20216    ROOT
1 < 2223     12050
2 > 4266     12049
3 > 20216*   12051
20309            3       3      0   20309    ROOT
1 < 7267     13033
2 > 7280     13032
3 > 20309*   13031
20320            1       2      0   20320    ROOT
1 < 4643     13929
20333            1       2      0   20333    ROOT
1 < 4262     13275
20433            3       3      0   20433    ROOT
1 < 5446     11939
2 > 5632     11938
3 > 20433*   11940
20707            1       2      0   20707    ROOT
1 > 24820    13375
20803            2       3      0   20803    ROOT
1 > 25231    9268
1 < 6356     12964
20914            3       3      0   20914    ROOT
1 < 6008     6038
2 > 9959     6037
3 > 20914*   6036
21018            1       2      0   21018    ROOT
1 < 5479     8725
21028            1       2      0   21028    ROOT
1 < 8146     7558
2117             1       2      0   2117     ROOT
1 > 6300     8692
2119             1       2      0   2119     ROOT
1 > 7637     2066
2120             1       2      0   2120     ROOT
1 > 8157     288
21206            2       3      0   21206    ROOT
1 > 23175    1940
2 > 25662    1837
21288            3       4      0   21288    ROOT
1 < 8557     6253
2 > 26191    6254
2 < 8308     6255
21303            1       2      0   21303    ROOT
1 < 734      12168
21515            4       4      0   21515    ROOT
1 > 21523    9322
2 < 7916     8070
3 > 21515*   8069
3 < 28       8068
21579            1       2      0   21579    ROOT
1 < 373      8982
21593            1       2      0   21593    ROOT
1 > 22325    9803
21623            1       2      0   21623    ROOT
1 > 22605    12034
21684            6       5      0   21684    ROOT
1 > 22719    9328
2 < 21821    5235
2 > 22720    5236
3 < 21684*   9329
2 > 24169    5237
3 < 21684*   9330
22021            1       2      0   22021    ROOT
1 < 8811     10312
22110            1       2      0   22110    ROOT
1 < 5231     11035
22378            1       2      0   22378    ROOT
1 < 2762     4741
22435            1       2      0   22435    ROOT
1 < 5406     13288
2248             1       2      0   2248     ROOT
1 > 8409     12903
22827            3       3      0   22827    ROOT
1 < 5256     13257
2 > 5257     13256
3 > 22827*   13258
22891            1       2      0   22891    ROOT
1 < 25       8058
2304             6       4      0   2304     ROOT
1 < 360      11887
2 > 7602     11888
3 < 2304*    11890
3 > 8307     11892
4 < 2304*    11891
4 < 360*     11889
23094            1       2      0   23094    ROOT
1 < 2561     13015
2339             1       2      0   2339     ROOT
1 > 24594    3965
23416            1       2      0   23416    ROOT
1 < 3074     7925
23465            1       2      0   23465    ROOT
1 < 598      4505
23480            1       2      0   23480    ROOT
1 > 23652    5173
23772            1       2      0   23772    ROOT
1 < 735      12948
23776            1       2      0   23776    ROOT
1 < 8708     460
23811            3       3      0   23811    ROOT
1 < 4038     13143
2 > 7058     13142
3 > 23811*   13144
23856            2       3      0   23856    ROOT
1 > 24451    5693
2 < 4252     1759
23881            1       2      0   23881    ROOT
1 < 5577     7611
24202            1       2      0   24202    ROOT
1 < 2624     7464
24214            1       2      0   24214    ROOT
1 < 4116     13349
24464            1       2      0   24464    ROOT
1 < 8336     9272
24504            1       2      0   24504    ROOT
1 < 8054     12033
24733            1       2      0   24733    ROOT
1 < 6776     13049
24957            3       3      0   24957    ROOT
1 < 4019     6240
2 > 5626     6239
3 > 24957*   6241
25062            1       2      0   25062    ROOT
1 < 8735     12072
25095            1       2      0   25095    ROOT
1 < 9130     13214
254              1       2      0   254      ROOT
1 > 7572     9945
25492            9       6      0   25492    ROOT
1 > 25511    13407
2 < 8512     13406
3 > 25492*   13405
3 > 9715     13404
4 > 25492*   11133
4 > 25511*   11134
4 < 4034     11130
5 > 9717     11131
6 < 9715*    11132
25510            1       2      0   25510    ROOT
1 < 3279     9327
2557             1       2      0   2557     ROOT
1 > 4272     9656
25676            3       3      0   25676    ROOT
1 < 350      3528
2 > 951      3527
3 > 25676*   3529
2569             1       2      0   2569     ROOT
1 > 5546     1820
25853            1       2      0   25853    ROOT
1 < 8404     8057
26022            3       3      0   26022    ROOT
1 < 4021     8577
2 > 4826     8576
3 > 26022*   8575
294              1       2      0   294      ROOT
1 > 8626     4086
2981             1       2      0   2981     ROOT
1 > 4427     9683
3206             2       3      0   3206     ROOT
1 > 6535     7976
1 > 6579     7977
3844             1       2      0   3844     ROOT
1 < 82       9542
410              1       2      0   410      ROOT
1 > 4366     13307
4254             1       2      0   4254     ROOT
1 > 9337     2681
4954             1       2      0   4954     ROOT
1 > 4955     9567
5083             3       3      0   5083     ROOT
1 > 5084     3494
2 > 8711     3496
3 < 5083*    3495
5137             1       2      0   5137     ROOT
1 > 6884     9209
5261             1       2      0   5261     ROOT
1 > 6282     13379
5409             1       2      0   5409     ROOT
1 > 8623     9568
5486             1       2      0   5486     ROOT
1 > 8366     13376
5490             1       2      0   5490     ROOT
1 > 8474     14046
5492             1       2      0   5492     ROOT
1 > 5853     13722
6072             1       2      0   6072     ROOT
1 > 8887     914
6161             1       2      0   6161     ROOT
1 > 7055     8578
6229             2       3      0   6229     ROOT
1 > 6423     7947
1 > 7647     7948
6446             1       2      0   6446     ROOT
1 > 8036     9161
6669             1       2      0   6669     ROOT
1 > 9865     9691
6744             1       2      0   6744     ROOT
1 > 9595     4285
7534             1       2      0   7534     ROOT
1 > 7536     10346
7588             1       2      0   7588     ROOT
1 > 7590     9718
8534             1       2      0   8534     ROOT
1 > 8676     12343
8615             1       2      0   8615     ROOT
1 < 925      3541

14838 rows selected.

Elapsed: 00:00:10.56
Network summary 1 - by network

---------- ------- ------- ----------
21593            2       2          1
21623            2       2          1
22021            2       2          1
22110            2       2          1
22378            2       2          1
22435            2       2          1
2248             2       2          1
22891            2       2          1
23094            2       2          1
2339             2       2          1
23416            2       2          1
23465            2       2          1
23480            2       2          1
23772            2       2          1
23776            2       2          1
23881            2       2          1
24202            2       2          1
24214            2       2          1
24464            2       2          1
24504            2       2          1
24733            2       2          1
25062            2       2          1
25095            2       2          1
254              2       2          1
25510            2       2          1
2557             2       2          1
2569             2       2          1
25853            2       2          1
294              2       2          1
2981             2       2          1
3844             2       2          1
410              2       2          1
4254             2       2          1
4954             2       2          1
5137             2       2          1
5261             2       2          1
5409             2       2          1
5486             2       2          1
5490             2       2          1
5492             2       2          1
6072             2       2          1
6161             2       2          1
6446             2       2          1
6669             2       2          1
6744             2       2          1
7534             2       2          1
7588             2       2          1
8534             2       2          1
8615             2       2          1
15208            2       2          1
15218            2       2          1
15249            2       2          1
15374            2       2          1
15387            2       2          1
15401            2       2          1
15423            2       2          1
15572            2       2          1
15609            2       2          1
15688            2       2          1
15712            2       2          1
15824            2       2          1
1589             2       2          1
15962            2       2          1
16015            2       2          1
16281            2       2          1
16312            2       2          1
16338            2       2          1
16470            2       2          1
16484            2       2          1
16622            2       2          1
16643            2       2          1
16922            2       2          1
1695             2       2          1
17470            2       2          1
17471            2       2          1
17503            2       2          1
17690            2       2          1
17951            2       2          1
17992            2       2          1
18098            2       2          1
18156            2       2          1
18183            2       2          1
18194            2       2          1
1836             2       2          1
18388            2       2          1
18417            2       2          1
18581            2       2          1
18721            2       2          1
18895            2       2          1
18908            2       2          1
18984            2       2          1
19052            2       2          1
19061            2       2          1
19145            2       2          1
19252            2       2          1
194              2       2          1
19473            2       2          1
1976             2       2          1
19906            2       2          1
19911            2       2          1
19941            2       2          1
2004             2       2          1
20084            2       2          1
20320            2       2          1
20333            2       2          1
20707            2       2          1
21018            2       2          1
21028            2       2          1
2117             2       2          1
2119             2       2          1
2120             2       2          1
21303            2       2          1
21579            2       2          1
10002            2       2          1
1010             2       2          1
10133            2       2          1
10148            2       2          1
10356            2       2          1
10382            2       2          1
10407            2       2          1
10433            2       2          1
1050             2       2          1
10517            2       2          1
10522            2       2          1
10559            2       2          1
10602            2       2          1
10638            2       2          1
10676            2       2          1
10897            2       2          1
1107             2       2          1
11215            2       2          1
11280            2       2          1
11411            2       2          1
11459            2       2          1
11462            2       2          1
11467            2       2          1
11579            2       2          1
11623            2       2          1
11641            2       2          1
11642            2       2          1
11718            2       2          1
11828            2       2          1
1187             2       2          1
11911            2       2          1
1194             2       2          1
11971            2       2          1
11991            2       2          1
12041            2       2          1
12050            2       2          1
12161            2       2          1
12248            2       2          1
12473            2       2          1
12504            2       2          1
12554            2       2          1
12751            2       2          1
12863            2       2          1
13022            2       2          1
13413            2       2          1
1344             2       2          1
1346             2       2          1
13621            2       2          1
13675            2       2          1
13717            2       2          1
1376             2       2          1
14               2       2          1
14130            2       2          1
14131            2       2          1
14132            2       2          1
14182            2       2          1
14338            2       2          1
14358            2       2          1
14763            2       2          1
14771            2       2          1
1490             2       2          1
14990            2       2          1
15181            2       2          1
15188            2       2          1
10115            3       3          1
1013             3       3          2
10317            3       3          1
10432            3       3          1
10513            3       3          1
1092             3       3          1
11216            3       3          2
12252            3       3          2
12256            3       3          1
12290            3       3          1
12617            3       3          2
12676            3       3          2
12703            3       3          1
12704            3       3          2
12798            3       3          2
12940            3       3          2
13485            3       3          2
14339            3       3          2
14842            3       3          2
14894            3       3          1
15259            3       3          2
1551             3       3          2
15681            3       3          1
15706            3       3          1
16523            3       3          2
16609            3       3          2
16957            3       3          2
18003            3       3          1
18171            3       3          1
20803            3       3          1
21206            3       3          2
23856            3       3          2
3206             3       3          1
6229             3       3          1
10178            4       3          3
10180            4       3          3
10252            4       3          3
1051             4       3          3
10818            4       3          3
10872            4       3          3
10936            4       4          1
11054            4       3          3
11120            4       4          1
11196            4       4          2
11197            4       3          3
11285            4       3          3
11465            4       3          3
11566            4       3          3
11893            4       3          3
11965            4       4          2
11969            4       3          3
12192            4       3          3
12307            4       3          3
12320            4       3          3
12338            4       3          3
12616            4       3          3
13012            4       3          3
13167            4       3          3
13202            4       4          3
13415            4       3          3
13486            4       3          3
13677            4       3          3
14560            4       3          3
14845            4       3          3
14868            4       3          3
15358            4       3          3
15415            4       4          2
15642            4       3          3
15847            4       3          3
16124            4       3          3
16129            4       3          3
1621             4       3          3
16358            4       3          3
16802            4       3          3
16892            4       3          3
16971            4       3          3
17179            4       3          3
17276            4       3          3
17291            4       3          3
17346            4       3          3
17461            4       3          3
17468            4       3          3
17865            4       4          2
1798             4       3          3
1821             4       3          3
18279            4       3          3
18544            4       3          3
18596            4       3          3
1916             4       3          3
19257            4       3          3
19466            4       3          3
19502            4       3          3
19554            4       3          3
1969             4       3          3
20150            4       3          3
20216            4       3          3
20309            4       3          3
20433            4       3          3
20914            4       3          3
21288            4       4          2
22827            4       3          3
23811            4       3          3
24957            4       3          3
25676            4       3          3
26022            4       3          3
5083             4       3          3
13031            5       4          3
12801            5       4          3
18669            5       4          3
14543            5       4          3
11427            5       4          4
10682            5       4          3
10679            5       4          3
15583            5       4          3
10637            5       4          3
21515            5       4          3
13357            5       4          3
12113            5       4          4
13932            5       4          4
14159            5       4          3
10154            6       4          4
11842            6       5          4
10906            6       6          2
15248            6       5          3
10355            6       6          2
11418            6       5          4
11279            6       5          3
11622            6       5          3
12689            7       4          4
11561            7       6          4
1154             7       5          5
11028            7       4          4
10672            7       4          4
10564            7       5          3
10341            7       4          4
2304             7       4          4
21684            7       5          3
15668            7       4          4
14353            7       5          4
13802            7       4          4
15396            8       5          4
11539            8       5          4
12046            8       8          3
10904            8       7          4
11879            8       6          5
12387            8       6          4
17280            8       5          4
17178            9       7          3
16620            9       6          5
16109           10       6          6
14770           10       7          4
25492           10       6          6
10884           11       7          6
11593           11      10          5
11662           11       5          5
15182           11       5          5
17782           11       5          5
18970           11       5          5
19015           11       5          5
1383            12       6          6
10792           12       7          6
10056           12       6          5
19932           13       7          5
11616           13       9          5
11009           13       8          5
12414           14       8          6
13325           15       8          7
10338           15       7          6
19314           16       6          6
12034           16       6          6
18034           19       8          6
11823           20       9          7
12042           21      12         10
12213           22       7          7
10163           25       8          8
10677           30      14         11
1000         13423    4158       1146

354 rows selected.

Elapsed: 00:00:01.75
Network summary 2 - grouped by numbers of nodes

#Nodes #Networks
------- ---------
2       177
3        98
4        30
5        17
6        12
7         8
8         6
9         2
10         1
12         1
14         1
4158         1

12 rows selected.

Elapsed: 00:00:01.44
```

The results show that the data set contains 354 connected networks, with one much larger than the rest, having 4158 nodes. This (more or less) matches with the results we got from source 3466. Actually, we should get one fewer record back than the number of nodes in the network, but as in the earlier article the SQL returns the source node in one record - we can easily fix this, but it's not worthwhile my redoing all the results, so I leave it as is.

As another check, we can run against the second largest network, using 10677 as the source, which should give 14 records. Here is the result for SP_GTTRSF_Q, LEVMAX=10.

```NODE                            LEV  MAXLEV  INTNOD  INTMAX PATH
------------------------------ ---- ------- ------- ------- --------------------------------------------------------------------------------
309                               1       2       1       2 309
9591                              1       2       1       2 9591
..2136                            2       2       2       2 9591,2136
..2186                            2       2       2       2 9591,2186
..6012                            2       2       2       2 9591,6012
..11030                           2       2       2       2 9591,11030
..13704                           2       2       2       2 9591,13704
..17453                           2       2       2       2 9591,17453
..23946                           2       2       2       2 9591,23946
..25611                           2       2       2       2 9591,25611
17015                             1       2       1       2 17015
..10677                           2       2       2       2 9591,10677
..20560                           2       2       2       2 309,20560
..21314                           2       2       2       2 17015,21314

14 rows selected.
```

This is consistent with the network output with source node appearing once. The network output (usually I indent the records by level, but the level is very high in some networks here) is:

```Network     #Links  #Nodes    Lev Node       Link
---------- ------- ------- ------ ---------- ----------
10677           29      14      0   10677    ROOT
1 > 17015    9130
2 > 20560    7465
3 < 309      7253
4 > 10677*   7250
4 > 17015*   7251
4 > 17453    7252
5 < 11030    10700
6 > 23946    10701
7 < 17453*   4282
7 > 25611    10703
8 < 11030*   10702
8 < 17453*   4283
8 < 6012     8987
9 > 11030*   8984
9 > 17453*   8985
9 > 23946*   8986
9 > 9591     8983
10 > 10677*   4275
10 > 11030*   4276
10 > 13704    4277
10 > 17015*   4278
10 > 17453*   4279
10 < 2136     4273
11 > 17453*   4274
10 < 2186     14089
10 > 23946*   4280
10 > 25611*   4281
10 < 309*     7249
2 > 21314    7466
```

Results: Friendship network of Brightkite users

Friendship network of Brightkite users

Brightkite was once a location-based social networking service provider where users shared their locations by checking-in. The friendship network was collected using their public API, and consists of 58,228 nodes and 214,078 edges. The network is originally directed but we have constructed a network with undirected edges when there is a friendship in both ways.

The data set comes with the reverse arcs already present, making a total of 428,156 arcs.

I took the first node in the first line in the data set file as the initial root node, 0, and tested only the method SP_GTTRSF_I/Q for values of LEVMAX of 5, 10. The complete results, including execution plans are in the attached file.

The exact solution has 56,739 nodes reached from the source node 0, with a maximum level of 10. The output is a bit large to embed, so attaching it in file, but here are the last few lines of the exact solution:

```122                               1      10       1      13 122
..0                               2      10       2      13 99,0
..6534                            2      10       2      13 122,6534
....15726                         3      10       3      13 40,647,15726
......42871                       4      10       4      13 69,4994,12608,42871
......45149                       4      10       4      13 40,2886,15714,45149
......45850                       4      10       4      13 122,6534,15726,45850
......45851                       4      10       4      13 40,2886,22440,45851
......45852                       4      10       4      13 122,6534,15726,45852
......45853                       4      10       4      13 40,2886,26635,45853
......45854                       4      10       4      13 36,2625,15736,45854
......45855                       4      10       4      13 122,6534,15726,45855
......45856                       4      10       4      13 122,6534,15726,45856
....15744                         3      10       3      13 40,647,15744
....34944                         3      10       3      13 122,6534,34944
....34945                         3      10       3      13 122,6534,34945
....34946                         3      10       3      13 122,6534,34946

56739 rows selected.

Elapsed: 00:01:32.49
```

In summary, as shown in the embedded Excel file, the exact solution is found in a total of 105 seconds and 344 seconds (based on Xplan timings) for LEVMAX=5 and 10 respectively.

I ran my network analysis program on this network too, and here is the final grouped results, showing consistency with the largest connected network of 56,739 nodes.

```Network summary 2 - grouped by numbers of nodes

#Nodes #Networks
------- ---------
2       362
3       103
4        40
5        21
6         9
7         3
8         2
9         1
10         2
11         2
49         1
56739         1

12 rows selected.

Elapsed: 00:00:24.17
```

We can do a further test by using a source from a smaller network. 51944 is a root of the network of size 49 nodes (as the full output shows). Here is the result of sourcing from that, again consistent witht he network analysis program that uses a completely diffferent algorithm:

```NODE                            LEV  MAXLEV  INTNOD  INTMAX PATH
------------------------------ ---- ------- ------- ------- --------------------------------------------------------------------------------
57077                             1       7       1       9 57077
..51944                           2       7       2       9 57077,51944
..57969                           2       7       2       9 57077,57969
....58151                         3       7       3       9 57077,57969,58151
......58195                       4       7       4       9 57077,57969,58151,58195
........58218                     5       7       5       9 57077,57969,58151,58195,58218
......58196                       4       7       4       9 57077,57969,58151,58196
......58197                       4       7       4       9 57077,57969,58151,58197
......58198                       4       7       4       9 57077,57969,58151,58198
......58199                       4       7       4       9 57077,57969,58151,58199
......58201                       4       7       4       9 57077,57969,58151,58201
......58202                       4       7       4       9 57077,57969,58151,58202
......58203                       4       7       4       9 57077,57969,58151,58203
....58152                         3       7       3       9 57077,57969,58152
......58205                       4       7       4       9 57077,57969,58152,58205
........58219                     5       7       5       9 57077,57969,58152,58205,58219
..........58224                   6       7       6       9 57077,57969,58152,58205,58219,58224
........58220                     5       7       5       9 57077,57969,58152,58205,58220
..........58226                   6       7       8       9 57077,57969,58152,58205,58220,58226
............58227                 7       7       8       9 57077,57969,58152,58205,58220,58225,58227
..........58225                   6       7       9       9 57077,57969,58152,58205,58220,58225
........58221                     5       7       5       9 57077,57969,58152,58205,58221
......58207                       4       7       4       9 57077,57969,58152,58207
....58153                         3       7       3       9 57077,57969,58153
....58154                         3       7       3       9 57077,57969,58154
......58208                       4       7       4       9 57077,57969,58154,58208
......58209                       4       7       4       9 57077,57969,58154,58209
....58156                         3       7       3       9 57077,57969,58156
....58159                         3       7       3       9 57077,57969,58159
......58200                       4       7       4       9 57077,57969,58159,58200
....58160                         3       7       3       9 57077,57969,58160
......58210                       4       7       4       9 57077,57969,58160,58210
........58222                     5       7       5       9 57077,57969,58160,58210,58222
......58211                       4       7       4       9 57077,57969,58160,58211
......58212                       4       7       4       9 57077,57969,58160,58212
........58223                     5       7       5       9 57077,57969,58160,58212,58223
....58161                         3       7       3       9 57077,57969,58161
......58204                       4       7       4       9 57077,57969,58161,58204
......58206                       4       7       4       9 57077,57969,58161,58206
..57970                           2       7       2       9 57077,57970
....58155                         3       7       3       9 57077,57970,58155
....58157                         3       7       3       9 57077,57970,58157
....58158                         3       7       3       9 57077,57970,58158
..57971                           2       7       2       9 57077,57971
..57972                           2       7       2       9 57077,57972
....58162                         3       7       3       9 57077,57972,58162
..57973                           2       7       2       9 57077,57973
..57974                           2       7       2       9 57077,57974
....58163                         3       7       3       9 57077,57974,58163

49 rows selected.

Elapsed: 00:00:00.21
```

[4 May 2015 - I will add attachments in the next few days after tidying up, including the network analysis package]
[11 May 2015: I added PL/SQL Pipelined Function for Network Analysis]

# NoCOUG SQL Challenge 2014 Illustrated

An SQL challenge was posted recently on the blog of the Northern California Oracle user group, SQL Mini-challenge. A query was given against Oracle's demo HR schema, with description:

It lists the locations containing a department that either contains an employee named Steven King or an employee who holds the title of President or an employee who has previously held the title of President.

The challenge was to rewrite the query avoiding the relatively expensive existence subqueries, and minimising the number of consistent gets reported on the small demo data set. Oracle's Cost-Based Optimiser will itself transform queries within its parsing phase, but at a relatively low level; for example, an 'OR' condition might be changed to a union if the CBO thinks that will aid performance. The solutions to the challenge present a nice illustration of how more extensive query transfomations can improve performance and modularity characteristics.

In this article I will list four equivalent queries for the problem, three based on solutions provided on the blog, using Ansi syntax here for consistency. For each query I give the output from DBMS_XPlan, and include a query structure diagram following my own diagramming notation. The example query provided in the challenge is not in fact the most literal translation of the requirement into SQL, and I think it will be interesting to start with my idea of what that would be.

Update 28 October 2014: I noticed that in the 'literal' query I had omitted the location condition on the third subquery. I have fixed this and the execution plan is much worse. The results reported here were from version 11.2; running on v12.1 gives some extremely interesting differences, and I have added the v12.1 results at the end for the 'literal' query. They show a much improved plan, with departments 'factorised' out.

Query 1: Literal
This my attempt at the most literal translation of the stated requirement into SQL. The three conditions are all separately expressed as existence subqueries.

QSD Literal

Query Literal
Note that in an earlier version of this article, I had omitted the final 'd.location_id = l.location_id' condition.

```SELECT l.location_id, l.city
FROM locations l
WHERE EXISTS
(SELECT *
FROM departments d
JOIN employees e
ON e.department_id = d.department_id
WHERE d.location_id = l.location_id
AND e.first_name = 'Steven' AND e.last_name = 'King'
) OR EXISTS
(SELECT *
FROM departments d
JOIN employees e
ON e.department_id = d.department_id
JOIN jobs j
ON j.job_id = e.job_id
WHERE d.location_id = l.location_id
AND j.job_title = 'President'
) OR EXISTS
(SELECT *
FROM departments d
JOIN employees e
ON e.department_id = d.department_id
JOIN job_history h
ON h.employee_id = e.employee_id
JOIN jobs j2
ON j2.job_id = h.job_id
WHERE d.location_id = l.location_id
AND j2.job_title   = 'President'
)
```

XPlan Literal

```-------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                   |      1 |        |      1 |00:00:00.01 |     426 |       |       |          |
|*  1 |  FILTER                          |                   |      1 |        |      1 |00:00:00.01 |     426 |       |       |          |
|   2 |   VIEW                           | index\$_join\$_001  |      1 |     23 |     23 |00:00:00.01 |       7 |       |       |          |
|*  3 |    HASH JOIN                     |                   |      1 |        |     23 |00:00:00.01 |       7 |  1023K|  1023K| 1150K (0)|
|   4 |     INDEX FAST FULL SCAN         | LOC_CITY_IX       |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
|   5 |     INDEX FAST FULL SCAN         | LOC_ID_PK         |      1 |     23 |     23 |00:00:00.01 |       4 |       |       |          |
|   6 |   NESTED LOOPS                   |                   |     23 |        |      1 |00:00:00.01 |      92 |       |       |          |
|   7 |    NESTED LOOPS                  |                   |     23 |      1 |     23 |00:00:00.01 |      69 |       |       |          |
|   8 |     TABLE ACCESS BY INDEX ROWID  | EMPLOYEES         |     23 |      1 |     23 |00:00:00.01 |      46 |       |       |          |
|*  9 |      INDEX RANGE SCAN            | EMP_NAME_IX       |     23 |      1 |     23 |00:00:00.01 |      23 |       |       |          |
|* 10 |     INDEX UNIQUE SCAN            | DEPT_ID_PK        |     23 |      1 |     23 |00:00:00.01 |      23 |       |       |          |
|* 11 |    TABLE ACCESS BY INDEX ROWID   | DEPARTMENTS       |     23 |      1 |      1 |00:00:00.01 |      23 |       |       |          |
|  12 |   NESTED LOOPS                   |                   |     22 |        |      0 |00:00:00.01 |     173 |       |       |          |
|  13 |    NESTED LOOPS                  |                   |     22 |      1 |     88 |00:00:00.01 |     166 |       |       |          |
|  14 |     NESTED LOOPS                 |                   |     22 |      2 |      6 |00:00:00.01 |     160 |       |       |          |
|* 15 |      TABLE ACCESS FULL           | JOBS              |     22 |      1 |     22 |00:00:00.01 |     132 |       |       |          |
|  16 |      TABLE ACCESS BY INDEX ROWID | DEPARTMENTS       |     22 |      2 |      6 |00:00:00.01 |      28 |       |       |          |
|* 17 |       INDEX RANGE SCAN           | DEPT_LOCATION_IX  |     22 |      2 |      6 |00:00:00.01 |      22 |       |       |          |
|* 18 |     INDEX RANGE SCAN             | EMP_DEPARTMENT_IX |      6 |     10 |     88 |00:00:00.01 |       6 |       |       |          |
|* 19 |    TABLE ACCESS BY INDEX ROWID   | EMPLOYEES         |     88 |      1 |      0 |00:00:00.01 |       7 |       |       |          |
|  20 |   NESTED LOOPS                   |                   |     22 |        |      0 |00:00:00.01 |     154 |       |       |          |
|  21 |    NESTED LOOPS                  |                   |     22 |      1 |      0 |00:00:00.01 |     154 |       |       |          |
|  22 |     NESTED LOOPS                 |                   |     22 |      1 |      0 |00:00:00.01 |     154 |       |       |          |
|  23 |      NESTED LOOPS                |                   |     22 |      1 |      0 |00:00:00.01 |     154 |       |       |          |
|* 24 |       TABLE ACCESS FULL          | JOBS              |     22 |      1 |     22 |00:00:00.01 |     132 |       |       |          |
|  25 |       TABLE ACCESS BY INDEX ROWID| JOB_HISTORY       |     22 |      1 |      0 |00:00:00.01 |      22 |       |       |          |
|* 26 |        INDEX RANGE SCAN          | JHIST_JOB_IX      |     22 |      1 |      0 |00:00:00.01 |      22 |       |       |          |
|  27 |      TABLE ACCESS BY INDEX ROWID | EMPLOYEES         |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 28 |       INDEX UNIQUE SCAN          | EMP_EMP_ID_PK     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 29 |     INDEX UNIQUE SCAN            | DEPT_ID_PK        |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 30 |    TABLE ACCESS BY INDEX ROWID   | DEPARTMENTS       |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(( IS NOT NULL OR  IS NOT NULL OR  IS NOT NULL))
3 - access(ROWID=ROWID)
9 - access("E"."LAST_NAME"='King' AND "E"."FIRST_NAME"='Steven')
10 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
11 - filter("D"."LOCATION_ID"=:B1)
15 - filter("J"."JOB_TITLE"='President')
17 - access("D"."LOCATION_ID"=:B1)
18 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
19 - filter("J"."JOB_ID"="E"."JOB_ID")
24 - filter("J2"."JOB_TITLE"='President')
26 - access("J2"."JOB_ID"="H"."JOB_ID")
28 - access("H"."EMPLOYEE_ID"="E"."EMPLOYEE_ID")
29 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
30 - filter("D"."LOCATION_ID"=:B1)
```

Query 2: NoCOUG Example
This is the example in the original challenge article, translated into Ansi syntax. It nests the job history existence subquery within an outer existence subquery, and references the departments and employees tables only once.

QSD NoCOUG Example

Query NoCOUG Example

```SELECT l.location_id, l.city
FROM locations l
WHERE EXISTS
(SELECT *
FROM departments d
JOIN employees e
ON e.department_id = d.department_id
JOIN jobs j
ON j.job_id = e.job_id
WHERE d.location_id = l.location_id
AND (
(e.first_name = 'Steven' AND e.last_name = 'King')
OR j.job_title = 'President'
OR EXISTS
(SELECT *
FROM job_history h
JOIN jobs j2
ON j2.job_id = h.job_id
WHERE h.employee_id = e.employee_id
AND j2.job_title   = 'President'
)
)
)
```

XPlan NoCOUG Example

```-------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                   |      1 |        |      1 |00:00:00.01 |     152 |       |       |          |
|*  1 |  HASH JOIN SEMI                  |                   |      1 |      7 |      1 |00:00:00.01 |     152 |  1156K|  1156K| 1120K (0)|
|   2 |   VIEW                           | index\$_join\$_001  |      1 |     23 |     23 |00:00:00.01 |       6 |       |       |          |
|*  3 |    HASH JOIN                     |                   |      1 |        |     23 |00:00:00.01 |       6 |  1023K|  1023K| 1443K (0)|
|   4 |     INDEX FAST FULL SCAN         | LOC_CITY_IX       |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
|   5 |     INDEX FAST FULL SCAN         | LOC_ID_PK         |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
|   6 |   VIEW                           | VW_SQ_1           |      1 |     11 |      1 |00:00:00.01 |     146 |       |       |          |
|*  7 |    FILTER                        |                   |      1 |        |      1 |00:00:00.01 |     146 |       |       |          |
|*  8 |     HASH JOIN                    |                   |      1 |    106 |    106 |00:00:00.01 |      15 |   876K|   876K|  895K (0)|
|   9 |      MERGE JOIN                  |                   |      1 |    107 |    107 |00:00:00.01 |       8 |       |       |          |
|  10 |       TABLE ACCESS BY INDEX ROWID| JOBS              |      1 |     19 |     19 |00:00:00.01 |       2 |       |       |          |
|  11 |        INDEX FULL SCAN           | JOB_ID_PK         |      1 |     19 |     19 |00:00:00.01 |       1 |       |       |          |
|* 12 |       SORT JOIN                  |                   |     19 |    107 |    107 |00:00:00.01 |       6 | 15360 | 15360 |14336  (0)|
|  13 |        TABLE ACCESS FULL         | EMPLOYEES         |      1 |    107 |    107 |00:00:00.01 |       6 |       |       |          |
|  14 |      TABLE ACCESS FULL           | DEPARTMENTS       |      1 |     27 |     27 |00:00:00.01 |       7 |       |       |          |
|  15 |     NESTED LOOPS                 |                   |    105 |        |      0 |00:00:00.01 |     131 |       |       |          |
|  16 |      NESTED LOOPS                |                   |    105 |      1 |     10 |00:00:00.01 |     121 |       |       |          |
|  17 |       TABLE ACCESS BY INDEX ROWID| JOB_HISTORY       |    105 |      1 |     10 |00:00:00.01 |     112 |       |       |          |
|* 18 |        INDEX RANGE SCAN          | JHIST_EMPLOYEE_IX |    105 |      1 |     10 |00:00:00.01 |     105 |       |       |          |
|* 19 |       INDEX UNIQUE SCAN          | JOB_ID_PK         |     10 |      1 |     10 |00:00:00.01 |       9 |       |       |          |
|* 20 |      TABLE ACCESS BY INDEX ROWID | JOBS              |     10 |      1 |      0 |00:00:00.01 |      10 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ITEM_1"="L"."LOCATION_ID")
3 - access(ROWID=ROWID)
7 - filter((("E"."FIRST_NAME"='Steven' AND "E"."LAST_NAME"='King') OR "J"."JOB_TITLE"='President' OR  IS NOT NULL))
8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
12 - access("J"."JOB_ID"="E"."JOB_ID")
filter("J"."JOB_ID"="E"."JOB_ID")
18 - access("H"."EMPLOYEE_ID"=:B1)
19 - access("J2"."JOB_ID"="H"."JOB_ID")
20 - filter("J2"."JOB_TITLE"='President')
```

Query 3: Subquery Factor Union
This converts the 'OR' conditions into a union of three driving subqueries that return the matching department ids from a subquery factor (which could equally be an inline view), and then joins departments and locations.

QSD Subquery Factor Union

Query Subquery Factor Union

```WITH driving_union AS (
SELECT e.department_id
FROM employees e
WHERE (e.first_name = 'Steven' AND e.last_name = 'King')
UNION
SELECT e.department_id
FROM jobs j
JOIN employees e
ON e.job_id        = j.job_id
WHERE j.job_title     = 'President'
UNION
SELECT e.department_id
FROM jobs j
JOIN job_history h
ON j.job_id        = h.job_id
JOIN employees e
ON e.employee_id    = h.employee_id
WHERE j.job_title     = 'President'
)
SELECT DISTINCT l.location_id, l.city
FROM driving_union u
JOIN departments d
ON d.department_id 	= u.department_id
JOIN locations l
ON l.location_id 	= d.location_id
```

XPlan Subquery Factor Union

```----------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                  |      1 |        |      1 |00:00:00.01 |      29 |       |       |          |
|   1 |  HASH UNIQUE                         |                  |      1 |      8 |      1 |00:00:00.01 |      29 |  1156K|  1156K|  464K (0)|
|*  2 |   HASH JOIN                          |                  |      1 |      8 |      1 |00:00:00.01 |      29 |  1517K|  1517K|  366K (0)|
|*  3 |    HASH JOIN                         |                  |      1 |      8 |      1 |00:00:00.01 |      23 |  1517K|  1517K|  382K (0)|
|   4 |     VIEW                             |                  |      1 |      8 |      1 |00:00:00.01 |      17 |       |       |          |
|   5 |      SORT UNIQUE                     |                  |      1 |      8 |      1 |00:00:00.01 |      17 |  2048 |  2048 | 2048  (0)|
|   6 |       UNION-ALL                      |                  |      1 |        |      2 |00:00:00.01 |      17 |       |       |          |
|   7 |        TABLE ACCESS BY INDEX ROWID   | EMPLOYEES        |      1 |      1 |      1 |00:00:00.01 |       2 |       |       |          |
|*  8 |         INDEX RANGE SCAN             | EMP_NAME_IX      |      1 |      1 |      1 |00:00:00.01 |       1 |       |       |          |
|   9 |        NESTED LOOPS                  |                  |      1 |        |      1 |00:00:00.01 |       8 |       |       |          |
|  10 |         NESTED LOOPS                 |                  |      1 |      6 |      1 |00:00:00.01 |       7 |       |       |          |
|* 11 |          TABLE ACCESS FULL           | JOBS             |      1 |      1 |      1 |00:00:00.01 |       6 |       |       |          |
|* 12 |          INDEX RANGE SCAN            | EMP_JOB_IX       |      1 |      6 |      1 |00:00:00.01 |       1 |       |       |          |
|  13 |         TABLE ACCESS BY INDEX ROWID  | EMPLOYEES        |      1 |      6 |      1 |00:00:00.01 |       1 |       |       |          |
|  14 |        NESTED LOOPS                  |                  |      1 |        |      0 |00:00:00.01 |       7 |       |       |          |
|  15 |         NESTED LOOPS                 |                  |      1 |      1 |      0 |00:00:00.01 |       7 |       |       |          |
|  16 |          NESTED LOOPS                |                  |      1 |      1 |      0 |00:00:00.01 |       7 |       |       |          |
|* 17 |           TABLE ACCESS FULL          | JOBS             |      1 |      1 |      1 |00:00:00.01 |       6 |       |       |          |
|  18 |           TABLE ACCESS BY INDEX ROWID| JOB_HISTORY      |      1 |      1 |      0 |00:00:00.01 |       1 |       |       |          |
|* 19 |            INDEX RANGE SCAN          | JHIST_JOB_IX     |      1 |      1 |      0 |00:00:00.01 |       1 |       |       |          |
|* 20 |          INDEX UNIQUE SCAN           | EMP_EMP_ID_PK    |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|  21 |         TABLE ACCESS BY INDEX ROWID  | EMPLOYEES        |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|  22 |     VIEW                             | index\$_join\$_011 |      1 |     27 |     27 |00:00:00.01 |       6 |       |       |          |
|* 23 |      HASH JOIN                       |                  |      1 |        |     27 |00:00:00.01 |       6 |  1096K|  1096K| 1547K (0)|
|  24 |       INDEX FAST FULL SCAN           | DEPT_ID_PK       |      1 |     27 |     27 |00:00:00.01 |       3 |       |       |          |
|  25 |       INDEX FAST FULL SCAN           | DEPT_LOCATION_IX |      1 |     27 |     27 |00:00:00.01 |       3 |       |       |          |
|  26 |    VIEW                              | index\$_join\$_013 |      1 |     23 |     23 |00:00:00.01 |       6 |       |       |          |
|* 27 |     HASH JOIN                        |                  |      1 |        |     23 |00:00:00.01 |       6 |  1023K|  1023K| 1426K (0)|
|  28 |      INDEX FAST FULL SCAN            | LOC_CITY_IX      |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
|  29 |      INDEX FAST FULL SCAN            | LOC_ID_PK        |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("L"."LOCATION_ID"="D"."LOCATION_ID")
3 - access("D"."DEPARTMENT_ID"="U"."DEPARTMENT_ID")
8 - access("E"."LAST_NAME"='King' AND "E"."FIRST_NAME"='Steven')
11 - filter("J"."JOB_TITLE"='President')
12 - access("E"."JOB_ID"="J"."JOB_ID")
17 - filter("J"."JOB_TITLE"='President')
19 - access("J"."JOB_ID"="H"."JOB_ID")
20 - access("E"."EMPLOYEE_ID"="H"."EMPLOYEE_ID")
23 - access(ROWID=ROWID)
27 - access(ROWID=ROWID)
```

Query 4: Outer Joins
This avoids existence subqueries using the idea that an outer join with a constraint that the joined record is not null, with a distinct qualifier to eliminate duplicates, can serve as a logical equivalent.

QSD Outer Joins

Query Outer Joins

```SELECT DISTINCT l.location_id, l.city
FROM employees e
LEFT JOIN jobs j
ON j.job_id        	= e.job_id
AND j.job_title      = 'President'
LEFT JOIN job_history h
ON h.employee_id    = e.employee_id
LEFT JOIN jobs j2
ON j2.job_id        = h.job_id
AND j2.job_title     = 'President'
JOIN departments d
ON d.department_id 	= e.department_id
JOIN locations l
ON l.location_id 	= d.location_id
WHERE (e.first_name = 'Steven' AND e.last_name = 'King')
OR j.job_id IS NOT NULL
OR j2.job_id IS NOT NULL
```

XPlan Outer Joins

```----------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                   |      1 |        |      1 |00:00:00.01 |      36 |       |       |          |
|   1 |  HASH UNIQUE                  |                   |      1 |    106 |      1 |00:00:00.01 |      36 |  1156K|  1156K|  464K (0)|
|*  2 |   FILTER                      |                   |      1 |        |      1 |00:00:00.01 |      36 |       |       |          |
|*  3 |    HASH JOIN RIGHT OUTER      |                   |      1 |    106 |    109 |00:00:00.01 |      36 |  1269K|  1269K|  369K (0)|
|*  4 |     TABLE ACCESS FULL         | JOBS              |      1 |      1 |      1 |00:00:00.01 |       6 |       |       |          |
|*  5 |     HASH JOIN RIGHT OUTER     |                   |      1 |    106 |    109 |00:00:00.01 |      30 |  1134K|  1134K|  751K (0)|
|   6 |      VIEW                     | index\$_join\$_004  |      1 |     10 |     10 |00:00:00.01 |       6 |       |       |          |
|*  7 |       HASH JOIN               |                   |      1 |        |     10 |00:00:00.01 |       6 |  1096K|  1096K| 1331K (0)|
|   8 |        INDEX FAST FULL SCAN   | JHIST_EMPLOYEE_IX |      1 |     10 |     10 |00:00:00.01 |       3 |       |       |          |
|   9 |        INDEX FAST FULL SCAN   | JHIST_JOB_IX      |      1 |     10 |     10 |00:00:00.01 |       3 |       |       |          |
|* 10 |      HASH JOIN OUTER          |                   |      1 |    106 |    106 |00:00:00.01 |      24 |   858K|   858K| 1270K (0)|
|* 11 |       HASH JOIN               |                   |      1 |    106 |    106 |00:00:00.01 |      18 |  1063K|  1063K| 1252K (0)|
|* 12 |        HASH JOIN              |                   |      1 |     27 |     27 |00:00:00.01 |      12 |  1156K|  1156K| 1133K (0)|
|  13 |         VIEW                  | index\$_join\$_010  |      1 |     23 |     23 |00:00:00.01 |       6 |       |       |          |
|* 14 |          HASH JOIN            |                   |      1 |        |     23 |00:00:00.01 |       6 |  1023K|  1023K| 1450K (0)|
|  15 |           INDEX FAST FULL SCAN| LOC_CITY_IX       |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
|  16 |           INDEX FAST FULL SCAN| LOC_ID_PK         |      1 |     23 |     23 |00:00:00.01 |       3 |       |       |          |
|  17 |         VIEW                  | index\$_join\$_008  |      1 |     27 |     27 |00:00:00.01 |       6 |       |       |          |
|* 18 |          HASH JOIN            |                   |      1 |        |     27 |00:00:00.01 |       6 |  1096K|  1096K| 1547K (0)|
|  19 |           INDEX FAST FULL SCAN| DEPT_ID_PK        |      1 |     27 |     27 |00:00:00.01 |       3 |       |       |          |
|  20 |           INDEX FAST FULL SCAN| DEPT_LOCATION_IX  |      1 |     27 |     27 |00:00:00.01 |       3 |       |       |          |
|  21 |        TABLE ACCESS FULL      | EMPLOYEES         |      1 |    107 |    107 |00:00:00.01 |       6 |       |       |          |
|* 22 |       TABLE ACCESS FULL       | JOBS              |      1 |      1 |      1 |00:00:00.01 |       6 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter((("E"."FIRST_NAME"='Steven' AND "E"."LAST_NAME"='King') OR "J"."JOB_ID" IS NOT NULL OR "J2"."JOB_ID" IS NOT NULL))
3 - access("J2"."JOB_ID"="H"."JOB_ID")
4 - filter("J2"."JOB_TITLE"='President')
5 - access("H"."EMPLOYEE_ID"="E"."EMPLOYEE_ID")
7 - access(ROWID=ROWID)
10 - access("J"."JOB_ID"="E"."JOB_ID")
11 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
12 - access("L"."LOCATION_ID"="D"."LOCATION_ID")
14 - access(ROWID=ROWID)
18 - access(ROWID=ROWID)
22 - filter("J"."JOB_TITLE"='President')
```

Query Summary Table v11.2

Here is a summary of some statistics on the queries, run on an Oracle 11.2 XE instance. Query lines depends on formatting of course.

 Query Buffers Table Instances XPlan Steps Query Lines Literal 426 10 30 30 NoCOUG Example 152 6 20 23 Subquery Factor Union 29 6 29 25 Outer Joins 36 6 22 17

Oracle 12c

While the original version of this article, posted 25 August 2014, was based on Oracle 11.2, I later ran my script on Oracle 12.1, and noted that the 'literal' query now had a much-changed execution plan. In particular, the departments table had been 'factorised' out, appearing only once, and giving a much reduced buffer count of 31. It seems that this comes from an improvement in the transformation phase of query execution. The other queries did not show so much difference in plans, see the summary table below.

Execution Plan for Literal v12.1

```-----------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                    | Name               | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |                    |      1 |        |      1 |00:00:00.05 |      31 |      1 |       |       |          |
|*  1 |  HASH JOIN SEMI                              |                    |      1 |      7 |      1 |00:00:00.05 |      31 |      1 |  1645K|  1645K| 1432K (0)|
|   2 |   VIEW                                       | index\$_join\$_001   |      1 |     23 |     23 |00:00:00.01 |       8 |      0 |       |       |          |
|*  3 |    HASH JOIN                                 |                    |      1 |        |     23 |00:00:00.01 |       8 |      0 |  1368K|  1368K| 1566K (0)|
|   4 |     INDEX FAST FULL SCAN                     | LOC_CITY_IX        |      1 |     23 |     23 |00:00:00.01 |       4 |      0 |       |       |          |
|   5 |     INDEX FAST FULL SCAN                     | LOC_ID_PK          |      1 |     23 |     23 |00:00:00.01 |       4 |      0 |       |       |          |
|   6 |   VIEW                                       | VW_SQ_1            |      1 |      8 |      1 |00:00:00.05 |      23 |      1 |       |       |          |
|   7 |    MERGE JOIN SEMI                           |                    |      1 |      8 |      1 |00:00:00.05 |      23 |      1 |       |       |          |
|   8 |     TABLE ACCESS BY INDEX ROWID              | DEPARTMENTS        |      1 |     27 |     10 |00:00:00.01 |       4 |      0 |       |       |          |
|   9 |      INDEX FULL SCAN                         | DEPT_ID_PK         |      1 |     27 |     10 |00:00:00.01 |       2 |      0 |       |       |          |
|* 10 |     SORT UNIQUE                              |                    |     10 |      8 |      1 |00:00:00.05 |      19 |      1 |  2048 |  2048 | 2048  (0)|
|  11 |      VIEW                                    | VW_JF_SET\$1236063A |      1 |      8 |      2 |00:00:00.05 |      19 |      1 |       |       |          |
|  12 |       UNION-ALL                              |                    |      1 |        |      2 |00:00:00.05 |      19 |      1 |       |       |          |
|  13 |        NESTED LOOPS                          |                    |      1 |        |      1 |00:00:00.05 |       9 |      1 |       |       |          |
|  14 |         NESTED LOOPS                         |                    |      1 |      6 |      1 |00:00:00.05 |       8 |      1 |       |       |          |
|* 15 |          TABLE ACCESS FULL                   | JOBS               |      1 |      1 |      1 |00:00:00.01 |       7 |      0 |       |       |          |
|* 16 |          INDEX RANGE SCAN                    | EMP_JOB_IX         |      1 |      6 |      1 |00:00:00.05 |       1 |      1 |       |       |          |
|  17 |         TABLE ACCESS BY INDEX ROWID          | EMPLOYEES          |      1 |      6 |      1 |00:00:00.01 |       1 |      0 |       |       |          |
|  18 |        TABLE ACCESS BY INDEX ROWID BATCHED   | EMPLOYEES          |      1 |      1 |      1 |00:00:00.01 |       2 |      0 |       |       |          |
|* 19 |         INDEX RANGE SCAN                     | EMP_NAME_IX        |      1 |      1 |      1 |00:00:00.01 |       1 |      0 |       |       |          |
|  20 |        NESTED LOOPS                          |                    |      1 |        |      0 |00:00:00.01 |       8 |      0 |       |       |          |
|  21 |         NESTED LOOPS                         |                    |      1 |      1 |      0 |00:00:00.01 |       8 |      0 |       |       |          |
|  22 |          NESTED LOOPS                        |                    |      1 |      1 |      0 |00:00:00.01 |       8 |      0 |       |       |          |
|* 23 |           TABLE ACCESS FULL                  | JOBS               |      1 |      1 |      1 |00:00:00.01 |       7 |      0 |       |       |          |
|  24 |           TABLE ACCESS BY INDEX ROWID BATCHED| JOB_HISTORY        |      1 |      1 |      0 |00:00:00.01 |       1 |      0 |       |       |          |
|* 25 |            INDEX RANGE SCAN                  | JHIST_JOB_IX       |      1 |      1 |      0 |00:00:00.01 |       1 |      0 |       |       |          |
|* 26 |          INDEX UNIQUE SCAN                   | EMP_EMP_ID_PK      |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
|  27 |         TABLE ACCESS BY INDEX ROWID          | EMPLOYEES          |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("VW_COL_1"="L"."LOCATION_ID")
3 - access(ROWID=ROWID)
10 - access("ITEM_1"="D"."DEPARTMENT_ID")
filter("ITEM_1"="D"."DEPARTMENT_ID")
15 - filter("J"."JOB_TITLE"='President')
16 - access("J"."JOB_ID"="E"."JOB_ID")
19 - access("E"."LAST_NAME"='King' AND "E"."FIRST_NAME"='Steven')
23 - filter("J2"."JOB_TITLE"='President')
25 - access("J2"."JOB_ID"="H"."JOB_ID")
26 - access("H"."EMPLOYEE_ID"="E"."EMPLOYEE_ID")

Note
-----
- this is an adaptive plan
```

Query Summary Table v12.1

 Query Buffers Table Instances XPlan Steps Query Lines Literal 31 10 27 31 NoCOUG Example 156 6 19 23 Subquery Factor Union 29 6 28 25 Outer Joins 45 6 22 17

Here is the v12.1 output:

NCOUG-3-121

# Low-Hanging Fruit: The Classic Performance Tuning Anti-pattern

The July/August 2014 edition of Oracle Magazine contains an interesting PL/SQL performance tuning article by Steve Feuerstein, The Joy of Low-Hanging Fruit.

Low-Hanging Cherries

The article is a case study in which SF finds a "cursor FOR loop that contained two nonquery DML statements" and which he calls a classic anti-pattern. As he says, "the inserts and updates are changing the tables on a row-by-row basis, which maximizes the number of context switches". He also identifies an inefficiency in the code that is more problem-specific: An update process occurs within the loop that only needs to be performed at the end of the loop. (Although this is a generic kind of performance 'bug' I would resist the temptation to call it an anti-pattern, preferring to reserve the term for more deliberate strategies). The article goes on to replace the row-by-row DML using bulk processing, and to move the in-loop update outside the loop. SF claims a two to three-fold performance improvement in testing.

The performance improvement sounds good, but at the same time it occurs to me that the article itself could be seen as an illustration of the classic performance-tuning anti-pattern. This is when the tuner looks for things to optimise before he or she knows what is actually taking up the bulk of the time (and the article's title summarises it nicely - so I will use it to name the anti-pattern 🙂 ). The main thrust of the article, as indicated in the subtitle, is the performance benefits of BULK COLLECT, but there is actually no indication that this is where the performance gains were made - it's possible, likely even, that all significant gains came from the other, more problem-specific, improvement.

The first paragraph of the final section of the article reads:

"It might be lots of fun to completely reorganize one's program in hopes of improving performance, but we can't just assume that the resulting code actually does run quickly."

This is exactly right, and is why the first step in any performance tuning process should be to identify the hot-spots. Tuning should then focus on those, ignoring areas where theoretical improvements will make no practical difference. If one might be permitted a second sylvan metaphor, it's important to perceive wood as well as trees.

The Wood of the Self-Murderers, by William Blake

There are many ways to identify the hot-spots, and I would recommend Oracle's PL/SQL profiling features, which I wrote about here, Notes on Profiling Oracle PL/SQL.

Notes on the Metaphors

Low-Hanging Fruit

For such a ubiquitous expression, this seems to be of surprisingly recent origin, according to: What Is the World’s Actual Lowest Hanging Fruit?.

The metaphorical usage doesn’t appear to have a very long history, though. According to Liberman, the Oxford English Dictionary’s first partial reference is from a 1968 Guardian article: “His rare images are picked aptly, easily, like low-hanging fruit.”

Not Seeing the Wood for the Trees

This expression is apparently much older, being traced back to (at least) 1546 here, Can't see the Economist for the Trees, where it is attributed to "The Proverbs of John Heywood" and the lines:

Plentie is no deyntie. ye see not your owne ease.
I see, ye can not see the wood for trees.

The article is ostensibly about an incorrect usage of the expression by The Economist, but is really more interesting than that, and includes equivalents in other languages. For example, Americans say:

Can't see the forest for the trees

while the French say:

L'arbre qui cache la forêt

.
The Wood of the Self-Murderers

This intriguing painting hangs at the Tate Gallery in London, and you can read more about it here, The Wood of the Self-Murderers: The Harpies and the Suicides

The work was completed between 1824 and 1827 and illustrates a passage from the Inferno canticle of the Divine Comedy by Dante Alighieri (1265–1321)

The passage concerned is:

Here the repellent harpies make their nests,
Who drove the Trojans from the Strophades
With dire announcements of the coming woe.
They have broad wings, a human neck and face,
Clawed feet and swollen, feathered bellies; they caw
Their lamentations in the eerie trees

# Brendan's 2-Page Oracle Programming Standards

I recently had some discussions on PL/SQL coding standards centred around some documents I’d come across on the internet, as well as some internal company standards documents.

Steve Feuerstein has a useful page PL/SQL Standards linking to his own document and to two others:

Steven's Naming Conventions and Coding Standards, by Steve Feuerstein (12 page pdf).
PLSQL Standards Developed for the PLSQL Starter Framework-1.pdf, by Bill Coulam (34 page pdf).
Trivadis PL/SQL & SQL Coding Guidelines, by Roger Troller (50 page pdf).

Another interesting document is Naming and Coding Standards for SQL and PL/SQL by William Robertson (html), which emphasizes common sense in application of standards.

These are all valuable documents, but it was thought that many developers would not read and remain familiar with long documents of this kind, and a 2-page summary standards might be useful. Here is my attempt, as a Word document on Scribd:

# SQL and Modularity: Patterns, Anti-Patterns and the Kitchen Sink

There's a lot of importance placed on code re-use in the database development world. In traditional procedural programming languages, such as C or Fortran, the value of modular programming and its application to promoting code re-use is well known and understood. When SQL enters the picture, however, the situation becomes less clear, and there is less consensus on how best to apply the traditional concept of modularity.

This article will consider the concept of modularity, and how it may best be applied to SQL, from the perspective of 'patterns' and 'anti-patterns'. Here is a definition of these terms from Wikipedia, Anti-pattern:

An anti-pattern (or antipattern) is a pattern used in social or business operations or software engineering that may be commonly used but is ineffective and/or counterproductive in practice.[1][2]

The term was coined in 1995 by Andrew Koenig,[3] inspired by Gang of Four's book Design Patterns, which developed the concept of design patterns in the software field. The term was widely popularized three years later by the book AntiPatterns, which extended the use of the term beyond the field of software design and into general social interaction. According to the authors of the latter, there must be at least two key elements present to formally distinguish an actual anti-pattern from a simple bad habit, bad practice, or bad idea:

• Some repeated pattern of action, process or structure that initially appears to be beneficial, but ultimately produces more bad consequences than beneficial results, and
• An alternative solution exists that is clearly documented, proven in actual practice and repeatable

Procedural Modularity

Modularity starts from the idea that a complex design can generally be broken down into a set of less complex component modules that is easier to work with. In programming terms, a long main program would be broken down into smaller subroutines, with a much shorter main program that calls the subroutines.

From this starting point emerges the possibility of code re-use, whereby the decomposition into modules aims at identifying common logic that can be placed in generic modules and called in multiple places. A simple example of this would be an error-logging module in PL/SQL that would write any Oracle errors to a table along with call stack information, that could be called wherever such errors need to be trapped. This might be termed an error-logging pattern, and it's clear that this kind of code re-use can lead to simpler and more maintainable systems.

SQL Modularity: Design Patterns

Transactional APIs

The concept of transactions is important for modular design within a database application.

The Oracle manual, Oracle Database Concepts, defines a transaction thus:
A transaction is a logical unit of work that contains one or more SQL statements. A transaction is an atomic unit. The effects of all the SQL statements in a transaction can be either all committed (applied to the database) or all rolled back (undone from the database).

When a transaction needs to be performed in more than one place, then the code can be placed in a PL/SQL module, sometimes called a 'transactional API'. This is really just the database-specific version of standard modularity, and is obviously a good design pattern to follow.

Data Access Layers

Transactional APIs are often used to form a Data Access Layer (DAL) for front end applications written in languages such as Java. Where the front end requires a record set from the database, the APIs may return a reference cursor, which is essentially a pointer to the data, and avoids the overhead of passing the whole data set at once. The data access layer pattern has a number of important advantages:

• performance is enhanced through reduced network traffic between application server and database
• SQL operates in an efficient set-based fashion for retrieving data in batches
• the PL/SQL language, highly integrated with SQL, is specifically designed for database processing
• storing the database processing code in database packages promotes modularity and code re-use

It is considered best practice to use data access layers even for clients, such as Oracle Forms, that have an embedded PL/SQL engine.

Of course, in order to achieve these benefits the data access layer has to be correctly written, avoiding the anti-pattern pitfalls discussed later. In particular, 'kitchen sink' style APIs that return far more data than required, to promote re-use, must be avoided; different client programs requiring different data should have separate APIs.

Views

Database views have been available in Oracle SQL from the earliest versions and can be used to avoid duplicating a complex SQL query that might be needed in multiple places.

This approach could be seens as a special case of transactional modularity, where a single query is the transaction, and might be regarded as a design pattern for re-use of SQL statements.

Views may form an alternative kind of data access layer, typically used by reporting tools such as Business Objects, and may also be used in conjumction with an API-based layer, which is a common approach in Oracle Forms applications.

Within-SQL Modularity

SQL is essentially a declarative, rather than procedural language for retrieving (and updating etc.) data from relational databases. The original idea was that the programmer specifies the tables and columns where the data are stored, as well as how the tables are related through key values, but does not specify algorithms for retrieving the data: The retrieval algorithms are performed by the SQL engine 'under the covers'. There might therefore seem to be little scope for modularity within a SQL select statement. However, this is not quite true for a couple of reasons: First, logical paths have to be specified between the tables, and the same path may need to be specified multiple times from different starting points; for example, the path to billing and shipping addresses on a sales order would typically involve the same sequence of steps from different id columns; second, as SQL has evolved, procedural capabilities have been added, such as analytic functions and recursion.

In Oracle in-line views were introduced in v7.2, and are in a sense a first step in modularising an SQL statement, followed in v9.2 by the 'WITH' clause for subquery factoring. Analytic functions were introduced in v8i.

Here is an example based on Oracle's HR demo schema. Suppose we want a list of employees with their current and previous jobs (if any), the same for their manager, and a count of the number of subordinates they have. To get the previous jobs, we need to find the latest records in the job_history table for the employee and his manager separately. This can be done using subqueries, but that is inefficient and it is normally better to join to aggregation views that use the DENSE_RANK clause to allow the required previous records to be obtained in a single pass each. Similarly, the subordinate count could be done by a scalar subquery, but again performance would usually dictate the use of another aggregation view.

Here is a query using in-line views to achieve this:

```SELECT emp.last_name || ', ' || emp.first_name name,
job.job_title,
job_p.job_title             job_title_prior,
emp_m.last_name || ', ' || emp_m.first_name name_mgr,
job_m.job_title             job_title_mgr,
job_pm.job_title            job_title_mgr_prior,
sub.n_sub
FROM hr.employees                emp
JOIN hr.jobs                     job
ON job.job_id                  = emp.job_id
LEFT JOIN (SELECT employee_id,
Max (job_id) KEEP (DENSE_RANK LAST ORDER BY end_date) job_id
FROM hr.job_history
GROUP BY employee_id
)                      jhs
ON jhs.employee_id             = emp.employee_id
LEFT JOIN hr.jobs                job_p
ON job_p.job_id                = jhs.job_id
LEFT JOIN hr.employees           emp_m
ON emp_m.employee_id           = emp.manager_id
LEFT JOIN hr.jobs                job_m
ON job_m.job_id                = emp_m.job_id
LEFT JOIN (SELECT employee_id,
Max (job_id) KEEP (DENSE_RANK LAST ORDER BY end_date) job_id
FROM hr.job_history
GROUP BY employee_id
)                      jhs_m
ON jhs_m.employee_id           = emp.manager_id
LEFT JOIN hr.jobs                job_pm
ON job_pm.job_id               = jhs_m.job_id
LEFT JOIN (SELECT manager_id,
Count(*)       n_sub
FROM hr.employees
GROUP BY manager_id
)                      sub
ON sub.manager_id              = emp.employee_id
WHERE emp.department_id           = 30
ORDER BY 1
```

Here is a query using subquery factors to achieve the same:

```WITH jhs_f AS (
SELECT employee_id,
Max (job_id) KEEP (DENSE_RANK LAST ORDER BY end_date) job_id
FROM hr.job_history
GROUP BY employee_id
), sub_f AS (
SELECT manager_id,
Count(*)                    n_sub
FROM hr.employees
GROUP BY manager_id
)
SELECT emp.last_name || ', ' || emp.first_name name,
job.job_title,
job_p.job_title             job_title_prior,
emp_m.last_name || ', ' || emp_m.first_name name_mgr,
job_m.job_title             job_title_mgr,
job_pm.job_title            job_title_mgr_prior,
sub.n_sub
FROM hr.employees                emp
JOIN hr.jobs                     job
ON job.job_id                  = emp.job_id
LEFT JOIN jhs_f                  jhs
ON jhs.employee_id             = emp.employee_id
LEFT JOIN hr.jobs                job_p
ON job_p.job_id                = jhs.job_id
LEFT JOIN hr.employees           emp_m
ON emp_m.employee_id           = emp.manager_id
LEFT JOIN hr.jobs                job_m
ON job_m.job_id                = emp_m.job_id
LEFT JOIN jhs_f                  jhs_m
ON jhs_m.employee_id           = emp.employee_id
LEFT JOIN hr.jobs                job_pm
ON job_pm.job_id               = jhs_m.job_id
LEFT JOIN sub_f                  sub
ON sub.manager_id              = emp.employee_id
WHERE emp.department_id           = 30
ORDER BY 1
```

The second query, although only a line shorter, could be said to be more modular in two ways:

1. The more complex processing is placed at the beginning, prior to the main select, which now contains only simple joins. This might be said to parallel the procedural modularity practice of having a simple main program calling subroutines, and may help maintainability
2. A single subquery factor replaces the two inline views for previous jobs, a more modular design, and one that may be more efficient for larger data sets since Oracle generally materialises subquery factors referenced multiple times

This approach might be regarded as a design pattern for modularity within individual SQL statements.

ANSI Join Syntax

The queries above are written using ANSI join syntax, introduced in v9. Oracle SQL originally used its own proprietary syntax, as shown below for the same query requirement:

```WITH jhs_f AS (
SELECT employee_id,
Max (job_id) KEEP (DENSE_RANK LAST ORDER BY end_date) job_id
FROM hr.job_history
GROUP BY employee_id
), sub_f AS (
SELECT manager_id,
Count(*)                    n_sub
FROM hr.employees
GROUP BY manager_id
)
SELECT emp.last_name || ', ' || emp.first_name name,
job.job_title,
job_p.job_title             job_title_prior,
emp_m.last_name || ', ' || emp_m.first_name name_mgr,
job_m.job_title             job_title_mgr,
job_pm.job_title            job_title_mgr_prior,
sub.n_sub
FROM hr.employees                emp,
hr.jobs                     job,
jhs_f                       jhs,
hr.jobs                     job_p,
hr.employees                emp_m,
hr.jobs                     job_m,
jhs_f                       jhs_m,
hr.jobs                     job_pm,
sub_f                       sub
WHERE emp.department_id           = 30
AND job.job_id                  = emp.job_id
AND jhs.employee_id (+)         = emp.employee_id
AND job_p.job_id (+)            = jhs.job_id
AND emp_m.employee_id (+)       = emp.manager_id
AND job_m.job_id (+)            = emp_m.job_id
AND jhs_m.employee_id (+)       = emp.employee_id
AND job_pm.job_id (+)           = jhs_m.job_id
AND sub.manager_id (+)          = emp.employee_id
ORDER BY 1```

The tables are listed together, join clauses are in a single block not separated from constraints, and outer joins are specified using a (+) token against every column in the 'left' table. The outer join syntax leads to widespread bugs when developers miss the (+) from one of the columns, which silently converts the join to an inner join.

• Greater functionality is available, including full outer joining
• Outer joining is much harder to get wrong
• The syntax follows an ANSI standard
• Locating the join conditions with the table being joined appears to be more modular and readable

ANSI join syntax might therefore be considered a good design pattern to follow.

SQL Modularity: Design Anti-patterns

Here is a thread from Tom Kyte's AskTom forum dealing with an SQL anti-pattern that is unfortunately common, and strongly opposed by Tom Kyte: Considering SQL as a Service. The idea behind the anti-pattern seems to be to avoid repeating even simple table joins in SQL by hiding them in a special type of data access layer designed to be called within individual SQL statements. This results in over-complex 'kitchen-sink' SQL within the layer itself and performance problems in the 'client' SQL. In addition, complex PL/SQL involving object types and arrays tends to be needed to glue it all together; the approach thus achieves the opposite of its intended purpose - simplification - in the manner of a classic anti-pattern.

We'll illustrate the anti-pattern by extending the HR example used above and working through an example. First we'll use the PL/SQL packaged procedure variant, then look at an older form based on views.

APIs as SQL Building Blocks Anti-pattern

Let's suppose that we start from the idea that we should centralise the SQL for employee information in a re-useable API. We could take the SQL above and add in department name, address and manager information to make it more general. We might think initially of making the API a function taking an employee id as input and returning a record. But what if we needed the information for a list of employees? It would be inefficient to call a function for every record in the list, which might lead us to think of making the function take a list as input and return a list of records. We might therefore define object and array types, and a function with the following signature:

`FUNCTION Emp_Info_List (p_emp_id_list SYS.ODCINumberList) RETURN emp_info_list_type;`

Now let's consider a scenario in which we have to provide an API for a web front end following the design pattern of returning a reference cursor. The data required are the following details for all employees in a given department:

• employee name
• manager name
• list of subordinates

The output for department 30 would be:

```NAME                      NAME_MGR               NAME_SUB
------------------------- ---------------------- -------------------
Baida, Shelli             Raphaely, Den
Colmenares, Karen         Raphaely, Den
Himuro, Guy               Raphaely, Den
Khoo, Alexander           Raphaely, Den
Raphaely, Den             King, Steven           Baida, Shelli
Raphaely, Den             King, Steven           Colmenares, Karen
Raphaely, Den             King, Steven           Himuro, Guy
Raphaely, Den             King, Steven           Khoo, Alexander
Raphaely, Den             King, Steven           Tobias, Sigal
Tobias, Sigal             Raphaely, Den

10 rows selected.
```

Here is a possible procedure implementation:

```PROCEDURE Get_Mgr_Subs_KS (p_dept_id PLS_INTEGER, x_mgr_sub_cur OUT SYS_REFCURSOR) IS
l_emp_id_list SYS.ODCINumberList;
BEGIN

SELECT employee_id
BULK COLLECT INTO l_emp_id_list
FROM hr.employees
WHERE department_id = p_dept_id;

OPEN x_mgr_sub_cur FOR
SELECT t.name,
t.name_mgr,
e.last_name || ', ' || e.first_name
FROM TABLE (KSink_Emp.Emp_Info_List (l_emp_id_list)) t
LEFT JOIN hr.employees e
ON e.manager_id = t.employee_id
ORDER BY 1, 2, 3;

END Get_Mgr_Subs_KS;
```

The first step is to get the list of employees for the department, which we then pass into the API, wrapped in the TABLE key word, and join the employees table to get the subordinates. Three SQL select statements are executed. You might argue that I have over-complicated this by having the API take a list of employees rather than the department id, but remember that in this design anti-pattern the API can't be designed for one specific caller, and the list input is more general. It is intended to cater for all calls for employee information so in practice such compromises will happen frequently.

We can compare this to an alternative implementation in which we simply join the tables required:

```PROCEDURE Get_Mgr_Subs_SQL (p_dept_id PLS_INTEGER, x_mgr_sub_cur OUT SYS_REFCURSOR) IS
BEGIN

OPEN x_mgr_sub_cur FOR
SELECT e.last_name || ', ' || e.first_name,
m.last_name || ', ' || m.first_name,
s.last_name || ', ' || s.first_name
FROM hr.employees e
LEFT JOIN hr.employees m
ON m.employee_id = e.manager_id
LEFT JOIN hr.employees s
ON s.manager_id = e.employee_id
WHERE e.department_id = p_dept_id
ORDER BY 1, 2, 3;

END Get_Mgr_Subs_SQL;
```

This joins three tables in one statement while the earlier procedure effectively makes a join through PL/SQL using an array, which is arguably slightly more complicated. In any case the real problems become apparent when you compare the execution plans. I have written a test driver program that calls each of the APIs and loops over the returned cursor.

Taking the plan for the straight SQL implementation first:

```-------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                   |      1 |        |     10 |00:00:00.01 |      14 |       |       |          |
|   1 |  SORT ORDER BY                         |                   |      1 |     35 |     10 |00:00:00.01 |      14 |  2048 |  2048 | 2048  (0)|
|   2 |   NESTED LOOPS OUTER                   |                   |      1 |     35 |     10 |00:00:00.01 |      14 |       |       |          |
|*  3 |    HASH JOIN OUTER                     |                   |      1 |      6 |      6 |00:00:00.01 |      10 |  1281K|  1281K|  544K (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES         |      1 |      6 |      6 |00:00:00.01 |       2 |       |       |          |
|*  5 |      INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX |      1 |      6 |      6 |00:00:00.01 |       1 |       |       |          |
|   6 |     VIEW                               | index\$_join\$_002  |      1 |    107 |    107 |00:00:00.01 |       8 |       |       |          |
|*  7 |      HASH JOIN                         |                   |      1 |        |    107 |00:00:00.01 |       8 |  1245K|  1245K| 1439K (0)|
|   8 |       INDEX FAST FULL SCAN             | EMP_NAME_IX       |      1 |    107 |    107 |00:00:00.01 |       4 |       |       |          |
|   9 |       INDEX FAST FULL SCAN             | EMP_EMP_ID_PK     |      1 |    107 |    107 |00:00:00.01 |       4 |       |       |          |
|  10 |    TABLE ACCESS BY INDEX ROWID BATCHED | EMPLOYEES         |      6 |      6 |      5 |00:00:00.01 |       4 |       |       |          |
|* 11 |     INDEX RANGE SCAN                   | EMP_MANAGER_IX    |      6 |      6 |      5 |00:00:00.01 |       3 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("M"."EMPLOYEE_ID"="E"."MANAGER_ID")
5 - access("E"."DEPARTMENT_ID"=:B1)
7 - access(ROWID=ROWID)
11 - access("S"."MANAGER_ID"="E"."EMPLOYEE_ID")
```

This is a relatively simple plan for the single SQL statement, with 14 buffers read.

For the anti-pattern version there are thee SQL select statements, but we'll ignore the plan for initial bulk collect SQL and consider the other two execution plans. First, the client API SQL:

```---------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                  |      1 |        |     10 |00:00:00.14 |    1195 |     41 |      1 |       |       |          |
|   1 |  SORT ORDER BY                      |                  |      1 |  48554 |     10 |00:00:00.14 |    1195 |     41 |      1 |  2048 |  2048 | 2048  (0)|
|*  2 |   HASH JOIN RIGHT OUTER             |                  |      1 |  48554 |     10 |00:00:00.14 |    1195 |     41 |      1 |  1368K|  1368K| 1322K (0)|
|   3 |    VIEW                             | index\$_join\$_002 |      1 |    107 |    106 |00:00:00.02 |       8 |     12 |      0 |       |       |          |
|*  4 |     HASH JOIN                       |                  |      1 |        |    106 |00:00:00.02 |       8 |     12 |      0 |  1519K|  1519K| 1575K (0)|
|   5 |      INDEX FAST FULL SCAN           | EMP_MANAGER_IX   |      1 |    107 |    106 |00:00:00.01 |       4 |      6 |      0 |       |       |          |
|   6 |      INDEX FAST FULL SCAN           | EMP_NAME_IX      |      1 |    107 |    107 |00:00:00.01 |       4 |      6 |      0 |       |       |          |
|   7 |    COLLECTION ITERATOR PICKLER FETCH| EMP_INFO_LIST    |      1 |   8168 |      6 |00:00:00.13 |    1187 |     29 |      1 |       |       |          |
---------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("E"."MANAGER_ID"=SYS_OP_ATG(VALUE(KOKBF\$),1,2,2))
4 - access(ROWID=ROWID)
```

Note the extreme inaccuracy of the cardinality estimates at steps 1 and 2, which originate in the step 7 estimate of 8168, which is a database-level default for an array function call. This is exposing the general problem that joining to arrays prevents accurate cardinality estimates. A total of 1195 buffers were read.

Next, the plan for the inner API SQL:

```------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                       | Name                      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                |                           |      1 |        |      6 |00:00:00.01 |      94 |      1 |      1 |       |       |          |
|   1 |  TEMP TABLE TRANSFORMATION                      |                           |      1 |        |      6 |00:00:00.01 |      94 |      1 |      1 |       |       |          |
|   2 |   LOAD AS SELECT                                |                           |      1 |        |      0 |00:00:00.01 |       6 |      0 |      1 |  1036K|  1036K|          |
|   3 |    SORT GROUP BY NOSORT                         |                           |      1 |      7 |      7 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|   4 |     TABLE ACCESS BY INDEX ROWID                 | JOB_HISTORY               |      1 |     10 |     10 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|   5 |      INDEX FULL SCAN                            | JHIST_EMP_ID_ST_DATE_PK   |      1 |     10 |     10 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|*  6 |   HASH JOIN OUTER                               |                           |      1 |      6 |      6 |00:00:00.01 |      85 |      1 |      0 |   735K|   735K|  506K (0)|
|*  7 |    HASH JOIN OUTER                              |                           |      1 |      6 |      6 |00:00:00.01 |      78 |      1 |      0 |   736K|   736K|  891K (0)|
|*  8 |     HASH JOIN OUTER                             |                           |      1 |      6 |      6 |00:00:00.01 |      75 |      1 |      0 |   737K|   737K|  550K (0)|
|*  9 |      HASH JOIN OUTER                            |                           |      1 |      6 |      6 |00:00:00.01 |      68 |      1 |      0 |   739K|   739K|  883K (0)|
|* 10 |       HASH JOIN OUTER                           |                           |      1 |      6 |      6 |00:00:00.01 |      62 |      0 |      0 |   740K|   740K|  903K (0)|
|* 11 |        HASH JOIN OUTER                          |                           |      1 |      6 |      6 |00:00:00.01 |      55 |      0 |      0 |   746K|   746K|  541K (0)|
|* 12 |         HASH JOIN OUTER                         |                           |      1 |      6 |      6 |00:00:00.01 |      48 |      0 |      0 |   754K|   754K|  534K (0)|
|* 13 |          HASH JOIN OUTER                        |                           |      1 |      6 |      6 |00:00:00.01 |      41 |      0 |      0 |   766K|   766K|  418K (0)|
|* 14 |           HASH JOIN OUTER                       |                           |      1 |      6 |      6 |00:00:00.01 |      33 |      0 |      0 |   773K|   773K|  414K (0)|
|  15 |            NESTED LOOPS OUTER                   |                           |      1 |      6 |      6 |00:00:00.01 |      26 |      0 |      0 |       |       |          |
|* 16 |             HASH JOIN OUTER                     |                           |      1 |      6 |      6 |00:00:00.01 |      23 |      0 |      0 |   833K|   833K|  414K (0)|
|* 17 |              HASH JOIN OUTER                    |                           |      1 |      6 |      6 |00:00:00.01 |      16 |      0 |      0 |   876K|   876K|  415K (0)|
|* 18 |               HASH JOIN                         |                           |      1 |      6 |      6 |00:00:00.01 |       9 |      0 |      0 |   905K|   905K| 1259K (0)|
|  19 |                MERGE JOIN                       |                           |      1 |    107 |    107 |00:00:00.01 |       9 |      0 |      0 |       |       |          |
|  20 |                 TABLE ACCESS BY INDEX ROWID     | JOBS                      |      1 |     19 |     19 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|  21 |                  INDEX FULL SCAN                | JOB_ID_PK                 |      1 |     19 |     19 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|* 22 |                 SORT JOIN                       |                           |     19 |    107 |    107 |00:00:00.01 |       7 |      0 |      0 | 18432 | 18432 |16384  (0)|
|  23 |                  TABLE ACCESS FULL              | EMPLOYEES                 |      1 |    107 |    107 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  24 |                COLLECTION ITERATOR PICKLER FETCH|                           |      1 |      6 |      6 |00:00:00.01 |       0 |      0 |      0 |       |       |          |
|  25 |               TABLE ACCESS FULL                 | DEPARTMENTS               |      1 |     27 |     27 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  26 |              TABLE ACCESS FULL                  | LOCATIONS                 |      1 |     23 |     23 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|* 27 |             INDEX UNIQUE SCAN                   | COUNTRY_C_ID_PK           |      6 |      1 |      6 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
|  28 |            TABLE ACCESS FULL                    | REGIONS                   |      1 |      4 |      4 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  29 |           VIEW                                  | index\$_join\$_024          |      1 |    107 |    107 |00:00:00.01 |       8 |      0 |      0 |       |       |          |
|* 30 |            HASH JOIN                            |                           |      1 |        |    107 |00:00:00.01 |       8 |      0 |      0 |  1245K|  1245K| 1550K (0)|
|  31 |             INDEX FAST FULL SCAN                | EMP_NAME_IX               |      1 |    107 |    107 |00:00:00.01 |       4 |      0 |      0 |       |       |          |
|  32 |             INDEX FAST FULL SCAN                | EMP_EMP_ID_PK             |      1 |    107 |    107 |00:00:00.01 |       4 |      0 |      0 |       |       |          |
|  33 |          TABLE ACCESS FULL                      | EMPLOYEES                 |      1 |    107 |    107 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  34 |         TABLE ACCESS FULL                       | JOBS                      |      1 |     19 |     19 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  35 |        VIEW                                     |                           |      1 |     18 |     19 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  36 |         HASH GROUP BY                           |                           |      1 |     18 |     19 |00:00:00.01 |       7 |      0 |      0 |  1558K|  1558K| 1185K (0)|
|  37 |          TABLE ACCESS FULL                      | EMPLOYEES                 |      1 |    107 |    107 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  38 |       VIEW                                      |                           |      1 |      7 |      7 |00:00:00.01 |       6 |      1 |      0 |       |       |          |
|  39 |        TABLE ACCESS FULL                        | SYS_TEMP_0FD9D6604_5813E5 |      1 |      7 |      7 |00:00:00.01 |       6 |      1 |      0 |       |       |          |
|  40 |      TABLE ACCESS FULL                          | JOBS                      |      1 |     19 |     19 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  41 |     VIEW                                        |                           |      1 |      7 |      7 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
|  42 |      TABLE ACCESS FULL                          | SYS_TEMP_0FD9D6604_5813E5 |      1 |      7 |      7 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
|  43 |    TABLE ACCESS FULL                            | JOBS                      |      1 |     19 |     19 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - access("JOB_P"."JOB_ID"="JHS"."JOB_ID")
7 - access("JHS"."EMPLOYEE_ID"="EMP"."EMPLOYEE_ID")
8 - access("JOB_MP"."JOB_ID"="JHS_M"."JOB_ID")
9 - access("JHS_M"."EMPLOYEE_ID"="EMP"."EMPLOYEE_ID")
10 - access("SUB"."MANAGER_ID"="EMP"."EMPLOYEE_ID")
11 - access("JOB_M"."JOB_ID"="EMP_M"."JOB_ID")
12 - access("EMP_M"."EMPLOYEE_ID"="EMP"."MANAGER_ID")
13 - access("EMP_DM"."EMPLOYEE_ID"="DEP"."MANAGER_ID")
14 - access("REG"."REGION_ID"="COU"."REGION_ID")
16 - access("LOC"."LOCATION_ID"="DEP"."LOCATION_ID")
17 - access("DEP"."DEPARTMENT_ID"="EMP"."DEPARTMENT_ID")
18 - access("EMP"."EMPLOYEE_ID"=VALUE(KOKBF\$))
22 - access("JOB"."JOB_ID"="EMP"."JOB_ID")
filter("JOB"."JOB_ID"="EMP"."JOB_ID")
27 - access("COU"."COUNTRY_ID"="LOC"."COUNTRY_ID")
30 - access(ROWID=ROWID)
```

This is pretty complex. We can't pretend to do a real performance analysis on such a small database (107 employees), but the potential for performance problems in real cases is clear. It's important to understand that any performance analysis on the client API has to take into account not just the client code, but the full complexity of the centralised SQL, so any apparent simplification from using it is to a great extent illusory.

Database Inter-Schema Data Access Layer Anti-pattern

There is a variant of the anti-pattern above in which data access layers are used to retrieve data across schema boundaries. This variant suffers from exactly the same problems as the first of course, and should equally be avoided.

Views as SQL Building Blocks Anti-Pattern

The same ideas as are behind the APIs as SQL Building Blocks Anti-Pattern can also be implemented through views, and in fact this variant form of the anti-pattern has been around longer I think. We can illustrate it on the same example, by creating a view instead of the central API cursor.

```CREATE OR REPLACE VIEW emp_ks_v (
employee_id,
name,
job_title,
job_title_p,
name_mgr,
job_title_mgr,
job_title_mgr_p,
n_sub,
department_id,
department_name,
name_d_mgr,
country_name,
region_name) AS
WITH jhs_f AS (
SELECT employee_id,
Max (job_id) KEEP (DENSE_RANK LAST ORDER BY end_date) job_id
FROM hr.job_history
GROUP BY employee_id
), sub AS (
SELECT manager_id,
Count(*)                  n_sub
FROM hr.employees
GROUP BY manager_id
)
SELECT emp.employee_id,
emp.last_name || ', ' || emp.first_name,
job.job_title,
job_p.job_title,
emp_m.last_name || ', ' || emp_m.first_name,
job_m.job_title,
job_mp.job_title,
sub.n_sub,
dep.department_id,
dep.department_name,
emp_dm.last_name || ', ' || emp_dm.first_name,
cou.country_name,
reg.region_name
FROM hr.employees              emp
JOIN hr.jobs                   job
ON job.job_id                = emp.job_id
LEFT JOIN jhs_f                jhs
ON jhs.employee_id           = emp.employee_id
LEFT JOIN hr.jobs              job_p
ON job_p.job_id              = jhs.job_id
LEFT JOIN hr.employees         emp_m
ON emp_m.employee_id         = emp.manager_id
LEFT JOIN hr.jobs              job_m
ON job_m.job_id              = emp_m.job_id
LEFT JOIN jhs_f                jhs_m
ON jhs_m.employee_id         = emp.employee_id
LEFT JOIN hr.jobs              job_mp
ON job_mp.job_id             = jhs_m.job_id
LEFT JOIN sub
ON sub.manager_id            = emp.employee_id
LEFT JOIN hr.departments       dep
ON dep.department_id         = emp.department_id
LEFT JOIN hr.employees         emp_dm
ON emp_dm.employee_id        = dep.manager_id
LEFT JOIN hr.locations         loc
ON loc.location_id           = dep.location_id
LEFT JOIN hr.countries         cou
ON cou.country_id            = loc.country_id
LEFT JOIN hr.regions           reg
ON reg.region_id             = cou.region_id
```

The view can then be called to get the employee details for example for a given department, thus:

```SELECT t.name,
t.name_mgr,
CASE WHEN e.last_name IS NOT NULL THEN e.last_name || ', ' || e.first_name END name_sub
FROM emp_ks_v t
LEFT JOIN hr.employees e
ON e.manager_id = t.employee_id
WHERE t.department_id = 30
ORDER BY 1, 2, 3
```

This is actually quite a lot better than the API-based approach as it's much simpler, avoiding the need for object arrays, and allowing use simply by joining. Let's look at the execution plan though (we'll just run the query rather than put it into a client API returning a reference cursor):

```---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                    | Name                      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |                           |      1 |        |     10 |00:00:00.01 |      42 |      1 |      1 |       |       |          |
|   1 |  SORT ORDER BY                               |                           |      1 |     35 |     10 |00:00:00.01 |      42 |      1 |      1 |  2048 |  2048 | 2048  (0)|
|   2 |   NESTED LOOPS OUTER                         |                           |      1 |     35 |     10 |00:00:00.01 |      42 |      1 |      1 |       |       |          |
|   3 |    VIEW                                      | EMP_KS_V                  |      1 |      6 |      6 |00:00:00.01 |      38 |      1 |      1 |       |       |          |
|   4 |     TEMP TABLE TRANSFORMATION                |                           |      1 |        |      6 |00:00:00.01 |      38 |      1 |      1 |       |       |          |
|   5 |      LOAD AS SELECT                          |                           |      1 |        |      0 |00:00:00.01 |       6 |      0 |      1 |  1036K|  1036K|          |
|   6 |       SORT GROUP BY NOSORT                   |                           |      1 |      7 |      7 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|   7 |        TABLE ACCESS BY INDEX ROWID           | JOB_HISTORY               |      1 |     10 |     10 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|   8 |         INDEX FULL SCAN                      | JHIST_EMP_ID_ST_DATE_PK   |      1 |     10 |     10 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|*  9 |      HASH JOIN OUTER                         |                           |      1 |      6 |      6 |00:00:00.01 |      29 |      1 |      0 |   883K|   883K|  517K (0)|
|* 10 |       HASH JOIN OUTER                        |                           |      1 |      6 |      6 |00:00:00.01 |      21 |      1 |      0 |   890K|   890K|  857K (0)|
|* 11 |        HASH JOIN OUTER                       |                           |      1 |      6 |      6 |00:00:00.01 |      14 |      1 |      0 |   895K|   895K|  886K (0)|
|* 12 |         HASH JOIN OUTER                      |                           |      1 |      6 |      6 |00:00:00.01 |      11 |      1 |      0 |   905K|   905K|  893K (0)|
|  13 |          NESTED LOOPS                        |                           |      1 |      6 |      6 |00:00:00.01 |       5 |      0 |      0 |       |       |          |
|  14 |           NESTED LOOPS OUTER                 |                           |      1 |      1 |      1 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
|  15 |            TABLE ACCESS BY INDEX ROWID       | DEPARTMENTS               |      1 |      1 |      1 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|* 16 |             INDEX UNIQUE SCAN                | DEPT_ID_PK                |      1 |      1 |      1 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|* 17 |            INDEX UNIQUE SCAN                 | LOC_ID_PK                 |      1 |      1 |      1 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  18 |           TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES                 |      1 |      6 |      6 |00:00:00.01 |       2 |      0 |      0 |       |       |          |
|* 19 |            INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX         |      1 |      6 |      6 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  20 |          VIEW                                |                           |      1 |      7 |      7 |00:00:00.01 |       6 |      1 |      0 |       |       |          |
|  21 |           TABLE ACCESS FULL                  | SYS_TEMP_0FD9D6606_5813E5 |      1 |      7 |      7 |00:00:00.01 |       6 |      1 |      0 |       |       |          |
|  22 |         VIEW                                 |                           |      1 |      7 |      7 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
|  23 |          TABLE ACCESS FULL                   | SYS_TEMP_0FD9D6606_5813E5 |      1 |      7 |      7 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
|  24 |        VIEW                                  |                           |      1 |     18 |     19 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  25 |         HASH GROUP BY                        |                           |      1 |     18 |     19 |00:00:00.01 |       7 |      0 |      0 |  1558K|  1558K| 1211K (0)|
|  26 |          TABLE ACCESS FULL                   | EMPLOYEES                 |      1 |    107 |    107 |00:00:00.01 |       7 |      0 |      0 |       |       |          |
|  27 |       VIEW                                   | index\$_join\$_013          |      1 |    107 |    107 |00:00:00.01 |       8 |      0 |      0 |       |       |          |
|* 28 |        HASH JOIN                             |                           |      1 |        |    107 |00:00:00.01 |       8 |      0 |      0 |  1245K|  1245K| 1410K (0)|
|  29 |         INDEX FAST FULL SCAN                 | EMP_NAME_IX               |      1 |    107 |    107 |00:00:00.01 |       4 |      0 |      0 |       |       |          |
|  30 |         INDEX FAST FULL SCAN                 | EMP_EMP_ID_PK             |      1 |    107 |    107 |00:00:00.01 |       4 |      0 |      0 |       |       |          |
|  31 |    TABLE ACCESS BY INDEX ROWID BATCHED       | EMPLOYEES                 |      6 |      6 |      5 |00:00:00.01 |       4 |      0 |      0 |       |       |          |
|* 32 |     INDEX RANGE SCAN                         | EMP_MANAGER_IX            |      6 |      6 |      5 |00:00:00.01 |       3 |      0 |      0 |       |       |          |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

9 - access("EMP_M"."EMPLOYEE_ID"="EMP"."MANAGER_ID")
10 - access("SUB"."MANAGER_ID"="EMP"."EMPLOYEE_ID")
11 - access("JHS_M"."EMPLOYEE_ID"="EMP"."EMPLOYEE_ID")
12 - access("JHS"."EMPLOYEE_ID"="EMP"."EMPLOYEE_ID")
16 - access("DEP"."DEPARTMENT_ID"=30)
17 - access("LOC"."LOCATION_ID"="DEP"."LOCATION_ID")
19 - access("EMP"."DEPARTMENT_ID"=30)
28 - access(ROWID=ROWID)
32 - access("E"."MANAGER_ID"="T"."EMPLOYEE_ID")
```

There is only one SQL statement, and the plan is better too, with better cardinality estimates owing to the absence of array processing, and with 42 buffers read. It's still a bad idea though because the client caller would be executing far more complex SQL than is required and as before performance analysis on the client requires the full complexity of the 'centralised' SQL to be included. Using complex views as SQL building blocks is generally considered to have poor performance characteristics.

SQL Modularity: Other Design Options

Splitting Up Long SQL Statements

Oracle's Cost Based Optimiser (CBO) has been greatly enhanced since it's introduction in v7, but remains imperfect. The combinatorial nature of the problem that it tries to solve suggests that there will always be larger queries where it makes a bad choice of plan. In some cases splitting a large query into smaller ones and using temporary tables to join them can give better performance. This may arise from new indexing options for the CBO, or by dynamic sampling capabilities on the temporary tables, or just from the CBO algorithms happening to work better on the divided queries.

It's important to understand though that any such splitting should be done purely on performance grounds: the splitting increases the code complexity and breaking a sequence of declarative joins into several subsequences is not comparable with standard modularisation of programs into subprograms.

Long SQL statements are not necessarily problematic, but obviously should only be as long as necessary, and avoiding the design anti-patterns mentioned helps to ensure this.

Simple Views

Simple views, without joins, are often used in areas such as access control; for example Oracle Applications multi-org features (upto release 11) generally involve transactional tables being referenced by such simple views. These do not cause the performance problems seen with the complex building-block views anti-pattern.

Data Access Layers for Back-end Programs

It is quite possible to use a similar approach for database programs as for front-end programs in terms of a data access layer. One could adopt a standard that stand-alone database PL/SQL programs should access data through packaged APIs rather than directly. This obviously does not have the performance or language advantages of the design pattern in relation to Java front-ends for example, but it may in some circumstances be a preferred method of code organisation. It is therefore not an anti-pattern of the kind we have considered - as long as it is for stand-alone programs only.

Conclusions

The main aim of this article has been to distinguish between good approaches to modularity in SQL (patterns) and bad ones (anti-patterns) based on personal experience of seeing both types applied.

• The Data Access Layer design pattern is an excellent approach for client applications developed in Java, .net etc. to access a database
• Using Data Access Layers for internal access within a database is a classic anti-pattern leading to overcomplication and performance problems
• A good design pattern used in an inappropriate context can become an anti-pattern

SQL Modularity Code

# SQL for the Travelling Salesman Problem

'The travelling salesman problem (TSP) or travelling salesperson problem asks the following question: Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city? It is an NP-hard problem in combinatorial optimization, important in operations research and theoretical computer science.' - Travelling salesman problem.

['or travelling salesperson problem' - :)]

I posted a couple of articles recently on approximate solution methods for 'hard' combinatorial problems using SQL, SQL for the Balanced Number Partitioning Problem and SQL for the Fantasy Football Knapsack Problem, and I wondered whether similar techniques could be applied to TSP. This article provides my answer.

Updated, 20 February 2016: Added attachment with the input data files, DDL and raw results at the end.

Test Problems

I used two test problems, the first being a simple constructed example of five towns arranged in an M-shape. The second is a more realistic problem based on 312 towns in USA and Canada, the data for which I found on a university web site.

Test Problem 1: Emland

The first problem is small enough to verify the solutions manually.

The Short Route

The Long Route

Test Problem 2: USCA312

I got the second problem here, City Distance Datasets, described thus:
'USCA312 describes 312 cities in the US and Canada. Distances between the city are computed from latitude and longitude, not from road mileage'

I took the distances based on the x-y values given, using the simple Euclidean distance formula. I am aware that this will not give accurate distances, but it seemed the simplest approach as the provided distances were in a format not so easy to load into a database, and I am interested only in the technical aspects.

SQL Solution with Recursive Subquery Factoring

SQL

```WITH count_towns AS (
SELECT Count(*) n_towns FROM towns
), dist_from_root AS (/* XTSP  */
SELECT a, b, dst, Row_Number () OVER (ORDER BY :SIGN * dst) rnk_by_dst, Count(DISTINCT a) OVER () + 1 n_towns
FROM distances
WHERE b > a
),  rsf (root, root_leg, path_rnk, nxt_id, lev, tot_price, path, n_towns) AS (
SELECT a, a || ' -> ' || b, 0, d.b, 2, d.dst,
CAST ('|' || LPad (d.a, 3, '0') || '|' || LPad (d.b, 3, '0') AS VARCHAR2(4000)) path,
d.n_towns
FROM dist_from_root d
WHERE d.rnk_by_dst <= :KEEP_NUM_ROOT
UNION ALL
SELECT r.root,
r.root_leg,
Row_Number() OVER (PARTITION BY r.root_leg ORDER BY :SIGN * (r.tot_price + d.dst)),
d.b,
r.lev + 1,
r.tot_price + d.dst,
r.path || '|' || LPad (d.b, 3, '0'),
r.n_towns
FROM rsf r
JOIN distances d
ON d.a = r.nxt_id
AND r.path NOT LIKE '%' || '|' || LPad (d.b, 3, '0') || '%'
WHERE r.path_rnk <= :KEEP_NUM
), circuits AS (
SELECT r.root_leg,
Row_Number() OVER (PARTITION BY r.root_leg ORDER BY :SIGN * (r.tot_price + d.dst)) path_rnk,
r.tot_price + d.dst tot_price,
r.path || '|' || LPad (d.b, 3, '0') path
FROM rsf r
JOIN distances d
ON d.a = r.nxt_id
AND d.b = r.root
WHERE r.lev = r.n_towns
AND r.path_rnk <= :KEEP_NUM
), top_n_paths AS (
SELECT root_leg,
tot_price,
path,
path_rnk,
town_index
FROM circuits
CROSS JOIN (SELECT LEVEL town_index FROM count_towns c CONNECT BY LEVEL <= c.n_towns + 1)
WHERE path_rnk <= :KEEP_NUM
), top_n_sets AS (
SELECT root_leg,
tot_price,
path,
path_rnk,
town_index,
To_Number (Substr (path, (town_index - 1) * 4 + 2, 3)) town_id,
Lag (To_Number (Substr (path, (town_index - 1) * 4 + 2, 3))) OVER (PARTITION BY root_leg, path_rnk ORDER BY town_index) town_id_prior
FROM top_n_paths
)
SELECT /*+ GATHER_PLAN_STATISTICS */
top.root_leg,
top.path_rnk,
Round (top.tot_price, 2) tot_dist,
top.town_id,
twn.name,
Round (dst.dst, 2) leg_dist,
Round (Sum (dst.dst) OVER (PARTITION BY root_leg, path_rnk ORDER BY town_index), 2) cum_dist
FROM top_n_sets top
JOIN towns twn
ON twn.id = top.town_id
LEFT JOIN distances dst
ON dst.a = top.town_id_prior
AND dst.b = top.town_id
ORDER BY top.root_leg, top.path_rnk, top.town_index
```

How It Works

Bind Variables
==============
SIGN - 1 means minimise distance; -1, maximise it
KEEP_NUM_ROOT - number of best anchor records (root legs) to retain (throughout)
KEEP_NUM - number of best records from previous iteration to retain, partitioning by root leg

The solution approach is based on the idea I had in my last article whereby recursive subquery factoring is at the heart of the method, and analytic row numbering is used to reduce searching to manageable proportions at each iteration. The bind variables control the level of searching desired.

• count_towns subquery - simply counts the total number of towns
• dist_from_root subquery - gets the distance between each pair of towns, without duplication, and ranks the pairs by distance
• rsf subquery, anchor branch - selects the KEEP_NUM_ROOT best legs as roots for the recursion
• rsf subquery, recursive branch - joins up to KEEP_NUM best records from last iteration all possible town via distances table, using pattern matching to exclude towns already visited
• The recursive branch stores the towns visited in a delimited string
• Analytic funtion Row_Number is used to rank the recursive branch result set at each iteration, partitioning by root leg for use in the next iteration
• circuits subquery - completes the circuit by joining the root leg via the distances table, doing the same constraint by rank and analytic ranking as the recursive branch
• top_n_paths subquery - uses a standard row generator subquery to generate indexed rows for the number of towns in the final path (my where clause looks redundant as I write)
• top_n_sets subquery - extracts the town id from the path using the index derived in the previous subquery, and gets the previous town id using analytic Lag
• Main query - joins towns table to get the town name and distances table for leg distances

Results

Test Problem 1: Emland

I used keep values of 5 and 5 and solved the shortest routes problem in 0.14 seconds, and the longest in 0.16 seconds.

```Shortest Routes
===============
ROOT_LEG       PATH_RNK   TOT_DIST    TOWN_ID NAME                             LEG_DIST   CUM_DIST
------------ ---------- ---------- ---------- ------------------------------ ---------- ----------
1 -> 2                1      10.94          1 Left Floor
2 Left Peak                            2.24       2.24
4 Right Peak                              2       4.24
5 Right Floor                          2.24       6.47
3 Midfield                             2.24       8.71
1 Left Floor                           2.24      10.94
2       11.3          1 Left Floor
2 Left Peak                            2.24       2.24
3 Midfield                             1.41       3.65
4 Right Peak                           1.41       5.06
5 Right Floor                          2.24        7.3
1 Left Floor                              4       11.3
3      11.73          1 Left Floor
2 Left Peak                            2.24       2.24
5 Right Floor                          3.61       5.84
4 Right Peak                           2.24       8.08
3 Midfield                             1.41       9.49
1 Left Floor                           2.24      11.73
4      11.73          1 Left Floor
2 Left Peak                            2.24       2.24
3 Midfield                             1.41       3.65
5 Right Floor                          2.24       5.89
4 Right Peak                           2.24       8.12
1 Left Floor                           3.61      11.73
5      11.89          1 Left Floor
2 Left Peak                            2.24       2.24
4 Right Peak                              2       4.24
3 Midfield                             1.41       5.65
5 Right Floor                          2.24       7.89
1 Left Floor                              4      11.89
2 -> 3                1       11.3          2 Left Peak
3 Midfield                             1.41       1.41
4 Right Peak                           1.41       2.83
5 Right Floor                          2.24       5.06
1 Left Floor                              4       9.06
2 Left Peak                            2.24       11.3
2      11.73          2 Left Peak
3 Midfield                             1.41       1.41
5 Right Floor                          2.24       3.65
4 Right Peak                           2.24       5.89
1 Left Floor                           3.61       9.49
2 Left Peak                            2.24      11.73
3       13.1          2 Left Peak
3 Midfield                             1.41       1.41
1 Left Floor                           2.24       3.65
4 Right Peak                           3.61       7.26
5 Right Floor                          2.24       9.49
2 Left Peak                            3.61       13.1
4      13.26          2 Left Peak
3 Midfield                             1.41       1.41
5 Right Floor                          2.24       3.65
1 Left Floor                              4       7.65
4 Right Peak                           3.61      11.26
2 Left Peak                               2      13.26
5      14.04          2 Left Peak
3 Midfield                             1.41       1.41
4 Right Peak                           1.41       2.83
1 Left Floor                           3.61       6.43
5 Right Floor                             4      10.43
2 Left Peak                            3.61      14.04
2 -> 4                1      10.94          2 Left Peak
4 Right Peak                              2          2
5 Right Floor                          2.24       4.24
3 Midfield                             2.24       6.47
1 Left Floor                           2.24       8.71
2 Left Peak                            2.24      10.94
2      11.89          2 Left Peak
4 Right Peak                              2          2
3 Midfield                             1.41       3.41
5 Right Floor                          2.24       5.65
1 Left Floor                              4       9.65
2 Left Peak                            2.24      11.89
3      11.89          2 Left Peak
4 Right Peak                              2          2
5 Right Floor                          2.24       4.24
1 Left Floor                              4       8.24
3 Midfield                             2.24      10.47
2 Left Peak                            1.41      11.89
4      13.26          2 Left Peak
4 Right Peak                              2          2
3 Midfield                             1.41       3.41
1 Left Floor                           2.24       5.65
5 Right Floor                             4       9.65
2 Left Peak                            3.61      13.26
5      13.68          2 Left Peak
4 Right Peak                              2          2
1 Left Floor                           3.61       5.61
3 Midfield                             2.24       7.84
5 Right Floor                          2.24      10.08
2 Left Peak                            3.61      13.68
3 -> 4                1       11.3          3 Midfield
4 Right Peak                           1.41       1.41
5 Right Floor                          2.24       3.65
1 Left Floor                              4       7.65
2 Left Peak                            2.24       9.89
3 Midfield                             1.41       11.3
2      11.73          3 Midfield
4 Right Peak                           1.41       1.41
5 Right Floor                          2.24       3.65
2 Left Peak                            3.61       7.26
1 Left Floor                           2.24       9.49
3 Midfield                             2.24      11.73
3      11.89          3 Midfield
4 Right Peak                           1.41       1.41
2 Left Peak                               2       3.41
1 Left Floor                           2.24       5.65
5 Right Floor                             4       9.65
3 Midfield                             2.24      11.89
4       13.1          3 Midfield
4 Right Peak                           1.41       1.41
1 Left Floor                           3.61       5.02
2 Left Peak                            2.24       7.26
5 Right Floor                          3.61      10.86
3 Midfield                             2.24       13.1
5      13.26          3 Midfield
4 Right Peak                           1.41       1.41
2 Left Peak                               2       3.41
5 Right Floor                          3.61       7.02
1 Left Floor                              4      11.02
3 Midfield                             2.24      13.26
3 -> 5                1      10.94          3 Midfield
5 Right Floor                          2.24       2.24
4 Right Peak                           2.24       4.47
2 Left Peak                               2       6.47
1 Left Floor                           2.24       8.71
3 Midfield                             2.24      10.94
2      11.73          3 Midfield
5 Right Floor                          2.24       2.24
4 Right Peak                           2.24       4.47
1 Left Floor                           3.61       8.08
2 Left Peak                            2.24      10.31
3 Midfield                             1.41      11.73
3      11.89          3 Midfield
5 Right Floor                          2.24       2.24
1 Left Floor                              4       6.24
2 Left Peak                            2.24       8.47
4 Right Peak                              2      10.47
3 Midfield                             1.41      11.89
4       13.1          3 Midfield
5 Right Floor                          2.24       2.24
2 Left Peak                            3.61       5.84
1 Left Floor                           2.24       8.08
4 Right Peak                           3.61      11.68
3 Midfield                             1.41       13.1
5      13.68          3 Midfield
5 Right Floor                          2.24       2.24
2 Left Peak                            3.61       5.84
4 Right Peak                              2       7.84
1 Left Floor                           3.61      11.45
3 Midfield                             2.24      13.68

150 rows selected.

Elapsed: 00:00:00.14
```
```Longest Routes
==============
ROOT_LEG       PATH_RNK   TOT_DIST    TOWN_ID NAME                             LEG_DIST   CUM_DIST
------------ ---------- ---------- ---------- ------------------------------ ---------- ----------
1 -> 2                1       13.1          1 Left Floor
2 Left Peak                            2.24       2.24
5 Right Floor                          3.61       5.84
3 Midfield                             2.24       8.08
4 Right Peak                           1.41       9.49
1 Left Floor                           3.61       13.1
2      11.89          1 Left Floor
2 Left Peak                            2.24       2.24
4 Right Peak                              2       4.24
3 Midfield                             1.41       5.65
5 Right Floor                          2.24       7.89
1 Left Floor                              4      11.89
3      11.73          1 Left Floor
2 Left Peak                            2.24       2.24
5 Right Floor                          3.61       5.84
4 Right Peak                           2.24       8.08
3 Midfield                             1.41       9.49
1 Left Floor                           2.24      11.73
4      11.73          1 Left Floor
2 Left Peak                            2.24       2.24
3 Midfield                             1.41       3.65
5 Right Floor                          2.24       5.89
4 Right Peak                           2.24       8.12
1 Left Floor                           3.61      11.73
5      10.94          1 Left Floor
2 Left Peak                            2.24       2.24
4 Right Peak                              2       4.24
5 Right Floor                          2.24       6.47
3 Midfield                             2.24       8.71
1 Left Floor                           2.24      10.94
1 -> 4                1      13.68          1 Left Floor
4 Right Peak                           3.61       3.61
2 Left Peak                               2       5.61
5 Right Floor                          3.61       9.21
3 Midfield                             2.24      11.45
1 Left Floor                           2.24      13.68
2      13.26          1 Left Floor
4 Right Peak                           3.61       3.61
2 Left Peak                               2       5.61
3 Midfield                             1.41       7.02
5 Right Floor                          2.24       9.26
1 Left Floor                              4      13.26
3       13.1          1 Left Floor
4 Right Peak                           3.61       3.61
5 Right Floor                          2.24       5.84
2 Left Peak                            3.61       9.45
3 Midfield                             1.41      10.86
1 Left Floor                           2.24       13.1
4       13.1          1 Left Floor
4 Right Peak                           3.61       3.61
3 Midfield                             1.41       5.02
5 Right Floor                          2.24       7.26
2 Left Peak                            3.61      10.86
1 Left Floor                           2.24       13.1
5      11.73          1 Left Floor
4 Right Peak                           3.61       3.61
5 Right Floor                          2.24       5.84
3 Midfield                             2.24       8.08
2 Left Peak                            1.41       9.49
1 Left Floor                           2.24      11.73
1 -> 5                1      14.04          1 Left Floor
5 Right Floor                             4          4
2 Left Peak                            3.61       7.61
3 Midfield                             1.41       9.02
4 Right Peak                           1.41      10.43
1 Left Floor                           3.61      14.04
2      13.26          1 Left Floor
5 Right Floor                             4          4
2 Left Peak                            3.61       7.61
4 Right Peak                              2       9.61
3 Midfield                             1.41      11.02
1 Left Floor                           2.24      13.26
3      13.26          1 Left Floor
5 Right Floor                             4          4
3 Midfield                             2.24       6.24
2 Left Peak                            1.41       7.65
4 Right Peak                              2       9.65
1 Left Floor                           3.61      13.26
4      11.89          1 Left Floor
5 Right Floor                             4          4
4 Right Peak                           2.24       6.24
2 Left Peak                               2       8.24
3 Midfield                             1.41       9.65
1 Left Floor                           2.24      11.89
5      11.89          1 Left Floor
5 Right Floor                             4          4
3 Midfield                             2.24       6.24
4 Right Peak                           1.41       7.65
2 Left Peak                               2       9.65
1 Left Floor                           2.24      11.89
2 -> 5                1      14.04          2 Left Peak
5 Right Floor                          3.61       3.61
1 Left Floor                              4       7.61
4 Right Peak                           3.61      11.21
3 Midfield                             1.41      12.63
2 Left Peak                            1.41      14.04
2      13.68          2 Left Peak
5 Right Floor                          3.61       3.61
3 Midfield                             2.24       5.84
1 Left Floor                           2.24       8.08
4 Right Peak                           3.61      11.68
2 Left Peak                               2      13.68
3      13.26          2 Left Peak
5 Right Floor                          3.61       3.61
1 Left Floor                              4       7.61
3 Midfield                             2.24       9.84
4 Right Peak                           1.41      11.26
2 Left Peak                               2      13.26
4       13.1          2 Left Peak
5 Right Floor                          3.61       3.61
4 Right Peak                           2.24       5.84
1 Left Floor                           3.61       9.45
3 Midfield                             2.24      11.68
2 Left Peak                            1.41       13.1
5      11.73          2 Left Peak
5 Right Floor                          3.61       3.61
4 Right Peak                           2.24       5.84
3 Midfield                             1.41       7.26
1 Left Floor                           2.24       9.49
2 Left Peak                            2.24      11.73
3 -> 5                1      13.68          3 Midfield
5 Right Floor                          2.24       2.24
2 Left Peak                            3.61       5.84
4 Right Peak                              2       7.84
1 Left Floor                           3.61      11.45
3 Midfield                             2.24      13.68
2      13.26          3 Midfield
5 Right Floor                          2.24       2.24
1 Left Floor                              4       6.24
4 Right Peak                           3.61       9.84
2 Left Peak                               2      11.84
3 Midfield                             1.41      13.26
3       13.1          3 Midfield
5 Right Floor                          2.24       2.24
2 Left Peak                            3.61       5.84
1 Left Floor                           2.24       8.08
4 Right Peak                           3.61      11.68
3 Midfield                             1.41       13.1
4      11.89          3 Midfield
5 Right Floor                          2.24       2.24
1 Left Floor                              4       6.24
2 Left Peak                            2.24       8.47
4 Right Peak                              2      10.47
3 Midfield                             1.41      11.89
5      11.73          3 Midfield
5 Right Floor                          2.24       2.24
4 Right Peak                           2.24       4.47
1 Left Floor                           3.61       8.08
2 Left Peak                            2.24      10.31
3 Midfield                             1.41      11.73

150 rows selected.

Elapsed: 00:00:00.16
```

Summary of Results
Here we list the best solutions found for each root leg. * denotes optimum.

```Shortest Routes
===============
ROOT_LEG       PATH_RNK   TOT_DIST
------------ ---------- ----------
1 -> 2                1      10.94*
2 -> 3                1       11.3
2 -> 4                1      10.94*
3 -> 4                1       11.3
3 -> 5                1      10.94*

Longest Routes
==============
ROOT_LEG       PATH_RNK   TOT_DIST
------------ ---------- ----------
1 -> 2                1       13.1
1 -> 4                1      13.68
1 -> 5                1      14.04*
2 -> 5                1      14.04*
3 -> 5                1      13.68
```

Test Problem 2: USCA312

I used keep values of 2 and 2 and solved the problems in around 85 seconds.

```Shortest Routes
===============
ROOT_LEG       PATH_RNK   TOT_DIST    TOWN_ID NAME                             LEG_DIST   CUM_DIST
------------ ---------- ---------- ---------- ------------------------------ ---------- ----------
184 -> 208            1   55744.86        184 Norfolk, VA
208 Portsmouth, VA                       1.19       1.19
294 Washington, DC                     151.18     152.37
18 Baltimore, MD                       40.06     192.43
141 Lancaster, PA                       55.83     248.26
129 Johnstown, PA                       16.64      264.9
302 Wilmington, DE                      48.48     340.58
283 Trenton, NJ                         34.35     404.99
83 Elizabeth, NJ                       48.02     453.01
177 Newark, NJ                           5.61     458.62
128 Jersey City, NJ                      6.57     465.19
181 New York, NY                         5.04     470.23
194 Paterson, NJ                        18.08     488.32
299 White Plains, NY                    29.39     517.71
266 Stamford, CT                        15.36     533.07
39 Bridgeport, CT                      24.55     557.61
179 New Haven, CT                       21.46     579.08
163 Meriden, CT                         17.96     597.04
178 New Britain,CT                       8.71     605.75
117 Hartford, CT                         9.62     615.37
263 Springfield, MA                     24.25     639.62
202 Pittsfield, MA                      51.31     690.93
285 Troy, NY                            36.34     727.27
3 Albany, NY                           6.88     734.16
289 Utica, NY                           91.52     842.56
272 Syracuse, NY                        63.31     905.86
29 Binghamtom, NY                      67.49     973.35
301 Wilkes-Barre, PA                    58.97    1032.33
6 Allentown, PA                       51.68       1084
116 Harrisburg, PA                      99.07    1183.07
13 Atlantic City, NJ                  181.31    1364.38
50 Central Islip, NY                  129.74    1494.12
210 Providence, RI                     142.75    1636.87
91 Fall River, MA                      19.72    1656.59
40 Brockton, MA                        28.03    1684.61
34 Boston, MA                          19.21    1703.83
46 Cambridge, MA                        3.36    1707.19
155 Lowell, MA                          23.03    1730.22
145 Lawrence, MA                        11.73    1741.95
159 Manchester, NH                      28.36     1770.3
67 Concord, NH                         15.76    1786.06
307 Worcester, MA                       67.84     1853.9
38 Brattleboro, VT                     66.17    1920.07
171 Montpelier, VT                      97.37    2017.43
43 Burlington, VT                      46.45    2063.89
172 Montreal, QC                        97.19    2161.08
284 Trois-Rivieres, QC                  98.03     2259.1
254 Sherbrooke, QC                      93.08    2352.18
205 Portland, ME                       157.79    2509.97
207 Portsmouth, NH                      53.74    2563.71
15 Augusta, ME                        109.27    2672.99
19 Bangor, ME                          77.07    2750.06
98 Fredericton, NB                    171.89    2921.95
229 Saint John, NB                      58.53    2980.48
169 Moncton, NB                         99.94    3080.42
113 Halifax, NS                        117.55    3197.97
55 Charlottetown, PE                  100.98    3298.95
271 Syndey, NS                         212.55     3511.5
230 Saint John's, NF                   514.07    4025.57
213 Quebec City, QC                   1290.47    5316.03
191 Ottawa, ON                         400.01    5716.04
137 Kingston, ON                        85.39    5801.42
24 Belleville, ON                      49.28    5850.71
197 Peterborough, ON                    72.87    5923.57
282 Toronto, ON                         82.62    6006.19
42 Burlington, ONT                     33.92    6040.11
115 Hamilton, ON                        19.51    6059.62
37 Brantford, ON                       28.55    6088.16
111 Guelph, ON                           29.4    6117.56
138 Kitchener, ON                       10.29    6127.86
152 London, ON                          67.63    6195.49
86 Erie, PA                            97.44    6292.93
310 Youngstown, OH                      81.12    6374.05
267 Steubenville, OH                    50.45     6424.5
297 Wheeling, WV                        21.97    6446.47
201 Pittsburgh, PA                      56.46    6502.93
47 Canton, OH                          98.69    6601.61
2 Akron, OH                            21.8    6623.41
61 Cleveland, OH                       31.35    6654.76
304 Windsor, ON                        102.38    6757.14
76 Detroit, MI                          7.21    6764.35
9 Ann Arbor, MI                       47.21    6811.56
125 Jackson, MI                         46.67    6858.23
142 Lansing, MI                         35.28    6893.51
21 Battle Creek, MI                    51.65    6945.16
132 Kalamazoo, MI                       28.23    6973.39
259 South Bend, IN                      62.16    7035.55
104 Gary, IN                            76.01    7111.56
58 Chicago, IL                         27.47    7139.03
135 Kenosha, WI                         52.13    7191.16
214 Racine, WI                          10.12    7201.28
165 Milwaukee, WI                       23.24    7224.52
224 Rockford, IL                        97.71    7322.22
78 Dubuque, IA                         95.84     7477.4
49 Cedar Rapids, IA                    75.74    7553.14
124 Iowa City, IA                       25.25    7578.39
295 Waterloo, IA                        80.35    7658.74
222 Rochester, MN                         106    7764.75
233 Saint Paul, MN                      76.94    7841.69
166 Minneapolis, MN                     12.03    7853.72
228 Saint Cloud, MN                     73.94    7927.66
79 Duluth, MN                         165.27    8092.93
270 Superior, WI                         4.32    8097.25
81 Eau Claire, WI                     138.42    8235.67
108 Green Bay, WI                      241.21    8476.87
253 Sheboygan, WI                       57.13       8534
106 Grand Rapids, MI                   151.51     8685.5
226 Saginaw, MI                        122.77    8808.27
22 Bay City, MI                        12.83     8821.1
94 Flint, MI                           42.55    8863.65
280 Toledo, OH                          93.64    8957.28
149 Lima, OH                            74.14    9031.42
265 Springfield, OH                     60.13    9091.56
71 Dayton, OH                          28.81    9120.37
114 Hamilton, OH                        35.63       9156
60 Cincinnati, OH                      17.93    9173.93
147 Lexington, KY                       81.08    9255.01
154 Louisville, KY                      90.44    9345.45
35 Bowling Green, KY                    99.3    9444.76
175 Nashville, TN                       61.65     9506.4
122 Huntsville, AL                     100.13    9606.53
30 Birmingham, AL                       64.7    9734.88
170 Montgomery, AL                      86.96    9821.85
65 Columbus, GA                         90.9    9912.75
12 Atlanta, GA                         98.17   10010.92
157 Macon, GA                           81.64   10092.56
14 Augusta, GA                        122.52   10215.08
110 Greenville, SC                      99.77   10314.85
260 Spartanburg, NC                     32.62   10347.46
54 Charlotte, NC                       77.64   10425.11
306 Winston-Salem, NC                   73.13   10498.24
109 Greensboro, NC                       31.3   10529.54
215 Raleigh, NC                         82.35   10611.89
80 Durham, NC                          23.61    10635.5
221 Roanoke, VA                        113.91   10749.41
53 Charleston, WV                     138.61   10888.02
11 Ashland, KY                         70.03   10958.04
312 Zanesville, OH                     109.84   11067.89
66 Columbus, OH                        68.11   11135.99
174 Muncie, IN                         165.75   11301.74
123 Indianapolis, IN                    60.87   11362.61
276 Terre Haute, IN                     89.24   11451.85
288 Urbana, IL                           70.6   11522.45
51 Champaign, IL                        2.52   11524.98
73 Decatur, IL                         52.73   11577.71
32 Bloomington, IL                     44.57   11622.28
196 Peoria, IL                           43.6   11665.88
262 Springfield, IL                     61.75   11727.63
232 Saint Louis, MO                     89.73   11817.36
63 Columbia, MO                       149.28   11966.64
264 Springfield, MO                    137.23   12103.88
130 Joplin, MO                          84.44   12188.32
287 Tulsa, OK                          120.76   12309.07
188 Oklahoma City, OK                  115.46   12424.54
85 Enid, OK                            68.83   12493.37
300 Wichita, KS                         97.07   12590.44
236 Salina, KS                          81.55   12671.99
281 Topeka, KS                         134.35   12806.34
133 Kansas City, KS                     72.73   12879.07
134 Kansas City, MO                      3.52    12882.6
231 Saint Joseph, MO                    49.79   12932.39
150 Lincoln, NE                        144.56   13076.95
189 Omaha, NE                           59.52   13136.47
257 Sioux City, IA                      91.54   13228.01
258 Sioux Falls, SD                     75.45   13303.46
92 Fargo, ND                          229.97   13533.43
305 Winnipeg, MB                       211.94   13745.38
36 Brandon, MB                        187.08   13932.46
167 Minot, ND                          148.55      14081
31 Bismarck, ND                       104.59   14185.59
200 Pierre, SD                         171.22   14356.81
216 Rapid City, SD                     199.98   14556.79
57 Cheyenne, WY                       230.96   14787.75
74 Denver, CO                          97.46   14885.21
62 Colorado Springs, CO                63.56   14948.77
212 Pueblo, CO                          42.63    14991.4
246 Santa Fe, NM                       199.75   15191.16
4 Albuquerque, NM                     64.52   15255.68
102 Gallup, NM                         147.72    15403.4
93 Flagstaff, AZ                      202.26   15605.66
199 Phoenix, AZ                        124.38   15730.04
286 Tucson, AZ                         116.06    15846.1
311 Yuma, AZ                           257.86   16103.95
240 San Diego, CA                      175.01   16278.97
239 San Bernardino, CA                  96.69   16375.66
153 Los Angeles, CA                      9.51   16444.29
244 Santa Barbara, CA                  103.68   16547.97
17 Bakersfield, CA                     80.84   16628.81
99 Fresno, CA                         108.31   16737.12
242 San Jose, CA                       152.25   16889.37
245 Santa Cruz, CA                      26.93    16916.3
186 Oakland, CA                         59.72   16976.02
241 San Francisco, CA                   10.47   16986.49
26 Berkeley, CA                        12.14   16998.63
268 Stockton, CA                        68.11   17066.74
225 Sacramento, CA                      45.35   17112.09
48 Carson City, NV                    125.92      17238
219 Reno, NV                            25.48   17263.49
88 Eureka, CA                         313.15   17576.64
87 Eugene, OR                         236.57    17813.2
235 Salem, OR                           61.63   17874.84
206 Portland, OR                        47.17   17922.01
273 Tacoma, WA                         120.57   18042.58
252 Seattle, WA                         25.62    18068.2
25 Bellingham, WA                      80.42   18148.62
290 Vancouver, BC                       56.66   18205.28
291 Victoria, BC                        41.45   18246.73
308 Yakima, WA                         246.37    18493.1
293 Walla Walla, WA                    153.98   18647.07
261 Spokane, WA                        127.07   18774.15
33 Boise, ID                          291.99   19066.14
203 Pocatello, ID                      264.67   19330.81
187 Ogden, UT                          118.47   19449.28
237 Salt Lake City, UT                  32.44   19481.72
211 Provo, UT                           39.79   19521.51
105 Grand Junction, CO                 229.45   19750.96
255 Sheridan, WY                       411.17   20162.14
27 Billings, MT                       126.61   20288.75
107 Great Falls, MT                    226.94   20515.69
118 Helena, MT                           80.7   20596.39
44 Butte, MT                           53.31    20649.7
146 Lethbridge, AB                     251.24   20900.94
45 Calgary, AB                        132.75   21033.69
82 Edmonton, AB                       173.35   21207.04
161 Medicine Hat, AB                   312.59   21519.63
173 Moose Jaw, SK                      149.68   21984.44
218 Regina, SK                          62.04   22046.48
77 Dodge City, KS                     933.12    22979.6
7 Amarillo, TX                       215.16   23194.75
156 Lubbock, TX                        113.61   23308.37
1 Abilene, TX                        166.25   23474.62
97 Ft Worth, TX                       167.57   23642.19
69 Dallas, TX                          36.19   23678.39
292 Waco, TX                            88.57   23766.96
16 Austin, TX                          97.72   23864.68
238 San Antonio, TX                     77.99   23942.67
143 Laredo, TX                         149.89   24092.56
68 Corpus Christi, TX                 147.28   24239.84
121 Houston, TX                        195.26    24435.1
103 Galveston, TX                       50.45   24485.55
204 Port Arthur, TX                     72.86   24558.41
23 Beaumont, TX                        17.62   24576.03
256 Shreveport, LA                     170.28   24746.32
160 Marshall, TX                        42.67   24788.99
277 Texarkana, TX                       64.71    24853.7
95 Ft Smith, AR                       137.64   24991.34
151 Little Rock, AR                    152.27   25143.61
162 Memphis, TN                         157.3   25300.91
89 Evansville, IN                      94.86   25562.74
140 Lafayette, IN                      175.16    25737.9
96 Ft Wayne, IN                       130.36   25868.26
139 Knoxville, TN                      366.84    26235.1
10 Asheville, NC                       97.65   26332.75
64 Columbia, SC                       152.46   26485.21
52 Charleston, SC                      113.9   26599.11
250 Savannah, GA                         93.9   26693.01
127 Jacksonville, FL                   126.96   26819.97
101 Gainesville, FL                     65.95   26885.92
72 Daytona Beach, FL                   94.98    26980.9
190 Orlando, FL                         52.58   27033.48
275 Tampa, FL                           85.01   27118.49
234 Saint Petersburg, FL                19.54   27138.03
247 Sarasota, FL                        31.73   27169.75
296 West Palm Beach, FL                176.46   27346.21
164 Miami, FL                           65.75   27411.96
136 Key West, FL                       138.36   27550.32
274 Tallahassee, FL                    441.61   27991.93
195 Pensacola, FL                      202.87    28194.8
168 Mobile, AL                          60.12   28254.92
28 Biloxi, MS                          61.74   28316.66
112 Gulfport, MS                        14.47   28331.13
180 New Orleans, LA                     73.62   28404.74
20 Baton Rouge, LA                     82.09   28486.83
176 Natchez, MS                         78.58   28565.41
126 Jackson, MS                         98.43   28663.84
56 Chattanooga, TN                    386.64   29050.48
303 Wilmington, NC                     512.01   29562.49
220 Richmond, VA                       232.37   29794.87
41 Buffalo, NY                        381.27   30176.14
182 Niagara Falls, ON                   20.54   30196.68
227 Saint Catherines, ON                 8.92   30205.61
223 Rochester, NY                      107.53   30313.14
185 North Bay, ON                      260.14   30573.28
269 Sudbury, ON                            93   30666.28
279 Timmins, ON                        140.17   30806.45
249 Sault Ste Marie, ON                260.02   31066.47
278 Thunder Bay, ON                    357.29   31423.76
75 Des Moines, IA                     553.68   31977.44
84 El Paso, TX                       1119.89   33097.34
144 Las Vegas, NV                      671.08   33768.41
209 Prince Rupert, BC                 1638.53   35406.94
131 Juneau, AK                         390.43   35797.37
298 Whitehorse, YK                     172.32   35969.69
70 Dawson, YT                         362.92   36332.61
90 Fairbanks, AK                         596   36928.62
8 Anchorage, AK                      292.11   37220.72
183 Nome, AK                          1095.15   38315.87
148 Lihue, HI                          2967.4   41283.27
120 Honolulu, HI                        114.5   41397.77
119 Hilo, HI                              176   41573.77
309 Yellowknife, NT                   4111.93    45685.7
59 Churchill, MB                     1431.71   47117.42
243 San Juan, PR                      4433.43   54293.23
184 Norfolk, VA                       1451.64   55744.86
2   55746.24        184 Norfolk, VA
208 Portsmouth, VA                       1.19       1.19
294 Washington, DC                     151.18     152.37
18 Baltimore, MD                       40.06     192.43
141 Lancaster, PA                       55.83     248.26
129 Johnstown, PA                       16.64      264.9
302 Wilmington, DE                      48.48     340.58
283 Trenton, NJ                         34.35     404.99
83 Elizabeth, NJ                       48.02     453.01
177 Newark, NJ                           5.61     458.62
128 Jersey City, NJ                      6.57     465.19
181 New York, NY                         5.04     470.23
194 Paterson, NJ                        18.08     488.32
299 White Plains, NY                    29.39     517.71
266 Stamford, CT                        15.36     533.07
39 Bridgeport, CT                      24.55     557.61
179 New Haven, CT                       21.46     579.08
163 Meriden, CT                         17.96     597.04
178 New Britain,CT                       8.71     605.75
117 Hartford, CT                         9.62     615.37
263 Springfield, MA                     24.25     639.62
202 Pittsfield, MA                      51.31     690.93
285 Troy, NY                            36.34     727.27
3 Albany, NY                           6.88     734.16
289 Utica, NY                           91.52     842.56
272 Syracuse, NY                        63.31     905.86
29 Binghamtom, NY                      67.49     973.35
301 Wilkes-Barre, PA                    58.97    1032.33
6 Allentown, PA                       51.68       1084
116 Harrisburg, PA                      99.07    1183.07
13 Atlantic City, NJ                  181.31    1364.38
50 Central Islip, NY                  129.74    1494.12
210 Providence, RI                     142.75    1636.87
91 Fall River, MA                      19.72    1656.59
40 Brockton, MA                        28.03    1684.61
34 Boston, MA                          19.21    1703.83
46 Cambridge, MA                        3.36    1707.19
155 Lowell, MA                          23.03    1730.22
145 Lawrence, MA                        11.73    1741.95
159 Manchester, NH                      28.36     1770.3
67 Concord, NH                         15.76    1786.06
307 Worcester, MA                       67.84     1853.9
38 Brattleboro, VT                     66.17    1920.07
171 Montpelier, VT                      97.37    2017.43
43 Burlington, VT                      46.45    2063.89
172 Montreal, QC                        97.19    2161.08
284 Trois-Rivieres, QC                  98.03     2259.1
254 Sherbrooke, QC                      93.08    2352.18
205 Portland, ME                       157.79    2509.97
207 Portsmouth, NH                      53.74    2563.71
15 Augusta, ME                        109.27    2672.99
19 Bangor, ME                          77.07    2750.06
98 Fredericton, NB                    171.89    2921.95
229 Saint John, NB                      58.53    2980.48
169 Moncton, NB                         99.94    3080.42
113 Halifax, NS                        117.55    3197.97
55 Charlottetown, PE                  100.98    3298.95
271 Syndey, NS                         212.55     3511.5
230 Saint John's, NF                   514.07    4025.57
213 Quebec City, QC                   1290.47    5316.03
191 Ottawa, ON                         400.01    5716.04
137 Kingston, ON                        85.39    5801.42
24 Belleville, ON                      49.28    5850.71
197 Peterborough, ON                    72.87    5923.57
282 Toronto, ON                         82.62    6006.19
42 Burlington, ONT                     33.92    6040.11
115 Hamilton, ON                        19.51    6059.62
37 Brantford, ON                       28.55    6088.16
111 Guelph, ON                           29.4    6117.56
138 Kitchener, ON                       10.29    6127.86
152 London, ON                          67.63    6195.49
86 Erie, PA                            97.44    6292.93
310 Youngstown, OH                      81.12    6374.05
267 Steubenville, OH                    50.45     6424.5
297 Wheeling, WV                        21.97    6446.47
201 Pittsburgh, PA                      56.46    6502.93
47 Canton, OH                          98.69    6601.61
2 Akron, OH                            21.8    6623.41
61 Cleveland, OH                       31.35    6654.76
304 Windsor, ON                        102.38    6757.14
76 Detroit, MI                          7.21    6764.35
9 Ann Arbor, MI                       47.21    6811.56
125 Jackson, MI                         46.67    6858.23
142 Lansing, MI                         35.28    6893.51
21 Battle Creek, MI                    51.65    6945.16
132 Kalamazoo, MI                       28.23    6973.39
259 South Bend, IN                      62.16    7035.55
104 Gary, IN                            76.01    7111.56
58 Chicago, IL                         27.47    7139.03
135 Kenosha, WI                         52.13    7191.16
214 Racine, WI                          10.12    7201.28
165 Milwaukee, WI                       23.24    7224.52
224 Rockford, IL                        97.71    7322.22
78 Dubuque, IA                         95.84     7477.4
49 Cedar Rapids, IA                    75.74    7553.14
124 Iowa City, IA                       25.25    7578.39
295 Waterloo, IA                        80.35    7658.74
222 Rochester, MN                         106    7764.75
233 Saint Paul, MN                      76.94    7841.69
166 Minneapolis, MN                     12.03    7853.72
228 Saint Cloud, MN                     73.94    7927.66
79 Duluth, MN                         165.27    8092.93
270 Superior, WI                         4.32    8097.25
81 Eau Claire, WI                     138.42    8235.67
108 Green Bay, WI                      241.21    8476.87
253 Sheboygan, WI                       57.13       8534
106 Grand Rapids, MI                   151.51     8685.5
226 Saginaw, MI                        122.77    8808.27
22 Bay City, MI                        12.83     8821.1
94 Flint, MI                           42.55    8863.65
280 Toledo, OH                          93.64    8957.28
149 Lima, OH                            74.14    9031.42
265 Springfield, OH                     60.13    9091.56
71 Dayton, OH                          28.81    9120.37
114 Hamilton, OH                        35.63       9156
60 Cincinnati, OH                      17.93    9173.93
147 Lexington, KY                       81.08    9255.01
154 Louisville, KY                      90.44    9345.45
35 Bowling Green, KY                    99.3    9444.76
175 Nashville, TN                       61.65     9506.4
122 Huntsville, AL                     100.13    9606.53
30 Birmingham, AL                       64.7    9734.88
170 Montgomery, AL                      86.96    9821.85
65 Columbus, GA                         90.9    9912.75
12 Atlanta, GA                         98.17   10010.92
157 Macon, GA                           81.64   10092.56
14 Augusta, GA                        122.52   10215.08
110 Greenville, SC                      99.77   10314.85
260 Spartanburg, NC                     32.62   10347.46
54 Charlotte, NC                       77.64   10425.11
306 Winston-Salem, NC                   73.13   10498.24
109 Greensboro, NC                       31.3   10529.54
215 Raleigh, NC                         82.35   10611.89
80 Durham, NC                          23.61    10635.5
221 Roanoke, VA                        113.91   10749.41
53 Charleston, WV                     138.61   10888.02
11 Ashland, KY                         70.03   10958.04
312 Zanesville, OH                     109.84   11067.89
66 Columbus, OH                        68.11   11135.99
174 Muncie, IN                         165.75   11301.74
123 Indianapolis, IN                    60.87   11362.61
276 Terre Haute, IN                     89.24   11451.85
288 Urbana, IL                           70.6   11522.45
51 Champaign, IL                        2.52   11524.98
73 Decatur, IL                         52.73   11577.71
32 Bloomington, IL                     44.57   11622.28
196 Peoria, IL                           43.6   11665.88
262 Springfield, IL                     61.75   11727.63
232 Saint Louis, MO                     89.73   11817.36
63 Columbia, MO                       149.28   11966.64
264 Springfield, MO                    137.23   12103.88
130 Joplin, MO                          84.44   12188.32
287 Tulsa, OK                          120.76   12309.07
188 Oklahoma City, OK                  115.46   12424.54
85 Enid, OK                            68.83   12493.37
300 Wichita, KS                         97.07   12590.44
236 Salina, KS                          81.55   12671.99
281 Topeka, KS                         134.35   12806.34
133 Kansas City, KS                     72.73   12879.07
134 Kansas City, MO                      3.52    12882.6
231 Saint Joseph, MO                    49.79   12932.39
150 Lincoln, NE                        144.56   13076.95
189 Omaha, NE                           59.52   13136.47
257 Sioux City, IA                      91.54   13228.01
258 Sioux Falls, SD                     75.45   13303.46
92 Fargo, ND                          229.97   13533.43
305 Winnipeg, MB                       211.94   13745.38
36 Brandon, MB                        187.08   13932.46
167 Minot, ND                          148.55      14081
31 Bismarck, ND                       104.59   14185.59
200 Pierre, SD                         171.22   14356.81
216 Rapid City, SD                     199.98   14556.79
57 Cheyenne, WY                       230.96   14787.75
74 Denver, CO                          97.46   14885.21
62 Colorado Springs, CO                63.56   14948.77
212 Pueblo, CO                          42.63    14991.4
246 Santa Fe, NM                       199.75   15191.16
4 Albuquerque, NM                     64.52   15255.68
102 Gallup, NM                         147.72    15403.4
93 Flagstaff, AZ                      202.26   15605.66
199 Phoenix, AZ                        124.38   15730.04
286 Tucson, AZ                         116.06    15846.1
311 Yuma, AZ                           257.86   16103.95
240 San Diego, CA                      175.01   16278.97
239 San Bernardino, CA                  96.69   16375.66
153 Los Angeles, CA                      9.51   16444.29
244 Santa Barbara, CA                  103.68   16547.97
17 Bakersfield, CA                     80.84   16628.81
99 Fresno, CA                         108.31   16737.12
242 San Jose, CA                       152.25   16889.37
245 Santa Cruz, CA                      26.93    16916.3
186 Oakland, CA                         59.72   16976.02
241 San Francisco, CA                   10.47   16986.49
26 Berkeley, CA                        12.14   16998.63
268 Stockton, CA                        68.11   17066.74
225 Sacramento, CA                      45.35   17112.09
48 Carson City, NV                    125.92      17238
219 Reno, NV                            25.48   17263.49
88 Eureka, CA                         313.15   17576.64
87 Eugene, OR                         236.57    17813.2
235 Salem, OR                           61.63   17874.84
206 Portland, OR                        47.17   17922.01
273 Tacoma, WA                         120.57   18042.58
252 Seattle, WA                         25.62    18068.2
25 Bellingham, WA                      80.42   18148.62
290 Vancouver, BC                       56.66   18205.28
291 Victoria, BC                        41.45   18246.73
308 Yakima, WA                         246.37    18493.1
293 Walla Walla, WA                    153.98   18647.07
261 Spokane, WA                        127.07   18774.15
33 Boise, ID                          291.99   19066.14
203 Pocatello, ID                      264.67   19330.81
187 Ogden, UT                          118.47   19449.28
237 Salt Lake City, UT                  32.44   19481.72
211 Provo, UT                           39.79   19521.51
105 Grand Junction, CO                 229.45   19750.96
255 Sheridan, WY                       411.17   20162.14
27 Billings, MT                       126.61   20288.75
107 Great Falls, MT                    226.94   20515.69
118 Helena, MT                           80.7   20596.39
44 Butte, MT                           53.31    20649.7
146 Lethbridge, AB                     251.24   20900.94
45 Calgary, AB                        132.75   21033.69
82 Edmonton, AB                       173.35   21207.04
161 Medicine Hat, AB                   312.59   21519.63
173 Moose Jaw, SK                      149.68   21984.44
218 Regina, SK                          62.04   22046.48
77 Dodge City, KS                     933.12    22979.6
7 Amarillo, TX                       215.16   23194.75
156 Lubbock, TX                        113.61   23308.37
1 Abilene, TX                        166.25   23474.62
97 Ft Worth, TX                       167.57   23642.19
69 Dallas, TX                          36.19   23678.39
292 Waco, TX                            88.57   23766.96
16 Austin, TX                          97.72   23864.68
238 San Antonio, TX                     77.99   23942.67
143 Laredo, TX                         149.89   24092.56
68 Corpus Christi, TX                 147.28   24239.84
121 Houston, TX                        195.26    24435.1
103 Galveston, TX                       50.45   24485.55
204 Port Arthur, TX                     72.86   24558.41
23 Beaumont, TX                        17.62   24576.03
160 Marshall, TX                       170.89   24746.92
256 Shreveport, LA                      42.67   24789.59
277 Texarkana, TX                       65.49   24855.08
95 Ft Smith, AR                       137.64   24992.72
151 Little Rock, AR                    152.27      25145
162 Memphis, TN                         157.3   25302.29
89 Evansville, IN                      94.86   25564.12
140 Lafayette, IN                      175.16   25739.28
96 Ft Wayne, IN                       130.36   25869.64
139 Knoxville, TN                      366.84   26236.49
10 Asheville, NC                       97.65   26334.13
64 Columbia, SC                       152.46    26486.6
52 Charleston, SC                      113.9   26600.49
250 Savannah, GA                         93.9   26694.39
127 Jacksonville, FL                   126.96   26821.35
101 Gainesville, FL                     65.95    26887.3
72 Daytona Beach, FL                   94.98   26982.28
190 Orlando, FL                         52.58   27034.86
275 Tampa, FL                           85.01   27119.87
234 Saint Petersburg, FL                19.54   27139.41
247 Sarasota, FL                        31.73   27171.14
296 West Palm Beach, FL                176.46    27347.6
164 Miami, FL                           65.75   27413.34
136 Key West, FL                       138.36    27551.7
274 Tallahassee, FL                    441.61   27993.31
195 Pensacola, FL                      202.87   28196.18
168 Mobile, AL                          60.12    28256.3
28 Biloxi, MS                          61.74   28318.04
112 Gulfport, MS                        14.47   28332.51
180 New Orleans, LA                     73.62   28406.13
20 Baton Rouge, LA                     82.09   28488.22
176 Natchez, MS                         78.58    28566.8
126 Jackson, MS                         98.43   28665.22
56 Chattanooga, TN                    386.64   29051.86
303 Wilmington, NC                     512.01   29563.88
220 Richmond, VA                       232.37   29796.25
41 Buffalo, NY                        381.27   30177.52
182 Niagara Falls, ON                   20.54   30198.06
227 Saint Catherines, ON                 8.92   30206.99
223 Rochester, NY                      107.53   30314.52
185 North Bay, ON                      260.14   30574.66
269 Sudbury, ON                            93   30667.66
279 Timmins, ON                        140.17   30807.83
249 Sault Ste Marie, ON                260.02   31067.85
278 Thunder Bay, ON                    357.29   31425.14
75 Des Moines, IA                     553.68   31978.82
84 El Paso, TX                       1119.89   33098.72
144 Las Vegas, NV                      671.08    33769.8
209 Prince Rupert, BC                 1638.53   35408.32
131 Juneau, AK                         390.43   35798.75
298 Whitehorse, YK                     172.32   35971.07
70 Dawson, YT                         362.92   36333.99
90 Fairbanks, AK                         596      36930
8 Anchorage, AK                      292.11    37222.1
183 Nome, AK                          1095.15   38317.26
148 Lihue, HI                          2967.4   41284.65
120 Honolulu, HI                        114.5   41399.15
119 Hilo, HI                              176   41575.15
309 Yellowknife, NT                   4111.93   45687.09
59 Churchill, MB                     1431.71    47118.8
243 San Juan, PR                      4433.43   54294.61
184 Norfolk, VA                       1451.64   55746.24
51 -> 288             1   56794.75         51 Champaign, IL
288 Urbana, IL                           2.52       2.52
73 Decatur, IL                         54.92      57.45
262 Springfield, IL                     47.66     105.11
196 Peoria, IL                          61.75     166.86
32 Bloomington, IL                      43.6     210.46
224 Rockford, IL                       123.66     334.12
135 Kenosha, WI                         90.57     424.69
165 Milwaukee, WI                       31.93     456.62
214 Racine, WI                          23.24     479.86
58 Chicago, IL                         61.23     541.09
104 Gary, IN                            27.47     568.56
259 South Bend, IN                      76.01     644.57
132 Kalamazoo, MI                       62.16     706.73
21 Battle Creek, MI                    28.23     734.96
142 Lansing, MI                         51.65     786.61
125 Jackson, MI                         35.28     821.89
9 Ann Arbor, MI                       46.67     868.56
280 Toledo, OH                          43.57     912.13
76 Detroit, MI                         58.03     970.16
304 Windsor, ON                          7.21     977.37
94 Flint, MI                           71.67    1049.04
226 Saginaw, MI                          33.5    1082.54
22 Bay City, MI                        12.83    1095.36
106 Grand Rapids, MI                   130.44     1225.8
96 Ft Wayne, IN                          132     1357.8
174 Muncie, IN                          67.15    1424.95
123 Indianapolis, IN                    60.87    1485.83
140 Lafayette, IN                       66.81    1552.63
276 Terre Haute, IN                     75.45    1628.09
89 Evansville, IN                     103.56    1731.64
175 Nashville, TN                      140.56    1967.07
35 Bowling Green, KY                   61.65    2028.71
154 Louisville, KY                       99.3    2128.02
147 Lexington, KY                       90.44    2218.46
60 Cincinnati, OH                      81.08    2299.54
114 Hamilton, OH                        17.93    2317.47
71 Dayton, OH                          35.63     2353.1
265 Springfield, OH                     28.81    2381.91
66 Columbus, OH                        56.03    2437.94
312 Zanesville, OH                      68.11    2506.04
47 Canton, OH                          73.78    2579.83
2 Akron, OH                            21.8    2601.63
61 Cleveland, OH                       31.35    2632.98
310 Youngstown, OH                      77.36    2710.33
267 Steubenville, OH                    50.45    2760.79
297 Wheeling, WV                        21.97    2782.75
201 Pittsburgh, PA                      56.46    2839.21
86 Erie, PA                           116.83    2956.05
37 Brantford, ON                       71.49    3027.54
138 Kitchener, ON                       22.82    3050.35
111 Guelph, ON                          10.29    3060.65
42 Burlington, ONT                     29.68    3090.32
115 Hamilton, ON                        19.51    3109.83
227 Saint Catherines, ON                52.51    3162.34
41 Buffalo, NY                         29.25    3191.58
182 Niagara Falls, ON                   20.54    3212.12
282 Toronto, ON                         42.01    3254.13
197 Peterborough, ON                    82.62    3336.75
24 Belleville, ON                      72.87    3409.61
137 Kingston, ON                        49.28     3458.9
191 Ottawa, ON                          85.39    3544.28
172 Montreal, QC                       146.68    3690.97
43 Burlington, VT                      97.19    3788.16
171 Montpelier, VT                      46.45    3834.61
38 Brattleboro, VT                     97.37    3931.97
263 Springfield, MA                     51.84    3983.81
117 Hartford, CT                        24.25    4008.06
178 New Britain,CT                       9.62    4017.69
163 Meriden, CT                          8.71     4026.4
179 New Haven, CT                       17.96    4044.36
39 Bridgeport, CT                      21.46    4065.82
266 Stamford, CT                        24.55    4090.37
299 White Plains, NY                    15.36    4105.73
181 New York, NY                        27.75    4133.48
128 Jersey City, NJ                      5.04    4138.52
177 Newark, NJ                           6.57    4145.09
83 Elizabeth, NJ                        5.61     4150.7
194 Paterson, NJ                        17.67    4168.37
283 Trenton, NJ                          62.4    4230.77
302 Wilmington, DE                      30.05    4295.18
129 Johnstown, PA                        27.2    4370.86
141 Lancaster, PA                       16.64     4387.5
116 Harrisburg, PA                      43.19    4430.69
18 Baltimore, MD                        70.5    4501.18
294 Washington, DC                      40.06    4541.24
220 Richmond, VA                        97.21    4638.45
208 Portsmouth, VA                      94.39    4732.84
184 Norfolk, VA                          1.19    4734.03
215 Raleigh, NC                        178.76    4912.79
80 Durham, NC                          23.61    4936.41
109 Greensboro, NC                      61.97    4998.37
306 Winston-Salem, NC                    31.3    5029.67
54 Charlotte, NC                       73.13     5102.8
260 Spartanburg, NC                     77.64    5180.45
110 Greenville, SC                      32.62    5213.06
10 Asheville, NC                       52.87    5265.93
139 Knoxville, TN                       97.65    5363.58
56 Chattanooga, TN                    114.91    5478.49
122 Huntsville, AL                      63.64    5628.15
30 Birmingham, AL                      84.91    5713.06
170 Montgomery, AL                      86.96    5800.02
65 Columbus, GA                         90.9    5890.93
157 Macon, GA                           97.26    5988.18
12 Atlanta, GA                         81.64    6069.82
14 Augusta, GA                        167.83    6237.65
64 Columbia, SC                        74.55    6312.21
52 Charleston, SC                      113.9     6426.1
250 Savannah, GA                         93.9       6520
127 Jacksonville, FL                   126.96    6646.96
101 Gainesville, FL                     65.95    6712.91
72 Daytona Beach, FL                   94.98    6807.89
190 Orlando, FL                         52.58    6860.48
275 Tampa, FL                           85.01    6945.48
234 Saint Petersburg, FL                19.54    6965.02
247 Sarasota, FL                        31.73    6996.75
296 West Palm Beach, FL                176.46    7173.21
164 Miami, FL                           65.75    7238.96
136 Key West, FL                       138.36    7377.32
274 Tallahassee, FL                    441.61    7818.92
195 Pensacola, FL                      202.87     8021.8
168 Mobile, AL                          60.12    8081.91
28 Biloxi, MS                          61.74    8143.65
112 Gulfport, MS                        14.47    8158.12
180 New Orleans, LA                     73.62    8231.74
20 Baton Rouge, LA                     82.09    8313.83
176 Natchez, MS                         78.58    8392.41
126 Jackson, MS                         98.43    8490.84
162 Memphis, TN                         197.2    8688.04
151 Little Rock, AR                     157.3    8845.33
277 Texarkana, TX                      151.96    8997.29
160 Marshall, TX                        64.71       9062
256 Shreveport, LA                      42.67    9104.67
23 Beaumont, TX                       170.28    9274.96
204 Port Arthur, TX                     17.62    9292.58
103 Galveston, TX                       72.86    9365.44
121 Houston, TX                         50.45    9415.89
16 Austin, TX                         168.07    9583.96
238 San Antonio, TX                     77.99    9661.95
68 Corpus Christi, TX                 135.39    9797.35
143 Laredo, TX                         147.28    9944.63
292 Waco, TX                            323.5   10268.13
97 Ft Worth, TX                        82.15   10350.28
69 Dallas, TX                          36.19   10386.47
188 Oklahoma City, OK                  191.95   10578.42
85 Enid, OK                            68.83   10647.25
300 Wichita, KS                         97.07   10744.32
236 Salina, KS                          81.55   10825.87
281 Topeka, KS                         134.35   10960.22
133 Kansas City, KS                     72.73   11032.95
134 Kansas City, MO                      3.52   11036.48
231 Saint Joseph, MO                    49.79   11086.27
189 Omaha, NE                          127.61   11213.88
150 Lincoln, NE                         59.52    11273.4
257 Sioux City, IA                     118.91    11392.3
258 Sioux Falls, SD                     75.45   11467.76
228 Saint Cloud, MN                    223.72   11691.48
166 Minneapolis, MN                     73.94   11765.42
233 Saint Paul, MN                      12.03   11777.45
222 Rochester, MN                       76.94   11854.39
81 Eau Claire, WI                       86.5   11940.89
270 Superior, WI                       138.42    12079.3
79 Duluth, MN                           4.32   12083.63
278 Thunder Bay, ON                    221.38   12305.01
108 Green Bay, WI                      281.05   12586.06
253 Sheboygan, WI                       57.13   12643.19
78 Dubuque, IA                         95.84   12864.62
49 Cedar Rapids, IA                    75.74   12940.36
124 Iowa City, IA                       25.25   12965.61
295 Waterloo, IA                        80.35   13045.96
75 Des Moines, IA                     107.02   13152.99
63 Columbia, MO                       203.13   13356.12
264 Springfield, MO                    137.23   13493.35
130 Joplin, MO                          84.44   13577.79
95 Ft Smith, AR                       117.62    13695.4
287 Tulsa, OK                          122.27   13817.67
77 Dodge City, KS                     299.19   14116.86
7 Amarillo, TX                       215.16   14332.02
156 Lubbock, TX                        113.61   14445.63
1 Abilene, TX                        166.25   14611.88
84 El Paso, TX                        469.27   15081.15
4 Albuquerque, NM                    230.08   15311.23
246 Santa Fe, NM                        64.52   15375.75
102 Gallup, NM                         194.11   15569.86
93 Flagstaff, AZ                      202.26   15772.12
199 Phoenix, AZ                        124.38    15896.5
286 Tucson, AZ                         116.06   16012.56
311 Yuma, AZ                           257.86   16270.41
240 San Diego, CA                      175.01   16445.43
239 San Bernardino, CA                  96.69   16542.12
153 Los Angeles, CA                      9.51   16610.75
244 Santa Barbara, CA                  103.68   16714.43
17 Bakersfield, CA                     80.84   16795.27
99 Fresno, CA                         108.31   16903.58
268 Stockton, CA                       134.15   17037.73
225 Sacramento, CA                      45.35   17083.08
26 Berkeley, CA                         72.8   17155.88
186 Oakland, CA                          4.65   17160.53
241 San Francisco, CA                   10.47      17171
242 San Jose, CA                        47.11   17218.11
245 Santa Cruz, CA                      26.93   17245.03
48 Carson City, NV                    217.59   17462.63
219 Reno, NV                            25.48   17488.11
88 Eureka, CA                         313.15   17801.26
87 Eugene, OR                         236.57   18037.82
235 Salem, OR                           61.63   18099.46
206 Portland, OR                        47.17   18146.63
273 Tacoma, WA                         120.57    18267.2
252 Seattle, WA                         25.62   18292.82
25 Bellingham, WA                      80.42   18373.24
290 Vancouver, BC                       56.66    18429.9
291 Victoria, BC                        41.45   18471.35
308 Yakima, WA                         246.37   18717.72
293 Walla Walla, WA                    153.98    18871.7
261 Spokane, WA                        127.07   18998.77
33 Boise, ID                          291.99   19290.76
203 Pocatello, ID                      264.67   19555.43
187 Ogden, UT                          118.47    19673.9
237 Salt Lake City, UT                  32.44   19706.34
211 Provo, UT                           39.79   19746.13
105 Grand Junction, CO                 229.45   19975.58
74 Denver, CO                         250.76   20226.34
62 Colorado Springs, CO                63.56    20289.9
212 Pueblo, CO                          42.63   20332.53
57 Cheyenne, WY                       199.91   20532.45
216 Rapid City, SD                     230.96    20763.4
200 Pierre, SD                         199.98   20963.38
31 Bismarck, ND                       171.22    21134.6
167 Minot, ND                          104.59   21239.19
36 Brandon, MB                        148.55   21387.74
305 Winnipeg, MB                       187.08   21574.82
92 Fargo, ND                          211.94   21786.76
218 Regina, SK                         597.09   22383.85
173 Moose Jaw, SK                       62.04   22445.89
161 Medicine Hat, AB                   315.13    22910.7
146 Lethbridge, AB                     146.13   23056.82
45 Calgary, AB                        132.75   23189.57
82 Edmonton, AB                       173.35   23362.92
107 Great Falls, MT                    446.74   23809.67
118 Helena, MT                           80.7   23890.36
44 Butte, MT                           53.31   23943.68
27 Billings, MT                       279.14   24222.81
255 Sheridan, WY                       126.61   24349.42
144 Las Vegas, NV                      821.24   25170.67
209 Prince Rupert, BC                 1638.53   26809.19
131 Juneau, AK                         390.43   27199.62
298 Whitehorse, YK                     172.32   27371.94
70 Dawson, YT                         362.92   27734.86
90 Fairbanks, AK                         596   28330.87
8 Anchorage, AK                      292.11   28622.97
183 Nome, AK                          1095.15   29718.13
148 Lihue, HI                          2967.4   32685.52
120 Honolulu, HI                        114.5   32800.02
119 Hilo, HI                              176   32976.02
309 Yellowknife, NT                   4111.93   37087.96
59 Churchill, MB                     1431.71   38519.67
249 Sault Ste Marie, ON               1073.27   39592.94
269 Sudbury, ON                        256.57   39849.51
185 North Bay, ON                          93   39942.51
279 Timmins, ON                        198.01   40140.52
152 London, ON                         387.85   40528.37
223 Rochester, NY                       249.8   40778.17
272 Syracuse, NY                       101.71   40879.88
289 Utica, NY                           63.31   40943.18
29 Binghamtom, NY                      83.89   41027.07
301 Wilkes-Barre, PA                    58.97   41086.05
6 Allentown, PA                       51.68   41137.72
13 Atlantic City, NJ                  113.26   41250.99
50 Central Islip, NY                  129.74   41380.73
202 Pittsfield, MA                      114.7   41495.43
285 Troy, NY                            36.34   41531.77
3 Albany, NY                           6.88   41538.65
307 Worcester, MA                      152.51   41708.04
210 Providence, RI                      40.53   41748.58
91 Fall River, MA                      19.72   41768.29
40 Brockton, MA                        28.03   41796.32
34 Boston, MA                          19.21   41815.53
46 Cambridge, MA                        3.36    41818.9
155 Lowell, MA                          23.03   41841.93
145 Lawrence, MA                        11.73   41853.65
159 Manchester, NH                      28.36   41882.01
67 Concord, NH                         15.76   41897.76
207 Portsmouth, NH                      54.37   41952.14
205 Portland, ME                        53.74   42005.88
15 Augusta, ME                         55.61   42061.49
19 Bangor, ME                          77.07   42138.56
98 Fredericton, NB                    171.89   42310.45
229 Saint John, NB                      58.53   42368.98
169 Moncton, NB                         99.94   42468.93
55 Charlottetown, PE                  108.58    42577.5
113 Halifax, NS                        100.98   42678.49
271 Syndey, NS                         254.97   42933.46
230 Saint John's, NF                   514.07   43447.53
213 Quebec City, QC                   1290.47   44737.99
284 Trois-Rivieres, QC                 190.67   44928.66
254 Sherbrooke, QC                      93.08   45021.74
221 Roanoke, VA                        802.17   45823.91
53 Charleston, WV                     138.61   45962.52
11 Ashland, KY                         70.03   46032.55
149 Lima, OH                           186.42   46218.96
232 Saint Louis, MO                    445.62   46664.59
303 Wilmington, NC                     899.59   47564.17
243 San Juan, PR                      1361.81   48925.99
51 Champaign, IL                     3435.34   56794.75
2   56794.89         51 Champaign, IL
288 Urbana, IL                           2.52       2.52
73 Decatur, IL                         54.92      57.45
262 Springfield, IL                     47.66     105.11
196 Peoria, IL                          61.75     166.86
32 Bloomington, IL                      43.6     210.46
224 Rockford, IL                       123.66     334.12
135 Kenosha, WI                         90.57     424.69
165 Milwaukee, WI                       31.93     456.62
214 Racine, WI                          23.24     479.86
58 Chicago, IL                         61.23     541.09
104 Gary, IN                            27.47     568.56
259 South Bend, IN                      76.01     644.57
132 Kalamazoo, MI                       62.16     706.73
21 Battle Creek, MI                    28.23     734.96
142 Lansing, MI                         51.65     786.61
125 Jackson, MI                         35.28     821.89
9 Ann Arbor, MI                       46.67     868.56
280 Toledo, OH                          43.57     912.13
76 Detroit, MI                         58.03     970.16
304 Windsor, ON                          7.21     977.37
94 Flint, MI                           71.67    1049.04
226 Saginaw, MI                          33.5    1082.54
22 Bay City, MI                        12.83    1095.36
106 Grand Rapids, MI                   130.44     1225.8
96 Ft Wayne, IN                          132     1357.8
174 Muncie, IN                          67.15    1424.95
123 Indianapolis, IN                    60.87    1485.83
140 Lafayette, IN                       66.81    1552.63
276 Terre Haute, IN                     75.45    1628.09
89 Evansville, IN                     103.56    1731.64
175 Nashville, TN                      140.56    1967.07
35 Bowling Green, KY                   61.65    2028.71
154 Louisville, KY                       99.3    2128.02
147 Lexington, KY                       90.44    2218.46
60 Cincinnati, OH                      81.08    2299.54
114 Hamilton, OH                        17.93    2317.47
71 Dayton, OH                          35.63     2353.1
265 Springfield, OH                     28.81    2381.91
66 Columbus, OH                        56.03    2437.94
312 Zanesville, OH                      68.11    2506.04
47 Canton, OH                          73.78    2579.83
2 Akron, OH                            21.8    2601.63
61 Cleveland, OH                       31.35    2632.98
310 Youngstown, OH                      77.36    2710.33
267 Steubenville, OH                    50.45    2760.79
297 Wheeling, WV                        21.97    2782.75
201 Pittsburgh, PA                      56.46    2839.21
86 Erie, PA                           116.83    2956.05
37 Brantford, ON                       71.49    3027.54
138 Kitchener, ON                       22.82    3050.35
111 Guelph, ON                          10.29    3060.65
42 Burlington, ONT                     29.68    3090.32
115 Hamilton, ON                        19.51    3109.83
227 Saint Catherines, ON                52.51    3162.34
182 Niagara Falls, ON                    8.92    3171.26
41 Buffalo, NY                         20.54     3191.8
282 Toronto, ON                         62.46    3254.26
197 Peterborough, ON                    82.62    3336.88
24 Belleville, ON                      72.87    3409.75
137 Kingston, ON                        49.28    3459.03
191 Ottawa, ON                          85.39    3544.41
172 Montreal, QC                       146.68     3691.1
43 Burlington, VT                      97.19    3788.29
171 Montpelier, VT                      46.45    3834.74
38 Brattleboro, VT                     97.37    3932.11
263 Springfield, MA                     51.84    3983.94
117 Hartford, CT                        24.25     4008.2
178 New Britain,CT                       9.62    4017.82
163 Meriden, CT                          8.71    4026.53
179 New Haven, CT                       17.96    4044.49
39 Bridgeport, CT                      21.46    4065.95
266 Stamford, CT                        24.55     4090.5
299 White Plains, NY                    15.36    4105.86
181 New York, NY                        27.75    4133.61
128 Jersey City, NJ                      5.04    4138.65
177 Newark, NJ                           6.57    4145.22
83 Elizabeth, NJ                        5.61    4150.84
194 Paterson, NJ                        17.67     4168.5
283 Trenton, NJ                          62.4     4230.9
302 Wilmington, DE                      30.05    4295.31
129 Johnstown, PA                        27.2    4370.99
141 Lancaster, PA                       16.64    4387.63
116 Harrisburg, PA                      43.19    4430.82
18 Baltimore, MD                        70.5    4501.31
294 Washington, DC                      40.06    4541.38
220 Richmond, VA                        97.21    4638.58
208 Portsmouth, VA                      94.39    4732.97
184 Norfolk, VA                          1.19    4734.16
215 Raleigh, NC                        178.76    4912.92
80 Durham, NC                          23.61    4936.54
109 Greensboro, NC                      61.97    4998.51
306 Winston-Salem, NC                    31.3     5029.8
54 Charlotte, NC                       73.13    5102.94
260 Spartanburg, NC                     77.64    5180.58
110 Greenville, SC                      32.62    5213.19
10 Asheville, NC                       52.87    5266.06
139 Knoxville, TN                       97.65    5363.71
56 Chattanooga, TN                    114.91    5478.63
122 Huntsville, AL                      63.64    5628.29
30 Birmingham, AL                      84.91    5713.19
170 Montgomery, AL                      86.96    5800.16
65 Columbus, GA                         90.9    5891.06
157 Macon, GA                           97.26    5988.32
12 Atlanta, GA                         81.64    6069.95
14 Augusta, GA                        167.83    6237.79
64 Columbia, SC                        74.55    6312.34
52 Charleston, SC                      113.9    6426.24
250 Savannah, GA                         93.9    6520.14
127 Jacksonville, FL                   126.96    6647.09
101 Gainesville, FL                     65.95    6713.05
72 Daytona Beach, FL                   94.98    6808.02
190 Orlando, FL                         52.58    6860.61
275 Tampa, FL                           85.01    6945.62
234 Saint Petersburg, FL                19.54    6965.15
247 Sarasota, FL                        31.73    6996.88
296 West Palm Beach, FL                176.46    7173.34
164 Miami, FL                           65.75    7239.09
136 Key West, FL                       138.36    7377.45
274 Tallahassee, FL                    441.61    7819.06
195 Pensacola, FL                      202.87    8021.93
168 Mobile, AL                          60.12    8082.04
28 Biloxi, MS                          61.74    8143.79
112 Gulfport, MS                        14.47    8158.25
180 New Orleans, LA                     73.62    8231.87
20 Baton Rouge, LA                     82.09    8313.96
176 Natchez, MS                         78.58    8392.54
126 Jackson, MS                         98.43    8490.97
162 Memphis, TN                         197.2    8688.17
151 Little Rock, AR                     157.3    8845.47
277 Texarkana, TX                      151.96    8997.42
160 Marshall, TX                        64.71    9062.13
256 Shreveport, LA                      42.67    9104.81
23 Beaumont, TX                       170.28    9275.09
204 Port Arthur, TX                     17.62    9292.71
103 Galveston, TX                       72.86    9365.57
121 Houston, TX                         50.45    9416.02
16 Austin, TX                         168.07     9584.1
238 San Antonio, TX                     77.99    9662.09
68 Corpus Christi, TX                 135.39    9797.48
143 Laredo, TX                         147.28    9944.76
292 Waco, TX                            323.5   10268.26
97 Ft Worth, TX                        82.15   10350.41
69 Dallas, TX                          36.19    10386.6
188 Oklahoma City, OK                  191.95   10578.55
85 Enid, OK                            68.83   10647.38
300 Wichita, KS                         97.07   10744.46
236 Salina, KS                          81.55      10826
281 Topeka, KS                         134.35   10960.35
133 Kansas City, KS                     72.73   11033.09
134 Kansas City, MO                      3.52   11036.61
231 Saint Joseph, MO                    49.79    11086.4
189 Omaha, NE                          127.61   11214.01
150 Lincoln, NE                         59.52   11273.53
257 Sioux City, IA                     118.91   11392.44
258 Sioux Falls, SD                     75.45   11467.89
228 Saint Cloud, MN                    223.72   11691.61
166 Minneapolis, MN                     73.94   11765.55
233 Saint Paul, MN                      12.03   11777.58
222 Rochester, MN                       76.94   11854.52
81 Eau Claire, WI                       86.5   11941.02
270 Superior, WI                       138.42   12079.44
79 Duluth, MN                           4.32   12083.76
278 Thunder Bay, ON                    221.38   12305.14
108 Green Bay, WI                      281.05   12586.19
253 Sheboygan, WI                       57.13   12643.32
78 Dubuque, IA                         95.84   12864.75
49 Cedar Rapids, IA                    75.74    12940.5
124 Iowa City, IA                       25.25   12965.74
295 Waterloo, IA                        80.35    13046.1
75 Des Moines, IA                     107.02   13153.12
63 Columbia, MO                       203.13   13356.25
264 Springfield, MO                    137.23   13493.48
130 Joplin, MO                          84.44   13577.92
95 Ft Smith, AR                       117.62   13695.54
287 Tulsa, OK                          122.27    13817.8
77 Dodge City, KS                     299.19      14117
7 Amarillo, TX                       215.16   14332.15
156 Lubbock, TX                        113.61   14445.76
1 Abilene, TX                        166.25   14612.02
84 El Paso, TX                        469.27   15081.29
4 Albuquerque, NM                    230.08   15311.37
246 Santa Fe, NM                        64.52   15375.88
102 Gallup, NM                         194.11   15569.99
93 Flagstaff, AZ                      202.26   15772.25
199 Phoenix, AZ                        124.38   15896.63
286 Tucson, AZ                         116.06   16012.69
311 Yuma, AZ                           257.86   16270.55
240 San Diego, CA                      175.01   16445.56
239 San Bernardino, CA                  96.69   16542.25
153 Los Angeles, CA                      9.51   16610.89
244 Santa Barbara, CA                  103.68   16714.56
17 Bakersfield, CA                     80.84   16795.41
99 Fresno, CA                         108.31   16903.71
268 Stockton, CA                       134.15   17037.87
225 Sacramento, CA                      45.35   17083.21
26 Berkeley, CA                         72.8   17156.01
186 Oakland, CA                          4.65   17160.66
241 San Francisco, CA                   10.47   17171.13
242 San Jose, CA                        47.11   17218.24
245 Santa Cruz, CA                      26.93   17245.17
48 Carson City, NV                    217.59   17462.76
219 Reno, NV                            25.48   17488.24
88 Eureka, CA                         313.15   17801.39
87 Eugene, OR                         236.57   18037.96
235 Salem, OR                           61.63   18099.59
206 Portland, OR                        47.17   18146.76
273 Tacoma, WA                         120.57   18267.33
252 Seattle, WA                         25.62   18292.95
25 Bellingham, WA                      80.42   18373.37
290 Vancouver, BC                       56.66   18430.03
291 Victoria, BC                        41.45   18471.48
308 Yakima, WA                         246.37   18717.85
293 Walla Walla, WA                    153.98   18871.83
261 Spokane, WA                        127.07    18998.9
33 Boise, ID                          291.99    19290.9
203 Pocatello, ID                      264.67   19555.56
187 Ogden, UT                          118.47   19674.03
237 Salt Lake City, UT                  32.44   19706.47
211 Provo, UT                           39.79   19746.26
105 Grand Junction, CO                 229.45   19975.72
74 Denver, CO                         250.76   20226.47
62 Colorado Springs, CO                63.56   20290.03
212 Pueblo, CO                          42.63   20332.67
57 Cheyenne, WY                       199.91   20532.58
216 Rapid City, SD                     230.96   20763.54
200 Pierre, SD                         199.98   20963.52
31 Bismarck, ND                       171.22   21134.74
167 Minot, ND                          104.59   21239.32
36 Brandon, MB                        148.55   21387.87
305 Winnipeg, MB                       187.08   21574.95
92 Fargo, ND                          211.94   21786.89
218 Regina, SK                         597.09   22383.98
173 Moose Jaw, SK                       62.04   22446.03
161 Medicine Hat, AB                   315.13   22910.83
146 Lethbridge, AB                     146.13   23056.96
45 Calgary, AB                        132.75    23189.7
82 Edmonton, AB                       173.35   23363.06
107 Great Falls, MT                    446.74    23809.8
118 Helena, MT                           80.7    23890.5
44 Butte, MT                           53.31   23943.81
27 Billings, MT                       279.14   24222.95
255 Sheridan, WY                       126.61   24349.56
144 Las Vegas, NV                      821.24    25170.8
209 Prince Rupert, BC                 1638.53   26809.33
131 Juneau, AK                         390.43   27199.75
298 Whitehorse, YK                     172.32   27372.07
70 Dawson, YT                         362.92      27735
90 Fairbanks, AK                         596      28331
8 Anchorage, AK                      292.11   28623.11
183 Nome, AK                          1095.15   29718.26
148 Lihue, HI                          2967.4   32685.65
120 Honolulu, HI                        114.5   32800.16
119 Hilo, HI                              176   32976.16
309 Yellowknife, NT                   4111.93   37088.09
59 Churchill, MB                     1431.71    38519.8
249 Sault Ste Marie, ON               1073.27   39593.07
269 Sudbury, ON                        256.57   39849.65
185 North Bay, ON                          93   39942.65
279 Timmins, ON                        198.01   40140.66
152 London, ON                         387.85   40528.51
223 Rochester, NY                       249.8    40778.3
272 Syracuse, NY                       101.71   40880.01
289 Utica, NY                           63.31   40943.32
29 Binghamtom, NY                      83.89   41027.21
301 Wilkes-Barre, PA                    58.97   41086.18
6 Allentown, PA                       51.68   41137.86
13 Atlantic City, NJ                  113.26   41251.12
50 Central Islip, NY                  129.74   41380.86
202 Pittsfield, MA                      114.7   41495.56
285 Troy, NY                            36.34    41531.9
3 Albany, NY                           6.88   41538.78
307 Worcester, MA                      152.51   41708.18
210 Providence, RI                      40.53   41748.71
91 Fall River, MA                      19.72   41768.43
40 Brockton, MA                        28.03   41796.45
34 Boston, MA                          19.21   41815.67
46 Cambridge, MA                        3.36   41819.03
155 Lowell, MA                          23.03   41842.06
145 Lawrence, MA                        11.73   41853.79
159 Manchester, NH                      28.36   41882.14
67 Concord, NH                         15.76    41897.9
207 Portsmouth, NH                      54.37   41952.27
205 Portland, ME                        53.74   42006.01
15 Augusta, ME                         55.61   42061.62
19 Bangor, ME                          77.07    42138.7
98 Fredericton, NB                    171.89   42310.58
229 Saint John, NB                      58.53   42369.11
169 Moncton, NB                         99.94   42469.06
55 Charlottetown, PE                  108.58   42577.64
113 Halifax, NS                        100.98   42678.62
271 Syndey, NS                         254.97   42933.59
230 Saint John's, NF                   514.07   43447.66
213 Quebec City, QC                   1290.47   44738.13
284 Trois-Rivieres, QC                 190.67    44928.8
254 Sherbrooke, QC                      93.08   45021.87
221 Roanoke, VA                        802.17   45824.05
53 Charleston, WV                     138.61   45962.65
11 Ashland, KY                         70.03   46032.68
149 Lima, OH                           186.42    46219.1
232 Saint Louis, MO                    445.62   46664.72
303 Wilmington, NC                     899.59   47564.31
243 San Juan, PR                      1361.81   48926.12
51 Champaign, IL                     3435.34   56794.89

1252 rows selected.

Elapsed: 00:01:25.25
```
```Longest Routes
==============
ROOT_LEG       PATH_RNK   TOT_DIST    TOWN_ID NAME                             LEG_DIST   CUM_DIST
------------ ---------- ---------- ---------- ------------------------------ ---------- ----------
183 -> 230            1  649684.75        183 Nome, AK
230 Saint John's, NF                  7870.89    7870.89
148 Lihue, HI                         7576.95   15447.85
119 Hilo, HI                          7786.39   31139.99
271 Syndey, NS                        6867.36   38007.35
120 Honolulu, HI                      6973.04   44980.39
55 Charlottetown, PE                 6769.69   51750.08
8 Anchorage, AK                     6084.17   57834.25
243 San Juan, PR                       6499.8   64334.05
90 Fairbanks, AK                     6485.59   70819.64
113 Halifax, NS                        5979.6   76799.24
70 Dawson, YT                        5390.11   82189.35
169 Moncton, NB                       5290.95    87480.3
298 Whitehorse, YK                    4966.85   92447.16
229 Saint John, NB                    4896.82   97343.98
131 Juneau, AK                        4819.35  102163.34
98 Fredericton, NB                   4768.78  106932.11
209 Prince Rupert, BC                 4454.24  111386.35
19 Bangor, ME                        4310.99  115697.34
88 Eureka, CA                        3836.77  119534.11
15 Augusta, ME                        3765.4  123299.51
291 Victoria, BC                      3718.78  127018.29
205 Portland, ME                      3689.93  130708.23
290 Vancouver, BC                      3676.9  134385.13
207 Portsmouth, NH                    3646.55  138031.68
87 Eugene, OR                        3615.89  141647.57
40 Brockton, MA                      3600.15  145247.72
235 Salem, OR                         3599.44  148847.16
34 Boston, MA                        3595.59  152442.75
25 Bellingham, WA                    3580.78  156023.53
91 Fall River, MA                    3580.15  159603.68
206 Portland, OR                      3569.55  163173.24
46 Cambridge, MA                     3569.83  166743.06
273 Tacoma, WA                        3563.14   170306.2
145 Lawrence, MA                      3557.08  173863.27
241 San Francisco, CA                 3557.84  177421.11
213 Quebec City, QC                      3607  181028.11
186 Oakland, CA                       3596.53  184624.65
155 Lowell, MA                        3536.42  188161.06
252 Seattle, WA                       3541.57  191702.63
210 Providence, RI                     3540.8  195243.43
26 Berkeley, CA                      3524.68  198768.11
254 Sherbrooke, QC                    3533.82  202301.93
245 Santa Cruz, CA                    3527.03  205828.96
159 Manchester, NH                    3519.15  209348.11
242 San Jose, CA                      3506.93  212855.04
67 Concord, NH                       3502.92  216357.96
225 Sacramento, CA                    3466.46  219824.42
307 Worcester, MA                     3442.81  223267.23
268 Stockton, CA                      3432.24  226699.47
284 Trois-Rivieres, QC                3408.48  230107.95
244 Santa Barbara, CA                 3351.71  233459.66
171 Montpelier, VT                     3326.1  236785.76
308 Yakima, WA                        3315.62  240101.39
263 Springfield, MA                   3325.25  243426.64
99 Fresno, CA                        3280.93  246707.57
38 Brattleboro, VT                   3289.35  249996.92
219 Reno, NV                          3273.12  253270.05
117 Hartford, CT                      3259.94  256529.98
48 Carson City, NV                   3258.04  259788.02
178 New Britain,CT                    3251.14  263039.16
17 Bakersfield, CA                   3224.24   266263.4
43 Burlington, VT                    3226.83  269490.23
153 Los Angeles, CA                   3193.66  272683.89
163 Meriden, CT                        3181.7  275865.59
309 Yellowknife, NT                   3219.55  279085.14
164 Miami, FL                         3467.82  282552.96
82 Edmonton, AB                      2998.65  285551.61
136 Key West, FL                      2971.34  288522.96
261 Spokane, WA                       2934.85  291457.81
179 New Haven, CT                     3105.67  294563.47
172 Montreal, QC                      3157.19  300883.76
240 San Diego, CA                     3118.23  304001.99
202 Pittsfield, MA                     3107.7  307109.68
293 Walla Walla, WA                   3125.94  310235.63
50 Central Islip, NY                 3140.18   313375.8
239 San Bernardino, CA                3080.98  316456.79
39 Bridgeport, CT                    3084.78  319541.56
33 Boise, ID                         2975.73  322517.29
266 Stamford, CT                      2952.96  325470.25
311 Yuma, AZ                           2896.3  328366.55
285 Troy, NY                          2911.41  331277.97
144 Las Vegas, NV                      2899.2  334177.16
3 Albany, NY                        2893.98  337071.14
45 Calgary, AB                       2842.81  339913.95
296 West Palm Beach, FL               2889.69  342803.64
146 Lethbridge, AB                    2761.59  345565.23
299 White Plains, NY                  2761.87   348327.1
44 Butte, MT                         2700.79  351027.89
181 New York, NY                      2687.05  353714.95
199 Phoenix, AZ                       2677.74  356392.69
203 Pocatello, ID                     2660.51  361766.36
128 Jersey City, NJ                    2655.1  364421.46
118 Helena, MT                         2653.8  367075.27
177 Newark, NJ                        2647.25  369722.51
93 Flagstaff, AZ                     2617.66  372340.18
194 Paterson, NJ                      2619.56  374959.74
187 Ogden, UT                         2611.96   377571.7
83 Elizabeth, NJ                     2609.47  380181.16
107 Great Falls, MT                   2605.85  382787.01
13 Atlantic City, NJ                  2609.3  385396.31
161 Medicine Hat, AB                  2613.82  388010.13
283 Trenton, NJ                        2576.5  390586.63
237 Salt Lake City, UT                2566.97  393153.59
289 Utica, NY                         2538.01   395691.6
286 Tucson, AZ                        2578.23  398269.82
191 Ottawa, ON                        2575.89  400845.71
211 Provo, UT                          2487.2  403332.91
27 Billings, MT                      2338.33  408192.87
302 Wilmington, DE                    2314.81  410507.68
247 Sarasota, FL                      2394.44  415219.54
59 Churchill, MB                     2311.24  417530.77
234 Saint Petersburg, FL              2279.52  419810.29
173 Moose Jaw, SK                     2220.06  422030.35
190 Orlando, FL                       2249.04  424279.39
218 Regina, SK                        2208.47  426487.86
72 Daytona Beach, FL                 2195.17  428683.03
255 Sheridan, WY                      2090.58  430773.61
6 Allentown, PA                     2193.28  432966.89
102 Gallup, NM                        2324.19  435291.08
29 Binghamtom, NY                    2312.96  437604.04
105 Grand Junction, CO                2264.43  439868.47
301 Wilkes-Barre, PA                  2262.27  442130.74
84 El Paso, TX                       2213.93  444344.68
137 Kingston, ON                      2237.67  446582.35
4 Albuquerque, NM                    2170.5  448752.84
272 Syracuse, NY                      2178.26   450931.1
246 Santa Fe, NM                      2120.23  453051.33
74 Denver, CO                        2008.13  457157.75
208 Portsmouth, VA                    1992.18  459149.93
57 Cheyenne, WY                      1993.01  461142.94
184 Norfolk, VA                       1993.78  463136.72
62 Colorado Springs, CO              1976.45  465113.17
129 Johnstown, PA                     1972.05  467085.22
212 Pueblo, CO                        1959.85  469045.06
141 Lancaster, PA                     1959.48  471004.54
216 Rapid City, SD                    1881.23  472885.77
303 Wilmington, NC                    1875.13   474760.9
167 Minot, ND                         1881.45  476642.36
275 Tampa, FL                         1912.76  478555.12
36 Brandon, MB                       1940.02  480495.14
101 Gainesville, FL                   1855.41  482350.55
31 Bismarck, ND                      1741.25   484091.8
18 Baltimore, MD                     1749.02  485840.82
143 Laredo, TX                        1779.18  487620.01
24 Belleville, ON                    1918.78  489538.79
156 Lubbock, TX                       1847.76  491386.54
116 Harrisburg, PA                    1786.27  493172.82
7 Amarillo, TX                      1758.66  494931.47
223 Rochester, NY                     1760.64  496692.12
68 Corpus Christi, TX                1730.17  498422.29
279 Timmins, ON                       1812.36  500234.65
238 San Antonio, TX                   1774.49  502009.14
185 North Bay, ON                     1763.41  503772.55
1 Abilene, TX                       1701.18  505473.73
197 Peterborough, ON                  1685.89  507159.62
16 Austin, TX                         1650.4  508810.02
269 Sudbury, ON                       1627.33  510437.35
121 Houston, TX                       1539.52  511976.87
41 Buffalo, NY                       1455.86  513432.73
77 Dodge City, KS                    1503.01  514935.73
294 Washington, DC                    1589.78  516525.51
200 Pierre, SD                        1654.69   518180.2
52 Charleston, SC                    1622.39  519802.59
305 Winnipeg, MB                      1683.01  521485.61
127 Jacksonville, FL                  1729.19   523214.8
92 Fargo, ND                          1549.3   524764.1
220 Richmond, VA                       1482.8  526246.89
292 Waco, TX                          1422.07  527668.96
282 Toronto, ON                       1483.47  529152.43
103 Galveston, TX                     1453.39  530605.82
182 Niagara Falls, ON                 1446.61  532052.43
97 Ft Worth, TX                      1451.32  533503.75
227 Saint Catherines, ON               1447.6  534951.35
69 Dallas, TX                        1414.51  536365.85
42 Burlington, ONT                   1383.45   537749.3
204 Port Arthur, TX                   1349.79   539099.1
111 Guelph, ON                        1334.48  540433.58
23 Beaumont, TX                      1333.91  541767.49
115 Hamilton, ON                      1332.75  543100.24
188 Oklahoma City, OK                 1326.53  544426.77
138 Kitchener, ON                     1306.17  545732.94
85 Enid, OK                          1303.54  547036.48
215 Raleigh, NC                       1330.04  548366.51
258 Sioux Falls, SD                   1358.74  549725.26
250 Savannah, GA                      1337.75  551063.01
228 Saint Cloud, MN                   1296.84  552359.85
274 Tallahassee, FL                   1248.21  553608.06
278 Thunder Bay, ON                   1287.21  554895.27
180 New Orleans, LA                   1273.63  556168.89
249 Sault Ste Marie, ON               1205.19  557374.08
20 Baton Rouge, LA                   1199.28  558573.36
37 Brantford, ON                     1151.44   559724.8
300 Wichita, KS                       1232.93  560957.73
80 Durham, NC                         1279.4  562237.14
236 Salina, KS                         1307.8  563544.94
109 Greensboro, NC                    1245.96   564790.9
257 Sioux City, IA                    1230.47  566021.37
64 Columbia, SC                      1213.26  567234.63
150 Lincoln, NE                       1177.83  568412.46
221 Roanoke, VA                       1181.08  569593.54
189 Omaha, NE                         1139.08  570732.62
306 Winston-Salem, NC                 1141.42  571874.04
79 Duluth, MN                        1103.04  572977.08
195 Pensacola, FL                     1179.96  574157.04
270 Superior, WI                      1175.77  575332.81
14 Augusta, GA                       1152.38  576485.19
166 Minneapolis, MN                    1113.9  577599.09
54 Charlotte, NC                     1091.15  578690.24
233 Saint Paul, MN                    1080.39  579770.63
157 Macon, GA                         1061.49  580832.12
81 Eau Claire, WI                     989.72  581821.84
28 Biloxi, MS                        1012.28  582834.12
152 London, ON                        1016.65  583850.76
160 Marshall, TX                      1158.56  585009.32
86 Erie, PA                          1188.43  586197.75
287 Tulsa, OK                          1174.1  587371.86
201 Pittsburgh, PA                    1144.29  588516.15
256 Shreveport, LA                    1096.48  589612.63
310 Youngstown, OH                    1081.83  590694.46
277 Texarkana, TX                     1066.85  591761.31
267 Steubenville, OH                  1043.65  592804.96
281 Topeka, KS                        1043.45  593848.41
297 Wheeling, WV                      1035.82  594884.23
95 Ft Smith, AR                       998.79  595883.02
47 Canton, OH                         974.26  596857.28
130 Joplin, MO                         943.13  597800.41
2 Akron, OH                          939.34  598739.75
176 Natchez, MS                        948.25  599688.01
22 Bay City, MI                       980.29   600668.3
112 Gulfport, MS                       982.13  601650.43
108 Green Bay, WI                      980.65  602631.08
168 Mobile, AL                         955.25  603586.33
222 Rochester, MN                      970.35  604556.68
260 Spartanburg, NC                    960.77  605517.44
231 Saint Joseph, MO                   952.41  606469.86
110 Greenville, SC                     925.02  607394.87
75 Des Moines, IA                     904.35  608299.23
10 Asheville, NC                      869.08   609168.3
133 Kansas City, KS                     868.8   610037.1
61 Cleveland, OH                      908.59  610945.69
134 Kansas City, MO                    905.45  611851.14
53 Charleston, WV                     895.98  612747.12
264 Springfield, MO                    809.82  613556.95
312 Zanesville, OH                     802.13  614359.08
151 Little Rock, AR                    795.57  615154.65
226 Saginaw, MI                        831.32  615985.97
126 Jackson, MS                         880.9  616866.87
94 Flint, MI                          865.78  617732.65
170 Montgomery, AL                     757.41  618490.06
295 Waterloo, IA                       814.78  619304.84
65 Columbus, GA                       859.51  620164.34
49 Cedar Rapids, IA                   804.18  620968.53
12 Atlanta, GA                        759.63  621728.16
78 Dubuque, IA                        744.13  622472.29
30 Birmingham, AL                     675.42   623147.7
253 Sheboygan, WI                      709.66  623857.37
56 Chattanooga, TN                    622.55  625831.37
124 Iowa City, IA                      627.43   626458.8
139 Knoxville, TN                      656.94  627115.74
63 Columbia, MO                       616.95  627732.69
304 Windsor, ON                         687.3  628419.98
162 Memphis, TN                        694.56  629114.55
76 Detroit, MI                        693.11  629807.66
122 Huntsville, AL                     579.38  630387.04
165 Milwaukee, WI                      581.29  630968.33
11 Ashland, KY                        481.46  631449.79
232 Saint Louis, MO                    522.45  631972.25
9 Ann Arbor, MI                      513.15   632485.4
142 Lansing, MI                        480.06  633457.26
175 Nashville, TN                      479.16  633936.42
106 Grand Rapids, MI                   475.97  634412.39
35 Bowling Green, KY                  416.17  634828.56
224 Rockford, IL                       408.25  635236.82
66 Columbus, OH                       450.37  635687.19
262 Springfield, IL                    459.25  636146.44
280 Toledo, OH                         439.91  636586.35
196 Peoria, IL                         422.25  637008.59
265 Springfield, OH                    402.89  637411.49
32 Bloomington, IL                    360.32  637771.81
147 Lexington, KY                      356.49  638128.31
214 Racine, WI                         399.12  638527.43
154 Louisville, KY                     339.14  638866.57
135 Kenosha, WI                         331.4  639197.97
60 Cincinnati, OH                     331.61  639529.58
73 Decatur, IL                        314.29  639843.88
125 Jackson, MI                        355.83   640199.7
89 Evansville, IN                     366.87  640566.58
21 Battle Creek, MI                   342.27  640908.84
51 Champaign, IL                       260.8  641169.64
149 Lima, OH                           289.17  641458.82
288 Urbana, IL                         286.77  641745.59
71 Dayton, OH                         278.52  642024.11
58 Chicago, IL                        279.24  642303.35
114 Hamilton, OH                       272.42  642575.77
104 Gary, IN                           244.97  642820.74
174 Muncie, IN                         166.43  642987.17
276 Terre Haute, IN                    148.82  643135.98
132 Kalamazoo, MI                      232.44  643368.42
123 Indianapolis, IN                   178.76  643547.18
259 South Bend, IN                     132.47  643679.65
140 Lafayette, IN                       97.61  643777.26
96 Ft Wayne, IN                       130.36  643907.62
183 Nome, AK                          5777.13  649684.75
2  649684.62        183 Nome, AK
230 Saint John's, NF                  7870.89    7870.89
148 Lihue, HI                         7576.95   15447.85
119 Hilo, HI                          7786.39   31139.99
271 Syndey, NS                        6867.36   38007.35
120 Honolulu, HI                      6973.04   44980.39
55 Charlottetown, PE                 6769.69   51750.08
8 Anchorage, AK                     6084.17   57834.25
243 San Juan, PR                       6499.8   64334.05
90 Fairbanks, AK                     6485.59   70819.64
113 Halifax, NS                        5979.6   76799.24
70 Dawson, YT                        5390.11   82189.35
169 Moncton, NB                       5290.95    87480.3
298 Whitehorse, YK                    4966.85   92447.16
229 Saint John, NB                    4896.82   97343.98
131 Juneau, AK                        4819.35  102163.34
98 Fredericton, NB                   4768.78  106932.11
209 Prince Rupert, BC                 4454.24  111386.35
19 Bangor, ME                        4310.99  115697.34
88 Eureka, CA                        3836.77  119534.11
15 Augusta, ME                        3765.4  123299.51
291 Victoria, BC                      3718.78  127018.29
205 Portland, ME                      3689.93  130708.23
290 Vancouver, BC                      3676.9  134385.13
207 Portsmouth, NH                    3646.55  138031.68
87 Eugene, OR                        3615.89  141647.57
40 Brockton, MA                      3600.15  145247.72
235 Salem, OR                         3599.44  148847.16
34 Boston, MA                        3595.59  152442.75
25 Bellingham, WA                    3580.78  156023.53
91 Fall River, MA                    3580.15  159603.68
206 Portland, OR                      3569.55  163173.24
46 Cambridge, MA                     3569.83  166743.06
273 Tacoma, WA                        3563.14   170306.2
145 Lawrence, MA                      3557.08  173863.27
241 San Francisco, CA                 3557.84  177421.11
213 Quebec City, QC                      3607  181028.11
186 Oakland, CA                       3596.53  184624.65
155 Lowell, MA                        3536.42  188161.06
252 Seattle, WA                       3541.57  191702.63
210 Providence, RI                     3540.8  195243.43
26 Berkeley, CA                      3524.68  198768.11
254 Sherbrooke, QC                    3533.82  202301.93
245 Santa Cruz, CA                    3527.03  205828.96
159 Manchester, NH                    3519.15  209348.11
242 San Jose, CA                      3506.93  212855.04
67 Concord, NH                       3502.92  216357.96
225 Sacramento, CA                    3466.46  219824.42
307 Worcester, MA                     3442.81  223267.23
268 Stockton, CA                      3432.24  226699.47
284 Trois-Rivieres, QC                3408.48  230107.95
244 Santa Barbara, CA                 3351.71  233459.66
171 Montpelier, VT                     3326.1  236785.76
308 Yakima, WA                        3315.62  240101.39
263 Springfield, MA                   3325.25  243426.64
99 Fresno, CA                        3280.93  246707.57
38 Brattleboro, VT                   3289.35  249996.92
219 Reno, NV                          3273.12  253270.05
117 Hartford, CT                      3259.94  256529.98
48 Carson City, NV                   3258.04  259788.02
178 New Britain,CT                    3251.14  263039.16
17 Bakersfield, CA                   3224.24   266263.4
43 Burlington, VT                    3226.83  269490.23
153 Los Angeles, CA                   3193.66  272683.89
163 Meriden, CT                        3181.7  275865.59
309 Yellowknife, NT                   3219.55  279085.14
164 Miami, FL                         3467.82  282552.96
82 Edmonton, AB                      2998.65  285551.61
136 Key West, FL                      2971.34  288522.96
261 Spokane, WA                       2934.85  291457.81
179 New Haven, CT                     3105.67  294563.47
172 Montreal, QC                      3157.19  300883.76
240 San Diego, CA                     3118.23  304001.99
202 Pittsfield, MA                     3107.7  307109.68
293 Walla Walla, WA                   3125.94  310235.63
50 Central Islip, NY                 3140.18   313375.8
239 San Bernardino, CA                3080.98  316456.79
39 Bridgeport, CT                    3084.78  319541.56
33 Boise, ID                         2975.73  322517.29
266 Stamford, CT                      2952.96  325470.25
311 Yuma, AZ                           2896.3  328366.55
285 Troy, NY                          2911.41  331277.97
144 Las Vegas, NV                      2899.2  334177.16
3 Albany, NY                        2893.98  337071.14
45 Calgary, AB                       2842.81  339913.95
296 West Palm Beach, FL               2889.69  342803.64
146 Lethbridge, AB                    2761.59  345565.23
299 White Plains, NY                  2761.87   348327.1
44 Butte, MT                         2700.79  351027.89
181 New York, NY                      2687.05  353714.95
199 Phoenix, AZ                       2677.74  356392.69
203 Pocatello, ID                     2660.51  361766.36
128 Jersey City, NJ                    2655.1  364421.46
118 Helena, MT                         2653.8  367075.27
177 Newark, NJ                        2647.25  369722.51
93 Flagstaff, AZ                     2617.66  372340.18
194 Paterson, NJ                      2619.56  374959.74
187 Ogden, UT                         2611.96   377571.7
83 Elizabeth, NJ                     2609.47  380181.16
107 Great Falls, MT                   2605.85  382787.01
13 Atlantic City, NJ                  2609.3  385396.31
161 Medicine Hat, AB                  2613.82  388010.13
283 Trenton, NJ                        2576.5  390586.63
237 Salt Lake City, UT                2566.97  393153.59
289 Utica, NY                         2538.01   395691.6
286 Tucson, AZ                        2578.23  398269.82
191 Ottawa, ON                        2575.89  400845.71
211 Provo, UT                          2487.2  403332.91
27 Billings, MT                      2338.33  408192.87
302 Wilmington, DE                    2314.81  410507.68
247 Sarasota, FL                      2394.44  415219.54
59 Churchill, MB                     2311.24  417530.77
234 Saint Petersburg, FL              2279.52  419810.29
173 Moose Jaw, SK                     2220.06  422030.35
190 Orlando, FL                       2249.04  424279.39
218 Regina, SK                        2208.47  426487.86
72 Daytona Beach, FL                 2195.17  428683.03
255 Sheridan, WY                      2090.58  430773.61
6 Allentown, PA                     2193.28  432966.89
102 Gallup, NM                        2324.19  435291.08
29 Binghamtom, NY                    2312.96  437604.04
105 Grand Junction, CO                2264.43  439868.47
301 Wilkes-Barre, PA                  2262.27  442130.74
84 El Paso, TX                       2213.93  444344.68
137 Kingston, ON                      2237.67  446582.35
4 Albuquerque, NM                    2170.5  448752.84
272 Syracuse, NY                      2178.26   450931.1
246 Santa Fe, NM                      2120.23  453051.33
74 Denver, CO                        2008.13  457157.75
208 Portsmouth, VA                    1992.18  459149.93
57 Cheyenne, WY                      1993.01  461142.94
184 Norfolk, VA                       1993.78  463136.72
62 Colorado Springs, CO              1976.45  465113.17
129 Johnstown, PA                     1972.05  467085.22
212 Pueblo, CO                        1959.85  469045.06
141 Lancaster, PA                     1959.48  471004.54
216 Rapid City, SD                    1881.23  472885.77
303 Wilmington, NC                    1875.13   474760.9
167 Minot, ND                         1881.45  476642.36
275 Tampa, FL                         1912.76  478555.12
36 Brandon, MB                       1940.02  480495.14
101 Gainesville, FL                   1855.41  482350.55
31 Bismarck, ND                      1741.25   484091.8
18 Baltimore, MD                     1749.02  485840.82
143 Laredo, TX                        1779.18  487620.01
24 Belleville, ON                    1918.78  489538.79
156 Lubbock, TX                       1847.76  491386.54
116 Harrisburg, PA                    1786.27  493172.82
7 Amarillo, TX                      1758.66  494931.47
223 Rochester, NY                     1760.64  496692.12
68 Corpus Christi, TX                1730.17  498422.29
279 Timmins, ON                       1812.36  500234.65
238 San Antonio, TX                   1774.49  502009.14
185 North Bay, ON                     1763.41  503772.55
1 Abilene, TX                       1701.18  505473.73
197 Peterborough, ON                  1685.89  507159.62
16 Austin, TX                         1650.4  508810.02
269 Sudbury, ON                       1627.33  510437.35
121 Houston, TX                       1539.52  511976.87
41 Buffalo, NY                       1455.86  513432.73
77 Dodge City, KS                    1503.01  514935.73
294 Washington, DC                    1589.78  516525.51
200 Pierre, SD                        1654.69   518180.2
52 Charleston, SC                    1622.39  519802.59
305 Winnipeg, MB                      1683.01  521485.61
127 Jacksonville, FL                  1729.19   523214.8
92 Fargo, ND                          1549.3   524764.1
220 Richmond, VA                       1482.8  526246.89
292 Waco, TX                          1422.07  527668.96
282 Toronto, ON                       1483.47  529152.43
103 Galveston, TX                     1453.39  530605.82
182 Niagara Falls, ON                 1446.61  532052.43
97 Ft Worth, TX                      1451.32  533503.75
227 Saint Catherines, ON               1447.6  534951.35
69 Dallas, TX                        1414.51  536365.85
42 Burlington, ONT                   1383.45   537749.3
23 Beaumont, TX                      1349.56  539098.86
111 Guelph, ON                        1333.91  540432.77
204 Port Arthur, TX                   1334.48  541767.25
115 Hamilton, ON                      1332.86  543100.11
188 Oklahoma City, OK                 1326.53  544426.64
138 Kitchener, ON                     1306.17  545732.81
85 Enid, OK                          1303.54  547036.35
215 Raleigh, NC                       1330.04  548366.39
258 Sioux Falls, SD                   1358.74  549725.13
250 Savannah, GA                      1337.75  551062.88
228 Saint Cloud, MN                   1296.84  552359.72
274 Tallahassee, FL                   1248.21  553607.93
278 Thunder Bay, ON                   1287.21  554895.14
180 New Orleans, LA                   1273.63  556168.77
249 Sault Ste Marie, ON               1205.19  557373.95
20 Baton Rouge, LA                   1199.28  558573.23
37 Brantford, ON                     1151.44  559724.67
300 Wichita, KS                       1232.93   560957.6
80 Durham, NC                         1279.4  562237.01
236 Salina, KS                         1307.8  563544.81
109 Greensboro, NC                    1245.96  564790.77
257 Sioux City, IA                    1230.47  566021.24
64 Columbia, SC                      1213.26   567234.5
150 Lincoln, NE                       1177.83  568412.33
221 Roanoke, VA                       1181.08  569593.41
189 Omaha, NE                         1139.08  570732.49
306 Winston-Salem, NC                 1141.42  571873.91
79 Duluth, MN                        1103.04  572976.95
195 Pensacola, FL                     1179.96  574156.91
270 Superior, WI                      1175.77  575332.68
14 Augusta, GA                       1152.38  576485.06
166 Minneapolis, MN                    1113.9  577598.96
54 Charlotte, NC                     1091.15  578690.11
233 Saint Paul, MN                    1080.39   579770.5
157 Macon, GA                         1061.49  580831.99
81 Eau Claire, WI                     989.72  581821.71
28 Biloxi, MS                        1012.28  582833.99
152 London, ON                        1016.65  583850.64
160 Marshall, TX                      1158.56  585009.19
86 Erie, PA                          1188.43  586197.63
287 Tulsa, OK                          1174.1  587371.73
201 Pittsburgh, PA                    1144.29  588516.02
256 Shreveport, LA                    1096.48   589612.5
310 Youngstown, OH                    1081.83  590694.33
277 Texarkana, TX                     1066.85  591761.18
267 Steubenville, OH                  1043.65  592804.83
281 Topeka, KS                        1043.45  593848.28
297 Wheeling, WV                      1035.82   594884.1
95 Ft Smith, AR                       998.79  595882.89
47 Canton, OH                         974.26  596857.16
130 Joplin, MO                         943.13  597800.28
2 Akron, OH                          939.34  598739.63
176 Natchez, MS                        948.25  599687.88
22 Bay City, MI                       980.29  600668.17
112 Gulfport, MS                       982.13   601650.3
108 Green Bay, WI                      980.65  602630.95
168 Mobile, AL                         955.25   603586.2
222 Rochester, MN                      970.35  604556.55
260 Spartanburg, NC                    960.77  605517.32
231 Saint Joseph, MO                   952.41  606469.73
110 Greenville, SC                     925.02  607394.74
75 Des Moines, IA                     904.35   608299.1
10 Asheville, NC                      869.08  609168.17
133 Kansas City, KS                     868.8  610036.97
61 Cleveland, OH                      908.59  610945.56
134 Kansas City, MO                    905.45  611851.01
53 Charleston, WV                     895.98  612746.99
264 Springfield, MO                    809.82  613556.82
312 Zanesville, OH                     802.13  614358.95
151 Little Rock, AR                    795.57  615154.52
226 Saginaw, MI                        831.32  615985.85
126 Jackson, MS                         880.9  616866.74
94 Flint, MI                          865.78  617732.52
170 Montgomery, AL                     757.41  618489.93
295 Waterloo, IA                       814.78  619304.71
65 Columbus, GA                       859.51  620164.22
49 Cedar Rapids, IA                   804.18   620968.4
12 Atlanta, GA                        759.63  621728.03
78 Dubuque, IA                        744.13  622472.16
30 Birmingham, AL                     675.42  623147.57
253 Sheboygan, WI                      709.66  623857.24
56 Chattanooga, TN                    622.55  625831.25
124 Iowa City, IA                      627.43  626458.68
139 Knoxville, TN                      656.94  627115.61
63 Columbia, MO                       616.95  627732.56
304 Windsor, ON                         687.3  628419.86
162 Memphis, TN                        694.56  629114.42
76 Detroit, MI                        693.11  629807.53
122 Huntsville, AL                     579.38  630386.91
165 Milwaukee, WI                      581.29   630968.2
11 Ashland, KY                        481.46  631449.67
232 Saint Louis, MO                    522.45  631972.12
9 Ann Arbor, MI                      513.15  632485.27
142 Lansing, MI                        480.06  633457.13
175 Nashville, TN                      479.16  633936.29
106 Grand Rapids, MI                   475.97  634412.26
35 Bowling Green, KY                  416.17  634828.44
224 Rockford, IL                       408.25  635236.69
66 Columbus, OH                       450.37  635687.06
262 Springfield, IL                    459.25  636146.31
280 Toledo, OH                         439.91  636586.22
196 Peoria, IL                         422.25  637008.47
265 Springfield, OH                    402.89  637411.36
32 Bloomington, IL                    360.32  637771.68
147 Lexington, KY                      356.49  638128.18
214 Racine, WI                         399.12   638527.3
154 Louisville, KY                     339.14  638866.44
135 Kenosha, WI                         331.4  639197.84
60 Cincinnati, OH                     331.61  639529.45
73 Decatur, IL                        314.29  639843.75
125 Jackson, MI                        355.83  640199.57
89 Evansville, IN                     366.87  640566.45
21 Battle Creek, MI                   342.27  640908.71
51 Champaign, IL                       260.8  641169.51
149 Lima, OH                           289.17  641458.69
288 Urbana, IL                         286.77  641745.46
71 Dayton, OH                         278.52  642023.98
58 Chicago, IL                        279.24  642303.22
114 Hamilton, OH                       272.42  642575.65
104 Gary, IN                           244.97  642820.61
174 Muncie, IN                         166.43  642987.04
276 Terre Haute, IN                    148.82  643135.86
132 Kalamazoo, MI                      232.44  643368.29
123 Indianapolis, IN                   178.76  643547.05
259 South Bend, IN                     132.47  643679.52
140 Lafayette, IN                       97.61  643777.13
96 Ft Wayne, IN                       130.36  643907.49
183 Nome, AK                          5777.13  649684.62
5 -> 148              1  650614.77          5 Alert, NT
148 Lihue, HI                         7905.76    7905.76
230 Saint John's, NF                  7576.95   15482.71
183 Nome, AK                          7870.89   23353.61
271 Syndey, NS                        7389.33   30742.93
120 Honolulu, HI                      6973.04   37715.97
55 Charlottetown, PE                 6769.69   44485.66
119 Hilo, HI                          6665.33   51150.99
113 Halifax, NS                       6613.34   57764.33
8 Anchorage, AK                     6075.13   63839.46
243 San Juan, PR                       6499.8   70339.26
90 Fairbanks, AK                     6485.59   76824.85
169 Moncton, NB                        5881.4   82706.26
70 Dawson, YT                        5290.95   87997.21
229 Saint John, NB                    5223.21   93220.42
298 Whitehorse, YK                    4896.82   98117.25
98 Fredericton, NB                   4845.34  102962.58
131 Juneau, AK                        4768.78  107731.36
19 Bangor, ME                        4630.48  112361.84
209 Prince Rupert, BC                 4310.99  116672.83
15 Augusta, ME                       4247.95  120920.78
88 Eureka, CA                         3765.4  124686.18
205 Portland, ME                      3729.96  128416.14
291 Victoria, BC                      3689.93  132106.07
207 Portsmouth, NH                     3659.1  135765.18
290 Vancouver, BC                     3646.55  139411.72
40 Brockton, MA                      3637.58  143049.31
235 Salem, OR                         3599.44  146648.74
34 Boston, MA                        3595.59  150244.33
87 Eugene, OR                        3596.62  153840.95
46 Cambridge, MA                     3593.43  157434.38
25 Bellingham, WA                    3577.51  161011.89
91 Fall River, MA                    3580.15  164592.04
206 Portland, OR                      3569.55  168161.59
145 Lawrence, MA                      3564.53  171726.12
241 San Francisco, CA                 3557.84  175283.96
213 Quebec City, QC                      3607  178890.96
186 Oakland, CA                       3596.53  182487.49
155 Lowell, MA                        3536.42  186023.91
273 Tacoma, WA                           3547  189570.91
210 Providence, RI                    3545.83  193116.74
252 Seattle, WA                        3540.8  196657.53
159 Manchester, NH                    3529.69  200187.22
26 Berkeley, CA                      3528.99  203716.21
254 Sherbrooke, QC                    3533.82  207250.03
245 Santa Cruz, CA                    3527.03  210777.06
67 Concord, NH                       3515.24   214292.3
242 San Jose, CA                      3502.92  217795.22
307 Worcester, MA                     3477.76  221272.98
225 Sacramento, CA                    3442.81  224715.79
284 Trois-Rivieres, QC                 3415.3  228131.09
268 Stockton, CA                      3408.48  231539.57
171 Montpelier, VT                    3393.96  234933.53
244 Santa Barbara, CA                  3326.1  238259.63
38 Brattleboro, VT                   3308.76  241568.39
308 Yakima, WA                           3323   244891.4
263 Springfield, MA                   3325.25  248216.65
99 Fresno, CA                        3280.93  251497.58
117 Hartford, CT                      3271.83  254769.41
219 Reno, NV                          3259.94  258029.35
178 New Britain,CT                     3253.1  261282.44
48 Carson City, NV                   3251.14  264533.58
163 Meriden, CT                        3248.8  267782.39
17 Bakersfield, CA                   3221.22  271003.61
43 Burlington, VT                    3226.83  274230.43
153 Los Angeles, CA                   3193.66   277424.1
179 New Haven, CT                      3170.9  280594.99
309 Yellowknife, NT                   3219.25  283814.25
164 Miami, FL                         3467.82  287282.06
82 Edmonton, AB                      2998.65  290280.72
136 Key West, FL                      2971.34  293252.06
261 Spokane, WA                       2934.85  296186.91
50 Central Islip, NY                 3092.23  299279.15
293 Walla Walla, WA                   3140.18  302419.32
39 Bridgeport, CT                    3137.06  305556.38
172 Montreal, QC                      3157.19  311856.26
240 San Diego, CA                     3118.23  314974.49
202 Pittsfield, MA                     3107.7  318082.19
239 San Bernardino, CA                3097.28  321179.47
285 Troy, NY                          3070.66  324250.12
33 Boise, ID                         2937.92  327188.04
266 Stamford, CT                      2952.96     330141
311 Yuma, AZ                           2896.3   333037.3
3 Albany, NY                        2905.84  335943.14
144 Las Vegas, NV                     2893.98  338837.12
45 Calgary, AB                       2828.13  344548.48
296 West Palm Beach, FL               2889.69  347438.17
146 Lethbridge, AB                    2761.59  350199.76
299 White Plains, NY                  2761.87  352961.63
44 Butte, MT                         2700.79  355662.42
181 New York, NY                      2687.05  358349.48
199 Phoenix, AZ                       2677.74  361027.22
128 Jersey City, NJ                   2673.06  363700.28
203 Pocatello, ID                      2655.1  366355.39
177 Newark, NJ                        2648.53  369003.92
118 Helena, MT                        2647.25  371651.17
13 Atlantic City, NJ                 2646.38  374297.55
161 Medicine Hat, AB                  2613.82  376911.37
83 Elizabeth, NJ                     2604.16  379515.53
93 Flagstaff, AZ                     2614.32  382129.85
194 Paterson, NJ                      2619.56  384749.41
187 Ogden, UT                         2611.96  387361.37
283 Trenton, NJ                       2573.35  389934.72
107 Great Falls, MT                   2575.55  392510.27
237 Salt Lake City, UT                2538.22  397599.21
289 Utica, NY                         2538.01  400137.22
286 Tucson, AZ                        2578.23  402715.44
191 Ottawa, ON                        2575.89  405291.33
211 Provo, UT                          2487.2  407778.53
6 Allentown, PA                     2499.13  410277.67
102 Gallup, NM                        2324.19  412601.86
29 Binghamtom, NY                    2312.96  414914.82
27 Billings, MT                       2265.6  417180.42
302 Wilmington, DE                    2314.81  419495.23
247 Sarasota, FL                      2394.44  424207.09
59 Churchill, MB                     2311.24  426518.32
234 Saint Petersburg, FL              2279.52  428797.84
173 Moose Jaw, SK                     2220.06   431017.9
190 Orlando, FL                       2249.04  433266.94
218 Regina, SK                        2208.47  435475.41
72 Daytona Beach, FL                 2195.17  437670.58
255 Sheridan, WY                      2090.58  439761.16
184 Norfolk, VA                       2189.22  441950.37
105 Grand Junction, CO                2234.59  444184.96
301 Wilkes-Barre, PA                  2262.27  446447.23
84 El Paso, TX                       2213.93  448661.17
137 Kingston, ON                      2237.67  450898.84
4 Albuquerque, NM                    2170.5  453069.33
272 Syracuse, NY                      2178.26  455247.59
246 Santa Fe, NM                      2120.23  457367.82
74 Denver, CO                        2008.13  461474.24
208 Portsmouth, VA                    1992.18  463466.42
57 Cheyenne, WY                      1993.01  465459.43
141 Lancaster, PA                     1971.66  467431.09
62 Colorado Springs, CO              1972.03  469403.12
129 Johnstown, PA                     1972.05  471375.16
212 Pueblo, CO                        1959.85  473335.01
18 Baltimore, MD                     1935.73  475270.74
216 Rapid City, SD                    1868.74  477139.48
303 Wilmington, NC                    1875.13  479014.61
167 Minot, ND                         1881.45  480896.06
275 Tampa, FL                         1912.76  482808.82
36 Brandon, MB                       1940.02  484748.84
101 Gainesville, FL                   1855.41  486604.25
31 Bismarck, ND                      1741.25  488345.51
127 Jacksonville, FL                  1744.35  490089.86
305 Winnipeg, MB                      1729.19  491819.04
52 Charleston, SC                    1683.01  493502.06
200 Pierre, SD                        1622.39  495124.45
294 Washington, DC                    1654.69  496779.14
156 Lubbock, TX                       1753.74  498532.87
24 Belleville, ON                    1847.76  500380.63
143 Laredo, TX                        1918.78  502299.41
279 Timmins, ON                       1920.31  504219.72
68 Corpus Christi, TX                1812.36  506032.08
185 North Bay, ON                      1786.7  507818.78
238 San Antonio, TX                   1763.41  509582.19
223 Rochester, NY                     1726.57  511308.76
7 Amarillo, TX                      1760.64   513069.4
116 Harrisburg, PA                    1758.66  514828.06
1 Abilene, TX                       1668.54   516496.6
197 Peterborough, ON                  1685.89  518182.49
16 Austin, TX                         1650.4  519832.89
269 Sudbury, ON                       1627.33  521460.22
121 Houston, TX                       1539.52  522999.73
41 Buffalo, NY                       1455.86   524455.6
77 Dodge City, KS                    1503.01   525958.6
220 Richmond, VA                      1558.59  527517.19
92 Fargo, ND                          1482.8  528999.99
250 Savannah, GA                      1489.99  530489.98
258 Sioux Falls, SD                   1337.75  531827.74
215 Raleigh, NC                       1358.74  533186.48
85 Enid, OK                          1330.04  534516.52
182 Niagara Falls, ON                 1380.26  535896.78
292 Waco, TX                          1483.03  537379.81
282 Toronto, ON                       1483.47  538863.29
97 Ft Worth, TX                       1449.6  540312.89
227 Saint Catherines, ON               1447.6  541760.49
103 Galveston, TX                     1444.61  543205.09
42 Burlington, ONT                   1421.73  544626.82
69 Dallas, TX                        1383.45  546010.27
115 Hamilton, ON                      1368.85  547379.12
23 Beaumont, TX                      1332.75  548711.87
111 Guelph, ON                        1333.91  550045.78
204 Port Arthur, TX                   1334.48  551380.26
138 Kitchener, ON                     1324.27  552704.53
188 Oklahoma City, OK                 1306.17   554010.7
37 Brantford, ON                     1299.42  555310.11
300 Wichita, KS                       1232.93  556543.05
80 Durham, NC                         1279.4  557822.45
236 Salina, KS                         1307.8  559130.25
109 Greensboro, NC                    1245.96  560376.22
257 Sioux City, IA                    1230.47  561606.68
64 Columbia, SC                      1213.26  562819.94
228 Saint Cloud, MN                   1208.61  564028.55
274 Tallahassee, FL                   1248.21  565276.76
278 Thunder Bay, ON                   1287.21  566563.97
180 New Orleans, LA                   1273.63   567837.6
249 Sault Ste Marie, ON               1205.19  569042.78
20 Baton Rouge, LA                   1199.28  570242.06
79 Duluth, MN                        1130.44  571372.51
195 Pensacola, FL                     1179.96  572552.47
270 Superior, WI                      1175.77  573728.24
14 Augusta, GA                       1152.38  574880.62
150 Lincoln, NE                       1134.43  576015.05
221 Roanoke, VA                       1181.08  577196.13
189 Omaha, NE                         1139.08  578335.21
306 Winston-Salem, NC                 1141.42  579476.63
166 Minneapolis, MN                   1088.91  580565.53
54 Charlotte, NC                     1091.15  581656.69
233 Saint Paul, MN                    1080.39  582737.08
157 Macon, GA                         1061.49  583798.57
81 Eau Claire, WI                     989.72  584788.28
28 Biloxi, MS                        1012.28  585800.56
152 London, ON                        1016.65  586817.21
160 Marshall, TX                      1158.56  587975.77
86 Erie, PA                          1188.43   589164.2
287 Tulsa, OK                          1174.1   590338.3
201 Pittsburgh, PA                    1144.29  591482.59
256 Shreveport, LA                    1096.48  592579.08
310 Youngstown, OH                    1081.83   593660.9
277 Texarkana, TX                     1066.85  594727.75
267 Steubenville, OH                  1043.65  595771.41
281 Topeka, KS                        1043.45  596814.86
297 Wheeling, WV                      1035.82  597850.68
95 Ft Smith, AR                       998.79  598849.47
61 Cleveland, OH                      974.07  599823.54
176 Natchez, MS                        959.96  600783.49
22 Bay City, MI                       980.29  601763.78
112 Gulfport, MS                       982.13  602745.92
108 Green Bay, WI                      980.65  603726.56
168 Mobile, AL                         955.25  604681.82
222 Rochester, MN                      970.35  605652.16
260 Spartanburg, NC                    960.77  606612.93
231 Saint Joseph, MO                   952.41  607565.34
47 Canton, OH                         933.28  608498.62
130 Joplin, MO                         943.13  609441.75
2 Akron, OH                          939.34  610381.09
133 Kansas City, KS                    915.85  611296.95
53 Charleston, WV                     899.41  612196.36
134 Kansas City, MO                    895.98  613092.34
110 Greenville, SC                     891.55  613983.89
75 Des Moines, IA                     904.35  614888.24
10 Asheville, NC                      869.08  615757.32
295 Waterloo, IA                       827.18   616584.5
65 Columbus, GA                       859.51  617444.01
253 Sheboygan, WI                      802.52  618246.52
126 Jackson, MS                         809.5  619056.02
226 Saginaw, MI                         880.9  619936.92
151 Little Rock, AR                    831.32  620768.24
304 Windsor, ON                        828.42  621596.66
264 Springfield, MO                    795.38  622392.04
312 Zanesville, OH                     802.13  623194.17
63 Columbia, MO                       716.36  623910.53
76 Detroit, MI                        682.93  624593.46
170 Montgomery, AL                      724.3  625317.77
12 Atlanta, GA                        731.47  626819.41
49 Cedar Rapids, IA                   759.63  627579.04
78 Dubuque, IA                        668.88   628923.8
30 Birmingham, AL                     675.42  629599.22
94 Flint, MI                          690.27  630289.49
162 Memphis, TN                        698.85  630988.33
142 Lansing, MI                           647  631635.33
122 Huntsville, AL                     570.44  632205.78
124 Iowa City, IA                      588.25  632794.02
139 Knoxville, TN                      656.94  633450.96
224 Rockford, IL                       563.81  634014.76
56 Chattanooga, TN                    563.58  634578.34
165 Milwaukee, WI                      580.71  635159.05
11 Ashland, KY                        481.46  635640.51
232 Saint Louis, MO                    522.45  636162.96
9 Ann Arbor, MI                      513.15  636676.12
125 Jackson, MI                        459.78   637627.7
175 Nashville, TN                      451.22  638078.92
106 Grand Rapids, MI                   475.97  638554.89
35 Bowling Green, KY                  416.17  638971.07
214 Racine, WI                         406.98  639378.05
147 Lexington, KY                      399.12  639777.17
196 Peoria, IL                         399.56  640176.73
66 Columbus, OH                       458.14  640634.87
262 Springfield, IL                    459.25  641094.13
280 Toledo, OH                         439.91  641534.03
73 Decatur, IL                        393.78  641927.82
265 Springfield, OH                    355.61  642283.42
32 Bloomington, IL                    360.32  642643.75
149 Lima, OH                           338.23  642981.98
89 Evansville, IN                     305.64  643287.62
21 Battle Creek, MI                   342.27  643629.89
154 Louisville, KY                     283.85  643913.74
135 Kenosha, WI                         331.4  644245.14
60 Cincinnati, OH                     331.61  644576.75
58 Chicago, IL                         288.4  644865.14
71 Dayton, OH                         279.24  645144.39
51 Champaign, IL                      281.04  645425.42
114 Hamilton, OH                       259.18   645684.6
288 Urbana, IL                         256.66  645941.26
132 Kalamazoo, MI                      235.55  646176.81
276 Terre Haute, IN                    232.44  646409.25
96 Ft Wayne, IN                        195.3  646604.55
104 Gary, IN                           156.52  646761.07
174 Muncie, IN                         166.43   646927.5
259 South Bend, IN                     118.99  647046.49
123 Indianapolis, IN                   132.47  647178.96
140 Lafayette, IN                       66.81  647245.77
148 Lihue, HI                         7905.76    7905.76
230 Saint John's, NF                  7576.95   15482.71
183 Nome, AK                          7870.89   23353.61
271 Syndey, NS                        7389.33   30742.93
120 Honolulu, HI                      6973.04   37715.97
55 Charlottetown, PE                 6769.69   44485.66
119 Hilo, HI                          6665.33   51150.99
113 Halifax, NS                       6613.34   57764.33
8 Anchorage, AK                     6075.13   63839.46
243 San Juan, PR                       6499.8   70339.26
90 Fairbanks, AK                     6485.59   76824.85
169 Moncton, NB                        5881.4   82706.26
70 Dawson, YT                        5290.95   87997.21
229 Saint John, NB                    5223.21   93220.42
298 Whitehorse, YK                    4896.82   98117.25
98 Fredericton, NB                   4845.34  102962.58
131 Juneau, AK                        4768.78  107731.36
19 Bangor, ME                        4630.48  112361.84
209 Prince Rupert, BC                 4310.99  116672.83
15 Augusta, ME                       4247.95  120920.78
88 Eureka, CA                         3765.4  124686.18
205 Portland, ME                      3729.96  128416.14
291 Victoria, BC                      3689.93  132106.07
207 Portsmouth, NH                     3659.1  135765.18
290 Vancouver, BC                     3646.55  139411.72
40 Brockton, MA                      3637.58  143049.31
235 Salem, OR                         3599.44  146648.74
34 Boston, MA                        3595.59  150244.33
87 Eugene, OR                        3596.62  153840.95
46 Cambridge, MA                     3593.43  157434.38
25 Bellingham, WA                    3577.51  161011.89
91 Fall River, MA                    3580.15  164592.04
206 Portland, OR                      3569.55  168161.59
145 Lawrence, MA                      3564.53  171726.12
241 San Francisco, CA                 3557.84  175283.96
213 Quebec City, QC                      3607  178890.96
186 Oakland, CA                       3596.53  182487.49
155 Lowell, MA                        3536.42  186023.91
273 Tacoma, WA                           3547  189570.91
210 Providence, RI                    3545.83  193116.74
252 Seattle, WA                        3540.8  196657.53
159 Manchester, NH                    3529.69  200187.22
26 Berkeley, CA                      3528.99  203716.21
254 Sherbrooke, QC                    3533.82  207250.03
245 Santa Cruz, CA                    3527.03  210777.06
67 Concord, NH                       3515.24   214292.3
242 San Jose, CA                      3502.92  217795.22
307 Worcester, MA                     3477.76  221272.98
225 Sacramento, CA                    3442.81  224715.79
284 Trois-Rivieres, QC                 3415.3  228131.09
268 Stockton, CA                      3408.48  231539.57
171 Montpelier, VT                    3393.96  234933.53
244 Santa Barbara, CA                  3326.1  238259.63
38 Brattleboro, VT                   3308.76  241568.39
308 Yakima, WA                           3323   244891.4
263 Springfield, MA                   3325.25  248216.65
99 Fresno, CA                        3280.93  251497.58
117 Hartford, CT                      3271.83  254769.41
219 Reno, NV                          3259.94  258029.35
178 New Britain,CT                     3253.1  261282.44
48 Carson City, NV                   3251.14  264533.58
163 Meriden, CT                        3248.8  267782.39
17 Bakersfield, CA                   3221.22  271003.61
43 Burlington, VT                    3226.83  274230.43
153 Los Angeles, CA                   3193.66   277424.1
179 New Haven, CT                      3170.9  280594.99
309 Yellowknife, NT                   3219.25  283814.25
164 Miami, FL                         3467.82  287282.06
82 Edmonton, AB                      2998.65  290280.72
136 Key West, FL                      2971.34  293252.06
261 Spokane, WA                       2934.85  296186.91
50 Central Islip, NY                 3092.23  299279.15
293 Walla Walla, WA                   3140.18  302419.32
39 Bridgeport, CT                    3137.06  305556.38
172 Montreal, QC                      3157.19  311856.26
240 San Diego, CA                     3118.23  314974.49
202 Pittsfield, MA                     3107.7  318082.19
239 San Bernardino, CA                3097.28  321179.47
285 Troy, NY                          3070.66  324250.12
33 Boise, ID                         2937.92  327188.04
266 Stamford, CT                      2952.96     330141
311 Yuma, AZ                           2896.3   333037.3
3 Albany, NY                        2905.84  335943.14
144 Las Vegas, NV                     2893.98  338837.12
45 Calgary, AB                       2828.13  344548.48
296 West Palm Beach, FL               2889.69  347438.17
146 Lethbridge, AB                    2761.59  350199.76
299 White Plains, NY                  2761.87  352961.63
44 Butte, MT                         2700.79  355662.42
181 New York, NY                      2687.05  358349.48
199 Phoenix, AZ                       2677.74  361027.22
128 Jersey City, NJ                   2673.06  363700.28
203 Pocatello, ID                      2655.1  366355.39
177 Newark, NJ                        2648.53  369003.92
118 Helena, MT                        2647.25  371651.17
13 Atlantic City, NJ                 2646.38  374297.55
161 Medicine Hat, AB                  2613.82  376911.37
83 Elizabeth, NJ                     2604.16  379515.53
93 Flagstaff, AZ                     2614.32  382129.85
194 Paterson, NJ                      2619.56  384749.41
187 Ogden, UT                         2611.96  387361.37
283 Trenton, NJ                       2573.35  389934.72
107 Great Falls, MT                   2575.55  392510.27
237 Salt Lake City, UT                2538.22  397599.21
289 Utica, NY                         2538.01  400137.22
286 Tucson, AZ                        2578.23  402715.44
191 Ottawa, ON                        2575.89  405291.33
211 Provo, UT                          2487.2  407778.53
6 Allentown, PA                     2499.13  410277.67
102 Gallup, NM                        2324.19  412601.86
29 Binghamtom, NY                    2312.96  414914.82
27 Billings, MT                       2265.6  417180.42
302 Wilmington, DE                    2314.81  419495.23
247 Sarasota, FL                      2394.44  424207.09
59 Churchill, MB                     2311.24  426518.32
234 Saint Petersburg, FL              2279.52  428797.84
173 Moose Jaw, SK                     2220.06   431017.9
190 Orlando, FL                       2249.04  433266.94
218 Regina, SK                        2208.47  435475.41
72 Daytona Beach, FL                 2195.17  437670.58
255 Sheridan, WY                      2090.58  439761.16
184 Norfolk, VA                       2189.22  441950.37
105 Grand Junction, CO                2234.59  444184.96
301 Wilkes-Barre, PA                  2262.27  446447.23
84 El Paso, TX                       2213.93  448661.17
137 Kingston, ON                      2237.67  450898.84
4 Albuquerque, NM                    2170.5  453069.33
272 Syracuse, NY                      2178.26  455247.59
246 Santa Fe, NM                      2120.23  457367.82
74 Denver, CO                        2008.13  461474.24
208 Portsmouth, VA                    1992.18  463466.42
57 Cheyenne, WY                      1993.01  465459.43
141 Lancaster, PA                     1971.66  467431.09
62 Colorado Springs, CO              1972.03  469403.12
129 Johnstown, PA                     1972.05  471375.16
212 Pueblo, CO                        1959.85  473335.01
18 Baltimore, MD                     1935.73  475270.74
216 Rapid City, SD                    1868.74  477139.48
303 Wilmington, NC                    1875.13  479014.61
167 Minot, ND                         1881.45  480896.06
275 Tampa, FL                         1912.76  482808.82
36 Brandon, MB                       1940.02  484748.84
101 Gainesville, FL                   1855.41  486604.25
31 Bismarck, ND                      1741.25  488345.51
127 Jacksonville, FL                  1744.35  490089.86
305 Winnipeg, MB                      1729.19  491819.04
52 Charleston, SC                    1683.01  493502.06
200 Pierre, SD                        1622.39  495124.45
294 Washington, DC                    1654.69  496779.14
156 Lubbock, TX                       1753.74  498532.87
24 Belleville, ON                    1847.76  500380.63
143 Laredo, TX                        1918.78  502299.41
279 Timmins, ON                       1920.31  504219.72
68 Corpus Christi, TX                1812.36  506032.08
185 North Bay, ON                      1786.7  507818.78
238 San Antonio, TX                   1763.41  509582.19
223 Rochester, NY                     1726.57  511308.76
7 Amarillo, TX                      1760.64   513069.4
116 Harrisburg, PA                    1758.66  514828.06
1 Abilene, TX                       1668.54   516496.6
197 Peterborough, ON                  1685.89  518182.49
16 Austin, TX                         1650.4  519832.89
269 Sudbury, ON                       1627.33  521460.22
121 Houston, TX                       1539.52  522999.73
41 Buffalo, NY                       1455.86   524455.6
77 Dodge City, KS                    1503.01   525958.6
220 Richmond, VA                      1558.59  527517.19
92 Fargo, ND                          1482.8  528999.99
250 Savannah, GA                      1489.99  530489.98
258 Sioux Falls, SD                   1337.75  531827.74
215 Raleigh, NC                       1358.74  533186.48
85 Enid, OK                          1330.04  534516.52
182 Niagara Falls, ON                 1380.26  535896.78
292 Waco, TX                          1483.03  537379.81
282 Toronto, ON                       1483.47  538863.29
97 Ft Worth, TX                       1449.6  540312.89
227 Saint Catherines, ON               1447.6  541760.49
103 Galveston, TX                     1444.61  543205.09
42 Burlington, ONT                   1421.73  544626.82
69 Dallas, TX                        1383.45  546010.27
115 Hamilton, ON                      1368.85  547379.12
23 Beaumont, TX                      1332.75  548711.87
111 Guelph, ON                        1333.91  550045.78
204 Port Arthur, TX                   1334.48  551380.26
138 Kitchener, ON                     1324.27  552704.53
188 Oklahoma City, OK                 1306.17   554010.7
37 Brantford, ON                     1299.42  555310.11
300 Wichita, KS                       1232.93  556543.05
80 Durham, NC                         1279.4  557822.45
236 Salina, KS                         1307.8  559130.25
109 Greensboro, NC                    1245.96  560376.22
257 Sioux City, IA                    1230.47  561606.68
64 Columbia, SC                      1213.26  562819.94
228 Saint Cloud, MN                   1208.61  564028.55
274 Tallahassee, FL                   1248.21  565276.76
278 Thunder Bay, ON                   1287.21  566563.97
180 New Orleans, LA                   1273.63   567837.6
249 Sault Ste Marie, ON               1205.19  569042.78
20 Baton Rouge, LA                   1199.28  570242.06
79 Duluth, MN                        1130.44  571372.51
195 Pensacola, FL                     1179.96  572552.47
270 Superior, WI                      1175.77  573728.24
14 Augusta, GA                       1152.38  574880.62
150 Lincoln, NE                       1134.43  576015.05
221 Roanoke, VA                       1181.08  577196.13
189 Omaha, NE                         1139.08  578335.21
306 Winston-Salem, NC                 1141.42  579476.63
166 Minneapolis, MN                   1088.91  580565.53
54 Charlotte, NC                     1091.15  581656.69
233 Saint Paul, MN                    1080.39  582737.08
157 Macon, GA                         1061.49  583798.57
81 Eau Claire, WI                     989.72  584788.28
28 Biloxi, MS                        1012.28  585800.56
152 London, ON                        1016.65  586817.21
160 Marshall, TX                      1158.56  587975.77
86 Erie, PA                          1188.43   589164.2
287 Tulsa, OK                          1174.1   590338.3
201 Pittsburgh, PA                    1144.29  591482.59
256 Shreveport, LA                    1096.48  592579.08
310 Youngstown, OH                    1081.83   593660.9
277 Texarkana, TX                     1066.85  594727.75
267 Steubenville, OH                  1043.65  595771.41
281 Topeka, KS                        1043.45  596814.86
297 Wheeling, WV                      1035.82  597850.68
95 Ft Smith, AR                       998.79  598849.47
61 Cleveland, OH                      974.07  599823.54
176 Natchez, MS                        959.96  600783.49
22 Bay City, MI                       980.29  601763.78
112 Gulfport, MS                       982.13  602745.92
108 Green Bay, WI                      980.65  603726.56
168 Mobile, AL                         955.25  604681.82
222 Rochester, MN                      970.35  605652.16
260 Spartanburg, NC                    960.77  606612.93
231 Saint Joseph, MO                   952.41  607565.34
47 Canton, OH                         933.28  608498.62
130 Joplin, MO                         943.13  609441.75
2 Akron, OH                          939.34  610381.09
133 Kansas City, KS                    915.85  611296.95
53 Charleston, WV                     899.41  612196.36
134 Kansas City, MO                    895.98  613092.34
110 Greenville, SC                     891.55  613983.89
75 Des Moines, IA                     904.35  614888.24
10 Asheville, NC                      869.08  615757.32
295 Waterloo, IA                       827.18   616584.5
65 Columbus, GA                       859.51  617444.01
253 Sheboygan, WI                      802.52  618246.52
126 Jackson, MS                         809.5  619056.02
226 Saginaw, MI                         880.9  619936.92
151 Little Rock, AR                    831.32  620768.24
304 Windsor, ON                        828.42  621596.66
264 Springfield, MO                    795.38  622392.04
312 Zanesville, OH                     802.13  623194.17
63 Columbia, MO                       716.36  623910.53
76 Detroit, MI                        682.93  624593.46
170 Montgomery, AL                      724.3  625317.77
12 Atlanta, GA                        731.47  626819.41
49 Cedar Rapids, IA                   759.63  627579.04
78 Dubuque, IA                        668.88   628923.8
30 Birmingham, AL                     675.42  629599.22
94 Flint, MI                          690.27  630289.49
162 Memphis, TN                        698.85  630988.33
142 Lansing, MI                           647  631635.33
122 Huntsville, AL                     570.44  632205.78
124 Iowa City, IA                      588.25  632794.02
139 Knoxville, TN                      656.94  633450.96
224 Rockford, IL                       563.81  634014.76
56 Chattanooga, TN                    563.58  634578.34
165 Milwaukee, WI                      580.71  635159.05
11 Ashland, KY                        481.46  635640.51
232 Saint Louis, MO                    522.45  636162.96
9 Ann Arbor, MI                      513.15  636676.12
125 Jackson, MI                        459.78   637627.7
175 Nashville, TN                      451.22  638078.92
106 Grand Rapids, MI                   475.97  638554.89
35 Bowling Green, KY                  416.17  638971.07
214 Racine, WI                         406.98  639378.05
147 Lexington, KY                      399.12  639777.17
196 Peoria, IL                         399.56  640176.73
66 Columbus, OH                       458.14  640634.87
262 Springfield, IL                    459.25  641094.13
280 Toledo, OH                         439.91  641534.03
73 Decatur, IL                        393.78  641927.82
265 Springfield, OH                    355.61  642283.42
32 Bloomington, IL                    360.32  642643.75
149 Lima, OH                           338.23  642981.98
89 Evansville, IN                     305.64  643287.62
21 Battle Creek, MI                   342.27  643629.89
154 Louisville, KY                     283.85  643913.74
135 Kenosha, WI                         331.4  644245.14
60 Cincinnati, OH                     331.61  644576.75
58 Chicago, IL                         288.4  644865.14
71 Dayton, OH                         279.24  645144.39
288 Urbana, IL                         278.52  645422.91
114 Hamilton, OH                       256.66  645679.57
51 Champaign, IL                      259.18  645938.75
132 Kalamazoo, MI                      237.21  646175.96
276 Terre Haute, IN                    232.44   646408.4
96 Ft Wayne, IN                        195.3   646603.7
104 Gary, IN                           156.52  646760.22
174 Muncie, IN                         166.43  646926.65
259 South Bend, IN                     118.99  647045.64
123 Indianapolis, IN                   132.47  647178.11
140 Lafayette, IN                       66.81  647244.92

1252 rows selected.

Elapsed: 00:01:24.28
```

Summary of Results

Here we list the best solutions found for each root leg, i.e. those with PATH_RNK = 1. We do not know what the optima are.

```Shortest Routes
===============
ROOT_LEG       TOT_DIST
------------ ----------
184 -> 208     55744.86
51 -> 288      56794.75

Longest Routes
==============
ROOT_LEG       TOT_DIST
------------ ----------
183 -> 230    649684.75
5 -> 148      650614.77
```

Execution Plan for Shortest Routes, USCA312

```----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                             | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                      |             |      1 |        |   1252 |00:01:24.88 |      10M|    113K|  10654 |       |       |          |
|   1 |  WINDOW SORT                                          |             |      1 |     10 |   1252 |00:01:24.88 |      10M|    113K|  10654 |   267K|   267K|  237K (0)|
|   2 |   NESTED LOOPS OUTER                                  |             |      1 |     10 |   1252 |00:01:24.88 |      10M|    113K|  10654 |       |       |          |
|   3 |    MERGE JOIN                                         |             |      1 |     10 |   1252 |00:01:24.87 |      10M|    113K|  10654 |       |       |          |
|   4 |     TABLE ACCESS BY INDEX ROWID                       | TOWNS       |      1 |      5 |    312 |00:00:00.01 |     205 |      0 |      0 |       |       |          |
|   5 |      INDEX FULL SCAN                                  | SYS_C008004 |      1 |      5 |    312 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|*  6 |     SORT JOIN                                         |             |    312 |     10 |   1252 |00:01:24.87 |      10M|    113K|  10654 |   133K|   133K|  118K (0)|
|   7 |      VIEW                                             |             |      1 |     10 |   1252 |00:01:24.86 |      10M|    113K|  10654 |       |       |          |
|   8 |       WINDOW SORT                                     |             |      1 |     10 |   1252 |00:01:24.86 |      10M|    113K|  10654 |  1895K|   658K| 1684K (0)|
|   9 |        MERGE JOIN CARTESIAN                           |             |      1 |     10 |   1252 |00:01:24.85 |      10M|    113K|  10654 |       |       |          |
|  10 |         VIEW                                          |             |      1 |      1 |    313 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  11 |          CONNECT BY WITHOUT FILTERING                 |             |      1 |        |    313 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  12 |           VIEW                                        |             |      1 |      1 |      1 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  13 |            SORT AGGREGATE                             |             |      1 |      1 |      1 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  14 |             INDEX FULL SCAN                           | SYS_C008004 |      1 |      5 |    312 |00:00:00.01 |       1 |      0 |      0 |       |       |          |
|  15 |         BUFFER SORT                                   |             |    313 |     10 |   1252 |00:01:24.85 |      10M|    113K|  10654 |  9216 |  9216 | 8192  (0)|
|* 16 |          VIEW                                         |             |      1 |     10 |      4 |00:01:24.85 |      10M|    113K|  10654 |       |       |          |
|* 17 |           WINDOW SORT PUSHED RANK                     |             |      1 |     10 |      4 |00:01:24.85 |      10M|    113K|  10654 |  9216 |  9216 | 8192  (0)|
|  18 |            NESTED LOOPS                               |             |      1 |        |      4 |00:00:34.80 |      10M|    113K|  10654 |       |       |          |
|  19 |             NESTED LOOPS                              |             |      1 |     10 |      4 |00:00:34.80 |      10M|    113K|  10654 |       |       |          |
|* 20 |              VIEW                                     |             |      1 |     12 |      4 |00:00:34.80 |      10M|    113K|  10654 |       |       |          |
|  21 |               UNION ALL (RECURSIVE WITH) BREADTH FIRST|             |      1 |        |    192K|00:00:04.53 |      10M|    113K|  10654 |       |       |          |
|* 22 |                VIEW                                   |             |      1 |     10 |      2 |00:00:00.10 |     693 |      1 |      0 |       |       |          |
|* 23 |                 WINDOW SORT PUSHED RANK               |             |      1 |     10 |      3 |00:00:00.10 |     693 |      1 |      0 |  2048 |  2048 | 2048  (0)|
|  24 |                  WINDOW BUFFER                        |             |      1 |     10 |  48516 |00:00:00.08 |     693 |      1 |      0 |  3100K|   779K| 2755K (0)|
|  25 |                   TABLE ACCESS BY INDEX ROWID         | DISTANCES   |      1 |     10 |  48516 |00:00:00.04 |     693 |      1 |      0 |       |       |          |
|* 26 |                    INDEX FULL SCAN                    | DISTANCE_PK |      1 |     10 |  48516 |00:00:00.02 |     212 |      0 |      0 |       |       |          |
|  27 |                WINDOW SORT                            |             |    311 |      2 |    192K|00:00:04.40 |    5664 |      1 |      0 |   619K|   472K|  550K (0)|
|  28 |                 NESTED LOOPS                          |             |    311 |        |    192K|00:00:05.39 |    5664 |      1 |      0 |       |       |          |
|  29 |                  NESTED LOOPS                         |             |    311 |      2 |    192K|00:00:05.08 |    2392 |      0 |      0 |       |       |          |
|  30 |                   RECURSIVE WITH PUMP                 |             |    311 |        |   1242 |00:00:00.01 |       0 |      0 |      0 |       |       |          |
|* 31 |                   INDEX RANGE SCAN                    | DISTANCE_PK |   1242 |      1 |    192K|00:00:06.49 |    2392 |      0 |      0 |       |       |          |
|  32 |                  TABLE ACCESS BY INDEX ROWID          | DISTANCES   |    192K|      1 |    192K|00:00:00.15 |    3272 |      1 |      0 |       |       |          |
|* 33 |              INDEX UNIQUE SCAN                        | DISTANCE_PK |      4 |      1 |      4 |00:00:00.01 |       6 |      0 |      0 |       |       |          |
|  34 |             TABLE ACCESS BY INDEX ROWID               | DISTANCES   |      4 |      1 |      4 |00:00:00.01 |       4 |      0 |      0 |       |       |          |
|  35 |    TABLE ACCESS BY INDEX ROWID                        | DISTANCES   |   1252 |      1 |   1248 |00:00:00.01 |    2498 |      0 |      0 |       |       |          |
|* 36 |     INDEX UNIQUE SCAN                                 | DISTANCE_PK |   1252 |      1 |   1248 |00:00:00.01 |    1250 |      0 |      0 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - access("TWN"."ID"="TOP"."TOWN_ID")
filter("TWN"."ID"="TOP"."TOWN_ID")
16 - filter("CIRCUITS"."PATH_RNK"<=:KEEP_NUM)
17 - filter(ROW_NUMBER() OVER ( PARTITION BY "R"."ROOT_LEG" ORDER BY :SIGN*("R"."TOT_PRICE"+"D"."DST"))<=:KEEP_NUM)
20 - filter(("R"."LEV"="R"."N_TOWNS" AND "R"."PATH_RNK"<=:KEEP_NUM))
22 - filter("D"."RNK_BY_DST"<=:KEEP_NUM_ROOT)
23 - filter(ROW_NUMBER() OVER ( ORDER BY :SIGN*"DST")<=:KEEP_NUM_ROOT) 26 - filter("B">"A")
31 - access("D"."A"="R"."NXT_ID")
33 - access("D"."A"="R"."NXT_ID" AND "D"."B"="R"."ROOT")
36 - access("DST"."A"="TOP"."TOWN_ID_PRIOR" AND "DST"."B"="TOP"."TOWN_ID")
```

Notes

• In the closed version of TSP that we have considered here, the starting point does not essentially affect the set of possible solutions; however, it may affect the solutions actually obtained by an approximate algorithm; partitioning by a set of root legs is designed to improve the quality of solutions obtained
• I found that the query uses increasing amounts of temp tablespace as the keep values are increased for the larger problem; in my previous article, on a knapsack problem, I included a PL/SQL solution that had the flexibility to discard all but the best solutions as it went, thus keeping space usage to a minimum; it also had the flexibility of the conventional 'branch and bound' algorithms to retain the best solution values globally and close off paths that could not beat them. This is difficult to do in SQL, but against that, the SQL is much simpler

Conclusions

Following my earlier two articles on related subjects, we have again seen that the recursive capabilities of Oracle's SQL from v11.2 can provide surprisingly simple approximate solutions to 'hard' combinatorial problems in reasonable execution times.

20 February 2016: Added attachment with the input data files, DDL and raw results:

TSP Files: input data files, DDL and raw results

# SQL for the Fantasy Football Knapsack Problem

Someone (FilippeSoaresRoza) asked a question 21 June 2013 on OTN about finding the best fantasy football team in SQL, Processing Cost - How to catch a soccer team with the highest combined score?. I saw that this was another knapsack problem, of the single container type. I had solved that problem on the forum before, and here, A Simple SQL Solution for the Knapsack Problem (SKP-1), so I decided to adapt the solution for this case. This is in fact a more general form of the problem, wherein the items now have categories, with constraints on the numbers in each category, and on the overall number of items. The first solution I posted provided an exact solution, as in the above article, and performed well enough on the simple sample data, returning in a few seconds. However, the poster reported that the query was still running on his full data set after a couple of hours. I therefore decided to look for a mechanism to reduce the work done by the query on what is a hard combinatorial problem, and to return 'good' solutions in a practical amount of time, but without guaranteeing optimality (I recently provided solutions like this for a related problem, SQL for the Balanced Number Partitioning Problem).

This article provides the SQL that does this, and also a PL/SQL package containing a pipelined function that applies a slightly different algorithm; the latter is also practical, although it proved less efficient on my test problems.

Test Problems

I used two test problems.

Test Problem 1: Brazilian League
The first problem was supplied by the OTN poster and appears to be based on a Brazilian league. It has 114 players, in seven positions (one being coach), with twelve players forming a team. The problem is to find the team with maximum total player points within a given maximum price, and matching the positional constraints:

```Input positions

ID MIN_PLAYERS MAX_PLAYERS
-- ----------- -----------
AL          12          12
CB           2           3
CO           1           1
FW           1           3
GK           1           1
MF           3           5
WB           0           2

6 rows selected.

Input players

ID  CLUB_NAME                      PLAYER_NAME                    PO      PRICE AVG_POINTS APPEARANCES      PRF_R      VFP_R      PRC_R
--- ------------------------------ ------------------------------ -- ---------- ---------- ----------- ---------- ---------- ----------
038 Portuguesa                     Ivan                           WB        755       1320         100          1          2         20
001 Atlético-PR                    Éderson                        FW       1712       1012         500          2         22         97
002 Vitória                        Maxi Biancucchi                FW       1962       1005         400          3         33        103
003 Fluminense                     Rafael Sobis                   FW       2303        955         400          4         47        112
098 Fluminense                     Digão                          CB        931        927         300          5          5         34
058 Internacional                  Fred                           MF       3028        892         500          6         92        114
059 Grêmio                         Zé Roberto                     MF       2593        878         400          7         73        113
039 Vasco                          Elsinho                        WB       1468        850         400          8         25         83
004 Bahia                          Fernandão                      FW       1328        822         500          9         19         70
060 Internacional                  Otavinho                       MF        762        807         300         10          4         21
078 Flamengo                       Jaime De AlMFda                CO       1156        803         100         11         12         52
022 Vitória                        Wilson                         GK       1239        794         500         12         17         59
021 Cruzeiro                       Fábio                          GK       2090        794         500         12         59        106
023 Coritiba                       Vanderlei                      GK       1858        776         500         14         45        101
005 São Paulo                      Luis Fabiano                   FW       2154        758         400         15         67        107
040 Cruzeiro                       Egídio                         WB       1482        752         500         16         34         84
041 Fluminense                     Carlinhos                      WB       1240        693         300         17         26         60
099 Flamengo                       Samir                          CB        267        680         100         18          1          1
061 Vasco                          Carlos Alberto                 MF       1501        675         200         19         42         85
006 Botafogo                       Rafael Marques                 FW       1974        668         500         20         74        105
062 Cruzeiro                       Nilton                         MF       2239        646         500         21         95        110
100 Cruzeiro                       Dedé                           CB       2254        640         500         22         97        111
063 Coritiba                       Júnior Urso                    MF       1438        622         500         23         43         81
064 Crisciúma                      João Vitor                     MF       1327        604         500         24         41         69
101 São Paulo                      Lúcio                          CB       2171        602         500         25         99        108
007 Cruzeiro                       Dagoberto                      FW       2211        594         500         26        102        109
102 Grêmio                         Bressan                        CB       1085        590         400         27         28         48
103 Atlético-PR                    Manoel                         CB       1699        588         500         28         70         96
065 Corinthians                    Guilherme                      MF        883        587         400         29         14         32
104 Ponte Preta                    Cléber                         CB       1461        578         500         30         55         82
008 Náutico                        Rogério                        FW       1062        570         500         31         29         44
066 Corinthians                    Ralf                           MF       1965        570         500         31         93        104
067 Vitória                        Escudero                       MF       1638        568         500         33         68         93
068 Portuguesa                     Correa                         MF        844        560         400         34         15         26
042 Náutico                        Auremir                        WB        773        548         400         35         11         22
079 Cruzeiro                       Marcelo Oliveira               CO       1611        543         500         36         75         92
080 Fluminense                     Abel Braga                     CO       1751        536         400         37         84         98
105 Cruzeiro                       Bruno Rodrigo                  CB       1547        528         500         38         72         88
043 Cruzeiro                       Mayke                          WB        374        525         200         39          3          3
069 Portuguesa                     Souza                          MF       1262        517         400         40         49         62
070 Coritiba                       Alex                           MF       1698        508         500         41         88         95
009 Flamengo                       Hernane                        FW       1387        498         500         42         65         75
071 Grêmio                         Souza                          MF       1380        498         400         42         64         74
106 Santos                         Edu Dracena                    CB       1682        497         300         44         90         94
010 Crisciúma                      Lins                           FW       1840        490         500         45        103        100
011 Santos                         Neilton                        FW        638        488         400         46          9         11
012 Fluminense                     Samuel                         FW       1001        487         300         47         36         37
072 Ponte Preta                    Cicinho                        MF       1142        472         500         48         48         51
024 Atlético-MG                    Victor                         GK       1163        467         400         49         52         53
045 Atlético-MG                    Richarlyson                    WB       1020        467         300         49         40         38
044 Portuguesa                     Luis Ricardo                   WB        858        467         300         49         27         28
013 Ponte Preta                    Chiquinho                      FW        997        464         500         52         38         36
081 Internacional                  Dunga                          CO       1422        463         500         53         79         80
047 São Paulo                      Juan                           WB        789        457         300         54         24         23
046 Internacional                  Fabrício                       WB        876        457         400         54         31         30
014 Atlético-MG                    Luan                           FW       1318        455         400         56         71         67
048 São Paulo                      Paulo Miranda                  WB       1053        454         500         57         44         41
049 Flamengo                       João Paulo                     WB        715        453         300         58         18         19
050 São Paulo                      Rodrigo Caio                   WB       1192        452         500         59         60         56
025 Bahia                          Marcelo Lomba                  GK       1364        450         500         60         78         71
073 Botafogo                       Fellype Gabriel                MF        860        447         400         61         32         29
082 Vitória                        Caio Júnior                    CO       1140        445         500         62         56         50
015 Ponte Preta                    William                        FW       1393        444         500         63         81         76
107 Náutico                        William Alves                  CB        556        443         300         64          8          8
083 Grêmio                         Vanderlei Luxemburgo           CO       1577        442         400         65         98         89
084 São Paulo                      Ney Franco                     CO       1515        439         500         66         94         86
074 Atlético-PR                    João Paulo                     MF       1056        438         500         67         46         42
026 Botafogo                       Renan                          GK        677        437         400         68         16         13
075 Vasco                          Sandro Silva                   MF       1076        428         500         69         53         46
108 Fluminense                     Gum                            CB       1218        422         400         70         69         58
085 Náutico                        Levi Gomes                     CO        708        420         200         71         21         18
109 Flamengo                       Wallace                        CB        429        420         200         71          6          4
051 Coritiba                       Victor Ferraz                  WB       1304        420         500         71         80         65
076 Santos                         Cícero                         MF       1415        418         500         74         91         78
027 Flamengo                       Felipe                         GK       1526        414         500         75        101         87
077 Fluminense                     Wagner                         MF        855        413         300         76         37         27
052 Bahia                          Jussandro                      WB        694        410         500         77         23         16
110 Náutico                        João Filipe                    CB        547        410         400         77         10          7
016 Botafogo                       Vitinho                        FW       1020        404         500         79         54         38
053 Santos                         Rafael Galhardo                WB       1288        404         500         79         83         64
111 Grêmio                         Werley                         CB       1590        403         400         81        105         90
055 Náutico                        Maranhão                       WB        653        402         500         82         20         12
054 Goiás                          William Matheus                WB        587        402         500         82         13          9
112 Corinthians                    Gil                            CB       1323        398         500         84         85         68
113 Vitória                        Gabriel Paulista               CB       1177        394         500         85         76         54
086 Atlético-PR                    Ricardo Drubscky               CO        796        392         500         86         35         24
087 Coritiba                       Marquinhos Santos              CO       1059        389         500         87         62         43
017 Coritiba                       Deivid                         FW       1590        376         500         88        107         90
028 Grêmio                         Dida                           GK       1132        375         400         89         77         49
114 Goiás                          Ernando                        CB       1024        374         500         90         63         40
029 Corinthians                    Cássio                         GK       1251        374         500         90         89         61
018 Grêmio                         Barcos                         FW       1896        367         400         92        110        102
088 Vasco                          Paulo Autuori                  CO       1313        361         500         93        100         66
030 Vasco                          Michel Alves                   GK        899        348         500         94         57         33
019 Atlético-MG                    Jô                             FW       1393        340         200         95        106         76
056 Internacional                  Gabriel                        WB       1181        338         500         96         96         55
057 Goiás                          Vítor                          WB        877        336         500         97         58         31
089 Portuguesa                     Edson Pimenta                  CO        367        326         400         98          7          2
090 Botafogo                       Oswaldo De Oliveira            CO       1077        323         500         99         87         47
031 Crisciúma                      Bruno                          GK       1066        320         500        100         86         45
092 Santos                         Claudinei Oliveira             CO       1192        317         300        101        104         56
091 Corinthians                    Tite                           CO       1368        317         500        101        108         73
020 São Paulo                      Osvaldo                        FW       1364        312         500        103        109         71
032 Internacional                  Muriel                         GK        981        310         400        104         82         35
033 Santos                         Rafael                         GK       1782        300         500        105        112         99
093 Bahia                          Cristóvão Borges               CO        827        292         500        106         66         25
094 Crisciúma                      Vadão                          CO        704        286         500        107         50         17
095 Goiás                          Enderson Moreira               CO        680        253         500        108         61         14
034 Atlético-PR                    Weverton                       GK        616        248         500        109         51         10
035 Fluminense                     Ricardo Berna                  GK        460        242         400        110         30          6
096 Atlético-MG                    Cuca                           CO       1262        232         400        111        111         62
036 Portuguesa                     Gledson                        GK        452        210         400        112         39          5
037 São Paulo                      Rogério Ceni                   GK       1420        117         400        113        114         79
097 Ponte Preta                    Zé Sérgio                      CO        685         75         100        114        113         15

114 rows selected.
```

Note that I dropped the poster's formations based data model in favour of the above, more general one. I used AL as a code for team size, and chose the maximum price arbitrarily (but having an influence on results). I also multiplied the points and prices by a factor of 100 to allow me to work in integers.

Test Problem 2: English Premier League
The second problem is be based on English Premier League and I got the data from a 'scraping' web-site, https://scraperwiki.com/scrapers/fantasy_premier_league_player_stats/. There are some data quality issues with the data, but it is good enough for technical testing. I summed the players' points over the last season and took their values at the last week as prices.

After excluding zero-point players, there remained 576 players, of five positions, with eleven players forming a team, and the problem is the same, with the positional constraints:

```Input positions

ID MIN_PLAYERS MAX_PLAYERS
-- ----------- -----------
AL          11          11
DF           3           5
FW           1           3
GK           1           1
MF           2           5

Input players

ID CLUB_NAME       PLAYER_NAME          PO      PRICE AVG_POINTS APPEARANCES      PRF_R      VFP_R      PRC_R
---------- --------------- -------------------- -- ---------- ---------- ----------- ---------- ---------- ----------
661 Tottenham       Gareth Bale          MF        111        240          38          1         36        573
286 Liverpool       Luis Suarez          FW        105        213          38          2         62        572
30 Arsenal         Santi Santi Cazorla  MF         97        198          36          3         57        569
149 Chelsea         Juan Mata            MF        102        190          36          4         90        571
265 Liverpool       Steven Gerrard       MF         92        187          38          5         60        562
533 Southampton     Rickie Lambert       FW         69        178          37          6          5        513
165 Everton         Leighton Baines      DF         78        173          38          7         25        537
318 Man City        Carlos Tevez         FW         92        172          38          8         87        562
139 Chelsea         Eden Hazard          MF         96        171          35          9         99        568
641 Swansea         Miguel Michu         MF         79        169          36         10         41        542
177 Everton         Marouane Fellaini    MF         73        168          38         11         14        527
47 Aston Villa     Christian Benteke    FW         74        166          35         12         20        530
204 Fulham          Dimitar Berbatov     FW         71        161          37         13         18        523
720 West Brom       Romelu Lukaku        FW         66        157          37         14          8        498
314 Man City        David Silva          MF         92        154          38         15        117        562
298 Man City        Joe Hart             GK         69        154          38         15         22        513
549 Stoke City      Asmir Begovic        GK         56        154          40         15          1        421
428 Norwich         Robert Snodgrass     MF         62        152          38         18          7        474
332 Man Utd         Patrice Evra         DF         73        152          38         18         51        527
126 Chelsea         Demba Ba             FW         78        149          37         20         77        537
616 Sunderland      Stephane Sessegnon   MF         67        148          38         21         26        504
575 Stoke City      Jonathan Walters     MF         63        147          40         22         13        482
770 West Ham        Kevin Nolan          MF         61        145          36         23          9        465
354 Man Utd         Wayne Rooney         FW        116        141          37         24        201        575
322 Man City        Yaya Yaya Toure      MF         82        141          37         24        108        548
268 Liverpool       Glen Johnson         DF         65        141          37         24         35        491
760 West Ham        Jussi Jaaskelainen   GK         52        139          36         27          2        361
198 Everton         Steven Pienaar       MF         66        139          38         27         46        498
609 Sunderland      Simon Mignolet       GK         53        139          38         27          3        379
598 Sunderland      Adam Johnson         MF         68        138          37         30         61        508
726 West Brom       James Morrison       MF         57        135          39         31         11        429
569 Stoke City      Ryan Shawcross       DF         56        133          40         32         10        421
248 Liverpool       Daniel Agger         DF         64        133          38         32         52        488
270 Liverpool       Sanchez Jose Enrique DF         61        133          37         32         33        465
239 Fulham          Mark Schwarzer       GK         51        133          38         32          4        344
800 Wigan           Arouna Kone          FW         69        131          37         36         78        513
684 Tottenham       Aaron Lennon         MF         71        131          38         36         91        523
161 Chelsea         Fernando Torres      FW         93        131          36         36        165        566
552 Stoke City      Peter Crouch         FW         60        131          40         36         31        458
594 Sunderland      Steven Fletcher      FW         67        131          36         36         71        504
295 Man City        Edin Dzeko           FW         68        130          38         41         76        508
700 Tottenham       Jan Vertonghen       DF         68        129          37         42         80        508
132 Chelsea         Petr Cech            GK         64        129          38         42         64        488
144 Chelsea         Frank Lampard        MF         85        128          36         44        150        553
289 Man City        Sergio Aguero        FW        111        127          39         45        217        573
278 Liverpool       Jose Reina           GK         58        126          38         46         34        438
628 Swansea         Jonathan De Guzman   MF         57        122          36         47         39        429
667 Tottenham       Jermain Defoe        FW         79        122          37         47        137        542
723 West Brom       Gareth McAuley       DF         52        122          38         47         12        361
802 Wigan           Shaun Maloney        MF         54        121          37         50         21        395
405 Norwich         Sebastien Bassong    DF         53        121          37         50         16        379
186 Everton         Phil Jagielka        DF         59        120          38         52         59        449
558 Stoke City      Robert Huth          DF         55        120          40         52         32        409
353 Man Utd         Rafael Rafael        DF         61        119          38         54         72        465
771 West Ham        Joey O'Brien         DF         48        119          36         54          6        269
196 Everton         Leon Osman           MF         62        119          37         54         75        474
650 Swansea         Wayne Routledge      MF         53        118          36         57         24        379
323 Man City        Pablo Zabaleta       DF         64        117          38         58         92        488
669 Tottenham       Clint Dempsey        MF         89        116          37         59        186        557
612 Sunderland      John O'Shea          DF         51        115          38         60         19        344
374 Newcastle       Papiss Cisse         FW         87        115          39         60        182        556
142 Chelsea         Branislav Ivanovic   DF         69        114          36         62        120        513
211 Fulham          Damien Duff          MF         58        114          38         62         69        438
364 Man Utd         David de Gea         GK         58        114          38         62         69        438
185 Everton         Tim Howard           GK         53        113          38         65         43        379
154 Chelsea         Emboaba Oscar        MF         79        113          35         65        163        542
602 Sunderland      Sebastian Larsson    MF         59        112          38         67         79        449
719 West Brom       Shane Long           FW         58        110          38         68         81        438
413 Norwich         Grant Holt           FW         59        110          38         68         89        449
713 West Brom       Ben Foster           GK         51        109          39         70         42        344
536 Southampton     Jason Puncheon       MF         47        107          37         71         17        238
232 Fulham          Sascha Riether       DF         48        107          37         71         23        269
145 Chelsea         David Luiz           DF         67        107          36         71        132        504
784 Wigan           Jean Beausejour      MF         53        106          38         74         65        379
60 Aston Villa     Bradley Guzan        GK         48        106          38         74         27        269
293 Man City        Gael Clichy          DF         58        106          38         74         93        438
476 QPR             Adel Taarabt         MF         53        105          38         77         66        379
804 Wigan           James McCarthy       MF         48        105          38         77         30        269
595 Sunderland      Craig Gardner        MF         49        104          38         79         44        293
131 Chelsea         Gary Cahill          DF         60        104          38         79        106        458
423 Norwich         Anthony Pilkington   MF         55        104          38         79         82        409
541 Southampton     Morgan Schneiderlin  MF         48        103          37         82         38        269
412 Norwich         Javier Garrido       DF         47        103          38         82         29        238
414 Norwich         Wes Hoolahan         MF         55        103          38         82         86        409
134 Chelsea         Ashley Cole A        DF         63        103          36         82        123        482
701 Tottenham       Kyle Walker          DF         61        103          37         82        115        465
540 Southampton     Jay Rodriguez        FW         52        103          37         82         67        361
173 Everton         Sylvain Distin       DF         54        102          38         88         83        395
236 Fulham          Bryan Ruiz           FW         50        102          38         88         58        313
501 Reading         Jobi McAnuff         MF         47        101          36         90         37        238
776 West Ham        Winston Reid         DF         48        101          36         90         47        269
576 Stoke City      Glenn Whelan         MF         49        101          40         90         54        293
233 Fulham          John Arne Riise      DF         52        100          38         93         74        361
358 Man Utd         Antonio Valencia     MF         82        100          38         93        200        548
331 Man Utd         Jonny Evans J        DF         53         99          37         95         88        379
285 Liverpool       Daniel Sturridge     FW         74         99          35         95        178        530
187 Everton         Nikica Jelavic       FW         77         98          37         97        190        534
498 Reading         Adam Le Fondre       FW         44         97          36         98         28        156
261 Liverpool       Stewart Downing      MF         57         97          37         98        109        429
417 Norwich         Bradley Johnson      MF         47         97          38         98         53        238
420 Norwich         Russell Martin R     DF         42         96          38        101         15         82
648 Swansea         Angel Rangel         DF         47         96          36        101         56        238
769 West Ham        Mark Noble           MF         46         96          36        101         50        216
267 Liverpool       Jordan Henderson     MF         48         95          38        104         68        269
156 Chelsea         Nascimento Ramires   MF         62         95          35        104        140        474
635 Swansea         Pablo Hernandez      MF         59         95          33        104        130        449
606 Sunderland      James McClean        MF         56         95          39        104        111        421
372 Newcastle       Yohan Cabaye         MF         65         94          38        108        158        491
327 Man Utd         Michael Carrick      MF         59         94          38        108        133        449
631 Swansea         Nathan Dyer          MF         50         94          36        108         84        313
305 Man City        James Milner         MF         61         93          38        111        143        465
532 Southampton     Adam Lallana         MF         56         93          37        111        118        421
550 Stoke City      Geoff Cameron        DF         43         92          38        113         40        114
627 Swansea         Ben Davies           DF         44         92          35        113         49        156
334 Man Utd         Rio Ferdinand        DF         58         92          38        113        135        438
705 West Brom       Chris Brunt          MF         53         92          37        113        105        379
731 West Brom       Jonas Olsson         DF         49         92          39        113         85        293
786 Wigan           Emmerson Boyce       DF         47         91          38        118         73        238
338 Man Utd         Javier Hernandez     FW         65         90          37        119        170        491
789 Wigan           Franco Di Santo      FW         52         90          38        119        107        361
191 Everton         Kevin Mirallas       FW         66         90          36        119        173        498
658 Swansea         Ashley Williams      DF         49         89          36        122         94        293
657 Swansea         Michel Vorm          GK         51         89          37        122        104        344
686 Tottenham       Hugo Lloris          GK         58         89          34        122        139        438
282 Liverpool       Martin Skrtel        DF         56         89          38        122        134        421
761 West Ham        Matthew Jarvis       MF         55         89          35        122        129        409
164 Everton         Victor Anichebe      FW         43         88          38        127         55        114
735 West Brom       Liam Ridgewell       DF         48         87          38        128         97        269
754 West Ham        Guy Demel            DF         41         87          36        128         45         60
433 Norwich         Michael Turner       DF         41         86          38        130         48         60
747 West Ham        Andy Carroll         FW         82         86          36        130        237        548
547 Stoke City      Charlie Adam         MF         65         85          38        132        184        491
291 Man City        Gareth Barry         MF         52         85          38        132        124        361
537 Southampton     Gaston Ramirez       MF         52         85          34        132        124        361
302 Man City        Vincent Kompany      DF         70         85          38        132        202        521
383 Newcastle       Jonas Gutierrez      MF         55         84          39        136        142        409
341 Man Utd         Shinji Kagawa        MF         79         84          37        136        235        542
306 Man City        Samir Nasri          MF         81         83          37        138        243        546
125 Chelsea         Cesar Azpilicueta    DF         56         83          34        138        154        421
519 Southampton     Nathaniel Clyne      DF         41         83          37        138         63         60
78 Aston Villa     Ashley Westwood      MF         49         83          35        138        112        293
445 QPR             Soares Cesar         GK         47         83          35        138        100        238
241 Fulham          Steve Sidwell        MF         49         83          38        138        112        293
755 West Ham        Mohamed Diame        MF         47         83          36        138        100        238
564 Stoke City      Steven Nzonzi        MF         50         81          35        145        128        313
284 Liverpool       Raheem Sterling      MF         46         81          37        145        102        216
359 Man Utd         Robin Van Persie     FW        137         80          12        147        330        576
727 West Brom       Youssouf Mulumbu     MF         53         80          39        147        149        379
77 Aston Villa     Andreas Weimann      FW         51         80          31        147        136        344
782 Wigan           Ali Al-Habsi         GK         49         80          38        147        126        293
597 Sunderland      Danny Graham         FW         54         79          38        151        156        395
803 Wigan           James McArthur       MF         54         78          38        152        159        395
224 Fulham          Alex Kacaniklic      MF         43         78          38        152         95        114
591 Sunderland      Carlos Cuellar       DF         43         78          37        152         95        114
303 Man City        Joleon Lescott       DF         58         77          38        155        179        438
668 Tottenham       Mousa Dembele        MF         58         77          37        155        179        438
696 Tottenham       Gylfi Sigurdsson     MF         78         76          37        157        260        537
511 Reading         Hal Robson-Kanu      MF         42         76          36        157         98         82
307 Man City        Matija Nastasic      DF         53         76          34        157        161        379
386 Newcastle       Tim Krul             GK         51         75          38        160        155        344
522 Southampton     Steven Davis         MF         45         74          37        161        122        183
730 West Brom       Peter Odemwingie     FW         69         74          39        161        233        513
503 Reading         Garath McCleary      MF         44         73          36        163        119        156
221 Fulham          Brede Hangeland      DF         48         73          38        163        146        269
589 Sunderland      Jack Colback         MF         45         73          38        163        127        183
370 Newcastle       Hatem Ben Arfa       MF         73         72          38        166        252        527
392 Newcastle       Davide Santon        DF         47         72          39        166        141        238
415 Norwich         Jonathan Howson      MF         45         72          39        166        131        183
496 Reading         Jimmy Kebe           MF         41         72          36        166        103         60
659 Tottenham       Emmanuel Adebayor    FW         91         71          37        170        297        560
235 Fulham          Hugo Rodallega       FW         54         71          37        170        183        395
172 Everton         Seamus Coleman       MF         46         71          38        170        138        216
792 Wigan           Maynor Figueroa      DF         43         71          38        170        121        114
509 Reading         Pavel Pogrebnyak     FW         42         71          36        170        114         82
560 Stoke City      Kenwyne Jones        FW         50         70          40        175        166        313
194 Everton         Steven Naismith      FW         59         70          37        175        207        449
328 Man Utd         Tom Cleverley        MF         56         70          37        175        194        421
469 QPR             Ryan Nelsen          DF         41         69          38        178        116         60
259 Liverpool       Phillippe Coutinho   MF         71         69          13        178        261        523
481 QPR             Bobby Zamora         FW         61         68          39        180        226        465
546 Southampton     Maya Yoshida         DF         45         68          34        180        148        183
524 Southampton     Jose Fonte           DF         40         68          37        180        110         34
425 Norwich         John Ruddy           GK         44         67          38        183        145        156
360 Man Utd         Nemanja Vidic        DF         66         67          38        183        245        498
781 West Ham        Ricardo Vaz Te       FW         51         66          36        185        188        344
663 Tottenham       Steven Caulker       DF         44         66          37        185        151        156
337 Man Utd         Ryan Giggs           MF         60         65          38        187        232        458
13 Arsenal         Olivier Giroud       FW         77         65          18        187        281        534
255 Liverpool       Jamie Carragher      DF         50         65          38        187        187        313
467 QPR             Stephane Mbia        DF         49         65          35        187        181        293
499 Reading         Mikele Leigertwood   MF         45         65          36        187        159        183
462 QPR             Clint Hill           DF         43         64          38        192        153        114
520 Southampton     Jack Cork            MF         44         64          37        192        157        156
666 Tottenham       Michael Dawson       DF         45         64          38        192        164        183
624 Swansea         Leon Britton         MF         42         64          36        192        144         82
715 West Brom       Zoltan Gera          MF         47         64          39        192        175        238
561 Stoke City      Michael Kightly      MF         51         64          38        192        193        344
625 Swansea          Chico               DF         46         64          36        192        169        216
718 West Brom       Billy Jones          DF         44         63          38        199        162        156
387 Newcastle       Sylvain Marveaux     MF         41         62          38        200        147         60
463 QPR             David Hoilett        MF         56         62          38        200        229        421
556 Stoke City      Matthew Etherington  MF         59         61          40        202        241        449
230 Fulham          Mladen Petric        FW         54         61          37        202        221        395
478 QPR             Armand Traore        DF         48         61          38        202        191        269
636 Swansea         Sung-Yeung Ki        MF         60         60          34        205        246        458
544 Southampton     Luke Shaw            DF         40         60          37        205        151         34
779 West Ham        Matthew Taylor       MF         46         60          36        205        185        216
614 Sunderland      Danny Rose           MF         44         60          38        205        173        156
407 Norwich         Mark Bunn            GK         43         60          34        205        167        114
160 Chelsea         John Terry           DF         65         59          36        210        269        491
796 Wigan           Jordi Gomez          MF         52         59          38        210        220        361
363 Man Utd         Ashley Young         MF         82         58          37        213        307        548
431 Norwich         Alexander Tettey     MF         43         58          36        213        176        114
710 West Brom       Graham Dorrans       MF         50         58          39        213        213        313
695 Tottenham       Raniere Sandro       MF         47         58          38        213        198        238
75 Aston Villa     Ron Vlaar            DF         45         58          38        213        189        183
361 Man Utd         Danny Welbeck        FW         78         56          37        218        304        537
466 QPR             Jamie Mackie         FW         50         56          38        218        223        313
744 West Brom       Claudio Yacob        MF         49         56          37        218        218        293
516 Southampton     Artur Boruc          GK         45         56          30        218        196        183
297 Man City        Francisco Garcia     MF         50         56          34        218        223        313
559 Stoke City      Cameron Jerome       FW         50         55          38        223        231        313
787 Wigan           Gary Caldwell        DF         47         55          38        223        211        238
301 Man City        Aleksandar Kolarov   DF         55         55          38        223        246        409
41 Aston Villa     Gabriel Agbonlahor   FW         68         54          21        226        295        508
38 Arsenal         Theo Walcott         MF         90         53          12        228        329        559
474 QPR             Loic Remy            FW         54         53          16        228        253        395
394 Newcastle       Moussa Sissoko       MF         54         53          15        228        253        395
274 Liverpool       Leiva Lucas          MF         46         53          38        228        216        216
506 Reading         Sean Morrison        DF         38         53          29        228        168          4
495 Reading         Jem Karacan          MF         42         53          36        228        192         82
153 Chelsea         Victor Moses         MF         62         53          35        228        277        474
249 Liverpool       Joe Allen            MF         45         52          37        235        214        183
406 Norwich         Elliott Bennett      MF         47         52          40        235        230        238
375 Newcastle       Fabricio Coloccini   DF         49         51          38        237        240        293
656 Swansea         Gerhard Tremmel      GK         41         51          38        237        197         60
470 QPR             Nedum Onuoha         DF         38         51          39        237        177          4
12 Arsenal         Kieran Gibbs         DF         53         51          15        237        262        379
26 Arsenal         Lukas Podolski       FW         81         50          12        241        322        546
674 Tottenham       William Gallas       DF         50         50          38        241        246        313
23 Arsenal         Nacho Monreal        DF         52         50          13        241        263        361
750 West Ham        Carlton Cole         FW         44         50          36        241        219        156
222 Fulham          Aaron Hughes         DF         40         50          38        241        194         34
772 West Ham        Gary O'Neil          MF         43         50          36        241        212        114
382 Newcastle       Yoan Gouffran        FW         62         50          15        241        290        474
477 QPR             Andros Townsend      MF         44         49          38        248        227        156
369 Newcastle       Vurnon Anita         MF         44         49          38        248        227        156
446 QPR             Djibril Cisse        FW         58         49          39        248        280        438
182 Everton         Johnny Heitinga      DF         50         49          38        248        255        313
225 Fulham          Giorgos Karagounis   MF         47         49          34        248        239        238
356 Man Utd         Chris Smalling       DF         45         48          38        253        234        183
570 Stoke City      Ryan Shotton         MF         46         48          38        253        238        216
319 Man City        Kolo Toure           DF         51         48          39        253        267        344
492 Reading         Danny Guthrie        MF         41         48          36        253        210         60
578 Stoke City      Andy Wilkinson       DF         40         48          40        253        204         34
502 Reading         Alex McCarthy        GK         40         48          36        253        204         34
690 Tottenham       Kyle Naughton        DF         39         48          37        253        199         19
810 Wigan           Ivan Ramis           DF         42         47          37        260        225         82
457 QPR             Esteban Granero      MF         52         47          36        260        270        361
180 Everton         Darron Gibson        MF         47         47          38        260        246        238
393 Newcastle       Danny Simpson        DF         46         46          38        263        246        216
440 QPR             Jose Bosingwa        DF         48         46          38        263        264        269
780 West Ham        James Tomkins        DF         41         46          36        263        222         60
579 Stoke City      Marc Wilson          DF         39         46          38        263        209         19
491 Reading         Chris Gunter         DF         38         46          36        263        203          4
240 Fulham          Philippe Senderos    DF         47         45          38        268        265        238
202 Fulham          Chris Baird          DF         39         45          38        268        215         19
288 Liverpool       Andre Wisdom         DF         38         45          32        268        208          4
207 Fulham          Ashkan Dejagah       MF         55         45          34        268        288        409
152 Chelsea          Mikel               MF         43         44          36        272        244        114
398 Newcastle       Steven Taylor S      DF         46         44          38        272        266        216
493 Reading         Ian Harte            DF         37         44          36        272        206          1
471 QPR             Ji-Sung Park         MF         52         44          38        272        279        361
479 QPR             Shaun Wright-Phillip MF         48         44          39        272        268        269
s

691 Tottenham       Scott Parker         MF         52         43          37        277        285        361
11 Arsenal         Yao Gervinho         MF         68         42          12        278        321        508
577 Stoke City      Dean Whitehead       MF         42         42          40        278        246         82
453 QPR             Fabio Fabio          DF         40         42          38        278        236         34
2 Arsenal         Mikel Arteta         MF         75         41          13        281        339        533
325 Man Utd         Oliveira Anderson    MF         51         41          38        281        292        344
712 West Brom       Marc-Antoine Fortune FW         48         41          38        281        278        269
752 West Ham        James Collins        DF         46         41          14        281        274        216
195 Everton         Phil Neville         MF         41         40          38        285        256         60
660 Tottenham       Benoit Assou-Ekotto  DF         60         40          38        285        313        458
21 Arsenal         Per Mertesacker      DF         53         39          13        287        303        379
513 Reading         Nicky Shorey         DF         38         39          36        287        242          4
16 Arsenal         Carl Jenkinson       DF         40         39          13        287        257         34
531 Southampton     Jos Hooiveld         DF         40         39          37        287        257         34
436 Norwich         Steven Whittaker     DF         40         39          38        287        257         34
643 Swansea         Luke Moore           FW         43         38          36        292        275        114
399 Newcastle       Cheick Tiote         MF         48         38          38        292        296        269
326 Man Utd         Alexander Buttner    DF         50         37          36        294        302        313
458 QPR             Rob Green            GK         41         37          38        294        271         60
494 Reading         Noel Hunt            FW         46         37          36        294        291        216
678 Tottenham       Tom Huddlestone      MF         45         37          37        294        287        183
389 Newcastle       James Perch          DF         44         37          38        294        283        156
655 Swansea         Dwight Tiendalli     DF         45         36          32        299        293        183
69 Aston Villa     Matthew Lowton       DF         45         36          12        299        293        183
37 Arsenal         Thomas Vermaelen     DF         67         36          12        299        343        504
128 Chelsea         Ryan Bertrand        DF         39         35          36        302        272         19
751 West Ham        Joe Cole             MF         51         35          37        302        310        344
805 Wigan           Callum McManaman     FW         45         35          38        302        298        183
401 Newcastle       Mike Williamson      DF         40         35          38        302        276         34
508 Reading         Alex Pearce          DF         38         34          36        306        273          4
350 Man Utd         Luis Nani            MF         82         34          38        306        369        548
368 Newcastle       Shola Ameobi         FW         51         34          38        306        313        344
610 Sunderland      Alfred N'Diaye       MF         42         34          17        306        289         82
584 Sunderland      Titus Bramble        DF         40         33          38        310        286         34
311 Man City        Micah Richards       DF         57         33          38        310        332        429
618 Sunderland      David Vaughan        MF         49         33          38        310        312        293
766 West Ham        George McCartney     DF         38         32          36        313        282          4
528 Southampton     Guly Guilherme       MF         47         32          37        313        311        238
753 West Ham        Jack Collison        MF         46         32          36        313        309        216
464 QPR             Jermaine Jenas       MF         42         32          38        313        300         82
489 Reading         Kaspars Gorkss       DF         37         31          36        317        284          1
521 Southampton     Kelvin Davis         GK         41         31          37        317        301         60
19 Arsenal         Vito Mannone         GK         40         31          36        317        299         34
448 QPR             Shaun Derry          MF         42         30          38        320        306         82
764 West Ham        Modibo Maiga         FW         50         30          36        320        324        313
818 Wigan           Ben Watson           MF         50         30          38        320        324        313
402 Newcastle       Mapou Yanga-Mbiwa    DF         49         29          15        323        328        293
737 West Brom       Markus Rosenberg     FW         59         29          37        323        357        449
355 Man Utd         Paul Scholes         MF         50         29          38        323        331        313
426 Norwich         Ryan Ryan Bennett    DF         39         28          38        326        304         19
526 Southampton     Daniel Fox           DF         40         28          37        326        308         34
51 Aston Villa     Ciaran Clark         DF         44         28          12        326        318        156
290 Man City        Mario Balotelli      FW         86         28          38        326        389        554
454 QPR             Alejandro Faurlin    MF         47         28          38        326        326        238
304 Man City        Sisenando Maicon     DF         62         28          34        326        363        474
45 Aston Villa     Joe Bennett          DF         44         28          36        326        318        156
791 Wigan           Roger Espinoza       MF         41         27          16        333        315         60
44 Aston Villa     Barry Bannan         MF         47         27          12        333        333        238
651 Swansea         Itay Shechter        FW         50         27          35        333        342        313
46 Aston Villa     Darren Bent          FW         78         27          18        333        384        537
280 Liverpool       Nuri Sahin           MF         54         27          35        333        352        395
27 Arsenal         Aaron Ramsey         MF         54         27          12        333        352        395
231 Fulham          Kieran Richardson    MF         53         27          36        333        351        379
527 Southampton     Paulo Gazzaniga      GK         40         26          37        340        316         34
497 Reading         Stephen Kelly        DF         40         26          38        340        316         34
783 Wigan           Antolin Alcaraz      DF         42         26          38        340        320         82
340 Man Utd         Phil Jones           DF         57         26          38        340        362        429
421 Norwich         Steve Morison        FW         49         26          38        340        345        293
346 Man Utd         Anders Lindegaard    GK         51         26          38        340        350        344
312 Man City        Jack Rodwell         MF         46         26          37        340        336        216
388 Newcastle       Gabriel Obertan      MF         41         25          39        347        323         60
728 West Brom       Boaz Myhill          GK         44         25          39        347        334        156
582 Sunderland      Phil Bardsley        DF         44         25          37        347        334        156
56 Aston Villa     Karim El Ahmadi      MF         42         25          16        347        327         82
798 Wigan           David Jones          MF         43         24          39        351        337        114
514 Reading         Jay Tabb             MF         43         24          36        351        337        114
269 Liverpool       Brad Jones           GK         44         24          37        351        340        156
62 Aston Villa     Brett Holman         MF         55         24          12        351        364        409
482 Reading         Hope Akpan           MF         45         24          17        351        344        183
376 Newcastle       Mathieu Debuchy      DF         47         24          17        351        348        238
416 Norwich         Simeon Jackson       FW         47         24          38        351        348        238
449 QPR             Samba Diakite        MF         44         24          39        351        340        156
672 Tottenham       Brad Friedel         GK         48         23          37        359        360        269
642 Swansea         Garry Monk           DF         42         22          36        360        346         82
5 Arsenal         Alex Chamberlain     MF         69         22          13        360        391        513
621 Swansea         Kemy Agustien        MF         45         22          36        360        358        183
545 Southampton     James Ward-Prowse    MF         43         22          37        360        347        114
371 Newcastle       Gael Bigirimana      MF         43         21          38        364        359        114
281 Liverpool       Jonjo Shelvey        MF         51         21          39        364        371        344
212 Fulham          Urby Emanuelson      MF         46         21          13        364        361        216
377 Newcastle       Rob Elliot           GK         40         20          38        367        352         34
734 West Brom       Steven Reid          MF         47         20          39        367        367        238
812 Wigan           Joel Robles          GK         40         20          15        367        352         34
815 Wigan           Ronnie Stam          DF         38         19          38        370        352          4
17 Arsenal         Laurent Koscielny    DF         53         19          12        370        382        379
64 Aston Villa     Stephen Ireland      MF         50         19          12        370        377        313
733 West Brom       Goran Popov          DF         44         19          34        370        366        156
510 Reading         Jason Roberts        FW         45         18          36        374        372        183
263 Liverpool        Fernandez Saez      FW         47         18          33        374        375        238
254 Liverpool       Fabio Borini         FW         72         18          37        374        409        526
315 Man City        Scott Sinclair       MF         60         18          37        374        392        458
197 Everton         Bryan Oviedo         MF         48         18          34        374        379        269
455 QPR             Anton Ferdinand      DF         41         17          39        379        369         60
67 Aston Villa     Eric Lichaj          DF         43         17          12        379        373        114
588 Sunderland      Lee Cattermole       MF         43         17          38        379        373        114
654 Swansea         Neil Taylor          DF         45         17          36        379        378        183
677 Tottenham       Lewis Holtby         MF         63         17          14        379        401        482
586 Sunderland      Fraizer Campbell     FW         49         17          37        379        383        293
742 West Brom       Jerome Thomas        MF         51         17          39        379        386        344
7 Arsenal         Vassiriki Diaby      MF         61         17          12        379        398        465
127 Chelsea         Yossi Benayoun       MF         61         17          36        379        398        465
29 Arsenal         Bacary Sagna         DF         47         17          15        379        381        238
404 Norwich         Leon Barnett         DF         37         16          38        389        365          1
210 Fulham          Mahamadou Diarra     MF         47         16          37        389        385        238
74 Aston Villa     Yacouba Sylla        MF         42         16          14        389        376         82
599 Sunderland      Matthew Kilgallon    DF         38         16          37        389        368          4
685 Tottenham       Jake Livermore       MF         41         15          39        393        380         60
209 Fulham          Clint Dempsey        MF         92         15           1        393        436        562
385 Newcastle       Steve Harper         GK         45         15          38        393        386        183
228 Fulham          Stanislav Manolev    DF         42         14          13        396        386         82
378 Newcastle       Shane Ferguson       MF         43         14          38        396        389        114
183 Everton         Tony Hibbert         DF         50         14          38        396        396        313
535 Southampton     Emmanuel Mayuka      FW         48         14          35        396        394        269
441 QPR             Jay Bothroyd         FW         47         14          40        396        393        238
48 Aston Villa     Jordan Bowery        FW         45         13          36        401        395        183
330 Man Utd         Jonathan Evans J     DF         48         13           1        401        400        269
620 Sunderland      Connor Wickham       FW         50         13          37        401        405        313
213 Fulham          Eyong Enoh           MF         50         13          13        401        405        313
31 Arsenal         Clarindo Santos      DF         49         13          14        401        403        293
192 Everton         Jan Mucha            GK         43         12          38        406        397        114
217 Fulham          Emmanuel Frimpong    MF         45         12          15        406        402        183
103 Bolton          Mark Davies M        MF         48         12           2        406        409        269
148 Chelsea         Marko Marin          MF         66         12          35        406        429        498
184 Everton         Thomas Hitzlsperger  MF         50         12          30        406        412        313
335 Man Utd         Darren Fletcher      MF         54         12          38        406        416        395
565 Stoke City      Michael Owen         FW         50         12          35        406        412        313
740 West Brom       Gabriel Tamas        DF         42         11          39        413        404         82
391 Newcastle       Sammy Sammy Ameobi   FW         43         11          39        413        408        114
72 Aston Villa     Charles N'Zogbia     MF         66         11          13        413        434        498
114 Bolton          Martin Petrov        MF         52         11           2        413        419        361
88 Blackburn       David Hoilett        FW         55         11           3        413        423        409
475 QPR             Tommy Smith          FW         45         11          40        413        411        183
43 Aston Villa     Nathan Baker         DF         39         10          12        419        407         19
662 Tottenham       Tom Carroll          MF         42         10          37        419        414         82
39 Arsenal         Jack Wilshere        MF         63         10          12        419        441        482
35 Arsenal         Wojciech Szczesny    GK         53         10          12        419        427        379
85 Blackburn       Morten Gamst Gamst P MF         62         10           2        419        439        474
edersen

615 Sunderland      Louis Saha           FW         49         10          37        419        422        293
205 Fulham          Matthew Briggs       DF         39          9          38        425        415         19
158 Chelsea         Oriol Romeu          MF         41          9          37        425        417         60
574 Stoke City      Matthew Upson        DF         41          9          39        425        417         60
216 Fulham          Kerim Frei           MF         43          9          37        425        420        114
613 Sunderland      Kieran Richardson    MF         58          9           1        425        444        438
585 Sunderland      Wes Brown            DF         46          9          38        425        424        216
201 Everton         Apostolos Vellios    FW         47          9          38        425        425        238
223 Fulham          Andrew Johnson A     FW         47          9           1        425        425        238
257 Liverpool       Sebastian Coates     DF         44          9          39        425        421        156
450 QPR             Kieron Dyer          MF         44          8          39        434        429        156
785 Wigan           Mauro Boselli        FW         50          8          37        434        440        313
54 Aston Villa     Fabian Delph         MF         46          8          12        434        432        216
486 Reading         Shaun Cummings       DF         38          7          36        437        428          4
273 Liverpool       Dirk Kuyt            MF         94          7           1        437        478        567
166 Everton         Ross Barkley         MF         41          7          39        437        433         60
797 Wigan           Angelo Henriquez     FW         42          7          16        437        434         82
743 West Brom       George Thorne        MF         43          7          39        437        437        114
430 Norwich         Andrew Surman        MF         43          7          38        437        437        114
580 Stoke City      Jonathan Woodgate    DF         45          7           2        437        442        183
352 Man Utd         Nick Powell          MF         45          7          37        437        442        183
465 QPR             Andrew Johnson       FW         46          7          38        437        446        216
6 Arsenal         Francis Coquelin     MF         47          7          13        437        448        238
82 Blackburn       Scott Dann           DF         47          7           1        437        448        238
272 Liverpool       Martin Kelly         DF         51          7          38        437        452        344
42 Aston Villa     Marc Albrighton      MF         52          7          12        437        453        361
109 Bolton          Ivan Klasnic         FW         59          7           2        437        456        449
515 Reading         Stuart Taylor        GK         40          7          35        437        431         34
162 Chelsea         Ross Turnbull        GK         39          6          36        452        445         19
409 Norwich         Lee Camp             GK         40          6          15        452        447         34
310 Man City        Karim Rekik          DF         43          6          38        452        450        114
1 Arsenal         Andrey Arshavin      MF         65          6          12        452        470        491
637 Swansea         Roland Lamah         MF         50          6          14        452        455        313
229 Fulham          Danny Murphy         MF         61          6           1        452        464        465
9 Arsenal         Lukasz Fabianski     GK         43          6          12        452        450        114
539 Southampton     Frazer Richardson    DF         41          5          37        459        454         60
680 Tottenham       Harry Kane           FW         43          5          38        459        457        114
151 Chelsea         Raul Meireles        MF         63          5          36        459        474        482
84 Blackburn       Mauro Formica        MF         49          5           2        459        462        293
244 Fulham          David Stockdale      GK         43          5          38        459        457        114
709 West Brom       Craig Dawson         DF         38          4          39        464        459          4
439 QPR             Tal Ben Haim         DF         39          4          17        464        460         19
801 Wigan           Adrian Lopez         DF         39          4          38        464        460         19
777 West Ham        Jordan Spence        DF         40          4          29        464        463         34
566 Stoke City      Wilson Palacios      MF         41          4          39        464        465         60
52 Aston Villa     Simon Dawkins        MF         42          4          14        464        466         82
699 Tottenham       Rafael Van der Vaart MF         89          4          37        464        509        557
717 West Brom       Gonzalo Jara         DF         43          4          39        464        468        114
61 Aston Villa     Chris Herd           MF         43          4          12        464        468        114
294 Man City        Nigel De Jong        MF         44          4          38        464        471        156
58 Aston Villa     Shay Given           GK         45          4          12        464        472        183
107 Bolton          Jussi Jaaskelainen   GK         48          4           2        464        473        269
251 Liverpool       Oussama Assaidi      MF         57          4          36        464        483        429
110 Bolton          Zat Knight           DF         42          4           2        464        466         82
538 Southampton     Ben Reeves           DF         38          3          37        478        475          4
567 Stoke City      Jermaine Pennant     MF         50          3          38        478        491        313
122 Chelsea         Nathan Ake           DF         40          3          20        478        477         34
384 Newcastle       Massadio Haidara     DF         41          3          15        478        479         60
608 Sunderland      David Meyler         MF         42          3          38        478        480         82
140 Chelsea         Henrique Hilario     GK         42          3          38        478        480         82
518 Southampton     Richard Chaplow      MF         42          3          37        478        480         82
309 Man City        Abdul Razak          MF         43          3          39        478        484        114
806 Wigan           Ryo Miyaichi         MF         43          3          12        478        484        114
774 West Ham        Emanuel Pogatetz     DF         43          3          14        478        484        114
373 Newcastle       Adam Campbell        FW         45          3          20        478        487        183
756 West Ham        Alou Diarra          MF         45          3          36        478        487        183
381 Newcastle       Dan Gosling          MF         46          3          38        478        489        216
181 Everton         Magaye Gueye         FW         46          3          38        478        489        216
568 Stoke City      Danny Pugh           MF         50          3           2        478        491        313
607 Sunderland      James McFadden       FW         50          3          28        478        491        313
487 Reading         Daniel Daniel Carric DF         38          3          17        478        475          4
o

653 Swansea         Alan Tate            DF         38          2          36        495        494          4
321 Man City        Gnegneri Yaya Toure  MF         77          2           1        495        530        534
459 QPR             Michael Harriman     DF         39          2          38        495        496         19
795 Wigan           Roman Golobart       DF         40          2          24        495        497         34
605 Sunderland      Kader Mangane        DF         40          2          16        495        497         34
400 Newcastle       Haris Vuckic         MF         42          2          40        495        499         82
119 Bolton          Gretar Rafn Steinsso DF         42          2           2        495        499         82
n

155 Chelsea         Lucas Piazon         MF         42          2          35        495        499         82
226 Fulham          Pajtim Kasami        MF         42          2          38        495        499         82
523 Southampton     Steve De Ridder      MF         42          2          37        495        499         82
411 Norwich         David Fox            MF         43          2          38        495        504        114
118 Bolton          Paul Robinson        DF         43          2           2        495        504        114
116 Bolton          Nigel Reo-Coker      MF         44          2           1        495        506        156
833 Wolves          Karl Henry           MF         44          2           1        495        506        156
113 Bolton          Fabrice Muamba       MF         44          2           1        495        506        156
707 West Brom       Simon Cox            FW         45          2          38        495        510        183
390 Newcastle       Nile Ranger          MF         45          2          26        495        510        183
832 Wolves          Wayne Hennessey      GK         46          2           1        495        512        216
593 Sunderland      Ahmed Elmohamady     MF         49          2          38        495        513        293
543 Southampton     Billy Sharp          FW         49          2          37        495        513        293
634 Swansea         Danny Graham         FW         50          2           1        495        515        313
679 Tottenham       Younes Kaboul        DF         50          2          38        495        515        313
91 Blackburn       Marcus Marcus Olsson MF         50          2           1        495        515        313
738 West Brom       Paul Scharner        MF         51          2           2        495        518        344
115 Bolton          Darren Pratley       MF         52          2           2        495        519        361
208 Fulham          Moussa Dembele       FW         52          2           1        495        519        361
95 Blackburn       Jason Roberts        FW         53          2           3        495        521        379
283 Liverpool       Jay Spearing         MF         54          2          38        495        522        395
175 Everton         Royston Drenthe      MF         54          2           1        495        522        395
102 Bolton          Kevin Davies K       FW         57          2           2        495        524        429
835 Wolves          Matthew Jarvis       MF         57          2           1        495        524        429
682 Tottenham       Niko Kranjcar        MF         60          2           1        495        526        458
83 Blackburn       David Dunn           MF         62          2           3        495        527        474
157 Chelsea          Ramires             MF         69          2           1        495        528        513
694 Tottenham       Louis Saha           FW         69          2           1        495        528        513
775 West Ham        Daniel Potts         DF         38          2          36        495        494          4
396 Newcastle       James Tavernier      DF         39          1          38        531        531         19
136 Chelsea         Didier Drogba        FW        101          1           1        531        576        570
73 Aston Villa     Enda Stevens         DF         39          1          12        531        531         19
138 Chelsea         Paulo Ferreira       DF         40          1          36        531        534         34
242 Fulham          Alex Smith           DF         40          1          34        531        534         34
227 Fulham          Stephen Kelly        DF         40          1           1        531        534         34
333 Man Utd         Fabio Fabio          DF         42          1           1        531        537         82
793 Wigan           Fraser Fyvie         MF         42          1          37        531        537         82
176 Everton         Shane Duffy          DF         42          1          38        531        537         82
57 Aston Villa     Gary Gardner         MF         42          1          13        531        537         82
14 Arsenal         Serge Gnabry         MF         43          1          29        531        541        114
443 QPR             DJ Campbell          FW         43          1          40        531        541        114
555 Stoke City      Maurice Edu          MF         43          1          36        531        541        114
848 Wolves          Stephen Ward         DF         43          1           1        531        541        114
820 Wolves          Christophe Berra     DF         43          1           1        531        541        114
846 Wolves          Richard Stearman     DF         43          1           1        531        541        114
758 West Ham        Robert Hall          FW         43          1          34        531        541        114
512 Reading         Dominic Samuel       FW         44          1          23        531        548        156
419 Norwich         Chris Martin C       FW         44          1          38        531        548        156
814 Wigan           Conor Sammon         FW         45          1          38        531        550        183
633 Swansea         Mark Gower           MF         45          1          36        531        550        183
553 Stoke City      Rory Delap           MF         45          1          40        531        550        183
837 Wolves          Eggert Jonsson       MF         45          1           1        531        550        183
206 Fulham          Simon Davies         MF         46          1          38        531        554        216
670 Tottenham       Yago Falque          MF         46          1          37        531        554        216
96 Blackburn       Ruben Rochina        FW         47          1           3        531        556        238
790 Wigan           Mohamed Diame        MF         48          1           1        531        557        269
397 Newcastle       Ryan Taylor R        DF         48          1          38        531        557        269
583 Sunderland      Phillip Bardsley     DF         48          1           1        531        557        269
741 West Brom       Somen Tchoyi         MF         48          1           2        531        557        269
825 Wolves          David Edwards        MF         49          1           1        531        561        293
53 Aston Villa     Nathan Delfouneso    FW         50          1          21        531        562        313
841 Wolves          Nenad Milijas        MF         52          1           1        531        563        361
827 Wolves          Steven Fletcher      FW         52          1           1        531        563        361
839 Wolves          Michael Kightly      MF         54          1           1        531        565        395
130 Chelsea         Jose Bosingwa        DF         55          1           1        531        566        409
86 Blackburn       David Goodwillie     FW         55          1           2        531        566        409
342 Man Utd         Will Keane           FW         55          1           2        531        566        409
823 Wolves          Kevin Doyle          FW         57          1           1        531        569        429
137 Chelsea         Michael Essien       MF         63          1          36        531        570        482
171 Everton         Tim Cahill           MF         65          1          38        531        571        491
692 Tottenham       Roman Pavlyuchenko   FW         70          1           1        531        572        521
146 Chelsea         Romelu Lukaku        FW         74          1           3        531        573        530
247 Liverpool       Charlie Adam         MF         86          1           1        531        574        554
256 Liverpool       Andy Carroll         FW         91          1           1        531        575        560
98 Bolton          Marcos Alonso        DF         39          1           1        531        531         19

576 rows selected.
```

SQL Solution with Recursive Subquery Factoring

SQL

Note that currently I have retained the fantasy league table and column names, but they could as well be the generic items and categories in place of players and teams: This is a generic solution.

```VAR KEEP_NUM NUMBER
VAR MAX_PRICE NUMBER
BEGIN
:KEEP_NUM := 40;
:MAX_PRICE := 900;
END;
/
PROMPT Top ten solutions
WITH  /* XS_EPL */ position_counts AS (
SELECT Max (CASE id WHEN 'AL' THEN min_players END) team_size
FROM positions
), pos_runs AS (
SELECT id, Sum (CASE WHEN id != 'AL' THEN min_players END) OVER (ORDER BY id DESC) num_remain, min_players, max_players
FROM positions
), players_ranked AS (
SELECT id,
position_id,
price,
avg_points,
appearances,
Row_Number() OVER (ORDER BY position_id, avg_points DESC) rnk,
Min (price) OVER () min_price
FROM players
), rsf (path_rnk, nxt_id, lev, tot_price, tot_profit, pos_id, n_pos, team_size, min_players, pos_path, path) AS (
SELECT 0, 0, 0, 0, 0, 'AL', 0, c.team_size, 0, CAST (NULL AS VARCHAR2(400)) pos_path, CAST (NULL AS VARCHAR2(400)) path
FROM position_counts c
UNION ALL
SELECT Row_Number() OVER (PARTITION BY r.pos_path || p.position_id ORDER BY r.tot_profit + p.avg_points DESC),
p.rnk,
r.lev + 1,
r.tot_price + p.price,
r.tot_profit + p.avg_points,
p.position_id,
CASE p.position_id WHEN r.pos_id THEN r.n_pos + 1 ELSE 1 END,
r.team_size,
m1.min_players,
r.pos_path || p.position_id,
r.path || LPad (p.id, 3, '0')
FROM rsf r
JOIN players_ranked p
ON p.rnk > r.nxt_id
JOIN pos_runs m1
ON m1.id = p.position_id
AND CASE p.position_id WHEN r.pos_id THEN r.n_pos + 1 ELSE 1 END <= m1.max_players
AND r.team_size - r.lev - 1 >= m1.num_remain - CASE p.position_id WHEN r.pos_id THEN r.n_pos + 1 ELSE 1 END
AND (r.lev = 0 OR p.position_id = r.pos_id OR r.n_pos >= r.min_players)
WHERE r.tot_price + p.price + (r.team_size - r.lev - 1) * p.min_price <= :MAX_PRICE
AND r.path_rnk < :KEEP_NUM
AND r.lev < r.team_size
), paths_ranked AS (
SELECT tot_price,
tot_profit,
team_size,
Row_Number () OVER (ORDER BY tot_profit DESC, tot_price) r_profit,
path
FROM rsf
WHERE lev = team_size
), top_ten_paths AS (
SELECT tot_price,
tot_profit,
r_profit,
path,
player_index
FROM paths_ranked
CROSS JOIN (SELECT LEVEL player_index FROM position_counts CONNECT BY LEVEL <= team_size)
WHERE r_profit <= 10
), top_ten_teams AS (
SELECT tot_price,
tot_profit,
r_profit,
path,
player_index,
Substr (path, (player_index - 1) * 3 + 1, 3) player_id
FROM top_ten_paths
)
SELECT  /*+ GATHER_PLAN_STATISTICS */  t.tot_profit,
t.tot_price,
t.r_profit rnk,
p.position_id,
t.player_id p_id,
p.player_name,
p.club_name,
p.price,
p.avg_points
FROM top_ten_teams t
JOIN players p
ON p.id = t.player_id
ORDER BY t.tot_profit DESC, t.tot_price, t.path, p.position_id, t.player_index```

How It Works

The solution approach is based on the method used to provide exact solutions for knapsack problems in my earlier article, but with a number of extensions to cater for the new category constraints, and to reduce searching to manageable proportions.

• position_counts subquery: Gets the team size
• pos_runs subquery: Computes the running sums of the item category minima going backwards by category id
• players_ranked subquery: Computes a unique rank for the items, ordered by category, then profit descending
• rsf subquery: A recursive subquery that returns a set of item sets in the form of strings of the concatenated item ids
• rsf anchor branch: Initialises the recursion with a single record
• rsf recursive branch: Items are joined having strictly higher rank, and such that the constraints are not violated, both at the current position and with any possible extrapolations
• Row_Number is used to rank the records by overall profit, and the where clause excludes records from the previous iteration that have rank below an input figure;
this exclusion is what makes the computation practical; the ranking is partitioned by the category path, which is important to avoid closing off solution paths too early
• Item category minima are treated differently from the maxima; once a category is in a position, the subsequent positions are required to be of the same category until the minimum number is reached
• paths_ranked subquery: Excludes records that are not of full length,, and ranks those that are by profit
• top_ten_paths subquery: Selects the top ten paths and cross-joins them with a row-generator to provide an indexed set of records with set size cardinality for each path
• top_ten_teams subquery: Builds the item records for each of the best sets by extracting the item id from the paths according to index
• Main query: Joins items table to provide additional attributes

PL/SQL Recursive Solution

This is a version in the form of a pipelined function.

SQL

```SELECT  /*+ GATHER_PLAN_STATISTICS XP_EPL */
t.sol_profit,
t.sol_price,
Dense_Rank() OVER (ORDER BY t.sol_profit DESC, t.sol_price) RNK,
p.position_id,
t.item_id,
p.club_name,
p.player_name,
p.price,
p.avg_points
FROM TABLE (Item_Cats.Best_N_Sets (
p_keep_size => 10,
p_max_calls => 100000,
p_n_size => 10,
p_max_price => 900,
p_cat_cur => CURSOR (
SELECT id, min_players, max_players
FROM positions
ORDER BY CASE WHEN id != 'AL' THEN 0 END, id
),
p_item_cur => CURSOR (
SELECT id, price, avg_points, position_id
FROM players
ORDER BY position_id, avg_points DESC
)
)
) t
JOIN players p
ON p.id = t.item_id
ORDER BY t.sol_profit DESC, t.sol_price, p.position_id, t.item_id
```

Package

```CREATE OR REPLACE PACKAGE Item_Cats AS
/**************************************************************************************************

Author:         Brendan Furey
Date:           7 July 2013
Description:    Brendan's pipelined function solution for the knapsack problem with one container,
and items having categories with validity bands, as described at
http://aprogrammerwrites.eu/?p=878 (SQL for the Fantasy Football Knapsack Problem)

***************************************************************************************************/
TYPE sol_detail_rec_type IS RECORD (
set_id                  NUMBER,
item_id                 VARCHAR2(100),
sol_price               NUMBER,
sol_profit              NUMBER
);
TYPE sol_detail_list_type IS VARRAY(100) OF sol_detail_rec_type;

FUNCTION Best_N_Sets (  p_keep_size     PLS_INTEGER,
p_max_calls     PLS_INTEGER,
p_n_size        PLS_INTEGER,
p_max_price     PLS_INTEGER,
p_cat_cur       SYS_REFCURSOR,
p_item_cur      SYS_REFCURSOR) RETURN sol_detail_list_type PIPELINED;

END Item_Cats;
/
SHO ERR

CREATE OR REPLACE PACKAGE BODY Item_Cats AS

c_cat_all           CONSTANT VARCHAR2(3) := 'AL';
c_hash_renew_point  CONSTANT PLS_INTEGER := 1000;
--
-- Bulk collect array types
--
TYPE cat_rec_type IS RECORD (
id                      VARCHAR2(3),
min_items               PLS_INTEGER,
max_items               PLS_INTEGER
);
TYPE cat_list_type IS VARRAY(100) OF cat_rec_type;

TYPE item_cat_rec_type IS RECORD (
id                      VARCHAR2(10),
price                   PLS_INTEGER,
profit                  PLS_INTEGER,
cat_id                  VARCHAR2(3)
);
TYPE item_cat_list_type IS VARRAY(1000) OF item_cat_rec_type;

TYPE chr_hash_type IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(30);
--
-- Input data LOL types
--
TYPE num_range_rec_type IS RECORD (
item_beg                PLS_INTEGER,
item_end                PLS_INTEGER
);
TYPE num_range_list_type IS VARRAY(1000) OF num_range_rec_type;
TYPE num_list_type IS VARRAY(100) OF PLS_INTEGER;
--
-- Solution types
--
TYPE id_list_type IS VARRAY(100) OF VARCHAR2(10);
TYPE sol_rec_type IS RECORD (                       -- trial solution and record in retained array
item_list               id_list_type,
price                   PLS_INTEGER,
profit                  PLS_INTEGER
);
TYPE sol_list_type IS VARRAY(100) OF sol_rec_type;  -- retained solutions

TYPE int_hash_type IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;

g_keep_size                 PLS_INTEGER;
g_max_calls                 PLS_INTEGER;
g_n_size                    PLS_INTEGER;
g_max_price                 PLS_INTEGER;

g_cat_hash                  chr_hash_type;
g_item_range_list           num_range_list_type := num_range_list_type();
g_hash_buffer               int_hash_type;
g_profit_hash               int_hash_type;
g_trial_sol                 sol_rec_type;
g_sol_list                  sol_list_type := sol_list_type();
g_cat_list                  cat_list_type;
g_item_cat_list             item_cat_list_type;

g_n_cats                    PLS_INTEGER;
g_n_items                   PLS_INTEGER;
g_set_size                  PLS_INTEGER;
g_nth_profit                PLS_INTEGER := 0;
g_min_item_price            PLS_INTEGER := 1000000;
g_max_item_profit           PLS_INTEGER := 0;
g_min_price_togo            num_list_type := num_list_type();
g_max_profit_togo           num_list_type := num_list_type();
g_n_recursive_calls         PLS_INTEGER := 0;
g_n_sols                    PLS_INTEGER := 0;

PROCEDURE Write_Log (p_line VARCHAR2, p_debug_level PLS_INTEGER DEFAULT 0) IS
BEGIN

IF Utils.g_debug_level >= p_debug_level THEN
Utils.Write_Log (p_line);
END IF;

END Write_Log;

FUNCTION Dedup_Hash (p_card PLS_INTEGER, p_key PLS_INTEGER, p_hash int_hash_type) RETURN PLS_INTEGER IS
l_trial_key       PLS_INTEGER := p_card * p_key;
BEGIN

LOOP

IF p_hash.EXISTS (l_trial_key) THEN
l_trial_key := l_trial_key + 1;
ELSE
EXIT;
END IF;

END LOOP;
RETURN l_trial_key;

END Dedup_Hash;

PROCEDURE Pop_Arrays (p_cat_cur SYS_REFCURSOR, p_item_cur SYS_REFCURSOR) IS
n_cat                     PLS_INTEGER := 0;
l_price                   PLS_INTEGER;
l_profit                  PLS_INTEGER;

l_last_cat                VARCHAR2(30) := '???';

l_item_price_hash         int_hash_type;
l_item_profit_hash        int_hash_type;

BEGIN

FETCH p_cat_cur BULK COLLECT INTO g_cat_list;
CLOSE p_cat_cur;
Write_Log ('Collected ' || g_cat_list.COUNT || ' cats');

FETCH p_item_cur BULK COLLECT INTO g_item_cat_list;
CLOSE p_item_cur;
Write_Log ('Collected ' || g_item_cat_list.COUNT || ' items');

Write_Log (g_n_cats || ' cats');

g_n_cats := g_cat_list.COUNT - 1;
g_item_range_list.EXTEND (g_n_cats);
FOR i IN 1..g_cat_list.COUNT LOOP

IF g_cat_list(i).id = c_cat_all THEN
g_set_size := g_cat_list(i).min_items;
ELSE
g_cat_hash (g_cat_list(i).id) := i;
END IF;

END LOOP;
g_cat_list.TRIM;

FOR i IN 1..g_item_cat_list.COUNT LOOP

IF g_item_cat_list(i).price < g_min_item_price THEN
g_min_item_price := g_item_cat_list(i).price;
END IF;

IF g_item_cat_list(i).profit > g_max_item_profit THEN
g_max_item_profit := g_item_cat_list(i).profit;
END IF;
l_item_price_hash (Dedup_Hash (p_card => g_item_cat_list.COUNT, p_key => g_item_cat_list(i).price, p_hash => l_item_price_hash)) := i;
l_item_profit_hash (Dedup_Hash (p_card => g_item_cat_list.COUNT, p_key => g_item_cat_list(i).profit, p_hash => l_item_profit_hash)) := i;

IF g_item_cat_list(i).cat_id != l_last_cat THEN
--
-- Cat has changed, so reset the itm number to zero, and assign the list of items
--  for previous cat
--
n_cat := n_cat + 1;
g_item_range_list (n_cat).item_beg := i;
IF i > 1 THEN
g_item_range_list (n_cat - 1).item_end := i - 1;
END IF;
l_last_cat := g_item_cat_list(i).cat_id;

END IF;

END LOOP;

g_n_items := g_item_cat_list.COUNT;
g_item_range_list (g_n_cats).item_end := g_n_items;
g_min_price_togo.EXTEND (g_set_size);
g_max_profit_togo.EXTEND (g_set_size);
l_price := l_item_price_hash.FIRST;
l_profit := l_item_profit_hash.LAST;
Write_Log ('Hash first price min / profit max ' || l_price || ' / ' || l_profit);
g_min_price_togo (g_set_size) := 0;
g_max_profit_togo (g_set_size) := 0;

FOR i IN 1..g_set_size - 1 LOOP

g_min_price_togo (g_set_size - i) := g_min_price_togo (g_set_size - i + 1) + l_price / g_item_cat_list.COUNT;
g_max_profit_togo (g_set_size - i) := g_max_profit_togo (g_set_size - i + 1) + l_profit / g_item_cat_list.COUNT;

l_price := l_item_price_hash.NEXT (l_price);
l_profit := l_item_profit_hash.PRIOR (l_profit);
Write_Log ((g_set_size - i) || ': price min / profit max ' || g_min_price_togo (g_set_size - i) || ' / ' || g_max_profit_togo (g_set_size - i));

END LOOP;

Write_Log ('Price min / profit max ' || g_min_item_price || ' / ' || g_max_item_profit);
FOR i IN 1..g_n_cats LOOP

Utils.Write_Log ('Cat ' || i || ' : ' || g_cat_list(i).id || ' - ' || g_cat_list(i).min_items || ' - ' || g_cat_list(i).max_items || ' - ' || g_item_range_list(i).item_beg || ' - ' || g_item_range_list(i).item_end);

END LOOP;

g_sol_list.EXTEND (g_n_size);
FOR i IN 1..g_n_size LOOP
g_profit_hash (i) := i;
END LOOP;
g_nth_profit := g_profit_hash.FIRST;
g_trial_sol.price := 0;
g_trial_sol.profit := 0;

END Pop_Arrays;

PROCEDURE Get_Best_Item_List (p_position PLS_INTEGER, p_item_index_beg PLS_INTEGER, p_item_index_end PLS_INTEGER, x_item_hash IN OUT NOCOPY int_hash_type) IS

PROCEDURE Check_Item (p_item_index PLS_INTEGER) IS

l_item_rec         item_cat_rec_type := g_item_cat_list (p_item_index);
l_item_list        num_list_type;
Item_Failed        EXCEPTION;
l_item_str         VARCHAR2(200) := LPad (l_item_rec.id, (p_position)*3, '.') || '-' || l_item_rec.cat_id  || '-' || l_item_rec.price  || '-' || l_item_rec.profit;

FUNCTION Price_LB (p_position PLS_INTEGER) RETURN PLS_INTEGER IS
BEGIN

RETURN g_min_price_togo (p_position);

END Price_LB;

FUNCTION Profit_UB (p_position PLS_INTEGER) RETURN PLS_INTEGER IS
BEGIN

RETURN g_max_profit_togo (p_position);

END Profit_UB;

BEGIN

IF l_item_rec.price + g_trial_sol.price + Price_LB (p_position) > g_max_price THEN

l_item_str := l_item_str || ' [price failed ' || (l_item_rec.price + g_trial_sol.price) || ']';
IF (g_set_size - p_position) = 0 THEN
Write_Log ('Solution fails with price of ' || (l_item_rec.price + g_trial_sol.price), 1);
END IF;
RAISE Item_Failed;
END IF;

IF l_item_rec.profit + g_trial_sol.profit + Profit_UB (p_position) <= g_nth_profit THEN
l_item_str := l_item_str || ' [profit failed ' || (l_item_rec.profit + g_trial_sol.profit) || ', nth = ' || g_nth_profit || ']';
IF (g_set_size - p_position) = 0 THEN
Write_Log ('Solution fails with profit of ' || (l_item_rec.profit + g_trial_sol.profit), 1);
g_n_sols := g_n_sols + 1;
END IF;
RAISE Item_Failed;
END IF;

x_item_hash (Dedup_Hash (p_card => g_keep_size, p_key => l_item_rec.profit + g_trial_sol.profit, p_hash => x_item_hash)) := p_item_index;

EXCEPTION

WHEN Item_Failed THEN
Write_Log (l_item_str, 2);

END Check_Item;

BEGIN

FOR i IN p_item_index_beg..p_item_index_end LOOP

Check_Item (i);

END LOOP;

END Get_Best_Item_List;

l_nth_index        PLS_INTEGER;
BEGIN

g_n_sols := g_n_sols + 1;
l_nth_index := g_profit_hash (g_profit_hash.FIRST);
Write_Log ('Solution replaces in position ' || l_nth_index || ' profit is ' || g_trial_sol.profit || ' price is ' || g_trial_sol.price, 1);

g_profit_hash.DELETE (g_profit_hash.FIRST);
g_profit_hash (Dedup_Hash (p_card => g_n_size, p_key => g_trial_sol.profit, p_hash => g_profit_hash)) := l_nth_index;

g_sol_list (l_nth_index) := g_trial_sol;
g_nth_profit := g_profit_hash.FIRST / g_n_size;

IF Mod (g_n_sols, c_hash_renew_point) = 0 THEN -- Not sur eif this works, but is intended to clear memory overhang
g_hash_buffer :=  g_profit_hash;
g_profit_hash :=  g_hash_buffer;
END IF;

PROCEDURE Add_Item_To_Trial (p_position PLS_INTEGER, p_item_index PLS_INTEGER) IS

l_item_rec          item_cat_rec_type := g_item_cat_list (p_item_index);

BEGIN

g_trial_sol.price := g_trial_sol.price + l_item_rec.price;
g_trial_sol.profit := g_trial_sol.profit + l_item_rec.profit;

IF g_trial_sol.item_list IS NULL THEN
g_trial_sol.item_list := id_list_type (l_item_rec.id);
ELSE
g_trial_sol.item_list.EXTEND;
g_trial_sol.item_list (p_position) := l_item_rec.id;
END IF;

IF p_position = g_set_size THEN

END IF;

FUNCTION Try_Position (p_position PLS_INTEGER, p_n_curr_cat PLS_INTEGER, p_cat_index_beg PLS_INTEGER, p_item_index_beg PLS_INTEGER) RETURN BOOLEAN IS

l_item_hash       int_hash_type;
l_item_index      PLS_INTEGER;
l_cat_index_beg   PLS_INTEGER := p_cat_index_beg;
l_item_index_beg  PLS_INTEGER := p_item_index_beg;
l_n_curr_cat      PLS_INTEGER := p_n_curr_cat;
l_profit          PLS_INTEGER;
BEGIN

g_n_recursive_calls := g_n_recursive_calls + 1;
IF g_n_recursive_calls > g_max_calls THEN
Write_Log (LPad ('*', p_position, '*') || 'Truncating search after ' || g_max_calls || ' recursive calls***');
RETURN TRUE;
END IF;

IF p_n_curr_cat = g_cat_list (p_cat_index_beg).max_items THEN
--
-- passed in the cat we were on in last position
-- check max not passed, if so go to next cat and reset item range
--
Write_Log ('Maxed Cat ' || p_cat_index_beg || ': ' || p_n_curr_cat || '-' || g_cat_list (p_cat_index_beg).max_items, 5);
l_cat_index_beg := p_cat_index_beg + 1;
IF l_cat_index_beg > g_n_cats THEN
RETURN FALSE;
END IF;
l_item_index_beg := g_item_range_list (l_cat_index_beg).item_beg;
l_n_curr_cat := 0;

END IF;

FOR j IN l_cat_index_beg..g_n_cats LOOP

IF l_item_index_beg < g_item_range_list (j).item_beg THEN
l_item_index_beg := g_item_range_list (j).item_beg;
END IF;

Write_Log ('Start Cat ' || j || ': ' || l_n_curr_cat || '-' || g_cat_list(j).min_items, 5);
l_n_curr_cat := l_n_curr_cat + 1;
l_item_hash.DELETE;
Get_Best_Item_List (p_position => p_position, p_item_index_beg => l_item_index_beg, p_item_index_end => g_item_range_list(j).item_end, x_item_hash => l_item_hash);
IF l_item_hash IS NOT NULL THEN

l_profit := l_item_hash.LAST;
FOR i IN 1..Least (g_keep_size, l_item_hash.COUNT) LOOP
l_item_index := l_item_hash (l_profit);
Write_Log (LPad (g_item_cat_list (l_item_index).id, (p_position)*3, '.') || '-' || g_item_cat_list (l_item_index).cat_id  || '-' || g_item_cat_list (l_item_index).price  || '-' || g_item_cat_list (l_item_index).profit, 1);
Add_Item_To_Trial (p_position => p_position, p_item_index => l_item_index);
IF p_position < g_set_size THEN

IF Try_Position (p_position => p_position + 1, p_n_curr_cat => l_n_curr_cat, p_cat_index_beg => j, p_item_index_beg => l_item_index + 1) THEN RETURN TRUE; END IF;

END IF;

IF g_trial_sol.item_list IS NOT NULL AND g_trial_sol.item_list.COUNT = p_position THEN
g_trial_sol.item_list.TRIM;
g_trial_sol.price := g_trial_sol.price - g_item_cat_list (l_item_index).price;
g_trial_sol.profit := g_trial_sol.profit - g_item_cat_list (l_item_index).profit;
END IF;

l_profit := l_item_hash.PRIOR (l_profit);

END LOOP;

ELSE
Write_Log ('No items found');
END IF;
--
--  Don't look at any more cats if we are not past the minimum for the current one at this position
--
Write_Log ('Cat ' || j || ': ' || l_n_curr_cat || '-' || g_cat_list(j).min_items, 5);
IF l_n_curr_cat <= g_cat_list(j).min_items THEN
EXIT;
END IF;

l_n_curr_cat := 0;

END LOOP;
RETURN FALSE;

END Try_Position;

FUNCTION Best_N_Sets (  p_keep_size     PLS_INTEGER,
p_max_calls     PLS_INTEGER,
p_n_size        PLS_INTEGER,
p_max_price     PLS_INTEGER,
p_cat_cur       SYS_REFCURSOR,
p_item_cur      SYS_REFCURSOR) RETURN sol_detail_list_type PIPELINED IS

l_sol_detail_rec          sol_detail_rec_type;
l_position                PLS_INTEGER := 1;
BEGIN

g_keep_size := p_keep_size; g_max_calls := p_max_calls; g_n_size := p_n_size; g_max_price := p_max_price;

Pop_Arrays (p_cat_cur, p_item_cur);

IF Try_Position (p_position => 1, p_n_curr_cat => 0, p_cat_index_beg => 1, p_item_index_beg => 1) THEN NULL; END IF;

FOR i IN 1..g_n_size LOOP

l_sol_detail_rec.set_id := i;
l_sol_detail_rec.sol_price := g_sol_list(i).price;
l_sol_detail_rec.sol_profit := g_sol_list(i).profit;

IF g_sol_list(i).item_list IS NOT NULL THEN

FOR j IN 1..g_sol_list(i).item_list.COUNT LOOP

l_sol_detail_rec.item_id := g_sol_list(i).item_list(j);
PIPE ROW (l_sol_detail_rec);

END LOOP;

END IF;

END LOOP;

Write_Log (g_n_sols || ' solutions found in ' || g_n_recursive_calls || ' recursive calls');

RETURN;

END Best_N_Sets;

END Item_Cats;
/
SHO ERR
```

How It Works

The solution approach uses a modified depth-first recursion, following a similar idea to the SQL method, of adding items in strictly increasing order of category and profit ranking. Treatment of constraints uses similar ideas to the SQL solution.

• The package is completely generic, with the items and categories being specified by means of input cursors
• Depth-first is modified by a ranking of the next sets of feasible items, partitioned by category, in order to limit the number progressed
• Hashes (associative arrays in Oracle) are used for ranking
• A function, Dedup_Hash is used to allow for duplicate hash keys; it works by storing as key the actual key multiplied by the ranking set cardinality, then adding one iteratively until no duplication occurs
• The recursion is truncated if the number of recursive calls exceeds an input limit
• The input cursors are read into arrays and all subsequent processing is in memory; this is not a scalability problem because only the current best solutions are retained; also, I reset hashes after a given number of updates

Results

Test Problem 1: Brazilian League

The pipelined function solved this in 5 seconds, while the SQL solution solved it in 21 seconds. The solutions were identical, as follows:

```TOT_PROFIT  TOT_PRICE        RNK PO P_ID PLAYER_NAME                    CLUB_NAME                           PRICE AVG_POINTS
---------- ---------- ---------- -- ---- ------------------------------ ------------------------------ ---------- ----------
10923      18176          1 CB 098  Digão                          Fluminense                            931        927
099  Samir                          Flamengo                              267        680
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 022  Wilson                         Vitória                              1239        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
19027          2 CB 098  Digão                          Fluminense                            931        927
099  Samir                          Flamengo                              267        680
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 021  Fábio                          Cruzeiro                             2090        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
10905      18795          3 CB 098  Digão                          Fluminense                            931        927
099  Samir                          Flamengo                              267        680
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 023  Vanderlei                      Coritiba                             1858        776
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
10833      18994          4 CB 098  Digão                          Fluminense                            931        927
102  Bressan                        Grêmio                               1085        590
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 022  Wilson                         Vitória                              1239        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
19845          5 CB 098  Digão                          Fluminense                            931        927
102  Bressan                        Grêmio                               1085        590
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 021  Fábio                          Cruzeiro                             2090        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
10831      19608          6 CB 098  Digão                          Fluminense                            931        927
103  Manoel                         Atlético-PR                          1699        588
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 022  Wilson                         Vitória                              1239        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
10825      18190          7 CB 098  Digão                          Fluminense                            931        927
099  Samir                          Flamengo                              267        680
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 022  Wilson                         Vitória                              1239        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
040  Egídio                         Cruzeiro                             1482        752
19041          8 CB 098  Digão                          Fluminense                            931        927
099  Samir                          Flamengo                              267        680
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 021  Fábio                          Cruzeiro                             2090        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
040  Egídio                         Cruzeiro                             1482        752
10821      19370          9 CB 098  Digão                          Fluminense                            931        927
104  Cléber                         Ponte Preta                          1461        578
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 022  Wilson                         Vitória                              1239        794
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850
10815      19613         10 CB 098  Digão                          Fluminense                            931        927
102  Bressan                        Grêmio                               1085        590
CO 078  Jaime De AlMFda                Flamengo                             1156        803
FW 001  Éderson                        Atlético-PR                          1712       1012
002  Maxi Biancucchi                Vitória                              1962       1005
003  Rafael Sobis                   Fluminense                           2303        955
GK 023  Vanderlei                      Coritiba                             1858        776
MF 058  Fred                           Internacional                        3028        892
059  Zé Roberto                     Grêmio                               2593        878
060  Otavinho                       Internacional                         762        807
WB 038  Ivan                           Portuguesa                            755       1320
039  Elsinho                        Vasco                                1468        850

120 rows selected.
```

Test Problem 2: English Premier League

I used a maximum price of 900, and a keep parameter of 40, meaning retain the best 40 records by partition during recursion for the SQL and a value of 10 for the pipelined function. The keep parameter operates differently in the two cases so does not need to be the same value.

The SQL solution took 98 seconds, while the pipelined function took 290 seconds. Both methods got the same best solution, but the tenth best was marginally better for the SQL, at 1965, compared with 1962 for pipelined function (which truncated after 100000 recursive calls).

[For followers of Manchester United, and David Moyes, it may be of interest to note that all of the best solutions included both Leighton Baines and Patrice Evra, and the best also had Marouane Fellaini. Will these players be united also in the real world next season?]

SQL Solution

```TOT_PROFIT  TOT_PRICE        RNK PO P_ID PLAYER_NAME                    CLUB_NAME            PRICE AVG_POINTS
---------- ---------- ---------- -- ---- ------------------------------ --------------- ---------- ----------
1973        898          1 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
265  Steven Gerrard                 Liverpool               92        187
641  Miguel Michu                   Swansea                 79        169
177  Marouane Fellaini              Everton                 73        168
1971        899          2 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
047  Christian Benteke              Aston Villa             74        166
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
265  Steven Gerrard                 Liverpool               92        187
641  Miguel Michu                   Swansea                 79        169
1970        893          3 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
047  Christian Benteke              Aston Villa             74        166
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
265  Steven Gerrard                 Liverpool               92        187
177  Marouane Fellaini              Everton                 73        168
1968        899          4 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
047  Christian Benteke              Aston Villa             74        166
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
265  Steven Gerrard                 Liverpool               92        187
177  Marouane Fellaini              Everton                 73        168
428  Robert Snodgrass               Norwich                 62        152
5 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
569  Ryan Shawcross                 Stoke City              56        133
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
149  Juan Mata                      Chelsea                102        190
641  Miguel Michu                   Swansea                 79        169
177  Marouane Fellaini              Everton                 73        168
900          6 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
204  Dimitar Berbatov               Fulham                  71        161
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
149  Juan Mata                      Chelsea                102        190
177  Marouane Fellaini              Everton                 73        168
1966        896          7 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
204  Dimitar Berbatov               Fulham                  71        161
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
265  Steven Gerrard                 Liverpool               92        187
641  Miguel Michu                   Swansea                 79        169
900          8 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
569  Ryan Shawcross                 Stoke City              56        133
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
047  Christian Benteke              Aston Villa             74        166
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
149  Juan Mata                      Chelsea                102        190
641  Miguel Michu                   Swansea                 79        169
1965        890          9 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
268  Glen Johnson                   Liverpool               65        141
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
204  Dimitar Berbatov               Fulham                  71        161
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
265  Steven Gerrard                 Liverpool               92        187
177  Marouane Fellaini              Everton                 73        168
894         10 DF 165  Leighton Baines                Everton                 78        173
332  Patrice Evra                   Man Utd                 73        152
569  Ryan Shawcross                 Stoke City              56        133
FW 286  Luis Suarez                    Liverpool              105        213
533  Rickie Lambert                 Southampton             69        178
047  Christian Benteke              Aston Villa             74        166
GK 549  Asmir Begovic                  Stoke City              56        154
MF 661  Gareth Bale                    Tottenham              111        240
030  Santi Santi Cazorla            Arsenal                 97        198
149  Juan Mata                      Chelsea                102        190
177  Marouane Fellaini              Everton                 73        168

110 rows selected.

Elapsed: 00:01:38.26
```

Pipelined Function Solution

```SOL_PROFIT  SOL_PRICE        RNK PO ITEM_ID    CLUB_NAME       PLAYER_NAME               PRICE AVG_POINTS
---------- ---------- ---------- -- ---------- --------------- -------------------- ---------- ----------
1973        898          1 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 177        Everton         Marouane Fellaini            73        168
265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
641        Swansea         Miguel Michu                 79        169
661        Tottenham       Gareth Bale                 111        240
1971        899          2 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
47         Aston Villa     Christian Benteke            74        166
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
641        Swansea         Miguel Michu                 79        169
661        Tottenham       Gareth Bale                 111        240
1970        893          3 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
47         Aston Villa     Christian Benteke            74        166
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 177        Everton         Marouane Fellaini            73        168
265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
661        Tottenham       Gareth Bale                 111        240
1968        900          4 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 204        Fulham          Dimitar Berbatov             71        161
286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 149        Chelsea         Juan Mata                   102        190
177        Everton         Marouane Fellaini            73        168
30         Arsenal         Santi Santi Cazorla          97        198
661        Tottenham       Gareth Bale                 111        240
1966        896          5 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 204        Fulham          Dimitar Berbatov             71        161
286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
641        Swansea         Miguel Michu                 79        169
661        Tottenham       Gareth Bale                 111        240
1965        890          6 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 204        Fulham          Dimitar Berbatov             71        161
286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 177        Everton         Marouane Fellaini            73        168
265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
661        Tottenham       Gareth Bale                 111        240
897          7 DF 165        Everton         Leighton Baines              78        173
248        Liverpool       Daniel Agger                 64        133
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 177        Everton         Marouane Fellaini            73        168
265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
641        Swansea         Miguel Michu                 79        169
661        Tottenham       Gareth Bale                 111        240
1964        895          8 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
720        West Brom       Romelu Lukaku                66        157
GK 549        Stoke City      Asmir Begovic                56        154
MF 149        Chelsea         Juan Mata                   102        190
177        Everton         Marouane Fellaini            73        168
30         Arsenal         Santi Santi Cazorla          97        198
661        Tottenham       Gareth Bale                 111        240
1963        898          9 DF 165        Everton         Leighton Baines              78        173
248        Liverpool       Daniel Agger                 64        133
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
47         Aston Villa     Christian Benteke            74        166
533        Southampton     Rickie Lambert               69        178
GK 549        Stoke City      Asmir Begovic                56        154
MF 265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
641        Swansea         Miguel Michu                 79        169
661        Tottenham       Gareth Bale                 111        240
1962        891         10 DF 165        Everton         Leighton Baines              78        173
268        Liverpool       Glen Johnson                 65        141
332        Man Utd         Patrice Evra                 73        152
FW 286        Liverpool       Luis Suarez                 105        213
533        Southampton     Rickie Lambert               69        178
720        West Brom       Romelu Lukaku                66        157
GK 549        Stoke City      Asmir Begovic                56        154
MF 265        Liverpool       Steven Gerrard               92        187
30         Arsenal         Santi Santi Cazorla          97        198
641        Swansea         Miguel Michu                 79        169
661        Tottenham       Gareth Bale                 111        240

110 rows selected.

Elapsed: 00:04:49.92
```

Conclusions

My idea for using recursive subquery factoring to solve combinatorial optimisation problems, such as knapsack problems, described in other articles on my blog, was previously only practical for small problems. The extensions described here render it a practical proposition even for larger problems. It is also relatively simple compared with procedural approaches.

# SQL for the Balanced Number Partitioning Problem

I noticed a post on AskTom recently that referred to an SQL solution to a version of the so-called Bin Fitting problem, where even distribution is the aim. The solution, How do I solve a Bin Fitting problem in an SQL statement?, uses Oracle's Model clause, and, as the poster of the link observed, has the drawback that the number of bins is embedded in the query structure. I thought it might be interesting to find solutions without that drawback, so that the number of bins could be passed to the query as a bind variable. I came up with three solutions using different techniques, starting here.

An interesting article in American Scientist, The Easiest Hard Problem, notes that the problem is NP-complete, or certifiably hard, but that simple greedy heuristics often produce a good solution, including one used by schoolboys to pick football teams. The article uses the more descriptive term for the problem of balanced number partitioning, and notes some practical applications. The Model clause solution implements a multiple-bin version of the main Greedy Algorithm, while my non-Model SQL solutions implement variants of it that allow other techniques to be used, one of which is very simple and fast: this implements the team picking heuristic for multiple teams.

Another poster, Stew Ashton, suggested a simple change to my Model solution that improved performance, and I use this modified version here. He also suggested that using PL/SQL might be faster, and I have added my own simple PL/SQL implementation of the Greedy Algorithm, as well as a second version of the recursive subquery factoring solution that performs better than the first.

This article explains the solutions, considers two simple examples to illustrate them, and reports on performance testing across dimensions of number of items and number of bins. These show that the solutions exhibit either linear or quadratic variation in execution time with number of items, and some methods are sensitive to the number of bins while others are not.

After I had posted my solutions on the AskTom thread, I came across a thread on OTN, need help to resolve this issue, that requested a solution to a form of bin fitting problem where the bins have fixed capacity and the number of bins required must be determined. I realised that my solutions could easily be extended to add that feature, and posted extended versions of two of the solutions there. I have added a section here for this.

Updated, 5 June 2013: added Model and RSF diagrams

Greedy Algorithm Variants

Say there are N bins and M items.

Greedy Algorithm (GDY)
Set bin sizes zero
Loop over items in descending order of size

• Add item to current smallest bin
• Calculate new bin size

End Loop

Greedy Algorithm with Batched Rebalancing (GBR)
Set bin sizes zero
Loop over items in descending order of size in batches of N items

• Assign batch to N bins, with bins in ascending order of size
• Calculate new bin sizes

End Loop

Greedy Algorithm with No Rebalancing - or, Team Picking Algorithm (TPA)
Assign items to bins cyclically by bin sequence in descending order of item size

Two Examples

Example: Four Items

Here we see that the Greedy Algorithm finds the perfect solution, with no difference in bin size, but the two variants have a difference of two.
Example: Six Items

Here we see that none of the algorithms finds the perfect solution. Both the standard Greedy Algorithm and its batched variant give a difference of two, while the variant without rebalancing gives a difference of four.

SQL Solutions

Original Model for GDY
See the link above for the SQL for the problem with three bins only.

The author has two measures for each bin and implements the GDY algorithm using CASE expressions and aggregation within the rules. The idea is to iterate over the items in descending order of size, setting the item bin to the bin with current smallest value. I use the word 'bin' for his 'bucket'. Some notes:

• Dimension by row number, ordered by item value
• Add measures for the iteration, it, and number of iterations required, counter
• Add measures for the bin name, bucket_name, and current minimum bin value, min_tmp (only first entry used)
• Add measures for each item bin value, bucket_1-3, being the item value if it's in that bin, else zero
• Add measures for each bin running sum, pbucket_1-3, being the current value of each bin (only first two entries used)
• The current minimum bin value, bin_tmp[1] is computed as the least of the running sums
• The current item bin value is set to the item value for the bin whose value matches the minimum just computed, and null for the others
• The current bin name is set similarly to be the bin matching the minimum
• The new running sums are computed for each bin

Brendan's Generic Model for GDY

```SELECT item_name, bin, item_value, Max (bin_value) OVER (PARTITION BY bin) bin_value
FROM (
SELECT * FROM items
MODEL
DIMENSION BY (Row_Number() OVER (ORDER BY item_value DESC) rn)
MEASURES (item_name,
item_value,
Row_Number() OVER (ORDER BY item_value DESC) bin,
item_value bin_value,
Row_Number() OVER (ORDER BY item_value DESC) rn_m,
0 min_bin,
Count(*) OVER () - :N_BINS - 1 n_iters
)
RULES ITERATE(100000) UNTIL (ITERATION_NUMBER >= n_iters[1]) (
min_bin[1] = Min(rn_m) KEEP (DENSE_RANK FIRST ORDER BY bin_value)[rn <= :N_BINS],
bin[ITERATION_NUMBER + :N_BINS + 1] = min_bin[1],
bin_value[min_bin[1]] = bin_value[CV()] + Nvl (item_value[ITERATION_NUMBER + :N_BINS + 1], 0)
)
)
WHERE item_name IS NOT NULL
ORDER BY item_value DESC
```

My Model solution works for any number of bins, passing the number of bins as a bind variable. The key idea here is to use values in the first N rows of a generic bin value measure to store all the running bin values, rather than as individual measures. I have included two modifications suggested by Stew in the AskTom thread.

• Dimension by row number, ordered by item value
• Initialise a bin measure to the row number (the first N items will remain fixed)
• Initialise a bin value measure to item value (only first N entries used)
• Add the row number as a measure, rn_m, in addition to a dimension, for referencing purposes
• Add a min_bin measure for current minimum bin index (first entry only)
• Add a measure for the number of iterations required, n_iters
• The first N items are correctly binned in the measure initialisation
• Set the minimum bin index using analytic Min function with KEEP clause over the first N rows of bin value
• Set the bin for the current item to this index
• Update the bin value for the corresponding bin only

Recursive Subquery Factor for GBR

```WITH bins AS (
SELECT LEVEL bin, :N_BINS n_bins FROM DUAL CONNECT BY LEVEL <= :N_BINS
), items_desc AS (
SELECT item_name, item_value, Row_Number () OVER (ORDER BY item_value DESC) rn
FROM items
), rsf (bin, item_name, item_value, bin_value, lev, bin_rank, n_bins) AS (
SELECT b.bin,
i.item_name,
i.item_value,
i.item_value,
1,
b.n_bins - i.rn + 1,
b.n_bins
FROM bins b
JOIN items_desc i
ON i.rn = b.bin
UNION ALL
SELECT r.bin,
i.item_name,
i.item_value,
r.bin_value + i.item_value,
r.lev + 1,
Row_Number () OVER (ORDER BY r.bin_value + i.item_value),
r.n_bins
FROM rsf r
JOIN items_desc i
ON i.rn = r.bin_rank + r.lev * r.n_bins
)
SELECT r.item_name,
r.bin, r.item_value, r.bin_value
FROM rsf r
ORDER BY item_value DESC```

The idea here is to use recursive subquery factors to iterate through the items in batches of N items, assigning each item to a bin according to the rank of the bin on the previous iteration.

• Initial subquery factors form record sets for the bins and for the items with their ranks in descending order of value
• The anchor branch assign bins to the first N items, assigning the item values to a bin value field, and setting the bin rank in ascending order of this bin value
• The recursive branch joins the batch of items to the record in the previous batch whose bin rank matches that of the item in the reverse sense (so largest item goes to smallest bin etc.)
• The analytic Row_Number function computes the updated bin ranks, and the bin values are updated by simple addition

Recursive Subquery Factor for GBR with Temporary Table
Create Table and Index

```DROP TABLE items_desc_temp
/
CREATE GLOBAL TEMPORARY TABLE items_desc_temp (
item_name  VARCHAR2(30) NOT NULL,
item_value NUMBER(8) NOT NULL,
rn         NUMBER
)
ON COMMIT DELETE ROWS
/
CREATE INDEX items_desc_temp_N1 ON items_desc_temp (rn)
/```

Insert into Temporary Table

```INSERT INTO items_desc_temp
SELECT item_name, item_value, Row_Number () OVER (ORDER BY item_value DESC) rn
FROM items;```

RSF Query with Temporary Table

```WITH bins AS (
SELECT LEVEL bin, :N_BINS n_bins FROM DUAL CONNECT BY LEVEL <= :N_BINS
), rsf (bin, item_name, item_value, bin_value, lev, bin_rank, n_bins) AS (
SELECT b.bin,
i.item_name,
i.item_value,
i.item_value,
1,
b.n_bins - i.rn + 1,
b.n_bins
FROM bins b
JOIN items_desc_temp i
ON i.rn = b.bin
UNION ALL
SELECT r.bin,
i.item_name,
i.item_value,
r.bin_value + i.item_value,
r.lev + 1,
Row_Number () OVER (ORDER BY r.bin_value + i.item_value),
r.n_bins
FROM rsf r
JOIN items_desc_temp i
ON i.rn = r.bin_rank + r.lev * r.n_bins
)
SELECT item_name, bin, item_value, bin_value
FROM rsf
ORDER BY item_value DESC```

The idea here is that in the initial RSF query a subquery factor of items was joined on a calculated field, so the whole record set had to be read, and performance could be improved by putting that initial record set into an indexed temporary table ahead of the main query. We'll see in the performance testing section that this changes quadratic variation with problem size into linear variation.

Plain Old SQL Solution for TPA

```WITH items_desc AS (
SELECT item_name, item_value,
Mod (Row_Number () OVER (ORDER BY item_value DESC), :N_BINS) + 1 bin
FROM items
)
SELECT item_name, bin, item_value, Sum (item_value) OVER (PARTITION BY bin) bin_total
FROM items_desc
ORDER BY item_value DESC
```

The idea here is that the TPA algorithm can be implemented in simple SQL using analyic functions.

• The subquery factor assigns the bins by taking the item rank in descending order of value and applying the modulo (N) function
• The main query returns the bin totals in addition by analytic summing by bin

Pipelined Function for GDY
Package

```CREATE OR REPLACE PACKAGE Bin_Fit AS

TYPE bin_fit_rec_type IS RECORD (item_name VARCHAR2(100), item_value NUMBER, bin NUMBER);
TYPE bin_fit_list_type IS VARRAY(1000) OF bin_fit_rec_type;

TYPE bin_fit_cur_rec_type IS RECORD (item_name VARCHAR2(100), item_value NUMBER);
TYPE bin_fit_cur_type IS REF CURSOR RETURN bin_fit_cur_rec_type;

FUNCTION Items_Binned (p_items_cur bin_fit_cur_type, p_n_bins PLS_INTEGER) RETURN bin_fit_list_type PIPELINED;

END Bin_Fit;
/
CREATE OR REPLACE PACKAGE BODY Bin_Fit AS

c_big_value                 CONSTANT NUMBER := 100000000;
TYPE bin_fit_cur_list_type  IS VARRAY(100) OF bin_fit_cur_rec_type;

FUNCTION Items_Binned (p_items_cur bin_fit_cur_type, p_n_bins PLS_INTEGER) RETURN bin_fit_list_type PIPELINED IS

l_min_bin              PLS_INTEGER := 1;
l_min_bin_val             NUMBER;
l_bins                    SYS.ODCINumberList := SYS.ODCINumberList();
l_bin_fit_cur_rec         bin_fit_cur_rec_type;
l_bin_fit_rec             bin_fit_rec_type;
l_bin_fit_cur_list        bin_fit_cur_list_type;

BEGIN

l_bins.Extend (p_n_bins);
FOR i IN 1..p_n_bins LOOP
l_bins(i) := 0;
END LOOP;

LOOP

FETCH p_items_cur BULK COLLECT INTO l_bin_fit_cur_list LIMIT 100;
EXIT WHEN l_bin_fit_cur_list.COUNT = 0;

FOR j IN 1..l_bin_fit_cur_list.COUNT LOOP

l_bin_fit_rec.item_name := l_bin_fit_cur_list(j).item_name;
l_bin_fit_rec.item_value := l_bin_fit_cur_list(j).item_value;
l_bin_fit_rec.bin := l_min_bin;

PIPE ROW (l_bin_fit_rec);
l_bins(l_min_bin) := l_bins(l_min_bin) + l_bin_fit_cur_list(j).item_value;

l_min_bin_val := c_big_value;
FOR i IN 1..p_n_bins LOOP

IF l_bins(i) < l_min_bin_val THEN
l_min_bin := i;
l_min_bin_val := l_bins(i);
END IF;

END LOOP;

END LOOP;

END LOOP;

END Items_Binned;
```

SQL Query

```SELECT item_name, bin, item_value, Sum (item_value) OVER (PARTITION BY bin) bin_value
FROM TABLE (Bin_Fit.Items_Binned (
CURSOR (SELECT item_name, item_value FROM items ORDER BY item_value DESC),
:N_BINS))
ORDER BY item_value DESC
```

The idea here is that procedural algorithms can often be implemented more efficiently in PL/SQL than in SQL.

• The first parameter to the function is a strongly-typed reference cursor
• The SQL call passes in a SELECT statement wrapped in the CURSOR keyword, so the function can be used for any set of records that returns name and numeric value pairs
• The item records are fetched in batches of 100 using the LIMIT clause to improves efficiency

Performance Testing
I tested performance of the various queries using my own benchmarking framework across grids of data points, with two data sets to split the queries into two sets based on performance.

Query Modifications for Performance Testing

• The RSF query with staging table was run within a pipelined function in order to easily include the insert in the timings
• A system context was used to pass the bind variables as the framework runs the queries from PL/SQL, not from SQL*Plus
• I found that calculating the bin values using analytic sums, as in the code above, affected performance, so I removed this for clarity of results, outputting only item name, value and bin

Test Data Sets
For a given depth parameter, d, random numbers were inserted within the range 0-d for d-1 records. The insert was:

``` INSERT INTO items
SELECT 'item-' || n, DBMS_Random.Value (0, p_point_deep) FROM
(SELECT LEVEL n FROM DUAL CONNECT BY LEVEL < p_point_deep);```

The number of bins was passed as a width parameter, but note that the original, linked Model solution, MODO, hard-codes the number of bins to 3.

Test Results

Data Set 1 - Small
This was used for the following queries:

• MODO - Original Model for GDY
• MODB - Brendan's Generic Model for GDY
• RSFQ - Recursive Subquery Factor for GBR
``` Depth         W3         W3         W3
Run Type=MODO
D1000       1.03       1.77       1.05
D2000       3.98       6.46       5.38
D4000      15.79       20.7      25.58
D8000      63.18      88.75      92.27
D16000      364.2     347.74     351.99
Run Type=MODB
Depth         W3         W6        W12
D1000        .27        .42        .27
D2000          1       1.58       1.59
D4000       3.86        3.8       6.19
D8000      23.26      24.57      17.19
D16000      82.29      92.04      96.02
Run Type=RSFQ
D1000       3.24       3.17       1.53
D2000       8.58       9.68       8.02
D4000      25.65      24.07      23.17
D8000      111.3     108.25      98.33
D16000     471.17     407.65     399.99
```

The results show:

• Quadratic variation of CPU time with number of items
• Little variation of CPU time with number of bins, although RSFQ seems to show some decline
• RSFQ is slightly slower than MODO, while my version of Model, MODB is about 4 times faster than MODO

Data Set 2 - Large
This was used for the following queries:

• RSFT - Recursive Subquery Factor for GBR with Temporary Table
• POSS - Plain Old SQL Solution for TPA
• PLFN - Pipelined Function for GDY

This table gives the CPU times in seconds across the data set:

```  Depth       W100      W1000     W10000
Run Type=PLFN
D20000        .31       1.92      19.25
D40000        .65       3.87      55.78
D80000       1.28       7.72      92.83
D160000       2.67      16.59     214.96
D320000       5.29      38.68      418.7
D640000      11.61      84.57      823.9
Run Type=POSS
D20000        .09        .13        .13
D40000        .18        .21        .18
D80000        .27        .36         .6
D160000        .74       1.07        .83
D320000       1.36       1.58       1.58
D640000       3.13       3.97       4.04
Run Type=RSFT
D20000        .78        .78        .84
D40000       1.41       1.54        1.7
D80000       3.02       3.39       4.88
D160000       6.11       9.56       8.42
D320000      13.05      18.93      20.84
D640000      41.62      40.98      41.09
```

The results show:

• Linear variation of CPU time with number of items
• Little variation of CPU time with number of bins for POSS and RSFT, but roughly linear variation for PLFN
• These linear methods are much faster than the earlier quadratic ones for larger numbers of items
• Its approximate proportionality of time to number of bins means that, while PLFN is faster than RSFT for small number of bins, it becomes slower from around 50 bins for our problem
• The proportionality to number of bins for PLFN presumably arises from the step to find the bin of minimum value
• The lack of proportionality to number of bins for RSFT may appear surprising since it performs a sort of the bins iteratively: However, while the work for this sort is likely to be proportional to the number of bins, the number of iterations is inversely proportional and thus cancels out the variation

Solution Quality

The methods reported above implement three underlying algorithms, none of which guarantees an optimal solution. In order to get an idea of how the quality compares, I created new versions of the second set of queries using analytic functions to output the difference between minimum and maximum bin values, with percentage of the maximum also output. I ran these on the same grid, and report below the results for the four corners.

```Method:			PLFN		RSFT		POSS
Point:	W100/D20000
Diff/%:			72/.004%	72/.004%	19,825/1%
Point:	W100/D640000
Diff/%:			60/.000003%	60/.000003%	633499/.03%
Point:	W10000/D20000
Diff/%:			189/.9%		180/.9%		19,995/67%
Point:	W10000/D640000
Diff/%:			695/.003%	695/.003%	639,933/3%```

The results indicate that GDY (Greedy Algorithm) and GBR (Greedy Algorithm with Batched Rebalancing) generally give very similar quality results, while TPA (Team Picking Algorithm) tends to be quite a lot worse.

Extended Problem: Finding the Number of Bins Required

An important extension to the problem is when the bins have fixed capacity, and it is desired to find the minimum number of bins, then spread the items evenly between them. As mentioned at the start, I posted extensions to two of my solutions on an OTN thread, and I reproduce them here. It turns out to be quite easy to make the extension. The remainder of this section is just lifted from my OTN post and refers to the table of the original poster.

Start OTN Extract
So how do we determine the number of bins? The total quantity divided by bin capacity, rounded up, gives a lower bound on the number of bins needed. The actual number required may be larger, but mostly it will be within a very small range from the lower bound, I believe (I suspect it will nearly always be the lower bound). A good practical solution, therefore, would be to compute the solutions for a base number, plus one or more increments, and this can be done with negligible extra work (although Model might be an exception, I haven't tried it). Then the bin totals can be computed, and the first solution that meets the constraints can be used. I took two bin sets here.

SQL POS

```WITH items AS (
SELECT sl_pm_code item_name, sl_wt item_amt, sl_qty item_qty,
Ceil (Sum(sl_qty) OVER () / :MAX_QTY) n_bins
FROM ow_ship_det
), items_desc AS (
SELECT item_name, item_amt, item_qty, n_bins,
Mod (Row_Number () OVER (ORDER BY item_qty DESC), n_bins) bin_1,
Mod (Row_Number () OVER (ORDER BY item_qty DESC), n_bins + 1) bin_2
FROM items
)
SELECT item_name, item_amt, item_qty,
CASE bin_1 WHEN 0 THEN n_bins ELSE bin_1 END bin_1,
CASE bin_2 WHEN 0 THEN n_bins + 1 ELSE bin_2 END bin_2,
Sum (item_amt) OVER (PARTITION BY bin_1) bin_1_amt,
Sum (item_qty) OVER (PARTITION BY bin_1) bin_1_qty,
Sum (item_amt) OVER (PARTITION BY bin_2) bin_2_amt,
Sum (item_qty) OVER (PARTITION BY bin_2) bin_2_qty
FROM items_desc
ORDER BY item_qty DESC, bin_1, bin_2```

SQL Pipelined

```SELECT osd.sl_pm_code item_name, osd.sl_wt item_amt, osd.sl_qty item_qty,
tab.bin_1, tab.bin_2,
Sum (osd.sl_wt) OVER (PARTITION BY tab.bin_1) bin_1_amt,
Sum (osd.sl_qty) OVER (PARTITION BY tab.bin_1) bin_1_qty,
Sum (osd.sl_wt) OVER (PARTITION BY tab.bin_2) bin_2_amt,
Sum (osd.sl_qty) OVER (PARTITION BY tab.bin_2) bin_2_qty
FROM ow_ship_det osd
JOIN TABLE (Bin_Even.Items_Binned (
CURSOR (SELECT sl_pm_code item_name, sl_qty item_value,
Sum(sl_qty) OVER () item_total
FROM ow_ship_det
ORDER BY sl_qty DESC, sl_wt DESC),
:MAX_QTY)) tab
ON tab.item_name = osd.sl_pm_code
ORDER BY osd.sl_qty DESC, tab.bin_1```

Pipelined Function

```CREATE OR REPLACE PACKAGE Bin_Even AS

TYPE bin_even_rec_type IS RECORD (item_name VARCHAR2(100), item_value NUMBER, bin_1 NUMBER, bin_2 NUMBER);
TYPE bin_even_list_type IS VARRAY(1000) OF bin_even_rec_type;

TYPE bin_even_cur_rec_type IS RECORD (item_name VARCHAR2(100), item_value NUMBER, item_total NUMBER);
TYPE bin_even_cur_type IS REF CURSOR RETURN bin_even_cur_rec_type;

FUNCTION Items_Binned (p_items_cur bin_even_cur_type, p_bin_max NUMBER) RETURN bin_even_list_type PIPELINED;

END Bin_Even;
/
SHO ERR
CREATE OR REPLACE PACKAGE BODY Bin_Even AS

c_big_value                 CONSTANT NUMBER := 100000000;
c_n_bin_sets                CONSTANT NUMBER := 2;

TYPE bin_even_cur_list_type IS VARRAY(100) OF bin_even_cur_rec_type;
TYPE num_lol_list_type      IS VARRAY(100) OF SYS.ODCINumberList;

FUNCTION Items_Binned (p_items_cur bin_even_cur_type, p_bin_max NUMBER) RETURN bin_even_list_type PIPELINED IS

l_min_bin                 SYS.ODCINumberList := SYS.ODCINumberList (1, 1);
l_min_bin_val             SYS.ODCINumberList := SYS.ODCINumberList (c_big_value, c_big_value);
l_bins                    num_lol_list_type := num_lol_list_type (SYS.ODCINumberList(), SYS.ODCINumberList());

l_bin_even_cur_rec        bin_even_cur_rec_type;
l_bin_even_rec            bin_even_rec_type;
l_bin_even_cur_list       bin_even_cur_list_type;

l_n_bins                  PLS_INTEGER;
l_n_bins_base             PLS_INTEGER;
l_is_first_fetch          BOOLEAN := TRUE;

BEGIN

LOOP

FETCH p_items_cur BULK COLLECT INTO l_bin_even_cur_list LIMIT 100;
EXIT WHEN l_Bin_Even_cur_list.COUNT = 0;
IF l_is_first_fetch THEN

l_n_bins_base := Ceil (l_Bin_Even_cur_list(1).item_total / p_bin_max) - 1;

l_is_first_fetch := FALSE;

l_n_bins := l_n_bins_base;
FOR i IN 1..c_n_bin_sets LOOP

l_n_bins := l_n_bins + 1;
l_bins(i).Extend (l_n_bins);
FOR k IN 1..l_n_bins LOOP
l_bins(i)(k) := 0;
END LOOP;

END LOOP;

END IF;

FOR j IN 1..l_Bin_Even_cur_list.COUNT LOOP

l_bin_even_rec.item_name := l_bin_even_cur_list(j).item_name;
l_bin_even_rec.item_value := l_bin_even_cur_list(j).item_value;
l_bin_even_rec.bin_1 := l_min_bin(1);
l_bin_even_rec.bin_2 := l_min_bin(2);

PIPE ROW (l_bin_even_rec);

l_n_bins := l_n_bins_base;
FOR i IN 1..c_n_bin_sets LOOP
l_n_bins := l_n_bins + 1;
l_bins(i)(l_min_bin(i)) := l_bins(i)(l_min_bin(i)) + l_Bin_Even_cur_list(j).item_value;

l_min_bin_val(i) := c_big_value;
FOR k IN 1..l_n_bins LOOP

IF l_bins(i)(k) < l_min_bin_val(i) THEN
l_min_bin(i) := k;
l_min_bin_val(i) := l_bins(i)(k);
END IF;

END LOOP;

END LOOP;

END LOOP;

END LOOP;

END Items_Binned;

END Bin_Even;```

Output POS
Note BIN_1 means bin set 1, which turns out to have 4 bins, while bin set 2 then necessarily has 5.

```ITEM_NAME         ITEM_AMT   ITEM_QTY      BIN_1      BIN_2  BIN_1_AMT  BIN_1_QTY  BIN_2_AMT  BIN_2_QTY
--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1239606-1080          4024        266          1          1      25562        995      17482        827
1239606-1045          1880        192          2          2      19394        886      14568        732
1239606-1044          1567        160          3          3      18115        835      14097        688
1239606-1081          2118        140          4          4      18988        793      17130        657
1239606-2094          5741         96          1          5      25562        995      18782        605
...
1239606-2107            80          3          4          2      18988        793      14568        732
1239606-2084           122          3          4          3      18988        793      14097        688
1239606-2110           210          2          2          3      19394        886      14097        688
1239606-4022           212          2          3          4      18115        835      17130        657
1239606-4021           212          2          4          5      18988        793      18782        605```

Output Pipelined

```ITEM_NAME         ITEM_AMT   ITEM_QTY      BIN_1      BIN_2  BIN_1_AMT  BIN_1_QTY  BIN_2_AMT  BIN_2_QTY
--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1239606-1080          4024        266          1          1      20627        878      15805        703
1239606-1045          1880        192          2          2      18220        877      16176        703
1239606-1044          1567        160          3          3      20425        878      15651        701
1239606-1081          2118        140          4          4      22787        876      14797        701
1239606-2094          5741         96          4          5      22787        876      19630        701
...
1239606-2089            80          3          4          1      22787        876      15805        703
1239606-2112           141          3          4          2      22787        876      16176        703
1239606-4022           212          2          1          1      20627        878      15805        703
1239606-4021           212          2          2          1      18220        877      15805        703
1239606-2110           210          2          3          2      20425        878      16176        703```

End OTN Extract

Conclusions

• Various solutions for the balanced number partitioning problem have been presented, using Oracle's Model clause, Recursive Subquery Factoring, Pipelined Functions and simple SQL
• The performance characteristics of these solutions have been tested across a range of data sets
• As is often the case, the best solution depends on the shape and size of the data set
• A simple extension has been shown to allow determining the number of bins required in the bin-fitting interpretation of the problem
• Replacing a WITH clause with a staging table can be a useful technique to allow indexed scans

# SQL for Network Grouping

I noticed an interesting question posted on OTN this (European) Saturday morning,
Hierarchical query to combine two groupings into one broad joint grouping. I quickly realised that the problem posed was an example of a very general class of network problems that arises quite often:

Given a set of nodes and a rule for pair-wise (non-directional) linking, obtain the set of implied networks

Usually, in response to such a problem someone will suggest a CONNECT BY query solution. Unfortunately, although hierarchical SQL techniques can be used theoretically to resolve these non-hierarchical networks, they tend to be extremely inefficient for networks of any size and are therefore often impractical. There are two problems in particular:

1. Non-hierarchical networks have no root nodes, so the traversal needs to be repeated from every node in the network set
2. Hierarchical queries retrieve all possible routes through a network

I illustrated the second problem in my last post, Notes on Profiling Oracle PL/SQL, and I intend to write a longer article on the subject of networks at a later date. The most efficient way to traverse generalised networks in Oracle involves the use of PL/SQL, such as in my Scribd article of June 2010, An Oracle Network Traversal PL SQL Program. For this article, though, I will stick to SQL-only techniques and will write down three solutions in a general format whereby the tables of the specific problem are read by initial subquery factors links_v and nodes_v that are used in the rest of the queries. I'll save detailed explanation and performance analysis for the later article (see update at end of this section).

The three queries use two hierarchical methods and a method involving the Model clause:

1. CONNECT BY: This is the least efficient
2. Recursive subquery factors: This is more efficient than the first but still suffers from the two problems above
3. Model clause: This is intended to bypass the performance problems of hierarchical queries, but is still slower than PL/SQL

[Update, 2 September 2015] I have since written two articles on related subjects, the first, PL/SQL Pipelined Function for Network Analysis describes a PL/SQL program that traverses all networks and lists their structure. The second, Recursive SQL for Network Analysis, and Duality, uses a series of examples to illustrate and explain the different characteristics of the first two recursive SQL methods.

Problem Definition
Data Structure
I have taken the data structure of the OTN poster, made all fields character, and added three more records comprising a second isolated node (10) and a subnetwork of nodes 08 and 09. ITEM_ID is taken to be the primary key.

```SQL> SELECT *
2    FROM item_groups
3  /

ITEM_ID    GROUP1   GROUP2
---------- -------- --------
01         A        100
02         A        100
03         A        101
04         B        100
05         B        102
06         C        103
07         D        101
08         E        104
09         E        105
10         F        106

10 rows selected.
```

Grouping Structure
The poster defines two items to be linked if they share the same value for either GROUP1 or GROUP2 attributes (which could obviously be generalised to any number of attributes), and items are in the same group if they can be connected by a chain of links. Observe that if there were only one grouping attribute then the problem would be trivial as that would itself group the items. Having more than one makes it more interesting and more difficult.

A real world example of such networks can be seen to be sibling networks if one takes people as the nodes and father and mother as the attributes.

Network Diagram

CONNECT BY Solution
SQL

```WITH links_v AS (
SELECT t_fr.item_id node_id_fr,
t_to.item_id node_id_to,
t_fr.item_id || '-' || Row_Number() OVER (PARTITION BY t_fr.item_id ORDER BY t_to.item_id) link_id
FROM item_groups t_fr
JOIN item_groups t_to
ON t_to.item_id > t_fr.item_id
AND (t_to.group1 = t_fr.group1 OR t_to.group2 = t_fr.group2)
), nodes_v AS (
SELECT item_id node_id
FROM item_groups
), tree AS (
CONNECT BY NOCYCLE (node_id_fr = PRIOR node_id_to OR node_id_to = PRIOR node_id_fr OR
node_id_fr = PRIOR node_id_fr OR node_id_to = PRIOR node_id_to)
FROM tree
SELECT g.group_id, l.node_id_fr node_id
UNION
SELECT g.group_id, l.node_id_to
)
SELECT l.group_id "Network", l.node_id "Node"
UNION ALL
FROM nodes_v n
WHERE n.node_id NOT IN (SELECT node_id FROM linked_nodes)
ORDER BY 1, 2
```

Output

```All networks by CONNECT BY - Unlinked nodes share network id 0
Network       Node
------------- ----
10
01-1          01
02
03
04
05
07
08-1          08
09

10 rows selected.
```

Notes on CONNECT BY Solution

• For convenience I have grouped the unlinked nodes into one dummy network; it's easy to assign them individual identifiers if desired

Recursive Subquery Factors (RSF) Solution
SQL

```WITH links_v AS (
SELECT t_fr.item_id node_id_fr,
t_to.item_id node_id_to,
t_fr.item_id || '-' || Row_Number() OVER (PARTITION BY t_fr.item_id ORDER BY t_to.item_id) link_id
FROM item_groups t_fr
JOIN item_groups t_to
ON t_to.item_id > t_fr.item_id
AND (t_to.group1 = t_fr.group1 OR t_to.group2 = t_fr.group2)
), nodes_v AS (
SELECT item_id node_id
FROM item_groups
), rsf (node_id, id, root_id) AS (
SELECT node_id, NULL, node_id
FROM nodes_v
UNION ALL
SELECT CASE WHEN l.node_id_to = r.node_id THEN l.node_id_fr ELSE l.node_id_to END,
FROM rsf r
ON (l.node_id_fr = r.node_id OR l.node_id_to = r.node_id)
AND l.link_id != Nvl (r.id, '0')
) CYCLE node_id SET is_cycle TO '*' DEFAULT ' '
SELECT DISTINCT Min (root_id) OVER (PARTITION BY node_id) "Network", node_id "Node"
FROM rsf
ORDER BY 1, 2
```

Output

```All networks by RSF - Unlinked nodes have their own network ids
Network       Node
------------- ----
01            01
02
03
04
05
07
06            06
08            08
09
10            10

10 rows selected.
```

Notes on Recursive Subquery Factors (RSF) Solution

• Here I have given the unlinked nodes their own network identifiers; they could equally have been grouped together under a dummy network

Model Clause Solution
SQL

```WITH links_v AS (
SELECT t_fr.item_id node_id_fr,
t_to.item_id node_id_to,
t_fr.item_id || '-' || Row_Number() OVER (PARTITION BY t_fr.item_id ORDER BY t_to.item_id) link_id
FROM item_groups t_fr
JOIN item_groups t_to
ON t_to.item_id > t_fr.item_id
AND (t_to.group1 = t_fr.group1 OR t_to.group2 = t_fr.group2)
), nodes_v AS (
SELECT item_id node_id
FROM item_groups
), lnk_iter AS (
SELECT *
CROSS JOIN (SELECT 0 iter FROM DUAL UNION SELECT 1 FROM DUAL)
), mod AS (
SELECT *
FROM lnk_iter
MODEL
DIMENSION BY (Row_Number() OVER (PARTITION BY iter ORDER BY link_id) rn, iter)
MEASURES (Row_Number() OVER (PARTITION BY iter ORDER BY link_id) id_rn, link_id id,
node_id_fr nd1, node_id_to nd2,
1 lnk_cur,
CAST ('x' AS VARCHAR2(100)) nd1_cur,
CAST ('x' AS VARCHAR2(100)) nd2_cur,
0 net_cur,
CAST (NULL AS NUMBER) net,
CAST (NULL AS NUMBER) lnk_prc,
1 not_done,
0 itnum)
RULES UPSERT ALL
ITERATE(100000) UNTIL (lnk_cur[1, Mod (iteration_number+1, 2)] IS NULL)
(
itnum[ANY, ANY] = iteration_number,
not_done[ANY, Mod (iteration_number+1, 2)] = Count (CASE WHEN net IS NULL THEN 1 END)[ANY, Mod (iteration_number, 2)],
lnk_cur[ANY, Mod (iteration_number+1, 2)] =
CASE WHEN not_done[CV(), Mod (iteration_number+1, 2)] > 0 THEN
Nvl (Min (CASE WHEN lnk_prc IS NULL AND net = net_cur THEN id_rn END)[ANY, Mod (iteration_number, 2)],
Min (CASE WHEN net IS NULL THEN id_rn END)[ANY, Mod (iteration_number, 2)])
END,
lnk_prc[ANY, Mod (iteration_number+1, 2)] = lnk_prc[CV(), Mod (iteration_number, 2)],
lnk_prc[lnk_cur[1, Mod (iteration_number+1, 2)], Mod (iteration_number+1, 2)] = 1,
net_cur[ANY, Mod (iteration_number+1, 2)] =
CASE WHEN Min (CASE WHEN lnk_prc IS NULL AND net = net_cur THEN id_rn END)[ANY, Mod (iteration_number, 2)] IS NULL THEN
net_cur[CV(), Mod (iteration_number, 2)] + 1
ELSE
net_cur[CV(), Mod (iteration_number, 2)]
END,
nd1_cur[ANY, Mod (iteration_number+1, 2)] = nd1[lnk_cur[CV(), Mod (iteration_number+1, 2)], Mod (iteration_number, 2)],
nd2_cur[ANY, Mod (iteration_number+1, 2)] = nd2[lnk_cur[CV(), Mod (iteration_number+1, 2)], Mod (iteration_number, 2)],
net[ANY, Mod (iteration_number+1, 2)] =
CASE WHEN (nd1[CV(),Mod (iteration_number+1, 2)] IN (nd1_cur[CV(),Mod (iteration_number+1, 2)], nd2_cur[CV(),Mod (iteration_number+1, 2)]) OR
nd2[CV(),Mod (iteration_number+1, 2)] IN (nd1_cur[CV(),Mod (iteration_number+1, 2)], nd2_cur[CV(),Mod (iteration_number+1, 2)]))
AND net[CV(),Mod (iteration_number, 2)] IS NULL THEN
net_cur[CV(),Mod (iteration_number+1, 2)]
ELSE
net[CV(),Mod (iteration_number, 2)]
END
)
)
SELECT To_Char (net) "Network", nd1 "Node"
FROM mod
WHERE not_done = 0
UNION
SELECT To_Char (net), nd2
FROM mod
WHERE not_done = 0
UNION ALL
FROM nodes_v n
WHERE n.node_id NOT IN (SELECT nd1 FROM mod WHERE nd1 IS NOT NULL UNION SELECT nd2 FROM mod WHERE nd2 IS NOT NULL)
ORDER by 1, 2
```

Output

```All networks by Model - Unlinked nodes share network id 00
Network       Node
------------- ----
10
1             01
02
03
04
05
07
2             08
09

10 rows selected.
```

Notes on Model Clause Solution

• For convenience I have grouped the unlinked nodes into one dummy network; it's easy to assign them individual identifiers if desired
• My Cyclic Iteration technique used here appears to be novel

Conclusions

• It is always advisable with a new problem in SQL to consider whether it falls into a general class of problems for which solutions have already been found
• Three solution methods for network resolution in pure SQL have been presented and demonstrated on a small test problem; the performance issues mentioned should be considered carefully before applying them on larger problems
• The Model clause solution is likely to be the most efficient on larger, looped networks, but if better performance is required then PL/SQL recursion-based methods would be faster
• For smaller problems with few loops the simpler method of recursive subquery factors may be preferred, or, for versions prior to v11.2, CONNECT BY