10 Tricks für die Arbeit mit Oracle

Es gibt verschiedene Oracle-Praktiken bei Sberbank, die Sie möglicherweise nützlich finden. Ich denke, einiges davon ist Ihnen vertraut, aber wir verwenden nicht nur ETL-Tools zum Laden, sondern auch gespeicherte Oracle-Prozeduren. Oracle PL / SQL implementiert die komplexesten Algorithmen zum Laden von Daten in Speicher, bei denen Sie "jedes Byte fühlen" müssen.



  • Automatische Kompilierungsprotokollierung
  • Was tun, wenn Sie eine Ansicht mit Parametern erstellen möchten?
  • Verwenden dynamischer Statistiken in Abfragen
  • So speichern Sie den Abfrageplan beim Einfügen von Daten über eine Datenbankverbindung
  • Ausführen von Prozeduren in parallelen Sitzungen
  • Reste durchziehen
  • Mehrere Geschichten zu einer kombinieren
  • Normalisierer
  • Rendern im SVG-Format
  • Oracle Metadata Search-Anwendung


Automatische Kompilierungsprotokollierung



In einigen Oracle-Datenbanken verfügt Sberbank über einen Kompilierungsauslöser, der merkt, wer, wann und was sich im Code von Serverobjekten geändert hat. Somit kann der Autor der Änderungen aus der Kompilierungsprotokolltabelle ermittelt werden. Ein Versionskontrollsystem wird ebenfalls automatisch implementiert. Wenn der Programmierer vergessen hat, die Änderungen an Git zu senden, wird dieser Mechanismus in jedem Fall abgesichert. Beschreiben wir ein Beispiel für die Implementierung eines solchen Systems der automatischen Kompilierungsprotokollierung. Eine der vereinfachten Versionen des Kompilierungsauslösers, der in Form der Tabelle ddl_changes_log in das Protokoll schreibt, sieht folgendermaßen aus:



create table DDL_CHANGES_LOG
(
  id               INTEGER,
  change_date      DATE,
  sid              VARCHAR2(100),
  schemaname       VARCHAR2(30),
  machine          VARCHAR2(100),
  program          VARCHAR2(100),
  osuser           VARCHAR2(100),
  obj_owner        VARCHAR2(30),
  obj_type         VARCHAR2(30),
  obj_name         VARCHAR2(30),
  previous_version CLOB,
  changes_script   CLOB
);

create or replace trigger trig_audit_ddl_trg
  before ddl on database
declare
  v_sysdate              date;
  v_valid                number;
  v_previous_obj_owner   varchar2(30) := '';
  v_previous_obj_type    varchar2(30) := '';
  v_previous_obj_name    varchar2(30) := '';
  v_previous_change_date date;
  v_lob_loc_old          clob := '';
  v_lob_loc_new          clob := '';
  v_n                    number;
  v_sql_text             ora_name_list_t;
  v_sid                  varchar2(100) := '';
  v_schemaname           varchar2(30) := '';
  v_machine              varchar2(100) := '';
  v_program              varchar2(100) := '';
  v_osuser               varchar2(100) := '';
begin
  v_sysdate := sysdate;
  -- find whether compiled object already presents and is valid
  select count(*)
    into v_valid
    from sys.dba_objects
   where owner = ora_dict_obj_owner
     and object_type = ora_dict_obj_type
     and object_name = ora_dict_obj_name
     and status = 'VALID'
     and owner not in ('SYS', 'SPOT', 'WMSYS', 'XDB', 'SYSTEM')
     and object_type in ('TRIGGER', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'VIEW');
  -- find information about previous compiled object
  select max(obj_owner) keep(dense_rank last order by id),
         max(obj_type) keep(dense_rank last order by id),
         max(obj_name) keep(dense_rank last order by id),
         max(change_date) keep(dense_rank last order by id)
    into v_previous_obj_owner, v_previous_obj_type, v_previous_obj_name, v_previous_change_date
    from ddl_changes_log;
  -- if compile valid object or compile invalid package body broken by previous compilation of package then log it
  if (v_valid = 1 or v_previous_obj_owner = ora_dict_obj_owner and
     (v_previous_obj_type = 'PACKAGE' and ora_dict_obj_type = 'PACKAGE BODY' or
     v_previous_obj_type = 'PACKAGE BODY' and ora_dict_obj_type = 'PACKAGE') and
     v_previous_obj_name = ora_dict_obj_name and
     v_sysdate - v_previous_change_date <= 1 / 24 / 60 / 2) and
     ora_sysevent in ('CREATE', 'ALTER') then
    -- store previous version of object (before compilation) from dba_source or dba_views in v_lob_loc_old
    if ora_dict_obj_type <> 'VIEW' then
      for z in (select substr(text, 1, length(text) - 1) || chr(13) || chr(10) as text
                  from sys.dba_source
                 where owner = ora_dict_obj_owner
                   and type = ora_dict_obj_type
                   and name = ora_dict_obj_name
                 order by line) loop
        v_lob_loc_old := v_lob_loc_old || z.text;
      end loop;
    else
      select sys.dbms_metadata_util.long2clob(v.textlength, 'SYS.VIEW$', 'TEXT', v.rowid) into v_lob_loc_old
        from sys."_CURRENT_EDITION_OBJ" o, sys.view$ v, sys.user$ u
       where o.obj# = v.obj#
         and o.owner# = u.user#
         and u.name = ora_dict_obj_owner
         and o.name = ora_dict_obj_name;
    end if;
    -- store new version of object (after compilation) from v_sql_text in v_lob_loc_new
    v_n := ora_sql_txt(v_sql_text);
    for i in 1 .. v_n loop
      v_lob_loc_new := v_lob_loc_new || replace(v_sql_text(i), chr(10), chr(13) || chr(10));
    end loop;
    -- find information about session that changed this object
    select max(to_char(sid)), max(schemaname), max(machine), max(program), max(osuser)
      into v_sid, v_schemaname, v_machine, v_program, v_osuser
      from v$session
     where audsid = userenv('sessionid');
    -- store changes in ddl_changes_log
    insert into ddl_changes_log
      (id, change_date, sid, schemaname, machine, program, osuser,
       obj_owner, obj_type, obj_name, previous_version, changes_script)
    values
      (seq_ddl_changes_log.nextval, v_sysdate, v_sid, v_schemaname, v_machine, v_program, v_osuser,
       ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name, v_lob_loc_old, v_lob_loc_new);
  end if;
exception
  when others then
    null;
end;


In diesem Trigger werden der Name und der neue Inhalt des kompilierten Objekts abgerufen, mit dem vorherigen Inhalt aus dem Datenwörterbuch ergänzt und in das Änderungsprotokoll geschrieben.



