PL/SQL Problem (hierarchical trees)

Hi friends,
(DB: 9.2.0.7) It's not an university or school problem. It's for the real job/work... This is only an example but with the same problematic of real problem.
I have to create a select , that receives a parameter (padre) which would be put into the where clause.. and that has to show all its "descendants" ...
It is for this structure... and I'm thinking about how to do it.. but I don't kow how!
Please help me with your code if you are able to do it.
It seems to be simple.. but it's not so.
------------------------------------------------------------------------
Imagine you have:
CREATE TABLE FAMILY
(PADRE VARCHAR2(10),
CHILD_LOW VARCHAR2(10),
CHILD_HIGH VARCHAR2(10)
);

INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('01','01','01');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('01','AA','AN');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('01','K0','K50');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('AA','100','199');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('K2','500','600');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('500','VV','VV');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('02','02','02');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('02','B0','B9');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('02','1000','2000');
INSERT INTO FAMILY (PADRE ,CHILD_LOW,CHILD_HIGH) VALUES ('1500','MA','MZ');

------------------------------------------------------------------------
This will show this structure, a "parent" with a range of values (low and high) for his sons and descendants

--------------------
PADRE CHILD_LOW CHILD_HIGH
01 01 01
01 AA AN
01 K0 K50
AA 100 199
K2 500 600
500 VV VV
02 02 02
02 B0 B9
02 1000 2000
1500 MA MZ
------------------------------
Te idea is give a "parent" value to the select and... the magic query  will return the collection of descendants ranges.
I.e. for '01' it would return:
AA AN
K0 K50
100 199
500 600
VV VV

for 'K2' it would return:
500 600
VV VV

I have the initial query:

select child_low ||'-' ||child_high DESCENDANTS
from (select padre,child_low,child_high from family where
padre<>child_low)
start with padre = '01'
connect by prior child_low <=padre and prior child_high >= padre;

The from clause where it appears padre<>child_low is to avoid the loops (i.e. when a parent and the child would be '01' in this example)

PROBLEM:
In this query it apears as descendant an interval that it isn't that, MA-MZ. this is because ranges are varchars,and, in this case, when we compare '1500'<'199' .. the problem appears.

Solution is not to change varchar to number in database..  We need to find the way to compare varchars... that's the difficulty

So.. your help would be very apreciated...
Thanks,
Jose L
joselfmAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

schwertnerCommented:
Possibly
TO_NUMBER('1500') < TO_NUMBER ('199')
0
joselfmAuthor Commented:
Oh please... read a little the question and try to execute in your database....
0
schwertnerCommented:
SELECT CASE WHEN TO_NUMBER('1500' )< TO_NUMBER('199')
             THEN 'LESS'
             ELSE 'EQUAL OR BIGGER'
             END
FROM DUAL;

Result:
EQUAL OR BIGGER
0
The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

Jinesh KamdarCommented:
@schwertner: He can't use TO_NUMBER because child_low and child_high fields have alphabetic data too.

@joselfm: Try using TRANSLATE function on these to replace all alphabets to 0 and then see if it works.
0
grzessioCommented:
Hi.

It seems that You will have to write a function that will compare those strings/varchars.

it would be something like this:

create function less_than (str1, str2)
-- return 1 if str1 < str2 else return 0
substr1 := left part of str1 (from beginning to the first number in string)
substr2 := left part of str2 (from beginning to the first number in string)
 
if length(substr1) > 0 and length(substr2) > 0 then
  -- string##number
  if (substr1 < substr2) then
    sufstr1 := to_number(sufix(str1))
    sufstr2 := to_number(sufix(str2))
    
    if (len(sufstr1) > 0) and (len(sufstr2) > 0)
    then 
    
      if (sufstr1 < sufstr2) and 
      then return 1
      else return 0
      end if
      
    else return 1 -- only strings compared
    end if
    
  else 
    return 0
  end if
  
