extract a string with a string quote issue pl sql - sql

I used the function below to extract a string from a character string in input,the issue is that when i put a string character with a string quote i can't extract it
exp as input :
AA015streetl'adeuilAB00201AC0041234AD012XXXXXXXXXXXXAE009TTTTTTTTT
FUNCTION get_string (p_name IN VARCHAR2,
p_strg IN VARCHAR2,
p_len OUT NOCOPY PLS_INTEGER,
p_value OUT NOCOPY VARCHAR2)
RETURN PLS_INTEGER
IS
v_counter PLS_INTEGER := 1;
v_strg VARCHAR2 (4096) := SUBSTR (p_strg, 5);
BEGIN
p_value := NULL;
p_len := 0.;
WHILE v_counter < LENGTH (v_strg)
LOOP
IF SUBSTR (v_strg, v_counter, 3.) = p_name
THEN
p_len :=
TO_NUMBER (SUBSTR (v_strg, v_counter + 3., 3.));
p_value :=
SUBSTR (v_strg, v_counter + 6., p_len);
RETURN (declaration_cst.ok);
END IF;
v_counter :=
v_counter
+ 6.
+ TO_NUMBER (SUBSTR (v_strg, v_counter + 3., 3.));
END LOOP;
RETURN (declaration_cst.nok);
END;
END get_string;

For the example string you showed, the substring offsets and counter adjustments in your code are off. This can extract any of the 'tags':
CREATE OR REPLACE FUNCTION get_string (p_name IN VARCHAR2, p_strg IN VARCHAR2,
p_len OUT NOCOPY PLS_INTEGER, p_value OUT NOCOPY VARCHAR2)
RETURN PLS_INTEGER IS
v_counter PLS_INTEGER := 1;
BEGIN
p_value := NULL;
p_len := 0;
WHILE v_counter < LENGTH (p_strg)
LOOP
IF SUBSTR (p_strg, v_counter, 2) = p_name
THEN
p_len := TO_NUMBER (SUBSTR (p_strg, v_counter + 2, 3));
p_value := SUBSTR (p_strg, v_counter + 5, p_len);
RETURN declaration_cst.ok;
END IF;
v_counter := v_counter + 5
+ TO_NUMBER (SUBSTR (p_strg, v_counter + 2, 3));
END LOOP;
RETURN declaration_cst.nok;
END get_string;
/
Your version was losing the first four characters completely in the v_strg assignment, and was then adjusting as if the tags were three characters, not two.
With a test block like:
set serveroutput on size unlimited
declare
str varchar2(256) := q'[AA015street l'adeuilAB00201AC0041234AD012XXXXXXXXXXXXAE009TTTTTTTTT]';
len pls_integer;
value varchar2(256);
rc pls_integer;
begin
rc := get_string('AB', str, len, value);
dbms_output.put_line('AB -> ' || rc ||':'|| len ||':'|| value);
end;
/
and changing the 'AB' tags this gets:
AA -> 0:15:street l'adeuil
AB -> 0:2:01
AC -> 0:4:1234
AD -> 0:12:XXXXXXXXXXXX
AE -> 0:9:TTTTTTTTT
AF -> 1:0:
SQL Fiddle demo with wrapper function so the extracted tag info can be shown more easily.
It doesn't make any difference if the string value contains a single quote, as long as the tag length is correct - it's just another character to substr and won't be interpreted any differently.
As Bob Jarvis mentioned, there are other ways to achieve this breakdown, but that's even more beyond the scope of what you asked.

When you have a single-quote embedded in a string literal you're trying to pass to a function or procedure you'll need to double it (i.e. type two single-quotes instead of one) so that PL/SQL interprets it correctly. The doubled single-quote will be sent to the function as just one single quote, as you intend, but this is how single-quotes in literals are handled in PL/SQL. Thus, instead of calling your function as
n := get_string ('AB',
'AA015streetl'adeuilAB00201AC0041234AD012XXXXXXXXXXXXAE009TTTTTTTTT',
p_len,
p_value );
you should double the single-quote in the second parameter:
n := get_string ('AB',
'AA015streetl''adeuilAB00201AC0041234AD012XXXXXXXXXXXXAE009TTTTTTTTT',
p_len,
p_value );
If you're reading the input from a file or some other external source you don't have to do this; it's only necessary when a string literal has a single-quote in it.
Share and enjoy.

Related

Multiple inline functions in Oracle

