Oracle SQL - Check duplicates in whole table - sql

I have a table with columns
BIN_1_1
BIN_1_2
BIN_1_3
all the way to BIN_10_10
The user enter a value, and the value needs to be checked in all the columns starting from BIN_1_1 to BIN_10_10.
If there is a duplicate value, it prints a msg and gets out of the procedure / function.
How do I go about this?

Do you mean something like this?
create or replace
procedure check_duplicate( p_val yourtable.bin_1_1%type) is
v_dupl number;
begin
begin
select 1 into v_dupl from yourtable
where p_val in (bin_1_1, bin_1_2, ... bin_10_10) and rownum <=1;
exception
when no_data_found
then v_dupl := 0;
end;
if v_dupl = 1
then
dbms_output.put_line('your message about duplication');
return;
else
dbms_output.put_line('here you can do anything');
end if;
end;

Try this query,
INSERT INTO yourTable values ('your values') where
WHERE BIN_1_1 NOT IN (
SELECT bins FROM (
SELECT BIN_1_1 FROM yourTable
UNION
SELECT BIN_1_2 FROM yourTable
UNION
SELECT BIN_1_3 FROM yourTable
) AS bins
)
P.S. I din't run this query.

Unpivot your table, then it's easy. You may want to write a query that will write the query below for you. ("Dynamic" SQL just to save yourself some work.)
select case when count(*) > 1 then 'Duplicate Found' end as result
from ( select *
from your_table
unpivot (val for col in (BIN_1_1, BIN_1_2, ........, BIN_10_10))
)
where val = :user_input;
Here :user_input is a bind variable - use whatever mechanism works for you (end-user interface, SQL Developer, whatever).
You need to decide what outcome you want when the value is not duplicated in the table - you didn't mention anything about that.

Related

Create Temp Table in Each Loop and Union After Loop Completion

Using BigQuery's standard SQL scripting functionality, I want to 1) create a temp table for each iteration of a loop, and 2) union those temp tables after the loop is complete. I've tried something like the following:
DECLARE i INT64 DEFAULT 1;
DECLARE ttable_name STRING;
WHILE i < 10 DO
SET ttable_name = CONCAT('temp_table_', CAST(i AS STRING));
CREATE OR REPLACE TEMP TABLE ttable_name AS
SELECT * FROM my_table AS mt WHERE mt.my_col = 1;
SET i = i + 1;
END LOOP;
SELECT * FROM temp_table_*; -- wildcard table to union all results
But I get the following error:
Exceeded rate limits: too many table update operations for this table.
How can I accomplish this task?
Your script does not work the way you think it does!
Instead of writing in each iteration into separate table named like temp_table_N - you actually writing to the very same temp table named ttable_name - thus the Exceeded rate limits error
BigQuery does not allow using variables for objects names
Don't create new tables. Add to an existing one with an INSERT INTO, or hold data in a variable (if it's not too much data), as in:
DECLARE steps INT64 DEFAULT 1;
DECLARE table_holder ARRAY<STRUCT<steps INT64, x INT64, y ARRAY<INT64>>>;
LOOP
SET table_holder = (
SELECT ARRAY_AGG(
STRUCT(steps, 1 AS x, [1,2,3] AS y))
FROM (SELECT '')
);
SET steps = steps+1;
IF steps=30 THEN LEAVE; END IF;
END LOOP;
CREATE TABLE temp.results
AS
SELECT *
FROM UNNEST(table_holder)
Related: https://stackoverflow.com/a/59314390/132438
Question asker/OP here. While I have selected #felipe-hoffa's answer as I believe it will be best for future readers of this question, I have actually gone a different route in solving my problem:
BEGIN
DECLARE i INT64 DEFAULT 1;
CREATE OR REPLACE TEMP TABLE ttable AS
SELECT
CAST(NULL AS INT64) AS col1 -- cast NULL as the type of target col
,CAST(NULL AS FLOAT64) AS col2
,CAST(NULL AS DATE) AS col3;
WHILE i < 10 DO
-- overwrite `ttable` with its previous contents union'ed
-- with new data results from current loop iteration
CREATE OR REPLACE TEMP TABLE ttable AS
SELECT mt.col1, mt.col2, mt.col3 FROM my_table AS mt WHERE mt.other_col = i
UNION ALL
SELECT * FROM ttable;
SET i = i + 1;
END LOOP;
SELECT * FROM ttable; -- UNION'ed results
DROP TABLE IF EXISTS ttable;
END;
Why? I find it easier to stay in "table land" than to venture into "STRUCT/ARRAY land".