Was tun, wenn Sie eine Ansicht mit Parametern erstellen möchten?



Ein solcher Wunsch kann oft von einem Entwickler bei Oracle besucht werden. Warum ist es möglich, eine Prozedur oder Funktion mit Parametern zu erstellen, aber es gibt keine Ansichten mit Eingabeparametern, die für Berechnungen verwendet werden können? Oracle hat etwas, um dieses unserer Meinung nach fehlende Konzept zu ersetzen.

Schauen wir uns ein Beispiel an. Für jeden Tag soll es eine Tabelle mit Verkäufen nach Geschäftsbereichen geben.



create table DIVISION_SALES
(
  division_id INTEGER,
  dt          DATE,
  sales_amt   NUMBER
);


Diese Abfrage vergleicht die Verkäufe nach Abteilungen über zwei Tage. In diesem Fall 30.04.2020 und 11.09.2020.



select t1.division_id,
       t1.dt          dt1,
       t2.dt          dt2,
       t1.sales_amt   sales_amt1,
       t2.sales_amt   sales_amt2
  from (select dt, division_id, sales_amt
          from division_sales
         where dt = to_date('30.04.2020', 'dd.mm.yyyy')) t1,
       (select dt, division_id, sales_amt
          from division_sales
         where dt = to_date('11.09.2020', 'dd.mm.yyyy')) t2
 where t1.division_id = t2.division_id;


Hier ist eine Ansicht, die ich schreiben möchte, um eine solche Anfrage zusammenzufassen. Ich möchte Daten als Parameter übergeben. Die Syntax lässt dies jedoch nicht zu.



create or replace view vw_division_sales_report(in_dt1 date, in_dt2 date) as
select t1.division_id,
       t1.dt          dt1,
       t2.dt          dt2,
       t1.sales_amt   sales_amt1,
       t2.sales_amt   sales_amt2
  from (select dt, division_id, sales_amt
          from division_sales
         where dt = in_dt1) t1,
       (select dt, division_id, sales_amt
          from division_sales
         where dt = in_dt2) t2
 where t1.division_id = t2.division_id;


Eine solche Problemumgehung wird vorgeschlagen. Erstellen wir aus dieser Ansicht einen Typ für die Linie.



create type t_division_sales_report as object
(
  division_id INTEGER,
  dt1         DATE,
  dt2         DATE,
  sales_amt1  NUMBER,
  sales_amt2  NUMBER
);


Und wir werden aus solchen Zeichenfolgen einen Typ für eine Tabelle erstellen.



create type t_division_sales_report_table as table of t_division_sales_report;


Schreiben wir anstelle einer Ansicht eine Pipeline-Funktion mit Datumseingabeparametern.



create or replace function func_division_sales(in_dt1 date, in_dt2 date)
  return t_division_sales_report_table
  pipelined as
begin
  for z in (select t1.division_id,
                   t1.dt          dt1,
                   t2.dt          dt2,
                   t1.sales_amt   sales_amt1,
                   t2.sales_amt   sales_amt2
              from (select dt, division_id, sales_amt
                      from division_sales
                     where dt = in_dt1) t1,
                   (select dt, division_id, sales_amt
                      from division_sales
                     where dt = in_dt2) t2
             where t1.division_id = t2.division_id) loop
    pipe row(t_division_sales_report(z.division_id,
                                     z.dt1,
                                     z.dt2,
                                     z.sales_amt1,
                                     z.sales_amt2));
  end loop;
end;


Sie können sich so darauf beziehen:



select *
  from table(func_division_sales(to_date('30.04.2020', 'dd.mm.yyyy'),
                                 to_date('11.09.2020', 'dd.mm.yyyy')));


Diese Anfrage liefert das gleiche Ergebnis wie die Anfrage am Anfang dieses Beitrags mit explizit ersetzten Daten.

Pipeline-Funktionen können auch nützlich sein, wenn Sie einen Parameter innerhalb einer komplexen Anforderung übergeben müssen.

Stellen Sie sich beispielsweise eine komplexe Ansicht vor, in der das Feld1, nach dem Sie Daten filtern möchten, irgendwo tief in der Ansicht versteckt ist.



create or replace view complex_view as
 select field1, ...
   from (select field1, ...
           from (select field1, ... from deep_table), table1
          where ...),
        table2
  where ...;


Und eine Abfrage aus einer Ansicht mit dem festen Wert von Feld1 hat möglicherweise einen schlechten Ausführungsplan.



select field1, ... from complex_view
 where field1 = 'myvalue';


Jene. Anstatt deep_table zuerst nach dem Bedingungsfeld1 = 'myvalue' zu filtern, kann die Abfrage zuerst alle Tabellen verbinden, eine unnötig große Datenmenge verarbeiten und dann das Ergebnis nach dem Bedingungsfeld1 = 'myvalue' filtern. Diese Komplexität kann vermieden werden, indem anstelle einer Pipeline-Ansicht eine Funktion mit einem Parameter erstellt wird, der Feld1 zugewiesen ist.



Verwenden dynamischer Statistiken in Abfragen



Es kommt vor, dass dieselbe Abfrage in der Oracle-Datenbank jedes Mal eine andere Datenmenge in den darin verwendeten Tabellen und Unterabfragen verarbeitet. Wie kann der Optimierer herausfinden, wie diesmal Tabellen verknüpft werden sollen und welche Indizes jedes Mal verwendet werden sollen? Stellen Sie sich beispielsweise eine Abfrage vor, die einen Teil der Kontostände, die sich seit dem letzten Laden geändert haben, mit dem Kontoverzeichnis verbindet. Der Teil des geänderten Kontostands variiert stark von Download zu Download und beläuft sich auf Hunderte von Zeilen, manchmal Millionen von Zeilen. Abhängig von der Größe dieses Teils ist es erforderlich, die geänderten Salden mit Konten entweder nach der Methode / * + use_nl * / oder nach der Methode / * + use_hash * / zu kombinieren. Es ist unpraktisch, Statistiken jedes Mal neu zu erfassen, insbesondere wenn sich die Anzahl der Zeilen von Laden zu Laden nicht in der verknüpften Tabelle, sondern in der verknüpften Unterabfrage ändert.Der Hinweis / * + dynamic_sampling () * / kann hier Abhilfe schaffen. Lassen Sie uns anhand einer Beispielanforderung zeigen, wie sich dies auswirkt. Lassen Sie die Tabelle change_balances die Änderungen an Salden und Konten enthalten - dem Verzeichnis der Konten. Wir verbinden diese Tabellen durch die Felder account_id, die in jeder der Tabellen verfügbar sind. Zu Beginn des Experiments werden wir weitere Zeilen in diese Tabellen schreiben und deren Inhalt nicht ändern.

