How to Process a Comma Delimited string in Batches in Oracle 11g - plsql

I have a comma delimited string which that exceeds the length of VARCHAR2(32767) in Oracle 11g and PL/SQL.
For example, a sample of my string might look like this:
my_field:= ‘ "BULL","ABCD","BEER","TOMM", "BULL1","ABCD1","BEER1","TOMM1", "BULL2","ABCD2","BEER2","TOMM2", "BULL3","ABCD3","BEER3","TOMM3", "BULL4","ABCD4","BEER4","TOMM4", "BULL5","ABCD5","BEER5","TOMM5"’;
As I am hitting the **Oracle error: ORA-06502: PL/SQL: numeric or value error: character string buffer too small**, what I would like to do is take my_field and process this in batches so that I do not exceed my limit of VARCHAR2(32767) in one.
So basically, grab the length of a string up to a specified length that ends with the “,” (comma) – process. Then from the “,” (comma) onwards, grab the next batch length up to a “,” (comma) and process, until I eventually hit the end of my_field, which doesn’t have a “,” (comma).
For example:
Batch 1: "BULL","ABCD","BEER","TOMM", - process row
Batch 2: "BULL1","ABCD1","BEER1","TOMM1", - process row
Batch 3: "BULL2","ABCD2","BEER2","TOMM2", - process row
Batch 4: "BULL3","ABCD3","BEER3","TOMM3", - process row
Batch 5: "BULL4","ABCD4","BEER4","TOMM4", - process row
Batch 6: "BULL5","ABCD5","BEER5","TOMM5" - process row
All processing finished.
How can I achieve the above in PL/SQL?