How to store multiple rows in a variable in pl/sql function?

I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.

Return all matches of a regular expression in Oracle

I have a table that contains a VARCHAR2 column called COMMANDS.
The data in this column is a bunch of difficult to read ZPL code that will be sent to a label printer, and amidst the ZPL there are several tokens in the form {TABLE.COLUMN}.
I would a like nice list of all the distinct {TABLE.COLUMN} tokens that are found in COMMANDS. I wrote the following regex to match the token format:
SELECT REGEXP_SUBSTR(COMMANDS,'\{\w+\.\w+\}') FROM MYTABLE;
The regex works, but it only returns the first matched token per row. Is there a way to return all regex matches for each row?
I'm using Oracle 11GR2.
Edit - Here is a small sample of data from a single row -- there are many such lines in each row:
^FO360,065^AEN,25,10^FD{CUSTOMERS.CUST_NAME}^FS
^FO360,095^AAN,15,12^FD{CUSTOMERS.CUST_ADDR1}^FS
So if that was the only row in table, I'd like to have returned:
{CUSTOMERS.CUST_NAME}
{CUSTOMERS.CUST_ADDR1}
You've provided sample of data saying that this is a single row but have presented it as two different rows. So this example based on your words.
-- Sample of data from your question + extra row for the sake of demonstration
-- id column is added to distinguish the rows(I assume you have one)
with t1(id, col) as(
select 1, '^FO360,065^AEN,25,10^FD{CUSTOMERS1.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS1.CUST_ADDR1}^FS' from dual union all
select 2, '^FO360,065^AEN,25,10^FD{CUSTOMERS2.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS2.CUST_ADDR2}^FS' from dual
),
cnt(c) as(
select level
from (select max(regexp_count(col, '{\w+.\w+}')) as o_c
from t1
) z
connect by level <= z.o_c
)
select t1.id, listagg(regexp_substr(t1.col, '{\w+.\w+}', 1, cnt.c)) within group(order by t1.id) res
from t1
cross join cnt
group by t1.id
Result:
ID RES
---------------------------------------------------------
1 {CUSTOMERS1.CUST_ADDR1}{CUSTOMERS1.CUST_NAME}
2 {CUSTOMERS2.CUST_ADDR2}{CUSTOMERS2.CUST_NAME}
As per #a_horse_with_no_name comment to the question, really, it's much simpler to just replace everything else that doesn't match the pattern. Here is an example:
with t1(col) as(
select '^FO360,065^AEN,25,10^FD{CUSTOMERS.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS.CUST_ADDR1}^FS' from dual
)
select regexp_replace(t1.col, '({\w+.\w+})|.', '\1') res
from t1
Result:
RES
-------------------------------------------
{CUSTOMERS.CUST_NAME}{CUSTOMERS.CUST_ADDR1}
I think there isn't. You should write some PL/SQL to get the others matching tokens. My best advice to you is to use a pipelined function.
First, create a type:
create type strings as table of varchar2(200);
Then the function:
CREATE OR REPLACE function let_me_show
return strings PIPELINED as
l_n number;
l_r varchar2(200);
begin
for r_rec in
( SELECT commands
FROM MYTABLE )
loop
l_n := 1;
l_r := REGEXP_SUBSTR(r_rec.COMMANDS,'\{\w+\.\w+\}', 1, l_n);
while l_r is not null
loop
pipe row(l_r);
l_n := l_n + 1;
l_r := REGEXP_SUBSTR(r_rec.COMMANDS,'\{\w+\.\w+\}', 1, l_n);
end loop;
end loop;
end;
Now you can use the function to return the results:
select *
from table(let_me_show())

how to return some default value in oracle cursor when nothing is found