I have the requirement to call my 2nd inline function into my 1st function. I am not able to achieve this one and getting error only.
with
function add_string(p_string in varchar2) return varchar2
is
--Function to add a string
l_buffer varchar2(32767);
begin
l_buffer := p_string || ' works!';
--
return l_buffer;
--
end ;
--
function doesnt_it(p_string in varchar2) return varchar2
is
l_buffer varchar2(32767);
pp_string varchar2(32767);
begin
select add_string(p_string) into pp_string from dual;
l_buffer := pp_string || ' Doesnt it?';
--
return l_buffer;
--
end ;
--
select doesnt_it('Yes, it') as outVal
from dual
;
Yes it is possible you should replace SELECT func() INTO variable with direct function call:
ORA-06553: PLS-231: function 'ADD_STRING' may not be used in SQL
with
function add_string(p_string in varchar2) return varchar2
is
--Function to add a string
l_buffer varchar2(32767);
begin
l_buffer := p_string || ' works!';
--
return l_buffer;
--
end ;
function doesnt_it(p_string in varchar2) return varchar2
is
l_buffer varchar2(32767);
-- pp_string varchar2(32767);
begin
-- select add_string(p_string) into pp_string from dual;
l_buffer := add_string(p_string) || ' Doesnt it?'; -- here is function call
--
return l_buffer;
--
end ;
select doesnt_it('Yes, it') as outVal
from dual;
db<>fiddle demo
Output:
OUTVAL
Yes, it works! Doesnt it?
Alternative solution:
pp_string := add_string(p_string);
l_buffer := pp_string || ' Doesnt it?';
db<>fiddle demo2
You could also have inline function/procedure in inline with block Procedures in the WITH Clause

Oracle how to instr for tab or end of line using regexp_instr

I have the following code
declare
l_clob clob;
l_line varchar2(32767);
l_field varchar2(32767);
l_line_start pls_integer := 1;
l_line_end pls_integer := 1;
l_field_start pls_integer := 1;
l_field_end pls_integer := 1;
begin
select response_clob
into l_clob
from xxhr.xxhr_web_service_response
where response_id = 290;
-- Loop through lines.
loop
l_line_end := dbms_lob.instr(l_clob, chr(10), l_line_start, 1);
l_line := dbms_lob.substr(l_clob, l_line_end - l_line_start + 1, l_line_start);
-- If this is a line with fields and not web service garbage.
if substr(l_line, 1, 1) = '"' then
l_field_start := 2;
-- Loop through fields.
loop
l_field_end := instr(l_line, chr(9), l_field_start, 1);
l_field := substr(l_line, l_field_start, l_field_end - l_field_start);
dbms_output.put(l_field || ',');
l_field_start := l_field_end + 1;
exit when l_field_end = 0;
end loop;
dbms_output.put_line('');
end if;
l_line_start := l_line_end + 1;
exit when l_line_end = 0;
end loop;
end;
with which I'm trying to parse this clob test data:
LINE_TEXT
"PERSON_ID_NUMBER 30000 1223445454"
"PERSON_DOB 30000 01-01-1900"
The clob data is tab separated and has a chr(10) at the end. I'm not familiar with regexp_instr, but currently I'm only using instr to search for the tab separators; so it's missing the end of line field and producing:
PERSON_ID_NUMBER,30000,,
PERSON_DOB,30000,,
How can I change the instr into a regexp_instr to also look for the end of line character in addition to the tab and then correctly pick up the last field?
I need the function to be performant, since it is parsing large files.
You can split the line of CLOB column by converting to char, and then apply regexp_substr() as
with t as
(
select level as row_num, regexp_substr(to_char(t.line_text),'^.*$',1,level,'m') as str
from tab t
connect by level <= length (to_char(t.line_text))
- length (replace (to_char(t.line_text), chr (10))) + 1
)
select row_num, regexp_replace(str,'[[:space:]]+',',') as str
from t;
ROW_NUM STR
------- -----------------------------------------
1 PERSON_ID_NUMBER,30000,1223445454
2 PERSON_DOB,30000,01-01-1900
Demo
Edit : even works without to_char() conversion, if your CLOB is huge then you need to split step by step by substr(str,1,4000), substr(str,4001,8000) ...
with t as
(
select level as row_num, regexp_substr(substr(t.line_text,1,4000),'^.*$',1,level,'m') str
from tab t
connect by level <= length (substr(t.line_text,1,4000))
- length (replace(substr(t.line_text,1,4000), chr (10))) + 1
)
select row_num, regexp_replace(substr(str,1,4000),'[[:space:]]+',',') as str
from t
Fixed it with:
declare
l_clob clob;
l_line varchar2(32767);
l_field varchar2(32767);
l_line_start pls_integer := 1;
l_line_end pls_integer := 1;
l_field_start pls_integer := 1;
l_field_end pls_integer := 1;
begin
select response_clob
into l_clob
from xxhr.xxhr_web_service_response
where response_id = 290;
-- Loop through lines.
loop
l_line_end := dbms_lob.instr(l_clob, chr(10), l_line_start, 1);
l_line := dbms_lob.substr(l_clob, l_line_end - l_line_start + 1, l_line_start);
-- If this is a line with fields and not web service garbage.
if substr(l_line, 1, 1) = '"' then
l_field_start := 2;
-- Loop through fields.
loop
l_field_end := instr(l_line, chr(9), l_field_start, 1);
l_field := substr(l_line, l_field_start, l_field_end - l_field_start);
dbms_output.put(l_field || ',');
exit when l_field_end = 0;
l_field_start := l_field_end + 1;
end loop;
l_field := substr(l_line, l_field_start);
dbms_output.put_line(l_field);
end if;
l_line_start := l_line_end + 1;
exit when l_line_end = 0;
end loop;
end;

