How to get random foreign key in SQL Developer? - oracle

After creating all tables in SQL Developer I need to fill them with at least 10000 entries. I had no problem with tables than had zero FK.
How can I get random values from other tables?
select rand() doesn't work in this statement. but max() does.
CREATE OR REPLACE PROCEDURE NAPOLNI_ARTIKEL
(
ST_ARTIKLOV IN VARCHAR2 DEFAULT 10000
) AS
naziv VARCHAR2(25);
opis VARCHAR2(25);
model VARCHAR2(10);
cena FLOAT(2);
gar INTEGER;
ddv INTEGER;
tip INTEGER;
CURSOR c1 IS
SELECT id_dobavitelj
FROM dobavitelj;
BEGIN
FOR rndx IN c1 LOOP
FOR st IN 1..ST_ARTIKLOV LOOP
naziv := 'naziv';
naziv := naziv ||' '|| TO_CHAR(st);
opis := 'opis';
opis := opis ||' '|| TO_CHAR(st);
model := 'model';
model := model ||' '|| TO_CHAR(st);
cena := dbms_random.value(1.25,230.0);
SELECT NVL(RAND(id_garancija),1)
INTO gar
FROM garancija;
SELECT NVL(RAND(id_ddv),1)
INTO ddv
FROM DDV;
SELECT NVL(RAND(id_tip),1)
INTO tip
FROM tip;
INSERT INTO ARTIKEL(ID_ARTIKLA, NAZIV, OPIS, MODEL, CENA, TIP_ID_TIP, DOBAVITELJ_ID_DOBAVITELJ, GARANCIJA_ID_GARANCIJE, DDV_ID_DDV) VALUES (st, naziv, opis, model, cena, tip, rndx, gar, ddv);
END LOOP;
END LOOP;
END NAPOLNI_ARTIKEL;
* id_artikla INTEGER NOT NULL ,
* naziv VARCHAR2 (25) NOT NULL ,
opis VARCHAR2 (25) ,
* model VARCHAR2 (10) NOT NULL ,
* cena FLOAT (2) NOT NULL ,
F * Tip_id_tip INTEGER NOT NULL ,
F * Dobavitelj_id_dobavitelj INTEGER NOT NULL ,
F Garancija_id_garancije INTEGER ,
F * DDV_id_ddv INTEGER NOT NULL

To pick a random value from a table you can use a subquery ordered randomly, and then pick the first row from that:
SELECT id_garancija
INTO gar
FROM (
SELECT id_garancija
FROM garancija
ORDER BY dbms_random.value
)
WHERE rownum = 1;
With a lot of data you could also use the sample() clause to avoid having to find and order all values from the table:
SELECT id_garancija
INTO gar
FROM garancija
SAMPLE(1)
WHERE rownum = 1;
You can set the sample size based on the size of the table; with 10000 rows you might be able to use 0.1, for example. You can read more here.
If you're populating your parent and child tables at the same time you could consider the INSERT ALL syntax to insert into multiple tables simultaneously, using the same values (eg from a sequence) consistently, rather than looking them up again later. It looks like you want random combinations of foreign keys though, so that might not be helpful here.

Related

Writing Oracle stored procedure with Oracle table as Input parameter