else 
  -- number
  sufstr1 := to_number(sufix(str1))
  sufstr2 := to_number(sufix(str2))
  
  if (len(sufstr1) > 0) and (len(sufstr2) > 0)
  then 
  
    if (sufstr1 < sufstr2) and 
    then return 1
    else return 0
    end if
    
  else return 0 -- empty string????
  end if
  
end if

Open in new window

0
joselfmAuthor Commented:
Hi grzessio,
the problem is to apply the fnction with the prior clause.. I've already tried some similar functions.. but... with errors when I tried to apply them.
Thanks a lot for your comments but.. if you wanna put  a solution... please.. write here the final query..
The test in a database would be very simple because I've posted here the example structure...


Jose
0
schwertnerCommented:
Yes, I understood this.
In this case I see only 2 solutions:
1. "Rectify" the model using either only numbers or only characters
2.  Use RPAD function with appropriate values to make the entries equal in length
3. Write a custom function that will do the comparison

In all cases you should rectify your question. You have to point us what is your problem.
Sofar I understood that you experience dificulties to compare two varcha2 values.
Because you have numbers and alpha strings could you please explain us
exactly the rules of the comparison operation?
I am correct?
0
joselfmAuthor Commented:
Hi,
1.-rectify the model is not possible.. Remember we are working with database of customer. And we work under Oracle Applications model.. so.. Impossible modify the model :-)
2.- Use RPAD isn't  asolution.. the problem persists if strings have the same length too..
3.- I've tried different solutions with functions to compare the strings.. the problem is that they fall in error when we try to use it in the where with the prior clause...
It's not so easy ;-)
Thanks a lot for your comments schwertner.

Regards,
Jose L.
0
schwertnerCommented:
Interesting how big is the customer table.
I have some ideas......
It seems that the order is not given by values.
The order is given declarativelly - using INSERT statement.
The only solution I see is to do transform the "ranges" in something
more comparable - either numbers or strings.

It is hard (for me, of course) to say how can we transform the values.
This is so because I have no knowledge on the underlying model.
So I see this as main task - how to order the entries.

But technically Oracle provides you many possibilities to
store the changed table - it could be a temporary table and
the transformation table also could be a temporary table.

I am very sorry if I have not understood correctly your task.
Hope other experts will also consider the case.
0
Jinesh KamdarCommented:
I concur with schwertner and even before we draw any self-assumed conclusions, we need to know the actual comparison that is desired. E.g.

1. 01 > AA or 01 < AA ?
2 AA > VV or AA < VV ?
etc ...
0
joselfmAuthor Commented:
Hi schwertner,
well.. table with descendants could have thousand of records...
Doing it with auxiliar tables would not be a properly solution because it would imply to spend lot of time in the query.
I got to do a query that takes 9 minutes... but it's not a good solution obviously.. because the results have to be shown in the screen immediately (os a few seconds after) the "parent "value" have been chosen

Thanks anyway

Jose L
0
joselfmAuthor Commented:
Hi jinesh_kamdar,
'01'<'AA' this (and all) are string comparations.. The problem is that when stringshave only numbers inside.. the comparation must be made as they were numbers... Tha's the main difficulty of this.
What is relevant too is the relation between parent and child... The important tip is find the way to compare numbers when they are into strings.. it's difficult because of the use of "prior"...

Regards,
Jose.
0
awking00Commented:
>>I.e. for '01' it would return:
AA AN
K0 K50
100 199
500 600
VV VV<<
Why should it return
500 600
VV VV
when K2 is padre value for 500 600 but is neither low or high child value for padre 01?
Shouldn't the result simply be
AA AN
K0 K50
100 199
?
0
joselfmAuthor Commented:
Why should it return
500 600
VV VV