How to split data from CSV file with UTF_FILE Oracle

I'm new at using UTF_FILE in Oracle. I have a CSV-file with over 300K records and 6 columns, the columns are separated by comma ",".
I'm looking to split all this data and set that to some variables and insert in some tables or columns by Pl/SQL but how can I do that?
For now the time of execution of the pl/sql is not important
[Oracle 11gR2 XE]
Solution to you question:
DECLARE
v_row VARCHAR2(200) := 'a,b,c';
BEGIN
FOR v_col IN (SELECT REGEXP_SUBSTR (v_row, '[^,]+', 1, LEVEL) cell FROM DUAL CONNECT BY REGEXP_SUBSTR (v_row, '[^,]+', 1, LEVEL) IS NOT NULL)
LOOP
DBMS_OUTPUT.PUT_LINE (v_col.cell);
END LOOP;
END;
But like the comments say: You shouldn't do this. Use SQL Loader: Oracle: Import CSV file
Hi I've used this code years ago and it works
you can change it to use regexp instead of complicated substrs
you can use index-by tables instead of nested table type I've used, the nested can be inserted in a table but index-by is only usable on pl/sql and cannot be passed as parameter to other stored programs(which nested can).
CREATE OR REPLACE TYPE ARRAY_OF_TEXTS AS TABLE OF VARCHAR2 (4000);
CREATE OR REPLACE TYPE ARRAY_OF_ARRAYS AS TABLE OF ARRAY_OF_TEXTS;
CREATE OR REPLACE FUNCTION SPLIT (
TEXT IN CLOB,
C_SEPARATOR IN VARCHAR2,
IGNORE_EMBTY_BLOCKS IN BOOLEAN DEFAULT FALSE)
RETURN ARRAY_OF_TEXTS
IS
S_LINE VARCHAR2 (4000);
S_TABLE ARRAY_OF_TEXTS;
I INTEGER;
OFFSET1 INTEGER := 1;
OFFSET2 INTEGER;
TEXT_LEN INTEGER;
BEGIN
S_TABLE := ARRAY_OF_TEXTS ();
OFFSET2 := INSTR (TEXT, C_SEPARATOR, OFFSET1);
TEXT_LEN := DBMS_LOB.GETLENGTH (TEXT);
IF OFFSET2 < 1 --if there is no c_separator (if offset2 is 0) or there is not any c_separator at the end of text
THEN
OFFSET2 := TEXT_LEN;
END IF;
WHILE (OFFSET2 = OFFSET1 + LENGTH (C_SEPARATOR) OR OFFSET2 = OFFSET1)
AND DBMS_LOB.SUBSTR (TEXT, LENGTH (C_SEPARATOR), OFFSET1) =
C_SEPARATOR -- if there are 2 c_separator sequentially
LOOP
IF NOT IGNORE_EMBTY_BLOCKS
THEN
S_TABLE.EXTEND;
S_TABLE (S_TABLE.LAST) := NULL;
END IF;
OFFSET1 := OFFSET2 + LENGTH (C_SEPARATOR);
OFFSET2 := DBMS_LOB.INSTR (TEXT, C_SEPARATOR, OFFSET1);
END LOOP;
IF OFFSET2 > OFFSET1 + LENGTH (C_SEPARATOR)
THEN
S_TABLE.EXTEND;
S_LINE := DBMS_LOB.SUBSTR (TEXT, OFFSET2 - OFFSET1, OFFSET1);
S_TABLE (S_TABLE.LAST) := S_LINE;
OFFSET1 := OFFSET2 + LENGTH (C_SEPARATOR);
END IF;
OFFSET2 := DBMS_LOB.INSTR (TEXT, C_SEPARATOR, OFFSET1);
WHILE OFFSET2 > 1
LOOP
S_TABLE.EXTEND;
S_LINE := DBMS_LOB.SUBSTR (TEXT, OFFSET2 - OFFSET1, OFFSET1);
S_TABLE (S_TABLE.LAST) := S_LINE;
OFFSET1 := OFFSET2 + LENGTH (C_SEPARATOR);
OFFSET2 := DBMS_LOB.INSTR (TEXT, C_SEPARATOR, OFFSET1);
WHILE ( OFFSET2 = OFFSET1 + LENGTH (C_SEPARATOR)
OR OFFSET2 = OFFSET1)
AND DBMS_LOB.SUBSTR (TEXT, LENGTH (C_SEPARATOR), OFFSET1) =
C_SEPARATOR -- if there are 2 c_separator sequentially
LOOP
IF NOT IGNORE_EMBTY_BLOCKS
THEN
S_TABLE.EXTEND;
S_TABLE (S_TABLE.LAST) := NULL;
END IF;
OFFSET1 := OFFSET2 + LENGTH (C_SEPARATOR);
OFFSET2 := DBMS_LOB.INSTR (TEXT, C_SEPARATOR, OFFSET1);
END LOOP;
END LOOP;
IF OFFSET1 < TEXT_LEN
THEN
S_TABLE.EXTEND;
S_LINE :=
DBMS_LOB.SUBSTR (
TEXT,
TEXT_LEN - OFFSET1 + LENGTH (C_SEPARATOR),
OFFSET1
);
S_TABLE (S_TABLE.LAST) := S_LINE;
ELSIF OFFSET1 = TEXT_LEN
AND DBMS_LOB.SUBSTR (TEXT, LENGTH (C_SEPARATOR), OFFSET1) <>
C_SEPARATOR
THEN
S_TABLE.EXTEND;
S_LINE.TEXT := DBMS_LOB.SUBSTR (TEXT, LENGTH (C_SEPARATOR), OFFSET1);
S_TABLE (S_TABLE.LAST) := S_LINE;
END IF;
RETURN S_TABLE;
END;
CREATE OR REPLACE FUNCTION LOAD_CSV_FILE (
P_DIRECTORY IN VARCHAR2,
P_FILENAME IN VARCHAR2
)
RETURN ARRAY_OF_ARRAYS
AS
SEPARATOR1 VARCHAR2 (2) := CHR (10);
-- In Some Cases you should use: CHR (13) || CHR (10);
SEPARATOR2 VARCHAR2 (1) := ',';
--if csv separator is ; or | use it here
V_TEXT CLOB;
V_BFILE BFILE;
V_LINES ARRAY_OF_TEXTS;
V_LINE ARRAY_OF_TEXTS;
V_ARRAY ARRAY_OF_ARRAYS;
BEGIN
SELECT EMPTY_CLOB () INTO V_TEXT FROM DUAL;
--V_TEXT := EMPTY_CLOB ();
DBMS_LOB.CREATETEMPORARY(V_TEXT, TRUE);
V_BFILE := BFILENAME (P_DIRECTORY, P_FILENAME);
IF DBMS_LOB.FILEEXISTS (V_BFILE) <> 1
THEN
RETURN NULL;
END IF;
DBMS_LOB.FILEOPEN (V_BFILE, DBMS_LOB.FILE_READONLY);
DBMS_LOB.LOADFROMFILE (V_TEXT, V_BFILE, DBMS_LOB.GETLENGTH (V_BFILE));
DBMS_LOB.FILECLOSE (V_BFILE);
V_LINES := SPLIT2 (V_TEXT, SEPARATOR1);
V_ARRAY := ARRAY_OF_ARRAYS ();
FOR R_LINE IN (SELECT *
FROM TABLE (V_LINES))
LOOP
V_LINE := SPLIT(R_LINE.COLUMN_VALUE, SEPARATOR2);
V_ARRAY.EXTEND ();
V_ARRAY (V_ARRAY.LAST) := V_LINE;
END LOOP;
RETURN V_ARRAY;
END;
now you can use it like this:
DECLARE
V_ARR ARRAY_OF_ARRAYS;
V_LINE VARCHAR2 (4000);
BEGIN
V_ARR := LOAD_CSV_FILE ('MY_DIR', 'file.csv');
FOR LINE IN (SELECT *
FROM TABLE (V_ARR))
LOOP
FOR FIELD IN (SELECT *
FROM TABLE (LINE.COLUMN_VALUE))
LOOP
DBMS_OUTPUT.PUT_LINE ('field:' || FIELD.COLUMN_VALUE);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('end of line');
END LOOP;
END;

