I wrote a function to break a string into parts delimitted by $ Now I need to access it with select from but getting error - string

I have created following function to split a given string delimited by $.
I want to call the function as part of SQL query and get the result as rows.
CREATE OR REPLACE FUNCTION string_tokenize2
( p_string IN CLOB
-- p_delim in varchar2
)
RETURN SYS_REFCURSOR
AS
cur1 SYS_REFCURSOR;
BEGIN
OPEN cur1 FOR
SELECT regexp_substr(p_string
,'[^$]+'
,1
,LEVEL) AS str
FROM sys.dual
CONNECT BY LEVEL <= regexp_count(p_string
,'\$') + 1;
RETURN cur1;
END string_tokenize2;
/
However when I tried it using in a SQL resulted in following error.
Kindly assist me how to proceed here ,

select string_tokenize2('')from dual;
put your IN variable in the ( )

Do you have any constraint to stick to the function as it is ? OR
Why not create an object type and change the function to return a table type and then use it in SQL ?
--create the type
CREATE OR REPLACE TYPE string_tokenize2_obj IS TABLE OF VARCHAR2(4000);
--Function changes
CREATE OR REPLACE FUNCTION string_tokenize2(p_string IN CLOB)
RETURN string_tokenize2_obj AS
l_tab string_tokenize2_obj;
BEGIN
SELECT to_char(str)
BULK COLLECT
INTO l_tab
FROM (SELECT regexp_substr(p_string
,'[^$]+'
,1
,LEVEL) str
FROM dual
CONNECT BY LEVEL <= regexp_count(p_string
,'\$') + 1);
RETURN l_tab;
END string_tokenize2;
/
SQL> SELECT column_value str FROM TABLE(string_tokenize2('abc$def$geh$ijkl'));
STR
--------------------------------------------------------------------------------
abc
def
geh
ijkl
SQL>

You can either
1) use xmltable and xmltype to read data from sys_refcursor functions results if you need that data in select statement:
ie you can use the same your function without any changes, but read it using xmltable+xmltype:
select *
from xmltable(
'/ROWSET/ROW'
passing xmltype(string_tokenize2('1$2$3$4'))
columns
str varchar2(100) path 'STR'
);
Results:
STR
---------------------------------------
1
2
3
4
2) or use plsql implicit results if you are just need to get data from any client: https://oracle-base.com/articles/12c/implicit-statement-results-12cr1
Example:
CREATE OR REPLACE PROCEDURE string_tokenize2
( p_string IN CLOB
-- p_delim in varchar2
)
AS
cur1 SYS_REFCURSOR;
BEGIN
OPEN cur1 FOR
SELECT regexp_substr(p_string
,'[^$]+'
,1
,LEVEL) AS str
FROM sys.dual
CONNECT BY LEVEL <= regexp_count(p_string
,'\$') + 1;
DBMS_SQL.RETURN_RESULT(cur1);
END string_tokenize2;
/
begin
string_tokenize2('1,2,3,4');
end;
Result:
SQL> exec string_tokenize2('1,2,3,4');
ResultSet #1
STR
---------------------------------------
1,2,3,4
3) or just return sys_refcursor to client app as a bind variable for fetching:
begin
:res := string_tokenize2('1,2,3,4');
end;
Example in sql*plus:
SQL> var res refcursor
SQL> exec :res := string_tokenize2('1$2$3$4');
SQL> print res
STR
--------------------
1
2
3
4
4) Or varchar2 collections/varrays as suggested by other users

Related

Replace One character in string with multiple characters in loop - ORACLE