I have following statement in one of the oracle SP:
OPEN CUR_NUM FOR
SELECT RTRIM(LTRIM(num)) as num
FROM USER WHERE USER_ID = v_user_id;
When the above does not return anything the SP result just has no column name in it. But I'd like the cursor to still have num column with some default value like NOTHING when no data is found.
Is this doable ?
If you really need to do that, you could use a UNION to select your dummy-row in case the user_id does not exist:
OPEN CUR_NUM FOR
SELECT RTRIM(LTRIM(num)) AS num
FROM user WHERE USER_ID = v_user_id
UNION ALL
SELECT 'NOTHING' AS num
FROM dual
WHERE NOT EXISTS (
SELECT 1
FROM user
WHERE user_id = v_user_Id
)
two choices i think:
add NVL() around the value if there is a row being returned but the column is null,
otherwise
add a MIN or MAX function if possible - this can force a row to be returned
If this code block is being used in a function, the following would work like a charm:
First, define your return variable
v_ret VARCHAR2(24);
Now, define cursor CUR_NUM as your select statement
CURSOR CUR_NUM IS
SELECT RTRIM(LTRIM(num)) as num
FROM USER WHERE USER_ID = v_user_id;
BEGIN
OPEN CUR_NUM;
FETCH CUR_NUM into v_ret;
CLOSE CUR_NUM;
RETURN nvl(v_ret,'NOTHING');
end;

Oracle PL/SQL: Dump query result into file

I'm working on a pl sql stored procedure.
What I need is to do a select, use a cursor and for every record build a string using values.
At the end I need to write this into a file.
I try to use dbms_output.put_line("toto") but the buffer size is to small because I have about 14 millions lines.
I call my procedure from a unix ksh.
I'm thinking at something like using "spool on" (on the ksh side) to dump the result of my procedure, but I don' know how to do it (if this is possible)
Anyone has any idea?
Unless it is really necessary, I would not use a procedure.
If you call the script using SQLPlus, just put the following into your test.sql (the SETs are from SQLPlus FAQ to remove noise):
SET ECHO OFF
SET NEWPAGE 0
SET SPACE 0
SET PAGESIZE 0
SET FEEDBACK OFF
SET HEADING OFF
SET TRIMSPOOL ON
SET TAB OFF
Select owner || ';' || object_name
From all_objects;
QUIT
and redirect output to a file (test.txt):
sqlplus user/passwd#instance # test.sql > test.txt
If you really need to do stuff in PL/SQL, consider putting that into a function and call it per record:
Create Or Replace Function calculate_my_row( in_some_data In Varchar2 )
Return Varchar2
As
Begin
Return in_some_data || 'something-complicated';
End calculate_my_row;
Call:
Select owner || ';' || calculate_my_row( object_name )
From all_objects;
Performance could suffer, but it should work. Make sure, that what you try can't be done in pure SQL, though.
Reading your comment I think that Analytic Function Lag is what you need.
This example appends * in case the value of val has changed:
With x As (
Select 1 id, 'A' val FROM dual
Union Select 2 id, 'A' val FROM dual
Union Select 3 id, 'B' val FROM dual
Union Select 4 id, 'B' val FROM dual
)
--# End of test-data
Select
id,
val,
Case When ( val <> prev_val Or prev_val Is Null ) Then '*' End As changed
From (
Select id, val, Lag( val ) Over ( Order By id ) As prev_val
From x
)
Order By id
Returns
ID V C
---------- - -
1 A *
2 A
3 B *
4 B
If every line of your output is the result of an operation on one row in the table, then a stored function, combined with Peter Lang's answer, can do what you need.
create function create_string(p_foobar foobar%rowtype) return varchar2 as
begin
do_some_stuff(p_foobar);
return p_foobar.foo || ';' ||p_foobar.bar;
end;
/
If it is more complicated than that, maybe you can use a pipelined table function
create type varchar_array
as table of varchar2(2000)
/
create function output_pipelined return varchar_array PIPELINED as
v_line varchar2(2000);
begin
for r_foobar in (select * from foobar)
loop
v_line := create_string(r_foobar);
pipe row(v_line);
end loop;
return;
end;
/
select * from TABLE(output_pipelined);
utl_file is your friend
http://www.adp-gmbh.ch/ora/plsql/utl_file.html
But is writes the data to the filesystem on the server so you probably need your DBA's help for this.
Tom Kyte has answered this, see
Flat
from this question on Ask Tom