Nehmen wir zunächst 10% der Änderungen in den Residuen in der Tabelle change_balances und sehen, wie der Plan dynamic_sampling verwendet:



SQL> EXPLAIN PLAN
  2   SET statement_id = 'test1'
  3   INTO plan_table
  4  FOR  with c as
  5   (select /*+ dynamic_sampling(change_balances 2)*/
  6     account_id, balance_amount
  7      from change_balances
  8     where mod(account_id, 10) = 0)
  9  select a.account_id, a.account_number, c.balance_amount
 10    from c, accounts a
 11   where c.account_id = a.account_id;

Explained.

SQL>
SQL> SELECT * FROM table (DBMS_XPLAN.DISPLAY);
Plan hash value: 874320301

----------------------------------------------------------------------------------------------
| Id  | Operation          | Name            | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                 |  9951K|   493M|       |   140K  (1)| 00:28:10 |
|*  1 |  HASH JOIN         |                 |  9951K|   493M|  3240K|   140K  (1)| 00:28:10 |
|*  2 |   TABLE ACCESS FULL| CHANGE_BALANCES |   100K|  2057K|       |  7172   (1)| 00:01:27 |
|   3 |   TABLE ACCESS FULL| ACCOUNTS        |    10M|   295M|       |   113K  (1)| 00:22:37 |
----------------------------------------------------------------------------------------------

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

   1 - access("ACCOUNT_ID"="A"."ACCOUNT_ID")
   2 - filter(MOD("ACCOUNT_ID",10)=0)

Note
-----
   - dynamic sampling used for this statement (level=2)

20 rows selected.


Wir sehen also, dass vorgeschlagen wird, die Tabellen change_balances und Accounts mit einem vollständigen Scan zu durchsuchen und sie mit einem Hash-Join zu verbinden.

Lassen Sie uns nun die Stichprobe von change_balances drastisch reduzieren. Nehmen wir 0,1% der verbleibenden Änderungen und sehen, wie der Plan dynamic_sampling verwendet:



SQL> EXPLAIN PLAN
  2   SET statement_id = 'test2'
  3   INTO plan_table
  4  FOR  with c as
  5   (select /*+ dynamic_sampling(change_balances 2)*/
  6     account_id, balance_amount
  7      from change_balances
  8     where mod(account_id, 1000) = 0)
  9  select a.account_id, a.account_number, c.balance_amount
 10    from c, accounts a
 11   where c.account_id = a.account_id;

Explained.

SQL>
SQL> SELECT * FROM table (DBMS_XPLAN.DISPLAY);
Plan hash value: 2360715730

-------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                        | 73714 |  3743K| 16452   (1)| 00:03:18 |
|   1 |  NESTED LOOPS                |                        |       |       |            |          |
|   2 |   NESTED LOOPS               |                        | 73714 |  3743K| 16452   (1)| 00:03:18 |
|*  3 |    TABLE ACCESS FULL         | CHANGE_BALANCES        |   743 | 15603 |  7172   (1)| 00:01:27 |
|*  4 |    INDEX RANGE SCAN          | IX_ACCOUNTS_ACCOUNT_ID |   104 |       |     2   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| ACCOUNTS               |    99 |  3069 |   106   (0)| 00:00:02 |
-------------------------------------------------------------------------------------------------------

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

   3 - filter(MOD("ACCOUNT_ID",1000)=0)
   4 - access("ACCOUNT_ID"="A"."ACCOUNT_ID")

Note
-----
   - dynamic sampling used for this statement (level=2)

22 rows selected.


Dieses Mal wird die Kontentabelle mit verschachtelten Schleifen an die Tabelle change_balances angehängt, und ein Index wird zum Lesen von Zeilen aus Konten verwendet.

Wenn der Hinweis dynamic_sampling entfernt wird, bleibt der Plan im zweiten Fall derselbe wie im ersten Fall, und dies ist nicht optimal.

Details zum Hinweis dynamic_sampling und mögliche Werte für das numerische Argument finden Sie in der Dokumentation.



So speichern Sie den Abfrageplan beim Einfügen von Daten über eine Datenbankverbindung



Wir lösen dieses Problem. Der Datenquellenserver verfügt über Tabellen, die verknüpft und in das Data Warehouse geladen werden müssen. Angenommen, auf dem Quellserver wird eine Ansicht geschrieben, die alle erforderlichen ETL-Transformationslogiken enthält. Die Ansicht ist optimal geschrieben und enthält Hinweise für das Optimierungsprogramm, die vorschlagen, wie Tabellen verknüpft werden und welche Indizes verwendet werden sollen. Auf der Serverseite des Data Warehouse müssen Sie eine einfache Sache tun: Fügen Sie die Daten aus der Ansicht in die Zieltabelle ein. Und hier können Schwierigkeiten auftreten. Wenn Sie mit einem Befehl wie in die Zieltabelle einfügen



insert into dwh_table
  (field1, field2)
  select field1, field2 from vw_for_dwh_table@xe_link;


Dann kann die gesamte Logik des Abfrageplans, die in der Ansicht enthalten ist, aus der wir Daten über die Datenbankverbindung lesen, ignoriert werden. Alle in diese Ansicht eingebetteten Hinweise können ignoriert werden.



SQL> EXPLAIN PLAN
  2   SET statement_id = 'test'
  3   INTO plan_table
  4  FOR  insert into dwh_table
  5    (field1, field2)
  6    select field1, field2 from vw_for_dwh_table@xe_link;

Explained.

SQL>
SQL> SELECT * FROM table (DBMS_XPLAN.DISPLAY);
Plan hash value: 1788691278

-------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name             | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |IN-OUT|
-------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |                  |     1 |  2015 |     2   (0)| 00:00:01 |        |      |
|   1 |  LOAD TABLE CONVENTIONAL | DWH_TABLE        |       |       |            |          |        |      |
|   2 |   REMOTE                 | VW_FOR_DWH_TABLE |     1 |  2015 |     2   (0)| 00:00:01 | XE_LI~ | R->S |
-------------------------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

   2 - SELECT /*+ OPAQUE_TRANSFORM */ "FIELD1","FIELD2" FROM "VW_FOR_DWH_TABLE" "VW_FOR_DWH_TABLE"
       (accessing 'XE_LINK' )


16 rows selected.


Um den Abfrageplan in der Ansicht zu speichern, können Sie Daten über den Cursor in die Zieltabelle einfügen:



declare
  cursor cr is
    select field1, field2 from vw_for_dwh_table@xe_link;
  cr_row cr%rowtype;
begin
  open cr;
  loop
    fetch cr
      into cr_row;
    insert into dwh_table
      (field1, field2)
    values
      (cr_row.field1, cr_row.field2);
    exit when cr%notfound;
  end loop;
  close cr;