Unscramble string code in sql

I have a function in Oracle SQL which scrambles a given input. I need to unscramble it.
I need a program which can basically reverse the output of the program below.
source IN VARCHAR2
sGARBLED VARCHAR2(510);
index NUMBER;
length NUMBER;
onec NUMBER;
BEGIN
length := LENGTH(source);
IF length > 255 THEN
length := 255;
END IF;
index := 1;
sGARBLED := '';
WHILE index <= length LOOP
onec := ASCII(SUBSTR(source, index, 1)) - 30;
IF (onec < 10) THEN
sGARBLED := sGARBLED || '0';
END IF;
sGARBLED := sGARBLED || CAST(onec AS VARCHAR2);
index := index + 1;
END LOOP;
RETURN sGARBLED;
END
It can be possible, if you are only allowing character values in your original string before scrambling it, to be characters of the alphabet.
Code would look something like this:
sgarbled in VARCHAR2
UNsGARBLED VARCHAR2(255);
length NUMBER;
index NUMBER;
onec NUMBER;
BEGIN
length := LENGTH(sgarbled);
index := 1;
UNsGARBLED := '';
WHILE index <= length LOOP
onec := SUBSTR(sgarbled, index, 1);
IF (onec = '0') THEN
onec := SUBSTR(sgarbled, (index + 1), 1) + 30;
ELSE
onec := SUBSTR(sgarbled, (index), 2) + 30;
END IF;
UNsGARBLED := UNsGARBLED || CAST(CHR(onec) AS VARCHAR2);
index := index + 2;
END LOOP;
RETURN UNsGARBLED;
END
As Mr. Llama pointed out if you are allowing the original input string to contain all ASCII characters then it may not be possible.

