modify CLOB to remove duplicates - oracle

I have a table, which contains a CLOB. Based on the CLOB I called a function to create a hash key.
Since some of the CLOBs were duplicates, which in turn generated duplicate hash keys, which prevents me from creating a PRIMARY KEY on the column hash_val.
Below are grouped duplicates.
What I want to do is modify every CLOB in the list depending on the group by appending a timestamp at the bottom of each CLOB so there will no longer be duplicates and then I can add the PRIMARY KEY.
I was hoping someone can help me out by generating a loop and appending the timestamp and I'll put an INSERT/update trigger on the table to generate future hash values.
CREATE table table_z(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
val NUMBER,
hash_val VARCHAR2(1000),
clob_val CLOB);
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
1, '4714870AFF6C97CA09D135834FDB58A6389A50C11FEF8EC4AFEF466FB60A23AC6B7A9C92658F14DF4993D6B40A4E4D8424196AFC347E97640D68DE61E1CF14B0', 'aaaaaaaaaa');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
1, 'F368A29B71BD201A7EF78B5DF88B1361FBE83F959756D33793837A5D7B2EAF660F2F6C7E2FBACE01965683C4CFAFDED3FF28AAB34E329AA79BC81E7703F68B86', 'aaaaa');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
2, '517C1CDB694A83ABF80A1D91EE91059B6443769DBEDDF3F5CC583CCCCC1CCDFE9E5330C61830D9E25AF03536909E8272F056C8FF1FBC9AABD3492C291A735B58', 'Xaaaaaaaaa');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
2, 'D597AD764E82E38DED6184527197C5CA39743F805F1D2355A89E62ECA275D62CD545DDFA57A36B37C711527A63717A69586CBE78AD056A92A0C6479391FC2349', 'xxxx');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
3, '9B71D224BD62F3785D96D46AD3EA3D73319BFBC2890CAADAE2DFF72519673CA72323C3D99BA5C11D7C7ACC6E14B8C5DA0C4663475C2E5C3ADEF46F73BCDEC043', 'hello');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
1, '4714870AFF6C97CA09D135834FDB58A6389A50C11FEF8EC4AFEF466FB60A23AC6B7A9C92658F14DF4993D6B40A4E4D8424196AFC347E97640D68DE61E1CF14B0', 'aaaaaaaaaa');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
2, '4714870AFF6C97CA09D135834FDB58A6389A50C11FEF8EC4AFEF466FB60A23AC6B7A9C92658F14DF4993D6B40A4E4D8424196AFC347E97640D68DE61E1CF14B0', 'aaaaaaaaaa');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
2, '6522DA2F3FE4F163D52ACEF62440C086BE5EC1203C2CE90A5427546A1CAFE6440618FD3AF2C8A3362AB7BC7544600CA77BED41F95D8038A8A7CC458177691474', 'oracle');
INSERT into table_z ( VAL, HASH_VAL, CLOB_VAL) VALUES (
3, '6522DA2F3FE4F163D52ACEF62440C086BE5EC1203C2CE90A5427546A1CAFE6440618FD3AF2C8A3362AB7BC7544600CA77BED41F95D8038A8A7CC458177691474', 'oracle');
SELECT
listagg(seq_num,',') within group(order by seq_num) seq_num,
hash_val, COUNT(hash_val)
FROM table_z
GROUP BY hash_val
HAVING COUNT(hash_val) > 1;
SEQ_NUM HASH_VAL COUNT(HASH_VAL)
1,6,7 4714870AFF6C97CA09D135834FDB58A6389A50C11FEF8EC4AFEF466FB60A23AC6B7A9C92658F14DF4993D6B40A4E4D8424196AFC347E97640D68DE61E1CF14B0 3
8,9 6522DA2F3FE4F163D52ACEF62440C086BE5EC1203C2CE90A5427546A1CAFE6440618FD3AF2C8A3362AB7BC7544600CA77BED41F95D8038A8A7CC458177691474 2

You don't really need a loop, you can do this with an UPDATE. I usually see it done like this, where you pick which duplicate you don't want to change, usually the min or max of a series, and change all the ones greater/less than that reference.
update table_z
set clob_val = clob_val || (systimestamp +seq_num/10000)
where exists -- only update if there's a duplicate with a lower seq_num
(select 1 from table_z min_z
where min_z.hash_val = table_z.hash_val
and min_z.seq_num < table_z.seq_num)
I added +seq_num/10000 to the timestamp so it'll add a few seconds for each row, so that none of the clobs in each series get the same timestamp.

Related

Oracle sql query to convert common rows to columns