end;


Abfrage vom Cursor



select field1, field2 from vw_for_dwh_table@xe_link;


im Gegensatz zu einfügen



insert into dwh_table
  (field1, field2)
  select field1, field2 from vw_for_dwh_table@xe_link;


speichert den Plan der Anforderung, der in der Ansicht auf dem Quellserver abgelegt ist.



Ausführen von Prozeduren in parallelen Sitzungen



Oft besteht die Aufgabe darin, mehrere parallele Berechnungen von einer übergeordneten Prozedur aus zu starten und die übergeordnete Prozedur auszuführen, nachdem Sie darauf gewartet haben, dass jede abgeschlossen ist. Dies kann beim parallelen Rechnen nützlich sein, wenn die Serverressourcen dies zulassen. Es gibt viele Möglichkeiten, dies zu tun.

Beschreiben wir eine sehr einfache Implementierung eines solchen Mechanismus. Parallele Prozeduren werden in parallelen "einmaligen" Jobs ausgeführt, während die übergeordnete Prozedur in einer Schleife auf den Abschluss all dieser Jobs wartet.

Erstellen wir Tabellen mit Metadaten für diesen Mechanismus. Lassen Sie uns zunächst eine Tabelle mit Gruppen parallel laufender Prozeduren erstellen:



create table PARALLEL_PROC_GROUP_LIST
(
  group_id   INTEGER,
  group_name VARCHAR2(4000)
);
comment on column PARALLEL_PROC_GROUP_LIST.group_id
  is '    ';
comment on column PARALLEL_PROC_GROUP_LIST.group_name
  is '    ';


Als Nächstes erstellen wir eine Tabelle mit Skripten, die parallel in Gruppen ausgeführt werden. Das Füllen dieser Tabelle kann entweder statisch oder dynamisch erstellt werden:



create table PARALLEL_PROC_LIST
(
  group_id    INTEGER,
  proc_script VARCHAR2(4000),
  is_active   CHAR(1) default 'Y'
);
comment on column PARALLEL_PROC_LIST.group_id
  is '    ';
comment on column PARALLEL_PROC_LIST.proc_script
  is 'Pl/sql    ';
comment on column PARALLEL_PROC_LIST.is_active
  is 'Y - active, N - inactive.          ';


Und wir werden eine Protokolltabelle erstellen, in der wir ein Protokoll darüber sammeln, welche Prozedur in welchem ​​Job gestartet wurde:



create table PARALLEL_PROC_LOG
(
  run_id      INTEGER,
  group_id    INTEGER,
  proc_script VARCHAR2(4000),
  job_id      INTEGER,
  start_time  DATE,
  end_time    DATE
);
comment on column PARALLEL_PROC_LOG.run_id
  is '   run_in_parallel';
comment on column PARALLEL_PROC_LOG.group_id
  is '    ';
comment on column PARALLEL_PROC_LOG.proc_script
  is 'Pl/sql    ';
comment on column PARALLEL_PROC_LOG.job_id
  is 'Job_id ,      ';
comment on column PARALLEL_PROC_LOG.start_time
  is '  ';
comment on column PARALLEL_PROC_LOG.end_time
  is '  ';

create sequence Seq_Parallel_Proc_Log;


Geben wir nun den Code der Prozedur zum Starten paralleler Streams an:



create or replace procedure run_in_parallel(in_group_id integer) as
  --        parallel_proc_list.
  --  -    parallel_proc_list
  v_run_id             integer;
  v_job_id             integer;
  v_job_id_list        varchar2(32767);
  v_job_id_list_ext    varchar2(32767);
  v_running_jobs_count integer;
begin
  select seq_parallel_proc_log.nextval into v_run_id from dual;
  -- submit jobs with the same parallel_proc_list.in_group_id
  -- store seperated with ',' JOB_IDs in v_job_id_list
  v_job_id_list     := null;
  v_job_id_list_ext := null;
  for z in (select pt.proc_script
              from parallel_proc_list pt
             where pt.group_id = in_group_id
               and pt.is_active = 'Y') loop
    dbms_job.submit(v_job_id, z.proc_script);
    insert into parallel_proc_log
      (run_id, group_id, proc_script, job_id, start_time, end_time)
    values
      (v_run_id, in_group_id, z.proc_script, v_job_id, sysdate, null);
    v_job_id_list     := v_job_id_list || ',' || to_char(v_job_id);
    v_job_id_list_ext := v_job_id_list_ext || ' union all select ' ||
                         to_char(v_job_id) || ' job_id from dual';
  end loop;
  commit;
  v_job_id_list     := substr(v_job_id_list, 2);
  v_job_id_list_ext := substr(v_job_id_list_ext, 12);
  -- loop while not all jobs finished
  loop
    -- set parallel_proc_log.end_time for finished jobs
    execute immediate 'update parallel_proc_log set end_time = sysdate where job_id in (' ||
                      v_job_id_list_ext ||
                      ' minus select job from user_jobs where job in (' ||
                      v_job_id_list ||
                      ') minus select job_id from parallel_proc_log where job_id in (' ||
                      v_job_id_list || ') and end_time is not null)';
    commit;
    -- check whether all jobs finished
    execute immediate 'select count(1) from user_jobs where job in (' ||
                      v_job_id_list || ')'
      into v_running_jobs_count;
    -- if all jobs finished then exit
    exit when v_running_jobs_count = 0;
    -- sleep a little
    sys.dbms_lock.sleep(0.1);
  end loop;
end;


Lassen Sie uns überprüfen, wie die Prozedur run_in_parallel funktioniert. Erstellen wir eine Testprozedur, die wir in parallelen Sitzungen aufrufen.



create or replace procedure sleep(in_seconds integer) as
begin
  sys.Dbms_Lock.Sleep(in_seconds);
end;


Geben Sie den Namen der Gruppe und die Tabelle mit Skripten ein, die parallel ausgeführt werden.



insert into PARALLEL_PROC_GROUP_LIST(group_id, group_name) values(1, ' ');

insert into PARALLEL_PROC_LIST(group_id, proc_script, is_active) values(1, 'begin sleep(5); end;', 'Y');
insert into PARALLEL_PROC_LIST(group_id, proc_script, is_active) values(1, 'begin sleep(10); end;', 'Y');


Beginnen wir mit einer Gruppe paralleler Prozeduren.



begin
  run_in_parallel(1);
end;


Wenn Sie fertig sind, sehen wir uns das Protokoll an.



select * from PARALLEL_PROC_LOG;


RUN_ID GRUPPEN-ID PROC_SCRIPT JOB-ID STARTZEIT END_TIME
1 1 Schlaf beginnen (5); Ende; 1 09/11/2020 15:00:51 09/11/2020 15:00:56
1 1 Schlaf beginnen (10); Ende; 2 09/11/2020 15:00:51 09/11/2020 15:01:01