How to write Oracle stored procedure with a table (X) as input parameter and that table X is used inside procedure to join with another table Y?
Table X will have thousands of records.
Not looking to pass table name as varchar and then using dynamic SQL (so, this option is out of picture)
From 19.6 you can create a SQL macro. This returns a string with your query fragment.
At parse time the database will do a find/replace of the table parameter with the table you've passed it:
create or replace function f ( tab dbms_tf.table_t )
return varchar2 sql_macro as
begin
return 'select * from tab
join ( select level rn from dual connect by level <= 2 )
on c1 = rn';
end f;
/
create table t1 (
c1 int
);
create table t2 (
c1 int
);
insert into t1 values ( 1 );
insert into t2 values ( 2 );
select * from f ( t1 );
C1 RN
1 1
select * from f ( t2 );
C1 RN
2 2
There's another approach you might find interesting: pass a cursor variable to pipelined table function, invoke it in SQL, allowing you literally pass the contents of the table (select * from...), bulk collect into collection, then join the collection with your other table!
DROP TYPE tickertype FORCE;
DROP TYPE tickertypeset FORCE;
DROP TABLE stocktable;
DROP TABLE tickertable;
CREATE TABLE stocktable
(
ticker VARCHAR2 (20),
trade_date DATE,
open_price NUMBER,
close_price NUMBER
)
/
BEGIN
FOR indx IN 1 .. 100
LOOP
INSERT INTO stocktable
VALUES ('STK' || indx,
SYSDATE,
indx,
indx + 15);
END LOOP;
COMMIT;
END;
/
CREATE TABLE tickertable
(
ticker VARCHAR2 (20),
pricedate DATE,
pricetype VARCHAR2 (1),
price NUMBER
)
/
CREATE TYPE tickertype AS OBJECT
(
ticker VARCHAR2 (20),
pricedate DATE,
pricetype VARCHAR2 (1),
price NUMBER
);
/
BEGIN
FOR indx IN 1 .. 100
LOOP
INSERT INTO tickertable
VALUES ('STK' || indx,
SYSDATE,
'O',
indx);
END LOOP;
COMMIT;
END;
/
CREATE TYPE tickertypeset AS TABLE OF tickertype;
/
CREATE OR REPLACE PACKAGE refcur_pkg
AUTHID DEFINER
IS
TYPE refcur_t IS REF CURSOR
RETURN stocktable%ROWTYPE;
TYPE dataset_tt IS TABLE OF stocktable%ROWTYPE;
END refcur_pkg;
/
CREATE OR REPLACE FUNCTION pipeliner (dataset refcur_pkg.refcur_t)
RETURN tickertypeset
PIPELINED
AUTHID DEFINER
IS
l_row_as_object tickertype
:= tickertype (NULL,
NULL,
NULL,
NULL);
l_dataset refcur_pkg.dataset_tt;
l_count PLS_INTEGER;
BEGIN
FETCH dataset BULK COLLECT INTO l_dataset;
CLOSE dataset;
/* Let's do a join with another table. */
SELECT COUNT (*) into l_count
FROM TABLE (l_dataset) st, tickertable tt
WHERE st.ticker = tt.ticker;
DBMS_OUTPUT.put_line ('Count = ' ||l_count);
l_row_as_object.ticker := 'ABC';
PIPE ROW (l_row_as_object);
RETURN;
END;
/
BEGIN
FOR rec
IN (SELECT * FROM TABLE (pipeliner (CURSOR (SELECT * FROM stocktable))))
LOOP
DBMS_OUTPUT.put_line (rec.ticker);
END LOOP;
END;
/
I see this output:
Count = 100
ABC
Create a table type in the SQL scope:
CREATE TYPE string_list AS TABLE OF VARCHAR2(5);
Then use that as the parameter for your stored procedure and join it to another table using a Table Collection Expression:
CREATE PROCEDURE test_proc(
p_list IN string_list
)
IS
v_cursor SYS_REFCURSOR;
v_string VARCHAR2(10);
BEGIN
OPEN v_cursor FOR
SELECT d.*
FROM DUAL d
INNER JOIN TABLE( p_list ) t
ON ( d.DUMMY = t.COLUMN_VALUE );
-- do something with the cursor.
LOOP
FETCH v_cursor into v_string;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_string );
END LOOP;
END;
/
Then you can call it:
BEGIN
test_proc( string_list( 'X', 'Y', 'Z' ) ) ;
END;
/
and it outputs:
X
db<>fiddle here

How it insert result of select statement into table of records (associative array)