I had a requirement to build a survey like application. Did this using Oracle APEX 18.2. I'm done designing the table structures and their relationships. I'm now on the part where I need to build a report (can be Interactive Report/Classic Report) where the columns will depend on what questions are available.
So basically, each question is supposed to have a column in the report. I can build the report and add these questions as column on design time. But I was wondering if there is a way to do this at runtime as well? More questions can be added in the future and I would like to report to be flexible enough to accommodate this. For now I would like to try via SQL statement.
Here's my table design for refence.
create table persons (
id number primary key
, name varchar2(100) not null
)
/
insert into persons values ( 1, 'Bruce' )
/
insert into persons values ( 2, 'Jennifer' )
/
create table questions (
id number primary key
, question varchar2(100) not null
)
/
insert into questions values ( 1, 'Gender' )
/
insert into questions values ( 2, 'Currently Employed' )
/
create table choices (
id number primary key
, choice varchar2(100) not null
)
/
insert into choices values ( 1, 'Male' )
/
insert into choices values ( 2, 'Female' )
/
insert into choices values ( 3, 'Yes' )
/
insert into choices values ( 4, 'No' )
/
create table question_choices (
id number primary key
, question_id number
, choice_id number
)
/
insert into question_choices values ( 1, 1, 1 )
/
insert into question_choices values ( 2, 1, 2 )
/
insert into question_choices values ( 3, 2, 3 )
/
insert into question_choices values ( 4, 2, 4 )
/
create table survey (
id number primary key
, name varchar2(100) not null
)
/
insert into survey values ( 1, 'Survey 1' )
/
create table survey_questions (
id number primary key
, survey_id number
, question_id number
)
/
insert into survey_questions values ( 1, 1, 1 )
/
insert into survey_questions values ( 2, 1, 2 )
/
create table survey_responses (
id number primary key
, survey_id number
, person_id number
, question_id number
, choice_id number
)
/
insert into survey_responses values ( 1, 1, 1, 1, 1 )
/
insert into survey_responses values ( 2, 1, 1, 2, 4 )
/
insert into survey_responses values ( 3, 1, 2, 1, 2 )
/
insert into survey_responses values ( 4, 1, 2, 2, 3 )
/
commit
/
I did a bit or research and found out about the pivot command. However, this approach also requires that the columns be defined at design time.
select *
from (
select s.name survey
, p.name respondent
, q.question
, c.choice
, row_number() over (partition by person_id, question order by choice ) rn
from persons p
, questions q
, choices c
, question_choices qc
, survey s
, survey_questions sq
, survey_responses sr
where p.id = sr.person_id
and q.id = sr.question_id
and c.id = qc.choice_id
and s.id = sq.survey_id
and sq.question_id = q.id
and sr.choice_id = c.id
)
pivot (max(question) question, max(choice) for question in ( 'Gender','Currently Employed' ))
Appreciate any comments/suggestions.

Oracle convert table to interval day(3) to second

