I'm trying to enforce groups of activities do not have duplicates without using triggers or materialized views if possible.
DDL to create table:
CREATE TABLE groups (group_id NUMBER, activity_id number) ;
INSERT INTO groups (group_id, activity_id) values (1,1) ;
INSERT INTO groups (group_id, activity_id) values (1,2) ;
INSERT INTO groups (group_id, activity_id) values (2,2) ;
INSERT INTO groups (group_id, activity_id) values (2,3) ;
The next query should return no duplicates if all groups are made of unique activities. I'd like to enforce that this query never returns any duplicates:
SELECT LISTAGG(ACTIVITY_ID, '|') WITHIN GROUP(ORDER BY ACTIVITY_ID) FROM GROUPS GROUP BY GROUP_ID ;
My current solution is to insert the result of the LISTAGG query into a table with a unique constraint and use that to control duplicates with my application code handling any constraint violations by searching for the existing row (and thereby getting the group ID to reuse the existing one)
Best Answer
TL;DR
What you want has been defined by ANSI SQL-92 as an
ASSERTION
. None of the major RDBMS supportASSERTION
... yet.Expanding what Paul(tm) mentioned in the comments - you must perform your DML for each "GROUP + List of ACTIVITIES" within a single Action (DML or Procedure call).
Assuming you need multi-user capability, you can't do what you want with a single table. You will need to use multiple tables. You can then hide the logic (including locking) for the processing against the multiple tables by using
VIEW
s and Transactional APIs (Packages
)DO NOT ALLOW USERS/APPLICATIONS TO PERFROM DML AGAINST THESE UNDERLYING TABLES!!
Example Underlying Tables
The APIs acquire a Row Lock against the
group_activity#lock
table. This allow multiple users to manipulate differentGROUP_IDs
simultaneously. Yes,group_activity#lock
could hold a Nested Table of Activities. But, from experience, using a UDT in a table gets messy when you try to change the UDT.The
activities_hash
column could be theLISTAGG()
result or even the SHA256 version of the result. The functionGROUP_ACTIVITY_API.HASH()
controls the calculation of this value. The APIs enforce proper locking and calculating of the Hash value. As such, you do not let user/application to perform DML operations against this table directly.The table
group_activity#activities
holds the list of activities that belong to eachGROUP_ID
. Because of the required locking againstgroup_activity#lock
, do not allow direct DML activity against this table.API Set 1 - Use 2 VIEWs
With this set of interfaces, the Users/Applications get access to two VIEWS.
One view looks exactly like what you have already. You will use this view for linking against other tables. Users/Applications will have only the
SELECT
Privilage against this VIEW. THEY DO NOT GET ACCESS TO THE UNDERLYING TABLES!The other view contains a
GROUP_ID
and aNested Table
. This VIEW is used to perform your DML activities. The actual processing is handled via INSTEAD OF trigger (not listed here). A variation of this VIEW could use a VARCHAR2 (or XMLType) that shows the LISTAGG() result.Adding constraints to the VIEWs, in
RELY DISABLE NOVALIDATE
state, might help the CBO create better plans. Your Millage May Vary.API Set 2 - use a GTT
Not every application framework can handle a Nested Table. Plus - I'm sure a programmer will eventually FUBAR the construction of the VARCHAR2.
Instead, of performing a DML against a VIEW with a Nested Table/VARCHAR2, this method allows a framework to:
GLOBAL TEMPORARY TABLE
.I would recommend this method for things like APEX's IG row processing.
You should still create at least the read only VIEW so that it can be used by applications to link to other tables.