I have a situation where say a string has one replaceable character.. For ex..
Thi[$] is a strin[$] I am [$]ew to Or[$]cle
Now I need to replace the [$] with s,g,n,a
Respectively...
How can I do that? Please help.
There is a special PL/SQL function UTL_LMS.FORMAT_MESSAGE:
You can use use it in your INLINE pl/sql function:
with function format(
str in varchar2
,s1 in varchar2 default null
,s2 in varchar2 default null
,s3 in varchar2 default null
,s4 in varchar2 default null
,s5 in varchar2 default null
,s6 in varchar2 default null
,s7 in varchar2 default null
,s8 in varchar2 default null
,s9 in varchar2 default null
,s10 in varchar2 default null
) return varchar2
as
begin
return utl_lms.format_message(replace(str,'[$]','%s'),s1,s2,s3,s4,s5,s6,s7,s8,s9,10);
end;
select format('Thi[$] is a strin[$] I am [$]ew to Or[$]cle', 's','g','n','a') as res
from dual;
Result:
RES
-------------------------------------
This is a string I am new to Oracle
Here is a hand-rolled solution using a recursive WITH clause, and INSTR and SUBSTR functions to chop the string and inject the relevant letter at each juncture.
with rcte(str, sigils, occ) as (
select 'Thi[$] is a strin[$] I am [$]ew to Or[$]cle' as str
, 'sgna' as sigils
, 0 as occ
from dual
union all
select substr(str, 1, instr(str,'[$]',1,1)-1)||substr(sigils, occ+1, 1)||substr(str, instr(str,'[$]',1,1)+3) as str
, sigils
, occ+1 as occ
from rcte
where occ <= length(sigils)
)
select *
from rcte
where occ = length(sigils)
Here is a working demo on db<>fiddle.
However, it looks like #sayanm has provided a neater solution.
Consider this method that lets the lookup values be table-based. See the comments within. The original string is split into rows using the placeholder as a delimiter. Then the rows are put back together using listagg, joining on it's order to the lookup table.
Table-driven using as many placeholders as you want. The order matters though of course just as with the other answers.
-- First CTE just sets up source data
WITH tbl(str) AS (
SELECT 'Thi[$] is a strin[$] I am [$]ew to Or[$]cle' FROM dual
),
-- Lookup table. Does not have to be a CTE here, but a normal table
-- in the database.
tbl_sub_values(ID, VALUE) AS (
SELECT 1, 's' FROM dual UNION ALL
SELECT 2, 'g' FROM dual UNION ALL
SELECT 3, 'n' FROM dual UNION ALL
SELECT 4, 'a' FROM dual
),
-- Split the source data using the placeholder as a delimiter
tbl_split(piece_id, str) AS (
SELECT LEVEL AS piece_id, REGEXP_SUBSTR(t.str, '(.*?)(\[\$\]|$)', 1, LEVEL, NULL, 1)
FROM tbl T
CONNECT BY LEVEL <= REGEXP_COUNT(t.str, '[$]') + 1
)
-- select * from tbl_split;
-- Put the string back together, joining with the lookup table
SELECT LISTAGG(str||tsv.value) WITHIN GROUP (ORDER BY piece_id) STRING
FROM tbl_split ts
LEFT JOIN tbl_sub_values tsv
ON ts.piece_id = tsv.id;
STRING
--------------------------------------------------------------------------------
This is a string I am new to Oracle

Query and get all database names and subquery especific tables from all databases