Wir sehen, dass die Ausführungszeit der Testprozedurinstanzen den Erwartungen entspricht.



Reste durchziehen



Beschreiben wir eine Variante zur Lösung eines ziemlich typischen Bankproblems, bei dem es darum geht, das Gleichgewicht zu halten. Angenommen, es gibt eine Tabelle mit Fakten zu Änderungen der Kontensalden. Es ist erforderlich, den aktuellen Kontostand für jeden Tag des Kalenders anzugeben (den letzten für den Tag). Solche Informationen werden häufig in Data Warehouses benötigt. Wenn an einem Tag keine Bewegungen in der Zählung aufgetreten sind, müssen Sie den letzten bekannten Rest wiederholen. Wenn die Datenmenge und die Rechenleistung des Servers dies zulassen, können Sie dieses Problem mithilfe einer SQL-Abfrage lösen, ohne auf PL / SQL zurückgreifen zu müssen. Die Funktion last_value (* Nullen ignorieren) over (Partition nach * order by *) hilft uns dabei, wodurch der letzte bekannte Rest auf nachfolgende Daten ausgedehnt wird, an denen keine Änderungen vorgenommen wurden.

Erstellen wir eine Tabelle und füllen sie mit Testdaten.



create table ACCOUNT_BALANCE
(
  dt           DATE,
  account_id   INTEGER,
  balance_amt  NUMBER,
  turnover_amt NUMBER
);
comment on column ACCOUNT_BALANCE.dt
  is '     ';
comment on column ACCOUNT_BALANCE.account_id
  is ' ';
comment on column ACCOUNT_BALANCE.balance_amt
  is '  ';
comment on column ACCOUNT_BALANCE.turnover_amt
  is '  ';

insert into account_balance(dt, account_id, balance_amt, turnover_amt) values(to_date('01.01.2020 00:00:00','dd.mm.yyyy hh24.mi.ss'), 1, 23, 23);
insert into account_balance(dt, account_id, balance_amt, turnover_amt) values(to_date('05.01.2020 01:00:00','dd.mm.yyyy hh24.mi.ss'), 1, 45, 22);
insert into account_balance(dt, account_id, balance_amt, turnover_amt) values(to_date('05.01.2020 20:00:00','dd.mm.yyyy hh24.mi.ss'), 1, 44, -1);
insert into account_balance(dt, account_id, balance_amt, turnover_amt) values(to_date('05.01.2020 00:00:00','dd.mm.yyyy hh24.mi.ss'), 2, 67, 67);
insert into account_balance(dt, account_id, balance_amt, turnover_amt) values(to_date('05.01.2020 20:00:00','dd.mm.yyyy hh24.mi.ss'), 2, 77, 10);
insert into account_balance(dt, account_id, balance_amt, turnover_amt) values(to_date('07.01.2020 00:00:00','dd.mm.yyyy hh24.mi.ss'), 2, 72, -5);


Die folgende Abfrage löst unser Problem. Die Unterabfrage 'cld' enthält den Datumskalender, in der Unterabfrage 'ab' gruppieren wir die Salden für jeden Tag, in der Unterabfrage 'a' merken wir uns die Liste aller Konten und das Startdatum des Verlaufs für jedes Konto, in der Unterabfrage 'pre' für jedes Konto erstellen wir einen Kalender von Tagen ab dem Beginn Geschichten. Die letzte Anforderung fügt die letzten Salden für jeden Tag zum Kalender der aktiven Tage jedes Kontos hinzu und erweitert sie auf die Tage, an denen keine Änderungen vorgenommen wurden.



with cld as
 (select /*+ materialize*/
   to_date('01.01.2020', 'dd.mm.yyyy') + level - 1 dt
    from dual
  connect by level <= 10),
ab as
 (select trunc(dt) dt,
         account_id,
         max(balance_amt) keep(dense_rank last order by dt) balance_amt,
         sum(turnover_amt) turnover_amt
    from account_balance
   group by trunc(dt), account_id),
a as
 (select min(dt) min_dt, account_id from ab group by account_id),
pre as
 (select cld.dt, a.account_id from cld left join a on cld.dt >= a.min_dt)
select pre.dt,
       pre.account_id,
       last_value(ab.balance_amt ignore nulls) over(partition by pre.account_id order by pre.dt) balance_amt,
       nvl(ab.turnover_amt, 0) turnover_amt
  from pre
  left join ab
    on pre.dt = ab.dt
   and pre.account_id = ab.account_id
 order by 2, 1;


Das Ergebnis der Abfrage ist wie erwartet.

DT ACCOUNT_ID BALANCE_AMT TURNOVER_AMT
01.01.2020 1 23 23
02.01.2020 1 23 0
03/01/2020 1 23 0
04/01/2020 1 23 0
01/05/2020 1 44 21
06.01.2020 1 44 0
07.01.2020 1 44 0
01.08.2020 1 44 0
09/01/2020 1 44 0
10.01.2020 1 44 0
01/05/2020 2 77 77
06.01.2020 2 77 0
07.01.2020 2 72 -fünf
01.08.2020 2 72 0
09/01/2020 2 72 0
10.01.2020 2 72 0


Mehrere Geschichten zu einer kombinieren



Beim Laden von Daten in Speicher wird das Problem häufig gelöst, wenn Sie einen einzelnen Verlauf für eine Entität erstellen müssen, der einen separaten Verlauf der Attribute dieser Entität aufweist, die aus verschiedenen Quellen stammen. Angenommen, es gibt eine Entität mit dem Primärschlüssel primary_key_id, über die der Verlauf (start_dt - end_dt) seiner drei verschiedenen Attribute bekannt ist und der sich in drei verschiedenen Tabellen befindet.



create table HIST1
(
  primary_key_id INTEGER,
  start_dt       DATE,
  attribute1     NUMBER
);

insert into HIST1(primary_key_id, start_dt, attribute1) values(1, to_date('2014-01-01','yyyy-mm-dd'), 7);
insert into HIST1(primary_key_id, start_dt, attribute1) values(1, to_date('2015-01-01','yyyy-mm-dd'), 8);
insert into HIST1(primary_key_id, start_dt, attribute1) values(1, to_date('2016-01-01','yyyy-mm-dd'), 9);
insert into HIST1(primary_key_id, start_dt, attribute1) values(2, to_date('2014-01-01','yyyy-mm-dd'), 17);
insert into HIST1(primary_key_id, start_dt, attribute1) values(2, to_date('2015-01-01','yyyy-mm-dd'), 18);
insert into HIST1(primary_key_id, start_dt, attribute1) values(2, to_date('2016-01-01','yyyy-mm-dd'), 19);

