Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Design Patterns in PLSQL and SQL [UKOUG 22]

Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Chargement dans…3
×

Consultez-les par la suite

1 sur 103 Publicité
Publicité

Plus De Contenu Connexe

Plus récents (20)

Publicité

Design Patterns in PLSQL and SQL [UKOUG 22]

  1. 1. Design Patterns in PL/SQL and SQL Oren Nakdimon www.db-oriented.com  oren@db-oriented.com  +972-54-4393763 @DBoriented select department_id, count(*) number_of_employees, avg(salary) avg_salary from employees group by department_id having max(salary) > 2 * min(salary) Can you read this query easily? If not, please sit closer
  2. 2. © Oren Nakdimon © Oren Nakdimon https://db-oriented.com Who Am I?
  3. 3. DESIGN PATTERNS General Definition
  4. 4. © Oren Nakdimon © Oren Nakdimon In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system. https://en.wikipedia.org/wiki/Software_design_pattern
  5. 5. DESIGN PATTERNS For Oracle Developers
  6. 6. © Oren Nakdimon © Oren Nakdimon SQL PL/SQL Data Model Concrete Task General Task Suggested Solution Challenges Variations “Mini-Patterns”
  7. 7. THE DEMO MODEL
  8. 8. © Oren Nakdimon © Oren Nakdimon ALBUM # id * title * release_date SONG # track# * title o is_album_favourite ARTIST # id * name * type * date_of_birth o date_of_death LOV_GENRE # id * name
  9. 9. OVERLAP CHECK
  10. 10. © Oren Nakdimon © Oren Nakdimon create table artists ( id integer generated as identity not null constraint artists_pk primary key, name varchar2(100) not null, type varchar2(6) not null constraint artists_type_chk check (type in ('Person','Group')), date_of_birth date not null constraint artists_dob_chk check (date_of_birth = trunc(date_of_birth)), date_of_death date constraint artists_dod_chk check (date_of_death = trunc(date_of_death)) );
  11. 11. © Oren Nakdimon © Oren Nakdimon create table artists ( id integer generated as identity not null constraint artists_pk primary key, name varchar2(100) not null, type varchar2(6) not null constraint artists_type_chk check (type in ('Person','Group')), date_of_birth date not null constraint artists_dob_chk check (date_of_birth = trunc(date_of_birth)), date_of_death date constraint artists_dod_chk check (date_of_death = trunc(date_of_death)) ); Mini-Pattern Implementing a “pure” date attribute (with no time part)
  12. 12. © Oren Nakdimon © Oren Nakdimon create or replace package music is ... procedure get_people ( i_from_date in date, i_to_date in date, o_artists_rc out sys_refcursor ); ... end music;
  13. 13. © Oren Nakdimon © Oren Nakdimon Find rows that (partially or fully) overlap a given range Find all the artists that were alive between 2 dates Get all the projects that were active in the last 7 days Get the employees that worked in the company during 2019
  14. 14. © Oren Nakdimon © Oren Nakdimon Disjoint Ranges
  15. 15. © Oren Nakdimon © Oren Nakdimon Disjoint Ranges Start1 End1 Start2 End2 Start2>End1
  16. 16. © Oren Nakdimon © Oren Nakdimon Disjoint Ranges Start1 End1 Start2 End2 Start2>End1 or Start1>End2
  17. 17. © Oren Nakdimon © Oren Nakdimon Overlapping Ranges Start1 End1 Start2 End2 Start2<End1 and Start1<End2
  18. 18. © Oren Nakdimon © Oren Nakdimon Overlapping Ranges Start1 End1 Start2 End2 Start2<End1 and Start1<End2
  19. 19. © Oren Nakdimon © Oren Nakdimon Overlapping Ranges Start1 End1 Start2 End2 Start2<End1 and Start1<End2
  20. 20. © Oren Nakdimon © Oren Nakdimon The Suggested Solution StartColumn < EndParameter and StartParameter < EndColumn
  21. 21. © Oren Nakdimon © Oren Nakdimon procedure get_people ( i_from_date in date, i_to_date in date, o_artists_rc out sys_refcursor ) is begin open o_artists_rc for select ar.name, ar.date_of_birth, ar.date_of_death from artists ar where ar.type = 'Person' and ar.date_of_birth < i_to_date and i_from_date < ar.date_of_death order by ar.date_of_birth, ar.name; end get_people; @over1
  22. 22. © Oren Nakdimon © Oren Nakdimon Variation: Inclusive vs. Exclusive Ranges
  23. 23. © Oren Nakdimon © Oren Nakdimon The Suggested Solution StartColumn < EndParameter and StartParameter < EndColumn StartColumn <= EndParameter and StartParameter <= EndColumn Exclusive Ranges Inclusive Ranges
  24. 24. © Oren Nakdimon © Oren Nakdimon procedure get_people ( i_from_date in date, i_to_date in date, o_artists_rc out sys_refcursor ) is begin open o_artists_rc for select ar.name, ar.date_of_birth, ar.date_of_death from artists ar where ar.type = 'Person' and ar.date_of_birth <= i_to_date and i_from_date <= ar.date_of_death order by ar.date_of_birth, ar.name; end get_people; @over2
  25. 25. © Oren Nakdimon © Oren Nakdimon Variation: Nullable vs. Mandatory Columns create table artists ( id integer generated as identity not null constraint artists_pk primary key, name varchar2(100) not null, type varchar2(6) not null constraint artists_type_chk check (type in ('Person','Band')), date_of_birth date not null constraint artists_dob_chk check (date_of_birth = trunc(date_of_birth)), date_of_death date constraint artists_dod_chk check (date_of_death = trunc(date_of_death)) );
  26. 26. © Oren Nakdimon © Oren Nakdimon procedure get_people ( i_from_date in date, i_to_date in date, o_artists_rc out sys_refcursor ) is begin open o_artists_rc for select ar.name, ar.date_of_birth, ar.date_of_death from artists ar where ar.type = 'Person' and ar.date_of_birth <= i_to_date and i_from_date <= nvl(ar.date_of_death, date '9999-12-31') order by ar.date_of_birth, ar.name; end get_people; @over3
  27. 27. EXTENDABLE LOV
  28. 28. © Oren Nakdimon © Oren Nakdimon create table lov_genres ( id integer generated as identity not null constraint lov_genres_pk primary key, name varchar2(20) not null );
  29. 29. © Oren Nakdimon © Oren Nakdimon create table albums ( id integer generated as identity not null constraint albums_pk primary key, title varchar2(100) not null, release_date date not null, genre_id constraint albums_fk_genre_id references lov_genres (id) );
  30. 30. © Oren Nakdimon © Oren Nakdimon create table albums ( id integer generated as identity not null constraint albums_pk primary key, title varchar2(100) not null, release_date date not null, genre_id constraint albums_fk_genre_id references lov_genres (id) ); Mini-Pattern The referencing column implicitly gets the data type of the referenced column
  31. 31. © Oren Nakdimon © Oren Nakdimon create or replace package music is ... procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, o_album_id out albums.id%type ); ... end music;
  32. 32. © Oren Nakdimon © Oren Nakdimon create or replace package body music is ... procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, o_album_id out albums.id%type ) is l_genre_id lov_genres.id%type; begin l_genre_id := get_genre_id(i_genre_name); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; end add_album;
  33. 33. © Oren Nakdimon © Oren Nakdimon Look-up a reference table and add new values if not exist Get genre id by its name. If it doesn’t exist, add it.
  34. 34. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  35. 35. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  36. 36. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  37. 37. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  38. 38. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  39. 39. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  40. 40. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  41. 41. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  42. 42. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id;
  43. 43. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = i_genre_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (i_genre_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id; @lov1
  44. 44. © Oren Nakdimon © Oren Nakdimon Look-up a reference table and add new values if not exist Get genre id by its name. If it doesn’t exist, add it. ➢Name Standardization
  45. 45. © Oren Nakdimon © Oren Nakdimon create table lov_genres ( id integer generated as identity not null constraint lov_genres_pk primary key, name varchar2(20) not null ); alter table lov_genres add constraint lov_genres_name_chk check (name = initcap(trim(name)));
  46. 46. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = l_clean_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (l_clean_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id; @lov2
  47. 47. © Oren Nakdimon © Oren Nakdimon Look-up a reference table and add new values if not exist Get genre id by its name. If it doesn’t exist, add it. ➢Name Standardization ➢Uniqueness
  48. 48. © Oren Nakdimon © Oren Nakdimon create table lov_genres ( id integer generated as identity not null constraint lov_genres_pk primary key, name varchar2(20) not null ); alter table lov_genres add constraint lov_genres_name_chk check (name = initcap(trim(name))); alter table lov_genres add constraint lov_genres_name_uk unique (name); @lov3
  49. 49. © Oren Nakdimon © Oren Nakdimon Look-up a reference table and add new values if not exist Get genre id by its name. If it doesn’t exist, add it. ➢Name Standardization ➢Uniqueness ➢Concurrency Control
  50. 50. © Oren Nakdimon © Oren Nakdimon function get_genre_id ( i_genre_name in lov_genres.name%type, i_add_if_not_exists in boolean default true ) return lov_genres.id%type is l_clean_name lov_genres.name%type := initcap(trim(i_genre_name)); l_id lov_genres.id%type; begin if i_genre_name is null then return null; end if; begin select g.id into l_id from lov_genres g where g.name = l_clean_name; exception when no_data_found then if i_add_if_not_exists then begin insert into lov_genres (name) values (l_clean_name) returning id into l_id; exception when dup_val_on_index then l_id := get_genre_id( i_genre_name => l_clean_name, i_add_if_not_exists => false); end; else raise_application_error(-20000, i_genre_name || ' not found'); end if; end; return l_id; end get_genre_id; @lov4
  51. 51. MULTI-VALUE PARAMETERS
  52. 52. © Oren Nakdimon © Oren Nakdimon create table songs ( album_id not null constraint songs_fk_album_id references albums on delete cascade, track# number(2) not null, constraint songs_pk primary key (album_id,track#), title varchar2(100) not null, artist_id not null constraint songs_fk_atrist_id references artists, is_album_favourite number(1) default 0 not null constraint songs_favourite_boolean_chk check (is_album_favourite in (0,1)) ); create index songs_artist_id_ix on songs (artist_id) compress;
  53. 53. © Oren Nakdimon © Oren Nakdimon create table songs ( album_id not null constraint songs_fk_album_id references albums on delete cascade, track# number(2) not null, constraint songs_pk primary key (album_id,track#), title varchar2(100) not null, artist_id not null constraint songs_fk_atrist_id references artists, is_album_favourite number(1) default 0 not null constraint songs_favourite_boolean_chk check (is_album_favourite in (0,1)) ); create index songs_artist_id_ix on songs (artist_id) compress; Mini-Pattern Implementing a Boolean attribute
  54. 54. © Oren Nakdimon © Oren Nakdimon create table songs ( album_id not null constraint songs_fk_album_id references albums on delete cascade, track# number(2) not null, constraint songs_pk primary key (album_id,track#), title varchar2(100) not null, artist_id not null constraint songs_fk_atrist_id references artists, is_album_favourite number(1) default 0 not null constraint songs_favourite_boolean_chk check (is_album_favourite in (0,1)) ); create index songs_artist_id_ix on songs (artist_id) compress; Mini-Pattern Implementing a weak entity
  55. 55. © Oren Nakdimon © Oren Nakdimon Pass multiple entities to procedures Add an album with its songs ➢Related Entities
  56. 56. © Oren Nakdimon © Oren Nakdimon create or replace package music is ... procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in ???, o_album_id out albums.id%type ); ... end music;
  57. 57. © Oren Nakdimon © Oren Nakdimon create type song_t as object ( track# number(2), title varchar2(100), artist_id integer ) /
  58. 58. © Oren Nakdimon © Oren Nakdimon create type song_t as object ( track# number(2), title varchar2(100), artist_id integer ) / create type song_tt as table of song_t /
  59. 59. © Oren Nakdimon © Oren Nakdimon create type song_t as object ( track# number(2), title varchar2(100), artist_id integer ) / create type song_tt as table of song_t / create or replace package music is ... procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type ); ... end music;
  60. 60. © Oren Nakdimon © Oren Nakdimon Pass multiple entities to procedures Add an album with its songs ➢Related Entities ➢Performance – less roundtrips
  61. 61. © Oren Nakdimon © Oren Nakdimon create or replace package body music is ... procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, o_album_id out albums.id%type ) is l_genre_id lov_genres.id%type; begin l_genre_id := get_genre_id(i_genre_name); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; end add_album;
  62. 62. © Oren Nakdimon © Oren Nakdimon procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type ) is l_genre_id lov_genres.id%type; begin l_genre_id := get_genre_id(i_genre_name); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; forall i in indices of i_songs insert into songs (album_id, track#, title, artist_id) values (o_album_id, i_songs(i).track#, i_songs(i).title, i_songs(i).artist_id); end add_album;
  63. 63. © Oren Nakdimon © Oren Nakdimon Pass multiple entities to procedures Add an album with its songs ➢Related Entities ➢Performance – less roundtrips ➢Performance – less context switches ➢Atomicity @coll1
  64. 64. THE GUARDIAN TRIGGER
  65. 65. © Oren Nakdimon © Oren Nakdimon Enforce the rule: an album must have at least one song ALBUM SONG
  66. 66. © Oren Nakdimon © Oren Nakdimon Enforce the rule: an album must have at least one song ALBUM SONG
  67. 67. © Oren Nakdimon © Oren Nakdimon Enforce the rule: an album must have at least one song ALBUM SONG
  68. 68. © Oren Nakdimon © Oren Nakdimon Enforce the rule: an album must have at least one song ALBUM SONG Whenever some DML is done on some table T, another piece of code should be executed Complex Validity Check Logging Maintaining Denormalized Entities
  69. 69. © Oren Nakdimon © Oren Nakdimon procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type) is l_genre_id lov_genres.id%type; begin if i_songs is null or i_songs is empty then raise_application_error( -20000, 'an album must have songs'); end if; l_genre_id := get_genre_id(i_genre_name); set_album_songs_integrity_enforced(true); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; forall i in indices of i_songs insert into songs (album_id, track#, title, artist_id) values (o_album_id, i_songs(i).track#, i_songs(i).title, i_songs(i).artist_id); set_album_songs_integrity_enforced (false); exception when others then set_album_songs_integrity_enforced (false); raise; end add_album;
  70. 70. © Oren Nakdimon © Oren Nakdimon procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type) is l_genre_id lov_genres.id%type; begin if i_songs is null or i_songs is empty then raise_application_error( -20000, 'an album must have songs'); end if; l_genre_id := get_genre_id(i_genre_name); set_album_songs_integrity_enforced(true); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; forall i in indices of i_songs insert into songs (album_id, track#, title, artist_id) values (o_album_id, i_songs(i).track#, i_songs(i).title, i_songs(i).artist_id); set_album_songs_integrity_enforced (false); exception when others then set_album_songs_integrity_enforced (false); raise; end add_album;
  71. 71. create or replace package body music is g_album_songs_integrity_enforced boolean not null := false; function is_album_songs_integrity_enforced return boolean is begin return g_album_songs_integrity_enforced; end is_album_songs_integrity_enforced; procedure set_album_songs_integrity_enforced(i_is_enforced in boolean) is begin g_album_songs_integrity_enforced := i_is_enforced; end set_album_songs_integrity_enforced; ... end music;
  72. 72. © Oren Nakdimon © Oren Nakdimon create or replace package body music is g_album_songs_integrity_enforced boolean not null := false; function is_album_songs_integrity_enforced return boolean is begin return g_album_songs_integrity_enforced; end is_album_songs_integrity_enforced; procedure set_album_songs_integrity_enforced(i_is_enforced in boolean) is begin g_album_songs_integrity_enforced := i_is_enforced; end set_album_songs_integrity_enforced; ... end music;
  73. 73. © Oren Nakdimon © Oren Nakdimon create or replace package body music is g_album_songs_integrity_enforced boolean not null := false; function is_album_songs_integrity_enforced return boolean is begin return g_album_songs_integrity_enforced; end is_album_songs_integrity_enforced; procedure set_album_songs_integrity_enforced(i_is_enforced in boolean) is begin g_album_songs_integrity_enforced := i_is_enforced; end set_album_songs_integrity_enforced; ... end music;
  74. 74. © Oren Nakdimon © Oren Nakdimon create or replace package body music is g_album_songs_integrity_enforced boolean not null := false; function is_album_songs_integrity_enforced return boolean is begin return g_album_songs_integrity_enforced; end is_album_songs_integrity_enforced; procedure set_album_songs_integrity_enforced(i_is_enforced in boolean) is begin g_album_songs_integrity_enforced := i_is_enforced; end set_album_songs_integrity_enforced; ... end music;
  75. 75. © Oren Nakdimon © Oren Nakdimon create or replace package body music is g_album_songs_integrity_enforced boolean not null := false; function is_album_songs_integrity_enforced return boolean is begin return g_album_songs_integrity_enforced; end is_album_songs_integrity_enforced; procedure set_album_songs_integrity_enforced(i_is_enforced in boolean) is begin g_album_songs_integrity_enforced := i_is_enforced; end set_album_songs_integrity_enforced; ... end music; create or replace package music is function is_album_songs_integrity_enforced return Boolean; ... end music;
  76. 76. © Oren Nakdimon © Oren Nakdimon create or replace trigger albums_guardian_trigger before insert on albums begin if not music.is_album_songs_integrity_enforced then raise_application_error( -20000, 'to maintain albums and songs, please use the procedures in the music package'); end if; end albums_guardian_trigger; / create or replace trigger songs_guardian_trigger before delete or update of album_id on songs begin if not music.is_album_songs_integrity_enforced then raise_application_error( -20000, 'to maintain albums and songs, please use the procedures in the music package'); end if; end songs_guardian_trigger; /
  77. 77. © Oren Nakdimon © Oren Nakdimon procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type) is l_genre_id lov_genres.id%type; begin if i_songs is null or i_songs is empty then raise_application_error( -20000, 'an album must have songs'); end if; l_genre_id := get_genre_id(i_genre_name); set_album_songs_integrity_enforced(true); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; forall i in indices of i_songs insert into songs (album_id, track#, title, artist_id) values (o_album_id, i_songs(i).track#, i_songs(i).title, i_songs(i).artist_id); set_album_songs_integrity_enforced (false); exception when others then set_album_songs_integrity_enforced (false); raise; end add_album;
  78. 78. © Oren Nakdimon © Oren Nakdimon procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type) is l_genre_id lov_genres.id%type; begin if i_songs is null or i_songs is empty then raise_application_error( -20000, 'an album must have songs'); end if; l_genre_id := get_genre_id(i_genre_name); set_album_songs_integrity_enforced(true); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; forall i in indices of i_songs insert into songs (album_id, track#, title, artist_id) values (o_album_id, i_songs(i).track#, i_songs(i).title, i_songs(i).artist_id); set_album_songs_integrity_enforced (false); exception when others then set_album_songs_integrity_enforced (false); raise; end add_album;
  79. 79. © Oren Nakdimon © Oren Nakdimon procedure add_album ( i_title in albums.title%type, i_release_date in albums.release_date%type, i_genre_name in lov_genres.name%type default null, i_songs in song_tt, o_album_id out albums.id%type) is l_genre_id lov_genres.id%type; begin if i_songs is null or i_songs is empty then raise_application_error( -20000, 'an album must have songs'); end if; l_genre_id := get_genre_id(i_genre_name); set_album_songs_integrity_enforced(true); insert into albums (title, release_date, genre_id) values (i_title, i_release_date, l_genre_id) returning id into o_album_id; forall i in indices of i_songs insert into songs (album_id, track#, title, artist_id) values (o_album_id, i_songs(i).track#, i_songs(i).title, i_songs(i).artist_id); set_album_songs_integrity_enforced (false); exception when others then set_album_songs_integrity_enforced (false); raise; end add_album; @trig
  80. 80. © Oren Nakdimon © Oren Nakdimon The Suggested Solution  Put the logic in a procedure (or procedures)  Use a global variable that indicates if the DML is allowed  Initialize it to FALSE  Set it to TRUE only for the duration of the procedure  A statement-level trigger on the DML verifies the flag is on
  81. 81. ARC RELATIONSHIP
  82. 82. © Oren Nakdimon © Oren Nakdimon ALBUM # id * title * release_date SONG # track# * title o is_album_favourite ARTIST # id * name * type * date_of_birth o date_of_death LOV_GENRE # id * name IMAGE # id * name * size * resolution
  83. 83. © Oren Nakdimon © Oren Nakdimon Implement an arc relationship from Images to Artists and Albums Implement an arc relationship from a generic entity
  84. 84. © Oren Nakdimon © Oren Nakdimon Option #1 – Multiple Dedicated Tables ALBUM_IMAGES # image_id * album_id (FK) * name * size * resolution . . . ARTIST_IMAGES # image_id * artist_id (FK) * name * size * resolution . . . ?_IMAGES # image_id * ?_id (FK) * name * size * resolution . . . Code Code Code
  85. 85. © Oren Nakdimon © Oren Nakdimon Option #2 – A Single Table with Dedicated Columns IMAGES # image_id o album_id (FK) o artist_id (FK) * name * size * resolution . . . CHECK (album_id is null and artist_id is not null or album_id is not null and artist_id is null)
  86. 86. © Oren Nakdimon © Oren Nakdimon CHECK (nvl2(album_id,1,0)+nvl2(artist_id,1,0)=1) Option #2 – A Single Table with Dedicated Columns IMAGES # image_id o album_id (FK) o artist_id (FK) * name * size * resolution . . . Code album/artist
  87. 87. © Oren Nakdimon © Oren Nakdimon Option #3 – A Generic Table IMAGES # image_id o owner_type o owner_id * name * size * resolution . . . Code check (owner_type in ('Artist','Album')) No Referential Integrity
  88. 88. © Oren Nakdimon © Oren Nakdimon The Suggested Solution – Option #4  The generic table solution (option #3)  + virtual columns to enforce referential integrity
  89. 89. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  90. 90. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  91. 91. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  92. 92. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  93. 93. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  94. 94. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  95. 95. © Oren Nakdimon © Oren Nakdimon create table images ( id integer generated as identity not null constraint images_pk primary key, name varchar2(200) not null, size_in_bytes integer not null, resolution integer not null, --- ... owner_type varchar2(6) not null constraint images_owner_type_chk check (owner_type in ('Artist','Album')), owner_id integer not null, artist_id invisible generated always as (decode(owner_type,'Artist',owner_id)) virtual constraint images_fk_artist_id references artists(id) on delete cascade, album_id invisible generated always as (decode(owner_type,'Album',owner_id)) virtual constraint images_fk_album_id references albums(id) on delete cascade );
  96. 96. © Oren Nakdimon © Oren Nakdimon create or replace package images_dl is procedure add_image ( i_owner_type in images.owner_type%type, i_owner_id in images.owner_id%type, i_name in images.name%type, i_size in images.size_in_bytes%type, i_resolution in images.resolution%type, o_id out images.id%type ); procedure delete_image(i_id in images.id%type); procedure get_images ( i_owner_type in images.owner_type%type, i_owner_id in images.owner_id%type, o_images_rc out sys_refcursor ); end images_dl;
  97. 97. © Oren Nakdimon © Oren Nakdimon procedure add_image ( i_owner_type in images.owner_type%type, i_owner_id in images.owner_id%type, i_name in images.name%type, i_size in images.size_in_bytes%type, i_resolution in images.resolution%type, o_id out images.id%type ) is begin insert into images (name, size_in_bytes, resolution, owner_type, owner_id) values (i_name, i_size, i_resolution, i_owner_type, i_owner_id) returning id into o_id; end add_image;
  98. 98. © Oren Nakdimon © Oren Nakdimon procedure delete_image(i_id in images.id%type) is begin delete images i where i.id = i_id; end delete_image;
  99. 99. © Oren Nakdimon © Oren Nakdimon procedure get_images ( i_owner_type in images.owner_type%type, i_owner_id in images.owner_id%type, o_images_rc out sys_refcursor ) is begin open o_images_rc for select i.owner_id, i.id, i.name, i.size_in_bytes, i.resolution from images i where i.owner_type = i_owner_type and i.owner_id = i_owner_id order by i.id; end get_images;
  100. 100. © Oren Nakdimon © Oren Nakdimon create or replace package music is ... procedure get_artist ( i_id in artists.id%type, o_name out artists.name%type, o_type out artists.type%type, o_date_of_birth out artists.date_of_birth%type, o_date_of_death out artists.date_of_death%type, o_images_rc out sys_refcursor ); procedure get_album ( i_id in albums.id%type, o_title out albums.title%type, o_songs_rc out sys_refcursor, o_images_rc out sys_refcursor ); ... end music;
  101. 101. © Oren Nakdimon © Oren Nakdimon procedure get_artist ( i_id in artists.id%type, o_name out artists.name%type, o_type out artists.type%type, o_date_of_birth out artists.date_of_birth%type, o_date_of_death out artists.date_of_death%type, o_images_rc out sys_refcursor ) is begin select ar.name, ar.type, ar.date_of_birth, ar.date_of_death into o_name, o_type, o_date_of_birth, o_date_of_death from artists ar where ar.id = i_id; images_dl.get_images(i_owner_type => 'Artist', i_owner_id => i_id, o_images_rc => o_images_rc); end get_artist;
  102. 102. © Oren Nakdimon © Oren Nakdimon procedure get_album ( i_id in albums.id%type, o_title out albums.title%type, o_songs_rc out sys_refcursor, o_images_rc out sys_refcursor ) is begin select al.title into o_title from albums al where al.id = i_id; open o_songs_rc for select s.album_id, s.track#, s.title, s.artist_id, s.is_album_favourite from songs s where s.album_id = i_id order by s.track#; images_dl.get_images(i_owner_type => 'Album', i_owner_id => i_id, o_images_rc => o_images_rc); end get_album;
  103. 103. THANK YOU☺ Oren Nakdimon www.db-oriented.com  oren@db-oriented.com  +972-54-4393763 @DBoriented

×