I have different databases. I have tables within each database.
I would like to know if I can ask how many databases excluding some such as 'schema' 'mysql' I have once know how to perform a subquery asked by a particular table of all the databases resulting from the first question.
example.
the structure would be
db1 -> user-> id,name,imei,telephone,etc..
db2 -> user-> id,nameuser,imei,telephone,etc..
db3 -> user-> id,nameuser,imei,telephone,etc..
....
db1000 -> user-> id,nameuser,imei,telephone,etc..
the query are how this, but this get error
SELECT CONCAT('SELECT * FROM ' schema_name 'where imei.'schema_name = nameimai)
FROM information_schema.schemata
WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys','performance_schema','phpmyadmin');
Results
name db id name imei phone
---------- ---------- ---------- ---------- ----------
db1 1 John 76876876 xxx
db2 2300 John 76876876 xxxx
...
db1000 45 John 76876876 xxx
its possible in one query
thanks..
Here's one way you could do it with a stored procedure.
If I understand correctly, you have multiple databases with identical tables (user) and you want to run a query against all these tables for a specific value.
I've made this fairly general so that you can pass in the table name and also the where clause. Your example seemed to be looking for user records with imei = '76876876', so if we use that example.
USE test;
DELIMITER //
DROP PROCEDURE IF EXISTS multidb_select //
-- escape any quotes in the query string
-- call multidb_select ('usertest','WHERE imei = \'76876876\'')
CREATE PROCEDURE multidb_select(IN tname VARCHAR(64), IN qwhere VARCHAR(1024))
READS SQL DATA
BEGIN
DECLARE vtable_schema VARCHAR(64);
DECLARE vtable_name VARCHAR(64);
DECLARE done BOOLEAN DEFAULT FALSE;
-- exclude views and system tables
DECLARE cur1 CURSOR FOR
SELECT `table_schema`, `table_name`
FROM `information_schema`.`tables`
WHERE `table_name` = tname
AND `table_type` = 'BASE TABLE'
AND `table_schema` NOT IN
('information_schema','mysql','performance_schema',
'sys','performance_schema','phpmyadmin')
ORDER BY `table_schema` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN cur1;
SET #unionall := '';
read_loop: LOOP
FETCH cur1 INTO vtable_schema, vtable_name;
IF done THEN
LEAVE read_loop;
END IF;
-- UNION ALL in case the id is the same
IF CHAR_LENGTH(#unionall) = 0 THEN
SET #unionall =
CONCAT("SELECT \'", vtable_schema , "\' AS 'Db', t.* FROM `",
vtable_schema, "`.`" , vtable_name, "` t ", qwhere);
ELSE
SET #unionall =
CONCAT(#unionall, " UNION ALL SELECT \'", vtable_schema ,
"\' AS 'Db', t.* FROM `", vtable_schema,
"`.`", vtable_name, "` t ", qwhere);
END IF;
END LOOP;
CLOSE cur1;
PREPARE stmt FROM #unionall;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
Run it with
call test.multidb_select('user','WHERE imei = \'76876876\'')

Teradata rename table if exists

I'm using Teradata. I'd like rename a table with a script sql and not using bteq, if a specific conditions is satisfied.
In particular:
if TABLE_A exists => rename table TABLE_B to TABLE_B_OLD
In Sql Server:
IF OBJECT_ID('TABLE_A', 'U') IS NULL
EXEC sp_rename 'TABLE_B', 'TABLE_B_OLD';
In Oracle:
DECLARE
cnt NUMBER;
BEGIN
select COUNT(*) INTO cnt from sys.user_tables where table_name = 'TABLE_A'
IF cnt>0 THEN
execute immediate 'rename table TABLE_B to TABLE_B_OLD';
END IF;
END;
How can I made it with Teradata,
Thanks
Fabio
How about this?
REPLACE PROCEDURE IF_EXISTS_RENAME
(
IN table_name VARCHAR(30),
IN new_table_name VARCHAR(30)
)
BEGIN
IF EXISTS(SELECT 1 FROM dbc.tables WHERE 1=1 AND tablename = table_name and databasename=DATABASE) THEN
CALL DBC.SysExecSQL('RENAME TABLE ' || table_name ||' to '|| new_table_name);
END IF;
END;
Changed code provided by #access_granted to include Database Name as variable
REPLACE PROCEDURE FAR.RENAME_TABLE
(
IN table_name VARCHAR(30),
IN new_table_name VARCHAR(30),
IN db_name VARCHAR(50)
)
BEGIN
declare my_sql VARCHAR(1000);
IF EXISTS(SELECT 1
FROM dbc.tables
WHERE 1=1 AND tablename = table_name and databasename= db_name)
THEN
set my_sql ='RENAME TABLE ' || table_name ||' to '|| new_table_name||';' ;
EXECUTE IMMEDIATE my_sql;
END IF;
END;
Calling the procedure with three arguments:
Old Table Name
New Table Name
Database Name
call FAR.RENAME_TABLE('TEST_ABC','TEST_11','FAR')
Assuming you're on a relatively modern version of Teradata, you can do this in SQL Assistant (or BTEQ):
select
count (*)
from
dbc.tablesv where tablename = '<your table>'
and databasename = '<your db>'
having count (*) > 0;
.if activitycount = 1 then .GOTO RenameTable;
.if activitycount <> 1 then .quit;
.LABEL RenameTable
rename table <your table> <your new name;

How can I return a CSV string from PL/SQL table type in Oracle

I have defined a table type PL/SQL variable and added some data there.
create or replace type varTableType as table of varchar2(32767);
my_table varTableType := varTableType()
...
my_table := some_function();
Now I have this my_table table type variable with several thousands of records.
I have to select only those records ending with specific character, say 'a' and get results in a comma separated string.
I think that COLLECT function could do this, but I do not understand exactly how.
I am using Oracle 10g.
Without getting into the question- why are you using a table type and not a table (or temporary table), you can do it like this:
declare
my_table varTableType;
i varchar2(32767);
begin
my_table := new
varTableType('bbbb', 'ccca', 'ddda', 'eee', 'fffa', 'gggg');
select trim(xmlagg(xmlelement(e, column_value || ','))
.extract('//text()'))
into i
from table(my_table)
where column_value like '%a';
dbms_output.put_line(i);
end;
There are more ways to concat rows- WM_CONCAT (if enabled) or LISTAGG (since 11g R2) but the basic idea of
select column_value
from table(my_table)
where column_value like '%a';
stays
There is another way without sql:
declare
my_table varTableType;
i varchar2(32767);
begin
my_table := new
varTableType('bbbb', 'ccca', 'ddda', 'eee', 'fffa', 'gggg');
FOR j IN my_table.first .. my_table.last LOOP
IF my_table(j) like '%a' THEN
i := i || my_table(j);
END IF;
END LOOP;
dbms_output.put_line(i);
end;
See this blog post by Tim Hall for a number of ways to do this, depending on Oracle version.

how to convert csv to table in oracle

How can I make a package that returns results in table format when passed in csv values.
select * from table(schema.mypackage.myfunction('one, two, three'))
should return
one
two
three
I tried something from ask tom but that only works with sql types.
I am using oracle 11g. Is there something built-in?
The following works
invoke it as
select * from table(splitter('a,b,c,d'))
create or replace function splitter(p_str in varchar2) return sys.odcivarchar2list
is
v_tab sys.odcivarchar2list:=new sys.odcivarchar2list();
begin
with cte as (select level ind from dual
connect by
level <=regexp_count(p_str,',') +1
)
select regexp_substr(p_str,'[^,]+',1,ind)
bulk collect into v_tab
from cte;
return v_tab;
end;
/
Alas, in 11g we still have to handroll our own PL/SQL tokenizers, using SQL types. In 11gR2 Oracle gave us a aggregating function to concatenate results into a CSV string, so perhaps in 12i they will provide the reverse capability.
If you don't want to create a SQL type especially you can use the built-in SYS.DBMS_DEBUG_VC2COLL, like this:
create or replace function string_tokenizer
(p_string in varchar2
, p_separator in varchar2 := ',')
return sys.dbms_debug_vc2coll
is
return_value SYS.DBMS_DEBUG_VC2COLL;
pattern varchar2(250);
begin
pattern := '[^('''||p_separator||''')]+' ;
select trim(regexp_substr (p_string, pattern, 1, level)) token
bulk collect into return_value
from dual
where regexp_substr (p_string, pattern, 1, level) is not null
connect by regexp_instr (p_string, pattern, 1, level) > 0;
return return_value;
end string_tokenizer;
/
Here it is in action:
SQL> select * from table (string_tokenizer('one, two, three'))
2 /
COLUMN_VALUE
----------------------------------------------------------------
one
two
three
SQL>
Acknowledgement: this code is a variant of some code I found on Tanel Poder's blog.
Here is another solution using a regular expression matcher entirely in sql.
SELECT regexp_substr('one,two,three','[^,]+', 1, level) abc
FROM dual
CONNECT BY regexp_substr('one,two,three', '[^,]+', 1, level) IS NOT NULL
For optimal performance, it is best to avoid using hierarchical (CONNECT BY) queries in the splitter function.
The following splitter function performs a good deal better when applied to greater data volumes
CREATE OR REPLACE FUNCTION row2col(p_clob_text IN VARCHAR2)
RETURN sys.dbms_debug_vc2coll PIPELINED
IS
next_new_line_indx PLS_INTEGER;
remaining_text VARCHAR2(20000);
next_piece_for_piping VARCHAR2(20000);
BEGIN
remaining_text := p_clob_text;
LOOP
next_new_line_indx := instr(remaining_text, ',');
next_piece_for_piping :=
CASE
WHEN next_new_line_indx <> 0 THEN
TRIM(SUBSTR(remaining_text, 1, next_new_line_indx-1))
ELSE
TRIM(SUBSTR(remaining_text, 1))
END;
remaining_text := SUBSTR(remaining_text, next_new_line_indx+1 );
PIPE ROW(next_piece_for_piping);
EXIT WHEN next_new_line_indx = 0 OR remaining_text IS NULL;
END LOOP;
RETURN;
END row2col;
/
This performance difference can be observed below (I used the function splitter as was given earlier in this discussion).
SQL> SET TIMING ON
SQL>
SQL> WITH SRC AS (
2 SELECT rownum||',a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'||rownum txt
3 FROM DUAL
4 CONNECT BY LEVEL <=10000
5 )
6 SELECT NULL
7 FROM SRC, TABLE(SYSTEM.row2col(txt)) t
8 HAVING MAX(t.column_value) > 'zzz'
9 ;
no rows selected
Elapsed: 00:00:00.93
SQL>
SQL> WITH SRC AS (
2 SELECT rownum||',a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'||rownum txt
3 FROM DUAL
4 CONNECT BY LEVEL <=10000
5 )
6 SELECT NULL
7 FROM SRC, TABLE(splitter(txt)) t
8 HAVING MAX(t.column_value) > 'zzz'
9 ;
no rows selected
Elapsed: 00:00:14.90
SQL>
SQL> SET TIMING OFF
SQL>
I don't have 11g installed to play with, but there is a PIVOT and UNPIVOT operation for converting columns to rows / rows to columns, that may be a good starting point.
http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-pivot.html
(Having actually done some further investigation, this doesn't look suitable for this case - it works with actual rows / columns, but not sets of data in a column).
There is also DBMS_UTILITY.comma_to_table and table_to_comma for converting CSV lists into pl/sql tables. There are some limitations (handling linefeeds, etc) but may be a good starting point.
My inclination would be to use the TYPE approach, with a simple function that does comma_to_table, then PIPE ROW for each entry in the result of comma_to_table (unfortunately, DBMS_UTILITY.comma_to_table is a procedure so cannot call from SQL).

Resources