create table HIST2
(
  primary_key_id INTEGER,
  start_dt       DATE,
  attribute2     NUMBER
);
 
insert into HIST2(primary_key_id, start_dt, attribute2) values(1, to_date('2015-01-01','yyyy-mm-dd'), 4);
insert into HIST2(primary_key_id, start_dt, attribute2) values(1, to_date('2016-01-01','yyyy-mm-dd'), 5);
insert into HIST2(primary_key_id, start_dt, attribute2) values(1, to_date('2017-01-01','yyyy-mm-dd'), 6);
insert into HIST2(primary_key_id, start_dt, attribute2) values(2, to_date('2015-01-01','yyyy-mm-dd'), 14);
insert into HIST2(primary_key_id, start_dt, attribute2) values(2, to_date('2016-01-01','yyyy-mm-dd'), 15);
insert into HIST2(primary_key_id, start_dt, attribute2) values(2, to_date('2017-01-01','yyyy-mm-dd'), 16);

create table HIST3
(
  primary_key_id INTEGER,
  start_dt       DATE,
  attribute3     NUMBER
);
 
insert into HIST3(primary_key_id, start_dt, attribute3) values(1, to_date('2016-01-01','yyyy-mm-dd'), 10);
insert into HIST3(primary_key_id, start_dt, attribute3) values(1, to_date('2017-01-01','yyyy-mm-dd'), 20);
insert into HIST3(primary_key_id, start_dt, attribute3) values(1, to_date('2018-01-01','yyyy-mm-dd'), 30);
insert into HIST3(primary_key_id, start_dt, attribute3) values(2, to_date('2016-01-01','yyyy-mm-dd'), 110);
insert into HIST3(primary_key_id, start_dt, attribute3) values(2, to_date('2017-01-01','yyyy-mm-dd'), 120);
insert into HIST3(primary_key_id, start_dt, attribute3) values(2, to_date('2018-01-01','yyyy-mm-dd'), 130);


Ziel ist es, einen einzelnen Änderungsverlauf von drei Attributen in eine Tabelle zu laden.

Unten finden Sie eine Abfrage, die dieses Problem löst. Es bildet zunächst eine Diagonaltabelle q1 mit Daten aus verschiedenen Quellen für verschiedene Attribute (Attribute, die in der Quelle fehlen, werden mit Nullen gefüllt). Mit der Funktion last_value (* ignore nulls) wird die Diagonaltabelle dann zu einem einzigen Verlauf zusammengefasst, und die letzten bekannten Attributwerte werden auf die Daten erweitert, an denen keine Änderungen vorgenommen wurden:



select primary_key_id,
       start_dt,
       nvl(lead(start_dt - 1)
           over(partition by primary_key_id order by start_dt),
           to_date('9999-12-31', 'yyyy-mm-dd')) as end_dt,
       last_value(attribute1 ignore nulls) over(partition by primary_key_id order by start_dt) as attribute1,
       last_value(attribute2 ignore nulls) over(partition by primary_key_id order by start_dt) as attribute2,
       last_value(attribute3 ignore nulls) over(partition by primary_key_id order by start_dt) as attribute3
  from (select primary_key_id,
               start_dt,
               max(attribute1) as attribute1,
               max(attribute2) as attribute2,
               max(attribute3) as attribute3
          from (select primary_key_id,
                       start_dt,
                       attribute1,
                       cast(null as number) attribute2,
                       cast(null as number) attribute3
                  from hist1
                union all
                select primary_key_id,
                       start_dt,
                       cast(null as number) attribute1,
                       attribute2,
                       cast(null as number) attribute3
                  from hist2
                union all
                select primary_key_id,
                       start_dt,
                       cast(null as number) attribute1,
                       cast(null as number) attribute2,
                       attribute3
                  from hist3) q1
         group by primary_key_id, start_dt) q2
 order by primary_key_id, start_dt;


Das Ergebnis ist wie folgt:

PRIMARY_KEY_ID START_DT END_DT ATTRIBUTE1 ATTRIBUTE2 ATTRIBUTE3
1 01/01/2014 31.12.2014 7 NULL NULL
1 01.01.2015 31.12.2015 8 4 NULL
1 01/01/2016 31.12.2016 neun fünf zehn
1 01.01.2017 31.12.2017 neun 6 20
1 01.01.2018 31.12.9999 neun 6 dreißig
2 01/01/2014 31.12.2014 17 NULL NULL
2 01.01.2015 31.12.2015 achtzehn vierzehn NULL
2 01/01/2016 31.12.2016 19 fünfzehn 110
2 01.01.2017 31.12.2017 19 Sechszehn 120
2 01.01.2018 31.12.9999 19 Sechszehn 130


Normalisierer



Manchmal tritt das Problem auf, Daten zu normalisieren, die im Format eines begrenzten Feldes vorliegen. Zum Beispiel in Form einer Tabelle wie dieser:



create table DENORMALIZED_TABLE
(
  id  INTEGER,
  val VARCHAR2(4000)
);

insert into DENORMALIZED_TABLE(id, val) values(1, 'aaa,cccc,bb');
insert into DENORMALIZED_TABLE(id, val) values(2, 'ddd');
insert into DENORMALIZED_TABLE(id, val) values(3, 'fffff,e');


Diese Abfrage normalisiert die Daten, indem die durch Kommas verknüpften Felder als mehrere Zeilen eingefügt werden:



select id, regexp_substr(val, '[^,]+', 1, column_value) val, column_value
  from denormalized_table,
       table(cast(multiset
                  (select level
                     from dual
                   connect by regexp_instr(val, '[^,]+', 1, level) > 0) as
                  sys.odcinumberlist))
 order by id, column_value;


Das Ergebnis ist wie folgt:

ICH WÜRDE VAL COLUMN_VALUE
1 aaa 1
1 cccc 2
1 bb 3
2 ddd 1
3 fffff 1
3 e 2


Rendern im SVG-Format



Oft besteht der Wunsch, die in der Datenbank gespeicherten numerischen Indikatoren irgendwie zu visualisieren. Erstellen Sie beispielsweise Diagramme, Histogramme und Diagramme. Spezialisierte Tools wie Oracle BI können helfen. Lizenzen für diese Tools können jedoch Geld kosten, und das Einrichten kann mehr Zeit in Anspruch nehmen als das Schreiben einer SQL-Abfrage "on the Knie" an Oracle, die das fertige Bild zurückgibt. Lassen Sie uns anhand eines Beispiels zeigen, wie Sie ein solches Bild mithilfe einer Abfrage schnell im SVG-Format zeichnen können.

Angenommen, wir haben eine Tabelle mit Daten



create table graph_data(dt date, val number, radius number);