I have the following table definition, with data that creates fine but I like to convert it to a more generic format but I'm having issues. Can someone point me in the right direction
CREATE TABLE partition_retention
(
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
TABLE_NAME VARCHAR2(30),
DAYS NUMBER(6),
CONSTRAINT
partition_retention_pk primary key (table_name));
/
INSERT into partition_retention(TABLE_NAME, DAYS)
WITH data as (
select 'T1', 0
from dual union all
select 'T3', 15
from dual union all
select 'T4', 10
from dual union all
select 'T5', 5
from dual)
SELECT * from data;
/
-- having problem creating
CREATE TABLE PARTITION_RETENTION AS (
TABLE_NAME AS VARCHAR2(30)
RETENTION DAY AS INTERVAL DAY(3) TO SECOND(0)
);
The three AS keywords are invalid (create table .. as select .. is valid, but you aren't doing that); you are missing a comma; and you have an unquoted column name with a space.
Correcting those things, this works:
CREATE TABLE PARTITION_RETENTION (
TABLE_NAME VARCHAR2(30),
RETENTION_DAY INTERVAL DAY(3) TO SECOND(0)
);
Your inserts will then have to insert interval values, not simple numbers, obviously.
db<>fiddle
how can I add a CONSTRAINT on the table to ensure the day>0 and the time is always 0
You can add separate constraints to check both things:
CREATE TABLE PARTITION_RETENTION (
TABLE_NAME VARCHAR2(30),
RETENTION_DAY INTERVAL DAY(3) TO SECOND(0),
CONSTRAINT CHK_NON_ZERO_DAYS CHECK (
RETENTION_DAY > INTERVAL '0' DAY
),
CONSTRAINT CHK_WHOLE_DAYS CHECK (
EXTRACT(HOUR FROM RETENTION_DAY) = 0
AND EXTRACT(MINUTE FROM RETENTION_DAY) = 0
AND EXTRACT(SECOND FROM RETENTION_DAY) = 0
)
);
to give slightly different errors (via the constraint names) - db<>fiddle - or combine them into one.
I'm not sure this is really any clearer or easier than having a number column constrained to integers between 1 and 999.

To split delimited value in column input in where clause

I need to split a column which is Pipe delimited and compare with records. Something like this
select 1
from T1 t1
where t1.date_col not between '01-JAN-2005' and '31-JAN-2005';
I need to fill the between clause value from reference table where the data is something like
ref_table
col_1
01-JAN-2005 | 31-JAN-2005
Query I am trying to achieve
REGEXP_SUBSTR ( col_1
, '^[^|]+') from ref_table
Which is resulting into 01-JAN-2005.
Table T1
date_col
01-Jan-05
15-Jan-05
31-Mar-05
Ref_table
col_1
01-JAN-2005 | 31-JAN-2005
You can do as this:
--Sample data
with t1 as (
select '01-Jan-05' col_1 union
select '15-Jan-05' union
select '31-Mar-05'
),
Ref_table as (
select '01-JAN-2005 | 31-JAN-2005' col_1
)
select *
from t1,
Ref_table r
where to_date(t1.col_1, 'DD/MON/YY')
not between to_date(trim(regexp_replace(r.col_1, '(.*)\|.*', '\1')), 'DD/MON/YY')
and to_date(trim(regexp_replace(r.col_1, '.*\|(.*)', '\1')), 'DD/MON/YY')
Although you really should improve your ref_table design. Store values with some char separated always turns out to be a problem.
Join the dates from table t1 with the intervals from the reference table and subtract the result from the original set of dates:
select t11.date_col
from t1 t11
minus
select t12.date_col
from t1 t12
join (
select to_date ( trim ( substr(col_1, 1, instr(col_1, '|') - 1) ), 'DD-Mon-YYYY' ) d_from
, to_date ( trim ( substr(col_1, instr(col_1, '|') + 1) ), 'DD-Mon-YYYY' ) d_to
from ref_table
) rt on (
rt.d_from <= t12.date_col
AND rt.d_to >= t12.date_col
)
;
I assume that col_1 from your reference table contains the excluded intervals in pairs.

select values from table and create oracle objects

i want to insert values to oracle Object type by selecting values from other table
And the tables and insert statement looks like this.
CREATE TYPE Test_obj AS OBJECT (
attr1 VARCHAR2(20),
attr2 VARCHAR2(20),
attr3 VARCHAR2(25) );
/
CREATE TABLE resultrow_obj (
resultrow Test_obj ,
RESULTTABLEID NUMBER(20,0),
ROWNUMBER NUMBER(20,0) );
/
INSERT INTO resultrow_obj VALUES (
Test_obj (select col1,col2,col3 from Table2 where rownum<=1),
1,123 );
/
You've got it nearly right:
SQL> INSERT INTO resultrow_obj
2 VALUES((SELECT Test_obj('A', 'B', 'C')
3 FROM dual WHERE rownum <= 1),
4 1, 123);
1 row inserted

how to create a temporary table or to select only distinct values from a column in a loop

I have a table with 2 columns, first column has repetitive values, now in a while loop i want to select each distinct value at a time, i created a temporary table in sql, but in oracle sql developer how do i write the code?
CREATE TABLE look_up_table
(row_id INT NOT NULL,
attribute VARCHAR(500),
VARCHAR(700)
)
/* now manually populating this table */
INSERT INTO look_up_table
VALUES
(1, grmacolor_frame_access, black);
(2, grmacolor_frame_access, blue);
(3, grmacolor_frame_access, red);
(4, grmamaterial_frame_access, acetate);
(5, grmamaterial_frame_access, metal);
(6, grmamaterial_frame_access, nylon);
(7, grmamaterial_frame_access, plastic);
DECLARE #temp_col_val NVARCHAR (700), #counter1 INT,
SET #counter1 = 0;
SET #column_count = (SELECT COUNT (DISTINCT attribute) FROM look_up_table);
CREATE TABLE #temp1 AS
SELECT DISTINCT attribute AS attrib,
ROW_NUMBER() OVER (ORDER BY attribute) AS seqno1,
FROM look_up_table;
WHILE (#counter1 < #column_count)
BEGIN;
SET #temp_col_val = (SELECT attrib FROM #temp1 WHERE seqno1 = #counter1;
please help
The following code will loop over each attribute and print it :
declare
curField varchar2(100);
resultCnt number ;
begin
select count(distinct attribute) into resultCnt from look_up_table;
for ind in 1..resultCnt loop
select attribute into curField from (
select attribute, rownum rwn
from
(
select distinct attribute
from look_up_table
)
) where rwn = ind;
dbms_output.put_line (curField);
end loop;
end;
/

Resources