You can use INSTR and SUBSTR to break the input string into processable substrings, as in the following:
DECLARE
strCSV VARCHAR2(32767) := '"BULL","ABCD","BEER","TOMM", "BULL1","ABCD1","BEER1","TOMM1", "BULL2","ABCD2","BEER2","TOMM2", "BULL3","ABCD3","BEER3","TOMM3", "BULL4","ABCD4","BEER4","TOMM4", "BULL5","ABCD5","BEER5","TOMM5"';
strRow_csv VARCHAR2(32767);
nStart_pos NUMBER := 1;
nEnd_pos NUMBER;
BEGIN
LOOP
nEnd_pos := INSTR(strCSV, ',', nStart_pos, 4);
strRow_csv := RTRIM(TRIM(SUBSTR(strCSV, nStart_pos, CASE
WHEN nEnd_pos > 0 THEN
nEnd_pos
ELSE
LENGTH(strCSV)
END - nStart_pos+1)), ',');
DBMS_OUTPUT.PUT_LINE('strRow_csv=''' || strRow_csv || '''');
IF nEnd_pos = 0 THEN
EXIT;
END IF;
nStart_pos := nEnd_pos + 1;
END LOOP;
END;
If you have concerns about commas appearing inside the double-quoted values you may wish to consider using REGEXP_INSTR.
Share and enjoy.

You can solve this using regular expressions:
select word from
(with t as (select '"aaaa", "bbbb" , "cccc","dddd eeee", "ffff aaaa"' as txt from dual)
select DISTINCT REGEXP_SUBSTR (txt, '("[^"]+")|^([:space:]*)', 1, level) as word
from t
connect by level <= length(regexp_replace(txt,'("[^"]+")|^([:space:])*'))+1)
where word is not null

Related

Create a table as, where 'date condition' in dynamic PL/SQL

I got assigned the following task.
Assume we have a table A structured with an id column and a date column.
Write a procedure in PL/SQL that: takes as parameters the table name (in our case A) and a date D, creates a backup table named A_bck containing only the records of A with dates < D and removes from the table A all the records inserted in A_bck.
Here there is my code.
Unluckily I get this error:
Error report -
ORA-00904: "MAY": invalid identifier
ORA-06512: at line 41
ORA-06512: at line 80
00904. 00000 - "%s: invalid identifier"
If I try to achieve the same result using a where condition on the id column instead that on the date one, I have no problems.
Where is the mistake? Am I implementing it completely in the wrong way?
The problem you have is that as you're executing dynamic sql you're query is built up as a string. Oracle does not know that the date you've given is actually a date, it is simply being treated as part of the string. To solve this you should be able to do the following:
my_query := 'CREATE TABLE ' || table_name_backup || ' AS (SELECT * FROM ' || table_name || ' WHERE table_date < to_date(''' || backup_date || '''))';
This should sort out your issue for you. As a side note, you will probably want to change your "table_exists" query, as table names are all stored in upper case, e.g.
SELECT COUNT(*) INTO table_exists FROM USER_TABLES WHERE TABLE_NAME = upper(my_table);
Edit: Further explanation following comment
To explain why you don't have the above problem when using integers, it is important to remember that using execute immediate simply executes the given string as an SQL query.
For example:
declare
x INTEGER := 1;
i integer;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where 1 = ' || x;
EXECUTE IMMEDIATE my_query INTO i;
end;
my_query in the above example would be executed as:
select 1 from dual where 1 = 1
which is perfectly valid sql. In your example however, you were ending up with something like this:
CREATE TABLE abaco_bck AS (SELECT * FROM abaco WHERE table_date < 27-MAY-17)
As it isn't wrapped in quotes, or explicitly converted to a date, the SQL engine is trying to subtract "MAY" from 27, but it doesn't know what "MAY" is.
One other thing to mention, is that for some operations you could use bind variables instead of quotes (although you can't for DDL) e.g.
declare
lToday DATE := SYSDATE;
i INTEGER;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where sysdate = :1';
EXECUTE IMMEDIATE my_query INTO i USING lToday;
end;

Hexadecimal to decimal through system defined function

I am working on creating one view in SAP HANA.
I have column A, Data type for A is NVARCHAR.
Values in A are something like below. I need to use only last 5 digits and convert it into decimal.
A
000000000000000000000000000EF80A
000000000000000000000000000EF812
000000000000000000000000000EF80E
000000000000000000000000000EF809
000000000000000000000000000EF80B
000000000000000000000000000EF80C
000000000000000000000000000EF80D
I made use of function
Select HEXTOBIN(0xEF80A) from dummy;
This gave me required result.
However 0x in above query is notation to mark number (EF80A) as hexadecimal.
Whenever I have to fetch 5 last digit dynamically, I am not able to assign 0x notation.
I tried following:
1) substr last 5 digits of A and concat it with 0x... This did not work, as '0x'is considered as string while it is just notation.
select distinct '0x' || right(A,5 ) from dummy;
Can someone help as to how I give 0x with last 5 char of column A to mark it hexadecimal?
Are there any direct function available for this conversion without user defined function?
The 0x... notation for hexadecimal numbers and the X'...' for strings are only valid for typed literals.
E.g. 0xEF80A explicitly types the literal a number given in hexadecimal notation. Internally, the number is of course dealt with as if you would've given an integer.
In order to be able to apply this to existing strings, a hex-string-to-number conversion function is required and SAP HANA doesn't come with one on board.
I've posted an example implementation for such a function here https://archive.sap.com/discussions/thread/3652555
To make it easy, here's it again:
drop function hexstr2int;
CREATE FUNCTION hexstr2int (IN i_hex VARCHAR(2000))
RETURNS o_result BIGINT
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
READS SQL DATA
AS
BEGIN
DECLARE pos INTEGER := 1;
DECLARE hex_len INTEGER;
DECLARE current_digit VARCHAR(1);
DECLARE current_val INTEGER;
DECLARE result BIGINT := 0;
DECLARE tmphex VARCHAR(2000);
DECLARE hexstr2int CONDITION FOR SQL_ERROR_CODE 10001;
DECLARE EXIT HANDLER FOR hexstr2int RESIGNAL;
-- some sanitation
tmphex := UPPER(:i_hex);
hex_len := LENGTH(:tmphex);
WHILE :pos <= :hex_len DO
result := :result * 16;
current_digit := SUBSTR(:tmphex, :pos, 1);
-- format checking
IF NOT ((:current_digit >= 'A' and :current_digit <= 'F') or
(:current_digit >= '0' and :current_digit <= '9')) THEN
SIGNAL hexstr2int SET MESSAGE_TEXT =
'Invalid hex cipher: ' || :current_digit || ' at position ' || :pos;
END IF;
current_val := MOD(to_number(to_binary(:current_digit)),30);
IF :current_val >= 11 THEN
result := :result + :current_val - 1;
ELSE
result := :result + :current_val;
END IF;
pos := :pos + 1;
END WHILE;
o_result := :result;
END;

I create one PL/SQL procedure i want simple show two messages?

The PL/SQL procedure below:
'DECLARE
V_EMPNO NUMBER(10):=&EMPNO;
V_EMPNO2 NUMBER(10):= 0;
CURSOR C1 IS SELECT EMPNO FROM EMP;
BEGIN
FOR I IN C1 LOOP
FETCH C1 INTO V_EMPNO2;
EXIT WHEN C1%FOUND;
END LOOP;
IF (LENGTH(V_EMPNO)) > 4 THEN
DBMS_OUTPUT.PUT_LINE ('LENGTH OF EMPNO GREATER THAN 4 NUMBER');
ELSIF (V_EMPNO = V_EMPNO2) THEN
DBMS_OUTPUT.PUT_LINE ('THIS EMPLOYEE NUMBER ALREADY EXIST');
END IF;
END;
/'
In this procedure I want show two messages
one is if lenght greater than number 4 than show message
and second is if v_empno = v_empno2 then show second message
empno = v_empno then show message:
DBMS_OUTPUT.PUT_LINE ('THIS EMPLOYEE NUMBER ALREADY EXIST')
this is error
Enter value for empno: 4444
DECLARE
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 7
FOR I IN C1 LOOP
already implicitly opens c1 and handles the fetching, so your explicit fetch after it is invalid.
btw i is normally used for numeric indexes rather than records.
Also your caps lock was on when you wrote that code ;)
I think there are a couple of problems with your code.
C1 does not restrict on employee number (meaning the loop will
return a single, largely random row from table emp
You are mixing FOR LOOP and FETCH syntax
Variable v_empno is a NUMBER and you need to be careful when checking the length - you need to explicitly TO_CHAR and control the format - often TO_CHAR will end up including space characters (an alternative would be to check the value of a number is < 10000)
I've not tested this code but this might be closer to what you're after :
DECLARE
l_empno NUMBER := &empno ;
CURSOR C_get_emp
IS
SELECT e.empno
FROM emp e
WHERE e.empno = l_empno
;
R_emp C_get_emp%ROWTYPE ;
BEGIN
IF LENGTH(TRIM(TO_CHAR(l_empno))) > 4 THEN
DBMS_OUTPUT.put_line('Length of empno > 4') ;
ELSE
OPEN C_get_emp ;
FETCH C_get_emp INTO R_emp ;
IF C_get_emp%FOUND THEN
DBMS_OUTPUT.put_line('Employee number already exists') ;
END IF ;
CLOSE C_get_emp ;
END IF ;
END ;

Parsing string in PL/SQL and insert values into database with RESTful web service

I am reading the values which I want to insert into database. I am reading them by lines. One line is something like this:
String line = "6, Ljubljana, Slovenija, 28";
Web service needs to separate values by comma and insert them into database. In PL/SQL language. How do I do that?
Here is some pl/sql that I have used to parse through delimited strings and then extract the individual words. You may have to mess with it a bit when using with the web service but it works fine when you are running it right in oracle.
declare
string_line varchar2(4000);
str_cnt number;
parse_pos_1 number := 1;
parse_pos_2 number;
parsed_string varchar2(4000);
begin
--counting the number of commas in the string so we know how many times to loop
select regexp_count(string_line, ',') into str_cnt from dual;
for i in 1..str_cnt + 1
loop
--grabbing the position of the comma
select regexp_instr(string_line, ',', parse_pos_1) into parse_pos_2 from dual;
--grabbing the individual words based of the comma positions using substr function
--handling the last loop
if i = str_cnt + 1 then
select substr(string_line, parse_pos_1, length(string_line)+1 - parse_pos_1) into parsed_string from dual;
execute immediate 'insert into your_table_name (your_column_name) values (' || parsed_string || ' )';
execute immediate 'commit';
--handles the rest
else
select substr(string_line, parse_pos_1, parse_pos2 - parse_pos_1) into parsed_string from dual;
execute immediate 'insert into your_table_name (your_column_name) values (' || parsed_string || ' )';
execute immediate 'commit';
end if;
parse_pos_1 := parse_pos_2+1;
end loop;
end;
I found an answer to that particular question. If you have similar values to those I posted for a question, like numbers, which look something like this:
String line = "145, 899";
This string is sent via POST request (RESTful web service, APEX). Now getting the values in PL/SQL and inserting them into table looks something like this:
DECLARE
val1 NUMBER;
val2 NUMBER;
str CLOB;
BEGIN
str := string_fnc.blob_to_clob(:body); // we have to convert body
val1 := TO_NUMBER(REGEXP_SUBSTR(str, '[^,]+', 1, 1));
val2 := TO_NUMBER(REGEXP_SUBSTR(str, '[^,]+', 1, 2));
// REGEXP_SUBSTR(source, pattern, start_position, nth_appearance)
INSERT INTO PRIMER VALUES (val1, val2);
END;
However, this is the method to insert line by line into database, so if you have large amount of rows in a file to insert, this isn't a way to do it. But here is the example which I requested. I hope it helps to someone.

How to place a plsql block inside a sequence

I am trying to create a user generated sequence. According to usual syntax of oracle sequence we can start with a number and increment a value.
Is there a method to write a plsql block (declare begin end) inside a sequence and generate my own sequnce.
example : ABC001
When i call the next val of sequence , the value should be ABC002
PLEASE CLEAR FIRST WHAT YOU EXACTLY WANT TO ASK.
If you are asking HOW TO DYNAMICALLY CREATE SEQUENCE USING PL/SQL, then check below.
Simplest way.
DECLARE
SQL_S VARCHAR2(100);
BEGIN
SQL_S := 'CREATE SEQUENCE SQN_NAME INCREMENT BY 1 START WITH 1';
EXECUTE IMMEDIATE SQL_S;
END;
/
If you want to dynamically create sequence with some DYNAMIC name as argument passed to procedure, then it will be like
CREATE OR REPLACE PROCEDURE DYNAMIC_SQN (ARG IN VARCHAR2) IS
SQL_S VARCHAR2(100);
PARAM1 VARCHAR2(20);
BEGIN
PARAM1 := 'SQN_NAME_' || ARG;
SQL_S := 'CREATE SEQUENCE ' || PARAM1 || ' INCREMENT BY 1 START WITH 1';
EXECUTE IMMEDIATE SQL_S;
END;
/
And if you simply want to insert in to any column, and create the PK using it in addition to some String, then
INSERT INTO TABLE_T VALUES('ABC'|| SEQUENCE_NAME.nextval, OTHER_VALUES);
It will still give you values like : ABC1, ABC2, .... ABC12, ABC13, .... ABC99, ABC100 and so on...
Considering the sample example you have given i m writing the following code
create your sequence, then
insert into seq values('ABC'||YOURSEQUENCENAME.nextval,YOUR_VALUE);

Resources