insert into graph_data(dt, val, radius) values (to_date('01.01.2020','dd.mm.yyyy'), 12, 3);
insert into graph_data(dt, val, radius) values (to_date('02.01.2020','dd.mm.yyyy'), 15, 4);
insert into graph_data(dt, val, radius) values (to_date('05.01.2020','dd.mm.yyyy'), 17, 5);
insert into graph_data(dt, val, radius) values (to_date('06.01.2020','dd.mm.yyyy'), 13, 6);
insert into graph_data(dt, val, radius) values (to_date('08.01.2020','dd.mm.yyyy'),  3, 7);
insert into graph_data(dt, val, radius) values (to_date('10.01.2020','dd.mm.yyyy'), 20, 8);
insert into graph_data(dt, val, radius) values (to_date('11.01.2020','dd.mm.yyyy'), 18, 9);


dt ist das Datum der Relevanz,

val ist ein numerischer Indikator, dessen Dynamik wir über die Zeit visualisieren,

Radius ist ein weiterer numerischer Indikator, den wir in Form eines Kreises mit einem solchen Radius zeichnen werden.

Lassen Sie uns ein paar Worte zum SVG-Format sagen. Es ist ein Vektorgrafikformat, das in modernen Browsern angezeigt und in andere Grafikformate konvertiert werden kann. Darin können Sie unter anderem Linien, Kreise zeichnen und Text schreiben:



<line x1="94" x2="94" y1="15" y2="675" style="stroke:rgb(150,255,255); stroke-width:1px"/>
<circle cx="30" cy="279" r="3" style="fill:rgb(255,0,0)"/>
<text x="7" y="688" font-size="10" fill="rgb(0,150,255)">2020-01-01</text>


Im Folgenden finden Sie eine SQL-Abfrage an Oracle, in der ein Diagramm aus den Daten in dieser Tabelle dargestellt wird. Hier enthält die const-Unterabfrage verschiedene konstante Einstellungen - Bildgröße, Anzahl der Beschriftungen auf den Diagrammachsen, Linien- und Kreisfarben, Schriftgrößen usw. In der Unterabfrage gd1 konvertieren wir die Daten aus der Tabelle graph_data in die x- und y-Koordinaten in der Abbildung. Die Unterabfrage gd2 merkt sich die vorherigen Zeitpunkte, von denen aus die Linien zu neuen Punkten gezogen werden sollen. Der 'Header'-Block ist der Header des Bildes mit weißem Hintergrund. Der Block 'Vertikale Linien' zeichnet vertikale Linien. Die Blockbezeichnungen "Daten unter vertikalen Linien" befinden sich auf der x-Achse. Der Block 'Horizontale Linien' zeichnet horizontale Linien. Der Block 'Werte in der Nähe horizontaler Linien' kennzeichnet die Werte auf der y-Achse. Der Block 'Kreise' zeichnet Kreise mit dem angegebenen Radius in der Tabelle graph_data.Der Block 'graph data' erstellt aus der Tabelle graph_data ein Diagramm der Dynamik des Wertindikators. Der 'Fußzeilen'-Block fügt ein nachfolgendes Tag hinzu.



with const as
 (select 700 viewbox_width,
         700 viewbox_height,
         30 left_margin,
         30 right_margin,
         15 top_margin,
         25 bottom_margin,
         max(dt) - min(dt) + 1 num_vertical_lines,
         11 num_horizontal_lines,
         'rgb(150,255,255)' stroke_vertical_lines,
         '1px' stroke_width_vertical_lines,
         10 font_size_dates,
         'rgb(0,150,255)' fill_dates,
         23 x_dates_pad,
         13 y_dates_pad,
         'rgb(150,255,255)' stroke_horizontal_lines,
         '1px' stroke_width_horizontal_lines,
         10 font_size_values,
         'rgb(0,150,255)' fill_values,
         4 x_values_pad,
         2 y_values_pad,
         'rgb(255,0,0)' fill_circles,
         'rgb(51,102,0)' stroke_graph,
         '1px' stroke_width_graph,
         min(dt) min_dt,
         max(dt) max_dt,
         max(val) max_val
    from graph_data),
gd1 as
 (select graph_data.dt,
         const.left_margin +
         (const.viewbox_width - const.left_margin - const.right_margin) *
         (graph_data.dt - const.min_dt) / (const.max_dt - const.min_dt) x,
         const.viewbox_height - const.bottom_margin -
         (const.viewbox_height - const.top_margin - const.bottom_margin) *
         graph_data.val / const.max_val y,
         graph_data.radius
    from graph_data, const),
gd2 as
 (select dt,
         round(nvl(lag(x) over(order by dt), x)) prev_x,
         round(x) x,
         round(nvl(lag(y) over(order by dt), y)) prev_y,
         round(y) y,
         radius
    from gd1)
/* header */
select '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' txt
  from dual
union all
select '<svg version="1.1" width="' || viewbox_width || '" height="' ||
       viewbox_height || '" viewBox="0 0 ' || viewbox_width || ' ' ||
       viewbox_height ||
       '" style="background:yellow" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">'
  from const
union all
select '<title>Test graph</title>'
  from dual
union all
select '<desc>Test graph</desc>'
  from dual
union all
select '<rect width="' || viewbox_width || '" height="' || viewbox_height ||
       '" style="fill:white" />'
  from const
union all
/* vertical lines */
select '<line x1="' ||
       to_char(round(left_margin +
                     (viewbox_width - left_margin - right_margin) *
                     (level - 1) / (num_vertical_lines - 1))) || '" x2="' ||
       to_char(round(left_margin +
                     (viewbox_width - left_margin - right_margin) *
                     (level - 1) / (num_vertical_lines - 1))) || '" y1="' ||
       to_char(round(top_margin)) || '" y2="' ||
       to_char(round(viewbox_height - bottom_margin)) || '" style="stroke:' ||
       const.stroke_vertical_lines || '; stroke-width:' ||
       const.stroke_width_vertical_lines || '"/>'
  from const
connect by level <= num_vertical_lines
union all
/* dates under vertical lines */
select '<text x="' ||
       to_char(round(left_margin +
                     (viewbox_width - left_margin - right_margin) *
                     (level - 1) / (num_vertical_lines - 1) - x_dates_pad)) ||
       '" y="' ||
       to_char(round(viewbox_height - bottom_margin + y_dates_pad)) ||
       '" font-size="' || font_size_dates || '" fill="' || fill_dates || '">' ||
       to_char(min_dt + level - 1, 'yyyy-mm-dd') || '</text>'
  from const