I am working on HR scheme of oracle. I have table of records
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type,
);
type emp_record_table is table of emp_record
index by pls_integer;
I want to insert into emp_record_table results of the next select statement
select first_name, last_name
from employees
where department_id=30;
can you explain me how to solve this problem? thank you.
Simplest approach is to use bulk collect:
declare
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
type emp_record_table is table of emp_record
index by pls_integer;
l_recs emp_record_table;
begin
select first_name, last_name
bulk collect into l_recs
from employees
where department_id=30;
for idx in l_recs.first()..l_recs.last() loop
dbms_output.put_line(l_recs(idx).emp_first_name ||' '|| l_recs(idx).emp_last_name);
end loop;
end;
/
Note that you don't really need an associative array to process records like this. You can ditch the index by pls_integer and things will still work just fine. The value of associative arrays is when we need to maintain an access path to specific rows. For instance, we might want to use the primary key of the employees table to index the array. This would create a sparse array, because the selected employee IDs are not guaranteed to form a contiguous sequence. Consequently the logic for wrangling the array is more verbose:
declare
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
type emp_record_table is table of emp_record
index by pls_integer;
l_recs emp_record_table;
idx pls_integer;
begin
for r in (select emp_id, first_name, last_name
from employees
where department_id=30 )
loop
l_recs(r.emp_id).emp_first_name := r.first_name;
l_recs(r.emp_id).emp_last_name := r.last_name;
end loop;
idx := l_recs.first();
while idx is not null loop
dbms_output.put_line(l_recs(idx).emp_first_name ||' '|| l_recs(idx).emp_last_name);
idx := l_recs.next(idx);
end loop;
end;
/
Here is a demo on db<>fiddle.
Oracle Setup:
CREATE TABLE employees (
id NUMBER(8,0) PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(80)
);
CREATE PACKAGE test_pkg IS
TYPE emp_record IS RECORD(
emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
TYPE emp_record_table IS TABLE OF emp_record INDEX BY pls_integer;
END;
/
INSERT INTO employees( id, first_name, last_name )
SELECT -1, 'a', 'aaa' FROM DUAL UNION ALL
SELECT +3, 'b', 'bbb' FROM DUAL;
PL/SQL Block:
DECLARE
x PLS_INTEGER;
emps test_pkg.emp_record_table;
BEGIN
-- Populate the associative array
FOR row IN ( SELECT * FROM employees ) LOOP
emps(row.id).emp_first_name := row.first_name;
emps(row.id).emp_last_name := row.last_name;
END LOOP;
-- Read the associative array
x := emps.FIRST;
WHILE x IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE( x || ': ' || emps(x).emp_first_name || ' ' || emps(x).emp_last_name );
x := emps.NEXT(x);
END LOOP;
END;
/
Output:
-1: a aaa
3: b bbb
db<>fiddle here
You don't need associative array for that operation. Nested table will be perfect for that. Just declare your table type as following:
type emp_record_table is table of emp_record;
Firstly you have to declare table of your created type. Now you have only declared type or record and type of table of these records. You don't have yet your table. You can daclare it following way in the DECLARE section:
l_emp_table emp_record_table;
Then initialize it:
l_emp_table := emp_record_table();
Then you create cursor that will get data from your SELECT query. If you don't know how to do it, please read about cursor declaration, and fetching.
Next step will be: for each cursor row insert its data into table. You write simple loop that will do following steps:
Extend your declared table l_emp_table.extend()
Save data into table l_emp_table(i).emp_first_name := FETCHED_ROW.first_name and so on...

ORA-00928: missing SELECT keyword

In MYTABLE there are courses and their predecessor courses.
What I am trying to is to find the courses to be taken after the specified course. I am getting missing SELECT keyword error. Why I am getting this error although I have SELECT statement in FOR statement ? Where am I doing wrong ?
DECLARE
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
BEGIN
WITH DATA AS
(select (select course_name
from MYTABLE
WHERE predecessors like ('''%' || coursename||'%''')
) str
from dual
)
FOR cursor1 IN (SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(cursor1);
END LOOP;
end;
Unless I'm wrong, WITH factoring clause can't be used that way; you'll have to use it as an inline view, such as this:
declare
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
begin
for cursor1 in (select str
from (select (select course_name
from mytable
where predecessors like '''%' || coursename||'%'''
) str
from dual
)
)
loop
dbms_output.put_line(cursor1.str);
end loop;
end;
/
Apart from the fact that it doesn't work (wrong LIKE condition), you OVERcomplicated it. This is how it, actually, does something:
SQL> create table mytable(course_name varchar2(20),
2 predecessors varchar2(20));
Table created.
SQL> insert into mytable values ('COURSE_101', 'COURSE_101');
1 row created.
SQL>
SQL> declare
2 coursename varchar2(20) := 'COURSE_101';
3 begin
4 for cursor1 in (select course_name str
5 from mytable
6 where predecessors like '%' || coursename || '%'
7 )
8 loop
9 dbms_output.put_line(cursor1.str);
10 end loop;
11 end;
12 /
COURSE_101
PL/SQL procedure successfully completed.
SQL>
Also, is that WHERE clause correct? PREDECESSORS LIKE COURSENAME? I'm not saying that it is wrong, just looks somewhat strange.
To extend #Littlefoot's answer a bit: you can use a common table expression (WITH clause) in your cursor, but the WITH must be part of the cursor SELECT statement, not separate from it:
DECLARE
coursename varchar2(200) := 'COURSE_101';
BEGIN
FOR aRow IN (WITH DATA AS (select course_name AS str
from MYTABLE
WHERE predecessors like '''%' || coursename||'%''')
SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.str);
END LOOP;
END;
Also note that the iteration variable in a cursor FOR-loop represents a row returned by the cursor's SELECT statement, so if you want to display whatever was returned by the cursor you must use dotted-variable notation (e.g. aRow.str) to extract fields from the row.
Best of luck.
CREATE TABLE product
(
PRODUCT_ID int Primary key,
NAME VARCHAR (20) not null,
Batchno int not null,
Rate int not null,
Tax int not null,
Expiredate date not null
);
INSERT INTO PRODUCT VALUSES(1 , 'vasocare', 32 , 15 , 2 , 01-JAN-2021);

How to initialise a nested table collection which resides inside a PL/SQL Record?

Friends,
Hope you can help.
What I am trying to achieve is to use a collection type(s) that can be accessed either inside and outside of PL/SQL so that an external program can declare a type of this collection and work with it's contents.
The collection will contain some scaler and one composite datatype.
Using the scott schema as an example, the goal is that each record within the collection should contain the department information and within the same record a collection containing the employee information for that department.
I have got the output I require when using PL/SQL associative arrays but they can only be used from with PL/SQL.
When I convert the code to use another type of collection, nested table, I receive a ORA-06531: Reference to uninitialized collection Which is because I haven't initialised the collection held within the record.
Is it possible to achieve this using this design? Or (as I increasing feel!) have I gone down the wrong path?
Two code samples follow.
Firstly the one that works with PL/SQL associative arrays:
DECLARE
TYPE emp_tab_type IS TABLE OF emp%ROWTYPE
INDEX BY BINARY_INTEGER;
TYPE dept_emp_rec IS RECORD (dept_id dept.deptno%TYPE,
dept_name dept.dname%TYPE,
dept_loc dept.loc%TYPE,
emp_data emp_tab_type);
TYPE dept_emp_tab_type IS TABLE OF dept_emp_rec
INDEX BY BINARY_INTEGER;
l_dept_emp_tab dept_emp_tab_type;
CURSOR dept_cur IS
SELECT d.*
FROM dept d
ORDER BY d.deptno;
CURSOR emps_cur (p_dept_id IN NUMBER ) IS
SELECT e.*
FROM emp e
WHERE e.deptno = p_dept_id
ORDER BY e.ename;
j PLS_INTEGER := 1;
k PLS_INTEGER;
BEGIN
FOR dept_rec IN dept_cur
LOOP
-- populate dept data
l_dept_emp_tab(j).dept_id := dept_rec.deptno;
-- other assignment statements
dbms_output.put_line('dept no ' || l_dept_emp_tab(j).dept_id);
-- populate emp data
k := 1;
FOR emp_row_rec IN emps_cur(dept_rec.deptno)
LOOP
l_dept_emp_tab(j).emp_data(k).empno := emp_row_rec.empno;
-- other assignment statements
dbms_output.put_line( l_dept_emp_tab(j).emp_data(k).empno);
k := k + 1;
END LOOP;
j := j + 1;
END LOOP;
END;
This is the example using nested tables that DOESN'T work
DECLARE
TYPE emp_tab_type IS TABLE OF emp%ROWTYPE;
--INDEX BY BINARY_INTEGER;
TYPE dept_emp_rec IS RECORD (dept_id dept.deptno%TYPE,
dept_name dept.dname%TYPE,
dept_loc dept.loc%TYPE,
emp_data emp_tab_type);
TYPE dept_emp_tab_type IS TABLE OF dept_emp_rec;
--INDEX BY BINARY_INTEGER;
l_dept_emp_tab dept_emp_tab_type := dept_emp_tab_type();
CURSOR dept_cur IS
SELECT d.*
FROM dept d
ORDER BY d.deptno;
CURSOR emps_cur (p_dept_id IN NUMBER ) IS
SELECT e.*
FROM emp e
WHERE e.deptno = p_dept_id
ORDER BY e.ename;
j PLS_INTEGER := 1;
k PLS_INTEGER;
BEGIN
FOR dept_rec IN dept_cur
LOOP
l_dept_emp_tab.EXTEND;
-- populate dept data
l_dept_emp_tab(j).dept_id := dept_rec.deptno;
-- other assignment statements
dbms_output.put_line('dept no ' || l_dept_emp_tab(j).dept_id);
-- populate emp data
k := 1;
FOR emp_row_rec IN emps_cur(dept_rec.deptno)
LOOP
l_dept_emp_tab(j).emp_data(k).empno := emp_row_rec.empno;
-- other assignment statements
dbms_output.put_line( l_dept_emp_tab(j).emp_data(k).empno);
k := k + 1;
END LOOP;
j := j + 1;
END LOOP;
END;
I am using Oracle Enterprise Edition 10.2.0.4
Thanks
You are indeed getting the ORA-06531 error because you haven't initialised the collections within each record. To do this, try adding the line
l_dept_emp_tab(j).emp_data := emp_tab_type();
to the other assignments to fields of l_dept_emp_tab(j).
You'll also need to add a call to l_dept_emp_tab(j).emp_data.EXTEND within the inner loop, to make space for the new entry about to be added. Insert this above all the assignments within the inner loop. If you don't add this, you'll get an ORA-06533: Subscript beyond count error.
You seem to be handling the outer nested table type (dept_emp_tab_type) correctly, by calling its constructor (in the DECLARE section) and by calling EXTEND to grow the nested table. All you need to do is to do the same for each instance of the inner nested table type,emp_tab_type .
Here is a different way, this accomplishes everything pretty much within a query (do note it requires the types to be created outside of the block)
http://download.oracle.com/docs/cd/B10501_01/appdev.920/a96624/05_colls.htm
Creation and cleanup of table and types used
/*
CREATE TABLE EMP (ENAME VARCHAR2(50) , DEPTNO NUMBER, empno number);
INSERT INTO EMP VALUES('m1e',1,1);
INSERT INTO EMP VALUES('m2e',1,2);
insert into emp values('m3e',2,3);
INSERT INTO EMP VALUES('m2e',2,4);
insert into emp values('m3e',3,5);
CREATE TABLE DEPT(deptno NUMBER, dname VARCHAR2(50), loc VARCHAR2(50));
INSERT INTO DEPT VALUES(1 ,'portland','tt');
INSERT INTO DEPT VALUES(2 ,'astoria','tt');
INSERT INTO DEPT VALUES(3 ,'eugene','tt');
Creation of types (note this is not within the package/block so that it is available to SQL)
---
drop type emptable force;
DROP TYPE EMP_TAB_TYPE force;
drop type emptable ;
DROP TYPE DEPT_EMP_REC force;
drop type dep_emp_rec_table force;
DROP TABLE DEPT;
drop table emp;
*/
Now create the types outside the package/block so the types are available to SQL
create or replace TYPE emp_tab_type as object (ENAME VARCHAR2(50) , DEPTNO NUMBER);
create or replace type emptable as table of emp_tab_type ;
CREATE OR REPLACE TYPE DEPT_EMP_REC AS OBJECT (
DEPT_ID NUMBER,
dept_name varchar2(50),
dept_loc varchar2(50),
emp_data emptable);
create or replace type dep_emp_rec_table as table of dept_emp_rec;
Now we can directly select the types into the query (note the use of the cast/MULTISET)
SELECT
DEPT_EMP_REC(
deptno,
dname ,
loc ,
CAST(MULTISET(SELECT ENAME, DEPTNO
FROM EMP e
WHERE e.DEPTNO = d.deptno)
AS emptable))
FROM DEPT D ;
/
DEPT_EMP_REC(DEPTNO,DNAME,LOC,CAST(MULTISET(SELECTENAME,DEPTNOFROMEMPEWHEREE.DEPTNO=D.DEPTNO)ASEMPTABLE)) DEPT_EMP_REC(1,'portland','tt',EMPTABLE(EMP_TAB_TYPE('m1e',1),EMP_TAB_TYPE('m2e',1)))
DEPT_EMP_REC(2,'astoria','tt',EMPTABLE(EMP_TAB_TYPE('m3e',2),EMP_TAB_TYPE('m2e',2)))
DEPT_EMP_REC(3,'eugene','tt',EMPTABLE(EMP_TAB_TYPE('m3e',3)))
Now the block is a bit simpler (putting it all together)
set serveroutput on
DECLARE
p_dep_emp_rec_table dep_emp_rec_table;
BEGIN
SELECT
DEPT_EMP_REC(
DEPTNO,
DNAME,
LOC,
CAST( MULTISET
(
SELECT
ENAME,
DEPTNO
FROM EMP E
WHERE E.DEPTNO = D.DEPTNO
) AS EMPTABLE )
)
BULK COLLECT INTO p_dep_emp_rec_table
FROM
DEPT d ;
FOR I IN P_DEP_EMP_REC_TABLE.FIRST..P_DEP_EMP_REC_TABLE.LAST LOOP
DBMS_OUTPUT.PUT_LINE(I || ':' || P_DEP_EMP_REC_TABLE(I).DEPT_ID || '|' || P_DEP_EMP_REC_TABLE(I).DEPT_NAME || '|' || P_DEP_EMP_REC_TABLE(I).DEPT_LOC);
DBMS_OUTPUT.PUT_LINE('-----------------------');
FOR J IN P_DEP_EMP_REC_TABLE(I).EMP_DATA.FIRST..P_DEP_EMP_REC_TABLE(I).EMP_DATA.LAST LOOP
NULL;
dbms_output.put_line(P_DEP_EMP_REC_TABLE(i).emp_data(j).ENAME || '/' || P_DEP_EMP_REC_TABLE(i).emp_data(j).DEPTNO);
end loop;
END LOOP;
END;
anonymous block completed
1:1|portland|tt
-----------------------
m1e/1
m2e/1
2:2|astoria|tt
-----------------------
m3e/2
m2e/2
3:3|eugene|tt
-----------------------
m3e/3

SELECT DISTINCT CLOB_COLUMN FROM TABLE;

I would like to find the distinct CLOB values that can assume the column called CLOB_COLUMN (of type CLOB) contained in the table called COPIA.
I have selected a PROCEDURAL WAY to solve this problem, but I would prefer to give a simple SELECT as the following: SELECT DISTINCT CLOB_COLUMN FROM TABLE avoiding the error "ORA-00932: inconsistent datatypes: expected - got CLOB"
How can I achieve this?
Thank you in advance for your kind cooperation. This is the procedural way I've thought:
-- Find the distinct CLOB values that can assume the column called CLOB_COLUMN (of type CLOB)
-- contained in the table called COPIA
-- Before the execution of the following PL/SQL script, the CLOB values (including duplicates)
-- are contained in the source table, called S1
-- At the end of the excecution of the PL/SQL script, the distinct values of the column called CLOB_COLUMN
-- can be find in the target table called S2
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE S1 DROP STORAGE';
EXECUTE IMMEDIATE 'DROP TABLE S1 CASCADE CONSTRAINTS PURGE';
EXCEPTION
WHEN OTHERS
THEN
BEGIN
NULL;
END;
END;
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE S2 DROP STORAGE';
EXECUTE IMMEDIATE 'DROP TABLE S2 CASCADE CONSTRAINTS PURGE';
EXCEPTION
WHEN OTHERS
THEN
BEGIN
NULL;
END;
END;
CREATE GLOBAL TEMPORARY TABLE S1
ON COMMIT PRESERVE ROWS
AS
SELECT CLOB_COLUMN FROM COPIA;
CREATE GLOBAL TEMPORARY TABLE S2
ON COMMIT PRESERVE ROWS
AS
SELECT *
FROM S1
WHERE 3 = 9;
BEGIN
DECLARE
CONTEGGIO NUMBER;
CURSOR C1
IS
SELECT CLOB_COLUMN FROM S1;
C1_REC C1%ROWTYPE;
BEGIN
FOR C1_REC IN C1
LOOP
-- How many records, in S2 table, are equal to c1_rec.clob_column?
SELECT COUNT (*)
INTO CONTEGGIO
FROM S2 BETA
WHERE DBMS_LOB.
COMPARE (BETA.CLOB_COLUMN,
C1_REC.CLOB_COLUMN) = 0;
-- If it does not exist, in S2, a record equal to c1_rec.clob_column,
-- insert c1_rec.clob_column in the table called S2
IF CONTEGGIO = 0
THEN
BEGIN
INSERT INTO S2
VALUES (C1_REC.CLOB_COLUMN);
COMMIT;
END;
END IF;
END LOOP;
END;
END;
If it is acceptable to truncate your field to 32767 characters this works:
select distinct dbms_lob.substr(FIELD_CLOB,32767) from Table1
You could compare the hashes of the CLOB to determine if they are different:
SELECT your_clob
FROM your_table
WHERE ROWID IN (SELECT MIN(ROWID)
FROM your_table
GROUP BY dbms_crypto.HASH(your_clob, dbms_crypto.HASH_SH1))
Edit:
The HASH function doesn't guarantee that there will be no collision. By design however, it is really unlikely that you will get any collision. Still, if the collision risk (<2^80?) is not acceptable, you could improve the query by comparing (with dbms_lob.compare) the subset of rows that have the same hashes.
add TO_CHAR after distinct keyword to convert CLOB to CHAR
SELECT DISTINCT TO_CHAR(CLOB_FIELD) from table1; //This will return distinct values in CLOB_FIELD
Use this approach. In table profile column content is NCLOB. I added the where clause to reduce the time it takes to run which is high,
with
r as (select rownum i, content from profile where package = 'intl'),
s as (select distinct (select min(i) from r where dbms_lob.compare(r.content, t.content) = 0) min_i from profile t where t.package = 'intl')
select (select content from r where r.i = s.min_i) content from s
;
It is not about to win any prizes for efficiency but should work.
select distinct DBMS_LOB.substr(column_name, 3000) from table_name;
If truncating the clob to the size of a varchar2 won't work, and you're worried about hash collisions, you can:
Add a row number to every row;
Use DBMS_lob.compare in a not exists subquery. Exclude duplicates (this means: compare = 0) with a higher rownum.
For example:
create table t (
c1 clob
);
insert into t values ( 'xxx' );
insert into t values ( 'xxx' );
insert into t values ( 'yyy' );
commit;
with rws as (
select row_number () over ( order by rowid ) rn,
t.*
from t
)
select c1 from rws r1
where not exists (
select * from rws r2
where dbms_lob.compare ( r1.c1, r2.c1 ) = 0
and r1.rn > r2.rn
);
C1
xxx
yyy
To bypass the oracle error, you have to do something like this :
SELECT CLOB_COLUMN FROM TABLE COPIA C1
WHERE C1.ID IN (SELECT DISTINCT C2.ID FROM COPIA C2 WHERE ....)
I know this is an old question but I believe I've figure out a better way to do what you are asking.
It is kind of like a cheat really...The idea behind it is that You can't do a DISTINCT of a Clob column but you can do a DISTINCT on a Listagg function of a Clob_Column...you just need to play with the partition clause of the Listagg function to make sure it will only return one value.
With that in mind...here is my solution.
SELECT DISTINCT listagg(clob_column,'| ') within GROUP (ORDER BY unique_id) over (PARTITION BY unique_id) clob_column
FROM copia;

Resources