4

There is a table articles including hierarchical articel structures. 1 assembly consists out of n components. So we are able to browse the structure and usages (up and down) for an article.

Using Oracles hierarchical queries this can be done very efficient on sql level.

SELECT item
FROM articles
START WITH component = '0815'
CONNECT BY NOCYCLE PRIOR assembly = component;

Imagine there is an article screw. This screw is used in lots of assemblies and again their assemblies. We want to figure out if the srew is used in specific assemblies identified by a WHERE clause several levels above.

SELECT item
FROM articles
WHERE attr1 = 'marker' --any condition
START WITH component = '0815'
CONNECT BY NOCYCLE PRIOR assembly = component;

This statement works great, but will evaluate all possible assemblies in the result. In our case we are just interested in if there is at least one assembly which matches and not in the whole result. The statement takes minutes for all assemblies but could be sigificant faster when it stops after the first row to answer the given question.

Is there a way to tell Oracle aborting this query after the first match?

wenzul
  • 3,948
  • 2
  • 21
  • 33
  • If there are not many articles having `attr1 = 'marker'`, why don't you `START WITH` those and reverse the direction of your `CONNECT BY` to find the screws? – Matthew McPeak Jan 26 '17 at 14:00
  • Thank your for your answer. There are `>4k` assemblies for the `WHERE` condition. The object I want to test is the screw. Screw is a worst case article. I want to answer the question is there at least one assembly having special attribute values. I'm sure with your idea the search tree will get even bigger. And I also don't have a chance to *abort* the query on the first match. – wenzul Jan 26 '17 at 14:18
  • At least you can stop every branch exploded after the first match. Kind of `CONNECT BY NOCYCLE PRIOR assembly = component AND PRIOR attr1 != 'marker'` – Serg Jan 26 '17 at 14:27
  • 1
    Why do you need `nocycle`? Can you have cycles in your data? Also: what version of Oracle do you have? In version 11 and above you can use a recursive query, which may be more flexible than hierarchical queries. –  Jan 26 '17 at 14:39
  • @Serg That's right. attr1 = 'marker' occurs almost always as leafs. It's like a shipment identifier. So the question is like: **Was this screw ever shipped within at least one shipped article?** If yes I have to take care in the screw's next revision regarding the change management. I can't derive a stop criteria before searching the next leaf... – wenzul Jan 26 '17 at 14:51
  • @mathguy There should be no cycles in the data. I've just used it for race conditions. Version 11.2.0.3.0. Which alternative could I use? (May beside writing my own PLSQL...). – wenzul Jan 26 '17 at 15:20
  • If you just want Oracle stop processing the query after one row, just add `AND ROWNUM = 1` condition. It may not help in this situation, because Oracle generally performs a breath-first search for hierarchical queries. You may also add a hint `/*+ no_connect_by_filtering */` after `SELECT` but in this case Oracle will try to load the whole `articles` table into memory (or at least used columns, but for sure all rows). – mik Jan 26 '17 at 18:37
  • @mik I can see no benefit... – wenzul Jan 27 '17 at 09:18
  • 1
    @wenzul because what you want is not merely stopping Oracle at the first row, but taking a completely different approach (dfs instead of bfs) which is not implemented in Oracle – mik Jan 27 '17 at 11:45

2 Answers2

2

You can use Recursive subquery factoring to stop all searching like this:

with h(it,art,match,anymatch) as
       (select item, assembly
             ,     case when attr1 = 'marker' then 1 else 0 end
             , max(case when attr1 = 'marker' then 1 else 0 end) over()
          from articles
         where component = '0815'
        union all
        select item, assembly
             ,     case when attr1 = 'marker' then 1 else 0 end
             , max(case when attr1 = 'marker' then 1 else 0 end) over()
          from h, articles
         where art = component
           and anymatch = 0)
cycle art set cycle to 1 default 0
select it item
  from h
 where match = 1
   and cycle = 0

It will return all matches that are found on a smallest possible level.

However as it is breadth first search, it will not be much faster if the first found marker is deep.

Changing condition anymatch = 0 to match = 0 (anymatch would not need to be calculated anymore) would stop only searching down the branch the match is on.

mik
  • 3,575
  • 3
  • 20
  • 29
  • Thank your for your comment. Yes `attr1 = 'marker'` is on a similar deep level (up to 7). – wenzul Jan 26 '17 at 14:46
  • @wenzul - Then I don't understand your question. I can see why, if the row you are searching for might be found at level 2 or 3, you don't want to let the query run all the way to level 7. But if the match will, in fact, be found at level 7, then how could the query take less time? You do have to search all rows at level 2, 3, 4... since you don't know which specific path will take you to your match. –  Jan 26 '17 at 14:59
  • @mathguy I didn't get your point. I want to search the first assembly in the *upper* hierarchie with `attr1=..` and stop if I have found the first occurance. It should behave like a depth-first search with a stop mechanism if the search condition matches. Real case: If a specific article was already in series assembly (imaginary attr1) we have to do additional things during the creation of a new revision of an article. So for a screw I don't want to list all the series assembly usages, I just want to know - oh at least once in series used - take care in revisioning. Becomes more clear? :) – wenzul Jan 26 '17 at 15:22
  • Will it really be a recursive sql result? – wenzul Jan 26 '17 at 16:26
  • Try adding `search depth first by art set o` before `cycle art`, but I do not think Oracle will do a depth-first search but merely will order the results differently. – mik Jan 26 '17 at 17:02
0

To do a real depth-first search you can use a following PL/SQL:

FUNCTION search(p_component IN VARCHAR2, p_attr1 IN VARCHAR2)
RETURN VARCHAR2 IS
  i VARCHAR2(4000);
BEGIN
  FOR q IN (SELECT * FROM articles WHERE component = p_component)
  LOOP
    IF q.attr1 = p_attr1 THEN
      RETURN q.item;
    END IF;
    i := search(q.assembly, p_attr1);
    IF i IS NOT NULL THEN
      RETURN i;
    END IF;
  END LOOP;
  RETURN NULL;
END;

You call the function like this:

search('0815', 'marker')

My guess is that this solution will be much slower if the marker does not appear at all. It also does not check for cycles, and works up to a limited level (a limit for open cursors or for the call stack can be exhausted).

In Oracle 12 you can put PL/SQL inside SQL:

WITH
  FUNCTION search(p_component IN VARCHAR2, p_attr1 IN VARCHAR2)
  RETURN VARCHAR2 IS
    i VARCHAR2(4000);
  BEGIN
    FOR q IN (SELECT * FROM articles WHERE component = p_component)
    LOOP
      IF q.attr1 = p_attr1 THEN
        RETURN q.item;
      END IF;
      i := search(q.assembly, p_attr1);
      IF i IS NOT NULL THEN
        RETURN i;
      END IF;
    END LOOP;
    RETURN NULL;
  END;
SELECT search('0815', 'marker')
  FROM dual
mik
  • 3,575
  • 3
  • 20
  • 29
  • Thank you for your second anwer. PL/SQL would be my last choice. I want to keep the locig inside the application due to maintenance reasons like deployment. The article `0815` doesn't have `attr1 = 'marker'`. The marker exists on another level. `0815` is just the starting article... – wenzul Jan 26 '17 at 16:20