connect by level <= num_vertical_lines
union all
/* horizontal lines */
select '<line x1="' || to_char(round(left_margin)) || '" x2="' ||
       to_char(round(viewbox_width - right_margin)) || '" y1="' ||
       to_char(round(top_margin +
                     (viewbox_height - top_margin - bottom_margin) *
                     (level - 1) / (num_horizontal_lines - 1))) || '" y2="' ||
       to_char(round(top_margin +
                     (viewbox_height - top_margin - bottom_margin) *
                     (level - 1) / (num_horizontal_lines - 1))) ||
       '" style="stroke:' || const.stroke_horizontal_lines ||
       '; stroke-width:' || const.stroke_width_horizontal_lines || '"/>'
  from const
connect by level <= num_horizontal_lines
union all
/* values near horizontal lines */
select '<text text-anchor="end" x="' ||
       to_char(round(left_margin - x_values_pad)) || '" y="' ||
       to_char(round(viewbox_height - bottom_margin -
                     (viewbox_height - top_margin - bottom_margin) *
                     (level - 1) / (num_horizontal_lines - 1) +
                     y_values_pad)) || '" font-size="' || font_size_values ||
       '" fill="' || fill_values || '">' ||
       to_char(round(max_val / (num_horizontal_lines - 1) * (level - 1), 2)) ||
       '</text>'
  from const
connect by level <= num_horizontal_lines
union all
/* circles */
select '<circle cx="' || to_char(gd2.x) || '" cy="' || to_char(gd2.y) ||
       '" r="' || gd2.radius || '" style="fill:' || const.fill_circles ||
       '"/>'
  from gd2, const
union all
/* graph data */
select '<line x1="' || to_char(gd2.prev_x) || '" x2="' || to_char(gd2.x) ||
       '" y1="' || to_char(gd2.prev_y) || '" y2="' || to_char(gd2.y) ||
       '" style="stroke:' || const.stroke_graph || '; stroke-width:' ||
       const.stroke_width_graph || '"/>'
  from gd2, const
union all
/* footer */
select '</svg>' from dual;


Das Abfrageergebnis kann in einer Datei mit der Erweiterung * .svg gespeichert und in einem Browser angezeigt werden. Wenn Sie möchten, können Sie jedes der Dienstprogramme verwenden, um es in andere Grafikformate zu konvertieren, es auf den Webseiten Ihrer Anwendung zu platzieren usw.

Das Ergebnis ist das folgende Bild:







Oracle Metadata Search-Anwendung



Stellen Sie sich vor, Sie versuchen, etwas im Quellcode von Oracle zu finden, indem Sie Informationen auf mehreren Servern gleichzeitig anzeigen. Hier geht es um das Durchsuchen von Oracle-Datenwörterbuchobjekten. Der Arbeitsplatz für die Suche ist die Weboberfläche, über die der Benutzerprogrammierer die Suchzeichenfolge eingibt und die Kontrollkästchen aktiviert, auf denen Oracle-Server diese Suche durchführen sollen.

Die Websuchmaschine kann in Oracle-Serverobjekten gleichzeitig in mehreren verschiedenen Datenbanken der Bank nach einer Zeile suchen. Zum Beispiel können Sie suchen nach:

  • Oracle 61209, ?
  • accounts ( .. database link)?
  • , , ORA-20001 “ ”?
  • IX_CLIENTID - SQL-?
  • - ( .. database link) , , , ..?
  • - - ? .
  • Oracle ? , wm_concat Oracle. .
  • - , , ? , Oracle sys_connect_by_path, regexp_instr push_subq.


Basierend auf den Suchergebnissen erhält der Benutzer Informationen darüber, auf welchem ​​Server im Code welche Funktionen, Prozeduren, Pakete, Trigger, Ansichten usw. vorhanden sind. fand die erforderlichen Ergebnisse.

Beschreiben wir, wie eine solche Suchmaschine implementiert wird.



Die Client-Seite ist nicht kompliziert. Die Weboberfläche empfängt die vom Benutzer eingegebene Suchzeichenfolge, die Liste der zu suchenden Server und die Anmeldung des Benutzers. Die Webseite übergibt sie an eine auf dem Handler-Server gespeicherte Oracle-Prozedur. Der Verlauf von Anfragen an die Suchmaschine, d.h. Wer welche Anfrage ausgeführt hat, wird für alle Fälle protokolliert.



Nach dem Empfang einer Suchabfrage führt die Serverseite auf dem Oracle-Suchserver mehrere Prozeduren in parallelen Jobs aus, die die folgenden Datenwörterbuchansichten für die Datenbankverknüpfungen auf den ausgewählten Oracle-Servern nach der erforderlichen Zeichenfolge durchsuchen: dba_col_comments, dba_jobs, dba_mviews, dba_objects, dba_scheduler_jobs, dba_tabsource, dba_scheduler_obs , dba_views. Jede der Prozeduren schreibt, wenn etwas gefunden wurde, das gefundene in die Suchergebnistabelle (mit der entsprechenden Suchabfrage-ID).



Wenn alle Suchvorgänge abgeschlossen sind, gibt der Client-Teil dem Benutzer alles, was in die Suchergebnistabelle geschrieben ist, mit der entsprechenden Suchabfrage-ID.

Aber das ist nicht alles. Neben der Suche im Oracle-Datenwörterbuch wurde auch die Suche im Informatica PowerCenter-Repository in den beschriebenen Mechanismus integriert. Informatica PowerCenter ist ein beliebtes ETL-Tool, mit dem die Sberbank verschiedene Informationen in Data Warehouses lädt. Informatica PowerCenter verfügt über eine gut dokumentierte Open Source-Repository-Struktur. In diesem Repository können Sie auf dieselbe Weise wie im Oracle-Datenwörterbuch nach Informationen suchen. Welche Tabellen und Felder werden in dem mit Informatica PowerCenter entwickelten Download-Code verwendet? Was finden Sie in Porttransformationen und expliziten SQL-Abfragen? Alle diese Informationen sind in den Strukturen des Repositorys verfügbar und können gefunden werden. Für PowerCenter-Kenner schreibe ich, dass unsere Suchmaschine die folgenden Repository-Speicherorte nach Zuordnungen, Sitzungen oder Workflows durchsucht.Enthält die Suchzeichenfolge irgendwo: SQL-Überschreibung, Mapplet-Attribute, Ports, Quelldefinitionen in Mappings, Quelldefinitionen, Zieldefinitionen in Mappings, Zieldefinitionen, Mappings, Mapplets, Workflows, Worklets, Sitzungen, Befehle, Ausdrucksports, Sitzungsinstanzen, Quelldefinitionsfelder, Zieldefinitionsfelder, E-Mail-Aufgaben.



: , SberProfi DWH/BigData.



SberProfi DWH/BigData , Hadoop, Teradata, Oracle DB, GreenPlum, BI Qlik, SAP BO, Tableau .



All Articles