Because  500-600 is the interval of "sons" of K2, which is one of the childs of '01'  (it's between K0 and K50)

Regards,
Jose.
0
grzessioCommented:
I dont like pl/sql

try this query:
WITH FAM AS (SELECT ROWID RID
     , PADRE
     , CHILD_LOW
     , CHILD_HIGH
     , NVL(REGEXP_SUBSTR(PADRE, '[[:alpha:]]*'), '-') PRE_PADRE
     , TO_NUMBER(NVL(substr(PADRE, NVL(length(REGEXP_SUBSTR(PADRE, '[[:alpha:]]*')) + 1, 1)), 0)) POST_PADRE
     , NVL(REGEXP_SUBSTR(CHILD_LOW, '[[:alpha:]]*'), '-') PRE_CHILD_LOW
     , TO_NUMBER(NVL(substr(CHILD_LOW, NVL(length(REGEXP_SUBSTR(CHILD_LOW, '[[:alpha:]]*')) + 1, 1)), 0)) POST_CHILD_LOW
     , NVL(REGEXP_SUBSTR(CHILD_HIGH, '[[:alpha:]]*'), '-') PRE_CHILD_HIGH
     , TO_NUMBER(NVL(substr(CHILD_HIGH, NVL(length(REGEXP_SUBSTR(CHILD_HIGH, '[[:alpha:]]*')) + 1, 1)), 0)) POST_CHILD_HIGH
  FROM FAMILY)
SELECT PADRE, CHILD_PADRE
  FROM (
       SELECT F1.RID PARENT_RID, F2.RID CHILD_RID, F1.PADRE, F1.CHILD_LOW, F1.CHILD_HIGH, F2.PADRE CHILD_PADRE
        FROM FAM F1
        JOIN FAM F2
          ON F1.PRE_CHILD_LOW <= F2.PRE_PADRE
             AND F1.PRE_CHILD_HIGH >= F2.PRE_PADRE
             AND F1.POST_CHILD_LOW <= F2.POST_PADRE
             AND F1.POST_CHILD_HIGH >= F2.POST_PADRE
             AND (F1.PADRE <> F2.PADRE)
      )
 CONNECT BY PRIOR CHILD_RID = PARENT_RID
 START WITH PADRE = '01'

Open in new window

0
joselfmAuthor Commented:
Hi again grzessio,
thanks again for you comments ... but... as I put in the 1st post, I work in a 9.2.0.7 DB and REGEXP_SUBSTR is for 10g...isn't it?
Ey, PLSQL is cool ;-P (too many years with it I think...hehe An eternity since my first little query... that now.. it's like the family..:P)

Regards,
Jose L
0
SujithData ArchitectCommented:
Does padding the columns to a fixed length string work?
select child_low ||'-' ||child_high DESCENDANTS
from (select padre,child_low,child_high from family where
padre<>child_low)
start with padre = '01'
connect by prior lpad(child_low,10, '0')  <= lpad(padre,10, '0') and 
           prior lpad(child_high,10, '0') >= lpad(padre,10, '0')
/

Open in new window

0
grzessioCommented:
Try to compile this function and use it in place of REGEXP_SUBSTR(CHILD_HIGH, '[[:alpha:]]*')

I am still supposing that there can be only keys like: [[LETTER]*][[NUMBER]*]

create or replace
FUNCTION GET_STRING(STR VARCHAR2) RETURN VARCHAR2
IS
PRESTR VARCHAR2(10) := NULL;
IT NUMBER := 1;
ZNAK CHAR(1);
BEGIN
 
  IF LENGTH(STR) = 0 THEN
    RETURN NULL;
  END IF;
  
  WHILE IT <= LENGTH(STR)
  LOOP
    ZNAK := SUBSTR(STR, IT, 1);
    IF NOT (ZNAK IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) THEN
      PRESTR := PRESTR || ZNAK;
    ELSE 
      RETURN PRESTR;
    END IF;
    IT := IT + 1;
  END LOOP;
  RETURN PRESTR;
END;
 
 
WITH FAM AS (SELECT ROWID RID
     , PADRE
     , CHILD_LOW
     , CHILD_HIGH
     , NVL(GET_STRING(PADRE), '-') PRE_PADRE
     , TO_NUMBER(NVL(substr(PADRE, NVL(length(GET_STRING(PADRE)) + 1, 1)), 0)) POST_PADRE
     , NVL(GET_STRING(CHILD_LOW), '-') PRE_CHILD_LOW
     , TO_NUMBER(NVL(substr(CHILD_LOW, NVL(length(GET_STRING(CHILD_LOW)) + 1, 1)), 0)) POST_CHILD_LOW
     , NVL(GET_STRING(CHILD_HIGH), '-') PRE_CHILD_HIGH
     , TO_NUMBER(NVL(substr(CHILD_HIGH, NVL(length(GET_STRING(CHILD_HIGH)) + 1, 1)), 0)) POST_CHILD_HIGH
  FROM FAMILY)
SELECT PADRE, CHILD_PADRE
  FROM (
       SELECT F1.RID PARENT_RID, F2.RID CHILD_RID, F1.PADRE, F1.CHILD_LOW, F1.CHILD_HIGH, F2.PADRE CHILD_PADRE
        FROM FAM F1
        JOIN FAM F2
          ON F1.PRE_CHILD_LOW <= F2.PRE_PADRE
             AND F1.PRE_CHILD_HIGH >= F2.PRE_PADRE
             AND F1.POST_CHILD_LOW <= F2.POST_PADRE
             AND F1.POST_CHILD_HIGH >= F2.POST_PADRE
             AND (F1.PADRE <> F2.PADRE)
      )
 CONNECT BY PRIOR CHILD_RID = PARENT_RID
 START WITH PADRE = '01'

Open in new window

0
joselfmAuthor Commented:
Hi sujith80:
I've tested it... It will work for my example but in general it fails when we have strings to compare like:
'010A' <='010'
 if we justify with left padding (i.e. with 10 positions) second value because it's number... we'll have the following
'010A' <='0000000010'
And this is not true.. so leftpadding will not work..
We could extend this to "parts" of a string which contains chars and numbers... but with these in the middle...
So.. more things to try?

Regards,
Jose L
0
joselfmAuthor Commented:
Hi grzessio:
>I am still supposing that there can be only keys like: [[LETTER]*][[NUMBER]*]

No, it's not correct... keys can be in all the forms you can imagine ...
Thanks anyway

By the way, your last function doesn't compile to me due to the WITH clause.. I don't understand.. because I work with 9.2.0.8 ... (I thinked I was with 9.2.0.7.. but.. it's 9.2.0.8 what is even better)

Jose L.
0
SujithData ArchitectCommented:
>> '010A' <= '010'
What is the expected output of this comparison?

padding has to be applied to both sides of the comparison.
as in :

select case when lpad('010A',10,'0') <= lpad('010',10,'0') then '010 greater'
            else '010 lesser' end x
from dual;
0
joselfmAuthor Commented:
sujith80,
the problem would appear because for "not numeric" strings we could'nt do the leftpadding...
'010A' is different from '0010A'...

Regards,
Jose L.
0
grzessioCommented:
Hi joselfm
>>I am still supposing that there can be only keys like: [[LETTER]*][[NUMBER]*]

>No, it's not correct... keys can be in all the forms you can imagine ...

Try to define then how keys look like and what does it mean that key1 <= key2 <= key3 because keys in the example are like [[LETTER]*][[NUMBER]*] (key is build from letters, numbers or letters||numbers.

My function was till the end; from with was the query which uses this function :)

0
grzessioCommented:
My function was till the "end;"
From "with" was the query which uses this function
0
joselfmAuthor Commented:
hahaha,
yes, grzessio,.. a copy-paste really fast... sorry :-P
I'll try it later and I'll tell you.
Thanks,
Jose.
0
joselfmAuthor Commented:
mmm what it doesn't recognizes is the with clause.. (ora-00900)...
0
Jinesh KamdarCommented:
Is ur DB really 9i? I believe the WITH clause (subquery_factoring_clause) is available in 9i.
Try this and see if it works -

WITH d AS (SELECT * FROM DUAL)
SELECT * FROM d;

subquery_factoring_clause
---------------------------------
The subquery_factoring_clause (WITH query_name) lets you assign names to subquery blocks. You can then reference the subquery block multiple places in the query by specifying the query name. Oracle optimizes the query by treating the query name as either an inline view or as a temporary table.

You can specify this clause in any top-level SELECT statement and in most types of subqueries. The query name is visible to all subsequent subqueries (except the subquery that defines the query name itself) and to the main query.

Restrictions on Subquery Factoring
------------------------------------------
You cannot nest this clause. That is, you cannot specify the subquery_factoring_clause as a subquery within another subquery_factoring_clause. In a query with set operators, the set operator subquery cannot contain the subquery_factoring_clause, but the FROM subquery can contain the subquery_factoring_clause.
0
joselfmAuthor Commented:
Hi, yes...
Just Starting...
>Connected to Oracle9i Enterprise Edition Release 9.2.0.8.0

;-)

SQL> WITH d AS (SELECT * FROM DUAL)
  2  SELECT * FROM d;

DUMMY
-----
X

So I don't understand too the error...

Thanks,
Jose L.
0
Jinesh KamdarCommented:
Hmmm ... might be b'coz of similar named fields. Try this change ...
WITH FAM AS ...
SELECT PARENT_PADRE, CHILD_PADRE
  FROM (SELECT F1.RID PARENT_RID, F2.RID CHILD_RID, F1.PADRE PARENT_PADRE, F1.CHILD_LOW, F1.CHILD_HIGH, F2.PADRE CHILD_PADRE
...

Open in new window

0
grzessioCommented:
Well we can omit the with clause but query will be much uglier (well it is a bit ugly now but the next one is really ugly one)

Try something like this:

I`ll say that I didn`t tested this one because I do not have access to database now but I will check it tommororw.

SELECT PADRE, CHILD_PADRE
  FROM (
       SELECT F1.RID PARENT_RID, F2.RID CHILD_RID, F1.PADRE, F1.CHILD_LOW, F1.CHILD_HIGH, F2.PADRE CHILD_PADRE
        FROM ((SELECT ROWID RID
     , PADRE
     , CHILD_LOW
     , CHILD_HIGH
     , NVL(GET_STRING(PADRE), '-') PRE_PADRE
     , TO_NUMBER(NVL(substr(PADRE, NVL(length(GET_STRING(PADRE)) + 1, 1)), 0)) POST_PADRE
     , NVL(GET_STRING(CHILD_LOW), '-') PRE_CHILD_LOW
     , TO_NUMBER(NVL(substr(CHILD_LOW, NVL(length(GET_STRING(CHILD_LOW)) + 1, 1)), 0)) POST_CHILD_LOW
     , NVL(GET_STRING(CHILD_HIGH), '-') PRE_CHILD_HIGH
     , TO_NUMBER(NVL(substr(CHILD_HIGH, NVL(length(GET_STRING(CHILD_HIGH)) + 1, 1)), 0)) POST_CHILD_HIGH
  FROM FAMILY)) F1
        JOIN ((SELECT ROWID RID
     , PADRE
     , CHILD_LOW
     , CHILD_HIGH
     , NVL(GET_STRING(PADRE), '-') PRE_PADRE
     , TO_NUMBER(NVL(substr(PADRE, NVL(length(GET_STRING(PADRE)) + 1, 1)), 0)) POST_PADRE
     , NVL(GET_STRING(CHILD_LOW), '-') PRE_CHILD_LOW
     , TO_NUMBER(NVL(substr(CHILD_LOW, NVL(length(GET_STRING(CHILD_LOW)) + 1, 1)), 0)) POST_CHILD_LOW
     , NVL(GET_STRING(CHILD_HIGH), '-') PRE_CHILD_HIGH
     , TO_NUMBER(NVL(substr(CHILD_HIGH, NVL(length(GET_STRING(CHILD_HIGH)) + 1, 1)), 0)) POST_CHILD_HIGH
  FROM FAMILY)) F2
          ON F1.PRE_CHILD_LOW <= F2.PRE_PADRE
             AND F1.PRE_CHILD_HIGH >= F2.PRE_PADRE
             AND F1.POST_CHILD_LOW <= F2.POST_PADRE
             AND F1.POST_CHILD_HIGH >= F2.POST_PADRE
             AND (F1.PADRE <> F2.PADRE)
      )
 CONNECT BY PRIOR CHILD_RID = PARENT_RID
 START WITH PADRE = '01'

Open in new window

0
grzessioCommented:
it works the same way as the previous
0
joselfmAuthor Commented:
Hi grzessio:
it doesn't compile due to ")" I don't see where...
May be after with more time I'll examine it..

Jose.
0
Jinesh KamdarCommented:
I guess its because there are 2 parentheses [ ((, )) ] for the inner SELECTs.
Try using just 1 and see if it works.
0
joselfmAuthor Commented:
No,jinesh_kamdar:
that's was the only i've tried ;-)

Jose
0
grzessioCommented:
Maybe You have not compiled the function (or maybe it is invalid) try to recompile the function. I copy the query and it works fine.
0
joselfmAuthor Commented:
Hi grzessio,
function was compiled OK, and.. I made the copy-paste of the query... but.. the error persists... ¿?

Jose L:
0
Jinesh KamdarCommented:
I was also able to run both the queries and got the same o/p in both cases as below.

PADRE         CHILD_PADRE
--------------------------------
01                 AA
01                 K2
K2                 500

Can u post what exactly ur doing?
0
joselfmAuthor Commented:
Damm, forget it, I tested in an 8.1.7 (agghhh toad with 8 sesions opened and I pasted code in the only that had a 8.x)
I'll test in 9.2.0.8 and tell you ;-)
Thanks,
Jose
0
joselfmAuthor Commented:
Hi, I've already tested the query...
but as you say, it returns
PADRE         CHILD_PADRE
--------------------------------
01                 AA
01                 K2
K2                 500

But the result must be:
AA-AN
100-199
K0-K50
500-600
VV-VV

      
0
joselfmAuthor Commented:
If anybody want it... I can send a a real file with inserts into the family table  to test with reals parent-child_low-child_high

Regards,
Jose.
0
joselfmAuthor Commented:
Hi all,
I get it!


select level, padre, child_low || '-' || child_high DESCENDIENTES
  from (select *
          from (select padre,
                       child_low,
                       child_high,
                       replace(translate(padre, '0123456789', '0000000000'),
                               '0',
                               '') padre_c,
                       replace(translate(child_low,
                                         '0123456789',
                                         '0000000000'),
                               '0',
                               '') child_low_c,
                       replace(translate(child_high,
                                         '0123456789',
                                         '0000000000'),
                               '0',
                               '') child_high_c,
                       nvl(to_number(replace(translate(padre,
                                                       'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ',
                                                       'AAAAAAAAAAAAAAAAAAAAAAAAAAA'),
                                             'A',
                                             '')),
                           -1) padre_n,
                       nvl(to_number(replace(translate(child_low,
                                                       'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ',
                                                       'AAAAAAAAAAAAAAAAAAAAAAAAAAA'),
                                             'A',
                                             '')),
                           -1) child_low_n,                       
                       nvl(to_number(replace(translate(child_high,
                                                       'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ',
                                                       'AAAAAAAAAAAAAAAAAAAAAAAAAAA'),
                                             'A',
                                             '')),
                           -1) child_high_n
                  from family)
         where padre != child_low) a
 start with padre = '01'
connect by ((padre_c is not null or prior child_low_c is not null or
           prior child_high_c is not null) and padre between prior child_low and prior child_high)
        or (padre_c is null and prior child_low_c is null and
           prior child_high_c is null and padre_n between prior child_low_n and
           prior child_high_n);

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Oracle Database

From novice to tech pro — start learning today.