How to encode string in Oracle?

I have problem with encoding data in Oracle database.
I want to xor string with another string (mask) and then
encode it with base64.
In Python this looks like:
def encode_str(s, mask):
xor_mask = mask
while len(xor_mask) < len(s):
xor_mask += mask
chrs2 = []
for i in range(len(s)):
chrs2.append(chr(ord(s[i]) ^ ord(xor_mask[i])))
s2 = ''.join(chrs2)
return base64.b64encode(s2)
#return binascii.hexlify(s2).lower()
In PL/SQL I got:
create or replace function ht_encode(str in varchar2, mask in varchar2) return varchar2 as
xor_mask varchar2(2000);
result_s varchar2(2000);
i integer;
xx integer;
x char(10);
ch1 char(10);
ch2 char(10);
chrx varchar2(10);
begin
result_s := '';
xor_mask := mask;
while length(xor_mask) < length(str) loop
xor_mask := xor_mask || mask;
end loop;
for i in 1..length(str) loop
ch1 := substr(str, i, 1);
ch2 := substr(xor_mask, i, 1);
xx := BITXOR(ascii(ch1), ascii(ch2));
x := xx;
chrx := rawtohex(x);
--result_s := result_s || ':' || chrx;
--result_s := result_s || chrx;
-- HELP ME HERE!
end loop;
--return lower(utl_encode.base64_encode(result_s));
--return result_s || ' | ' || rawtohex(result_s);
-- HELP ME HERE!
return result_s;
end;
(bitxor comes from http://forums.oracle.com/forums/thread.jspa?threadID=496773 )
I don't know how to create "binary" string and then encode it to hex or preferable
to base64.
SELECT UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(UTL_RAW.bit_xor(UTL_RAW.cast_to_raw('text'), UTL_RAW.cast_to_raw('mask'))))
FROM dual