The Problem
When working with items that allow multiple selections in Oracle APEX such as checkbox, list manager, radio group, or shuttle, these values are stored in a single colon-delimited string.
Storing the value in this format into the database does not comply with the database normalization forms and is considered a bad practice.
Below I explain my approach to solve this problem. I created a demo application where you can try the solution and/or copy the script to generate the required database objects in your own environment to follow along with this example. Login using the credentials Demo/Demo.
The Solution
In this example, let’s assume we have the following data model:
Since a movie can have one or more categories, and a category can be assigned to zero or more movies, we will use an associate table (MOVIES_CATEGORIES) to link the values between these two tables (MOVIES and CATEGORIES). Instead of storing CATEGORIES as a column on the MOVIES table.
The first step is to create a "Form on a Table with Report" based on the MOVIES table. Click on "Create Page", select "Form" and "Form on a Table with Report". Modify the following values and leave the rest as default:
Under Report Page
Implementation: ClassicPage Name: Movies Report
Under Data Source
Table / View Name: MOVIESUnder Form Page
Page Mode: Modal DialogPage Name: Movies Form
Primary Key Type: Select Primary Key Column(s)
Primary Key Column 1: ID (Number)
Select Columns: NAME (Varchar2)
You will end up with two pages, e.g.:
- Page 2 – Movies Report
- Page 3 – Movies Form
On the Movies Report, change the SQL query as follow:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
select | |
m.id, | |
m.name, | |
listagg(c.category, ', ') within group (order by c.category) as categories | |
from | |
movies m | |
left outer join movies_categories mc on m.id = mc.movie_id | |
left outer join categories c on mc.category_id = c.id | |
group by | |
m.id, | |
m.name | |
order by | |
m.name asc |
Create a new Page Item "P3_CATEGORIES". Make it a Shuttle. Under the List of Values properties, specify the following:
Type: SQL Query
SQL Query: select category d, id r from categories order by category
Create a new process "Process Categories", right after "Process Row of MOVIES" and before "reset page" with the following PL/SQL Code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_vc_arr2 apex_application_global.vc_arr2; | |
begin | |
l_vc_arr2 := apex_util.string_to_table(:P3_CATEGORIES); | |
delete from movies_categories | |
where movie_id = :P3_ID; | |
forall i in l_vc_arr2.first..l_vc_arr2.last | |
insert into movies_categories (movie_id, category_id) | |
values (:P3_ID, l_vc_arr2(i)); | |
end; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_vc_arr2 apex_application_global.vc_arr2; | |
begin | |
select category_id | |
bulk collect | |
into l_vc_arr2 | |
from movies_categories | |
where movie_id = :P3_ID | |
order by 1; | |
:P3_CATEGORIES := apex_util.table_to_string ( | |
p_table => l_vc_arr2, | |
p_string => ':' | |
); | |
end; |
Final Words
This demo was designed with Oracle Database 11g and APEX 5.0 in mind. In Oracle Database 12c, you can take advantage of Identity Columns when creating new tables. Also, the latest APEX 5.1 comes with a new package APEX_STRING that includes two functions (SPLIT and JOIN) that can be used instead of the package APEX_UTIL and the functions STRING_TO_TABLE and TABLE_TO_STRING.
Regardless of the method you use, it’s important to always keep your database normalized to reduce data redundancy and improve data integrity.
As suggested by fac586 in the original thread, instead of creating the fetch process "Fetch Categories", change the source of P3_CATEGORIES to "SQL Query (return colon separated values)" and specify the SQL Query: "select category_id from movies_categories where movie_id = :P3_ID".
ReplyDelete