This turns up a lot in the Telco space (where I work). There are several approaches you can use to get reasonable performance while still remaining entirely in the database.
In all cases, you need to drop the "%" from your DIGITS column, that extra character isn't helping you at all.
Option 1. Iterative function.
DECLARE
p VARCHAR(30);
BEGIN
p := :full_number;
WHILE (LENGTH (p) > 0)
LOOP
BEGIN
SELECT [whatever]
INTO :whatever
FROM numbers t
WHERE digits = p;
RETURN;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p := SUBSTR (p, 0, LENGTH (p) - 1);
END;
END LOOP;
END;
This kind of function performs reasonably well, assuming you have a nice index on DIGITS.
Option 1. START/END range keys. Create two secondary columns (BOTH INDEXED).
DIGITS_RANGE_START
DIGITS_RANGE_END
These are derived values that you set by an insert/update trigger.
DIGITS_RANGE_START should be automatically set to DIGITS || CHAR(0)
DIGITS_RANGE_END should be automatically set to DIGITS || CHAR(255)
Now your query can be
SELECT ... FROM numbers t
WHERE (number_to_match || CHR(1) > digits_range_start)
AND (number_to_match || CHR(1) < digits_range_end) AND ... [other conditions]
ORDER BY LENGTH (digits) DESC;
This will hit the indexes with a nice comparison match, and Oracle should be able to do a decent job. It may return multiple matches, you need to take the first one.
More Options
See also this famous thread for fancy options with Index Organized Tables if you want to get real clever and real fast.
http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:4246230700346756268
The dummy root is probably the right track, except you don't actually have to create a dummy root to make multiple roots. Your table also appears to be simultaneously an adjacency pair as well, so you've got a couple of immediate options on how you want to go using the table. Both should probably be tested for suitability, as I have a sneaky feeling that depending on read-a-lot or write-a-lot usage, your mileage from either case may vary.
Since the needs of both your developers and fellow DBAs must be considered, most of the other options are probably out of the question ( though there are loads and loads ). This SQL Fiddle illustrates a couple of ways to leverage the materialized paths inherit in your structure, first through leveraging a multiple-root HIERARCHYID
, then through adjacency pair traversal through recursive CTEs.
To implement multiple roots, the only real "trick" to it is to preserve the sub-sequence on the root level. Instead of simply calling NULL
descendants during root node creation, like so,
INSERT INTO dbo.FailCode ( IDOrg, Name )
VALUES ( HIERARCHYID::GetRoot().GetDescendant( NULL, NULL ), '1' );
each root node can be created using the root as the base level ( though the first one obviously doesn't matter ):
INSERT INTO dbo.FailCode ( IDOrg, Name )
VALUES ( HIERARCHYID::GetRoot().GetDescendant( (
SELECT MAX( IDOrg )
FROM dbo.FailCode
WHERE IDOrg.GetAncestor( 1 ) = HIERARCHYID::GetRoot() ), NULL ), '1' );
INSERT INTO dbo.FailCode ( IDOrg, Name )
VALUES ( HIERARCHYID::GetRoot().GetDescendant( (
SELECT MAX( IDOrg )
FROM dbo.FailCode
WHERE IDOrg.GetAncestor( 1 ) = HIERARCHYID::GetRoot() ), NULL ), '2' );
If you're in more of an UPDATE
situtation, you can create the multiple root nodes off the primary key, if you like:
SELECT CAST( '/' + CONVERT( VARCHAR( 16 ), ID ) + '/' AS HIERARCHYID ).ToString()
FROM dbo.FailCode
WHERE Name IN ( '1', '2' );
Children can then be added to each root node as necessary, business as usual stuff from there.
On the adjacency pairs side of your structure, either of the above inserts would do the trick - the ParentID
for any root node just has to be NULL
. Again, at this point, adding children is pretty evident.
Either way you go about it, the hierarchy capabilities are largely equivalent and either solution can be effective and efficient ( though the adjacency list model will certainly appear to be a little more involved! ).
HIERARCHYID
SELECT Org = IDOrg.ToString(), ID, Name
FROM dbo.FailCode
ORDER BY ID;
Adjacency List
;WITH cte_AdjacencyList AS (
SELECT Lvl = ROW_NUMBER() OVER (
ORDER BY ID ),
Org = CAST( '/' + CONVERT( VARCHAR( 16 ), ROW_NUMBER() OVER (
ORDER BY ID ) ) + '/' AS VARCHAR( 32 ) ),
ParentID, ID, Name
FROM dbo.FailCode
WHERE ParentID IS NULL
UNION ALL
SELECT Lvl = ROW_NUMBER() OVER (
ORDER BY fc.ID ),
Org = CAST ( al.Org + CONVERT( VARCHAR( 16 ), ROW_NUMBER() OVER (
ORDER BY fc.ID ) ) + '/' AS VARCHAR( 32 ) ),
fc.ParentID, fc.ID, fc.Name
FROM dbo.FailCode fc
INNER JOIN cte_AdjacencyList al
ON fc.ParentID = al.ID )
SELECT Org, ID, Name
FROM cte_AdjacencyList
ORDER BY ID;
Best Answer
To do that you could try:
Datapump uses a similar strategy to prevent constraint violations. I hope this helps.