How to use variable in a declare block? - oracle-sqldeveloper

Declare str varchar2(100);
Begin
str := 'first,last,middle';
select regexp_substr(str, '[^,]+', 1, rownum) as SeparatedStr
from dual
connect by rownum <= length(str) - length(replace(str, ',', '')) + 1;
end;
I'm pretty new to PL/SQL and used to be T-SQL guy. I'm really not sure how to use variable after searching so many articles. Just need help with teaching me how to use the declared variables.

Variable helps to store the result of a select query but it depends on whether the select query is giving a single row or multiple rows as the output.
Replicating your query as below:
1.
DECLARE
str VARCHAR2(100);
result VARCHAR2(100);
BEGIN
str := 'first,last,middle';
SELECT str INTO result FROM dual;
dbms_output.put_line('str: '||result);
END;
/
2. As your SELECT query is producing more than one row it can not be assigned into a scalar variable.
DECLARE
str VARCHAR2(100);
BEGIN
FOR I IN(
WITH TEST AS
(SELECT 'first,last,middle' AS str FROM dual)
SELECT regexp_substr(str, '[^,]+', 1, ROWNUM) AS separatedstr
FROM TEST
CONNECT BY ROWNUM <= LENGTH(str) - LENGTH(REPLACE(str, ',', '')) + 1) LOOP
dbms_output.put_line(I.separatedstr);
END LOOP;
END;
/
Hope this helps.

Related

In psql how to run a Loop for a Select query with CTEs and get the output shown in read-only db?

EDIT: I am creating a new question as per suggestion from sticky bit.
I have a query with CTEs to output results for several values (1 to 12). Below is a simplified example. Running it I got the following error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function inline_code_block line 8 at SQL statement
I can't output the result of Select in a table. How can I solve this problem?
DO $$
DECLARE r integer;
BEGIN
r := 1;
WHILE r <= 2
LOOP
r := r + 1;
WITH params AS (
SELECT r AS rownumber
),
time AS (
SELECT id
FROM params, analysis
ORDER BY date DESC
LIMIT 1
OFFSET (SELECT rownumber - 1 from params)
)
SELECT * FROM time;
END LOOP;
END; $$;
An example of what I mentioned in my comment:
DO $$
DECLARE
r integer;
int_var integer;
BEGIN
r := 1;
WHILE r <= 12 LOOP
WITH params AS (
SELECT r AS rownumber
),
time AS (
SELECT id
FROM params, analysis
ORDER BY date DESC
LIMIT 1
OFFSET (SELECT rownumber - 1 from params)
)
SELECT INTO int_var id FROM time;
RAISE NOTICE 'id: %', int_var;
r := r + 1;
END LOOP;
END; $$;
You can't RETURN a value from a DO function, but you can RAISE NOTICE a value as above. The SELECT INTO will eliminate the error as you are giving the SELECT a destination(the int_var) for its output. NOTE: SELECT INTO inside plpgsql is different then the same command outside. Outside it is equivalent to CREATE TABLE AS.

Trying to use temporary table in IBM DB2 and facing issues

I am getting the following error while creating a stored procedure for testing purpose:
SQL Error [42601]: An unexpected token "DECLARE GLOBAL TEMPORARY TABLE
SESSION" was found following "RSOR WITH RETURN FOR". Expected tokens may include: "".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.21.29
Code:
CREATE OR REPLACE PROCEDURE Test ( IN GE_OutPutType SMALLINT)
----------------------------------------------------------------------------------------------------
DYNAMIC RESULT SETS 1 LANGUAGE SQL
BEGIN
DECLARE C CURSOR WITH RETURN FOR DECLARE GLOBAL TEMPORARY TABLE
SESSION.TEMP (DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2));
INSERT
INTO
SESSION.TEMP (DATE,
SALARY,
COMM) SELECT
VARCHAR_FORMAT(CURRENT_DATE,
'MM/DD/YYYY'),
10.2,
11.5
FROM
sysibm.sysdummy1
IF GE_OutPutType = 1
BEGIN
SELECT
*
FROM
TEMP
ELSEIF GE_OutPutType = 2 SELECT
'HEADER' CONCAT SPACE(1979) CONCAT 'H'
FROM
sysibm.sysdummy1
END OPEN C;
END
Your syntax is not valid.
You must declare your temporary table independently of your cursor.
You cannot combine these in a single statement.
Use dynamic-SQL features to achieve what you need.
Use instead the format:
Declare c1 cursor with return to caller for Statement1
and
set v_cursor_text = 'select ... from session.temp ; `
then use
prepare Statement1 from v_cursor_text;
and before you exit the stored procedure you need to leave the cursor opened:
open c1;
Do study the Db2 online documentation to learn more about these features.
Here is a small fragment of your procedure showing what I mean:
CREATE OR REPLACE PROCEDURE mytest ( IN GE_OutPutType SMALLINT)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
specific mytest
BEGIN
DECLARE v_cursor_text varchar(1024);
DECLARE C1 CURSOR WITH RETURN FOR Statement1;
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP (
DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2))
with replace on commit preserve rows not logged;
INSERT INTO SESSION.TEMP (DATE, SALARY, COMM)
SELECT VARCHAR_FORMAT(CURRENT_DATE, 'MM/DD/YYYY'),
10.2,
11.5
FROM sysibm.sysdummy1 ;
if GE_OutPutType = 1
then
set v_cursor_text = 'select * from session.temp';
end if;
if GE_OutPutType = 2
then
set v_cursor_text = 'select ''header'' concat space(1979) concat ''H'' from sysibm.sysdummy1';
end if;
prepare Statement1 from v_cursor_text;
open c1;
END#

Best way to remove ordered sequential duplicates in a comma separated list with postgresSQL

I have a column with data that looks like this in a single field:
"a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b"
Using some sort of regex or SQL function I would like to make it look like this:
"a,b,c,a,b,a,c,a,b"
Essentially I am trying to get rid of repeated values that appear in order but keep the unique changes from one value to another.
My knowledge of reg-expressions pretty much ends at removing duplicates. Any help is greatly appreciated!
use regexp:
SELECT regexp_replace('a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b', '(\w)(,\1)+', '\1', 'g')
(\w)(,\1)+ mutches: (any word char) and following (, and this same word char) more than one time...
Fiddle example
RegExr example
You can convert the elements into rows, check if the previous row is different to the current and then keep only those where something changed. This can then be aggregated back into a comma separated list:
select string_agg(ch, ',' order by idx)
from (
select u.ch, u.idx,
coalesce(u.ch <> lag(u.ch) over (order by u.idx), true) as is_change
from unnest(string_to_array('a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b', ',')) with ordinality as u(ch, idx)
) t
where is_change
The with ordinality returns the original array index, so that we can sort the elements correctly when aggregating them.
This can also be put into a function:
create or replace function cleanup(p_input text)
returns text
as
$$
select string_agg(ch, ',' order by idx)
from (
select u.ch, u.idx,
coalesce(u.ch <> lag(u.ch) over (order by u.idx), true) as is_change
from unnest(string_to_array(p_input, ',')) with ordinality as u(ch, idx)
) t
where is_change;
$$
language sql;
Online example
My understanding is:
If the character is the same as previous character, you want to remove it from the string.
So I will use while loop and if statement in this case:
--CREATE TABLE TEST (ID VARCHAR(100));
--INSERT INTO TEST VALUES ('a,a,b,b,c,a,b,b,b,a,a,a,a,a,a,c,a,a,b');
DO $$
DECLARE
V_NEWSTRING VARCHAR(100) := '';
V_I INTEGER := 1;
V_LENGTH INTEGER := 0;
V_CURRENT VARCHAR(10) := '';
V_LAST VARCHAR(10) := '';
BEGIN
SELECT LENGTH(ID) FROM TEST INTO V_LENGTH;
WHILE V_I <= V_LENGTH LOOP
SELECT SUBSTRING(ID,V_I,1) from TEST INTO V_CURRENT;
IF V_CURRENT <> V_LAST THEN
V_NEWSTRING = V_NEWSTRING || V_CURRENT || ',';
END IF;
V_LAST = V_CURRENT;
V_I = V_I + 2;
END LOOP;
raise notice 'Value: %', V_NEWSTRING;
END $$;
Test Result (PostgreSQL-9.4):

Split string with two delimiters and convert type

I have a PL/pgSQL function like this (thanks to the guy who made this possible):
CREATE OR REPLACE FUNCTION public.split_string(text, text)
RETURNS SETOF text
LANGUAGE plpgsql
AS $function$
DECLARE
pos int;
delim_length int := length($2);
BEGIN
WHILE $1 <> ''
LOOP
pos := strpos($1,$2);
IF pos > 0 THEN
RETURN NEXT substring($1 FROM 1 FOR pos - 1);
$1 := substring($1 FROM pos + delim_length);
ELSE
RETURN NEXT $1;
EXIT;
END IF;
END LOOP;
RETURN;
END;
$function$
It splits a string with a delimiter. Like this:
select * from split_string('3.584731 60.739211,3.590472 60.738030,3.592740 60.736220', ' ');
"3.584731"
"60.739211,3.590472"
"60.738030,3.592740"
"60.736220"
How can I save the results in a temp_array or temp_table. So I can get the the results in temp_x and split up these points again. Like:
"3.584731"
"60.739211"
"3.590472"
"60.738030"
"3.592740"
"60.736220"
and return the values as double precision. And all of this should be done in the function.
If you need the intermediary step:
SELECT unnest(string_to_array(a, ' '))::float8
-- or do something else with the derived table
FROM unnest(string_to_array('3.584731 60.739211,3.590472 60.738030', ',')) a;
This is more verbose than regexp_split_to_table(), but may still be faster because regular expressions are typically more expensive. (Test with EXPLAIN ANALYZE.)
I first split at ',', and next at ' ' - the reversed sequence of what you describe seems more adequate.
If need be, you can wrap this into a PL/pgSQL function:
CREATE OR REPLACE FUNCTION public.split_string(_str text
, _delim1 text = ','
, _delim2 text = ' ')
RETURNS SETOF float8 AS
$func$
BEGIN
RETURN QUERY
SELECT unnest(string_to_array(a, _delim2))::float8
-- or do something else with the derived table from step 1
FROM unnest(string_to_array(_str, _delim1)) a;
END
$func$ LANGUAGE plpgsql IMMUTABLE;
Or just an SQL function:
CREATE OR REPLACE FUNCTION public.split_string(_str text
, _delim1 text = ','
, _delim2 text = ' ')
RETURNS SETOF float8 AS
$func$
SELECT unnest(string_to_array(a, _delim2))::float8
FROM unnest(string_to_array(_str, _delim1)) a
$func$ LANGUAGE sql IMMUTABLE;
Make it IMMUTABLE to allow performance optimization and other uses.
Call (using the provided defaults for _delim1 and _delim2):
SELECT * FROM split_string('3.584731 60.739211,3.590472 60.738030');
Or:
SELECT * FROM split_string('3.584731 60.739211,3.590472 60.738030', ',', ' ');
Fastest
For top performance, combine translate() with unnest(string_to_array(...)):
SELECT unnest(
string_to_array(
translate('3.584731 60.739211,3.590472 60.738030', ' ', ',')
, ','
)
)::float8
You do not need special functions, use built-in regexp_split_to_table:
SELECT *
FROM regexp_split_to_table(
'3.584731 60.739211,3.590472 60.738030,3.592740 60.736220',
'[, ]') s;
EDIT:
I don't see why you wish to stick with PL/pgSQL function if there's a built-in one.
Anyway, consider this example:
WITH s AS
(
SELECT ' ,'::text sep,
'3.584731 60.739211,3.590472 60.738030,3.592740 60.736220'::text str
)
SELECT sep, left(sep,1), right(sep,-1),
str,
translate(str, right(sep,-1), left(sep,1))
FROM s;
This means, that you can:
do similar transformations before you call your function or
integrate this code inside, but this will mean you'll need to introduce at least one extra variable, unless you feel comfortable replacing all $1 into translate($1, right($2,-1), left($2,1)) throughout the code. Obviously, plain $2 should be changed to left($2,1).
If I understand to your questions well, you can do:
-- store context to temp table
CREATE TEMP TABLE foo AS SELECT v::double precision FROM split_string('...') g(v);
-- store context to ARRAY
SELECT ARRAY(SELECT v::double precision FROM split_string('....') g(v))

How to select into a variable in PL/SQL when the result might be null?

Is there a way in to just run a query once to select into a variable, considering that the query might return nothing, then in that case the variable should be null.
Currently, I can't do a select into a variable directly, since if the query returns nothing, the PL/SQL would complain variable not getting set. I can only run the query twice, with the first one do the count and if the count is zero, set the variable to null, and if the count is 1, select into the variable.
So the code would be like:
v_column my_table.column%TYPE;
v_counter number;
select count(column) into v_counter from my_table where ...;
if (v_counter = 0) then
v_column := null;
elsif (v_counter = 1) then
select column into v_column from my_table where ...;
end if;
thanks.
Update:
The reason I didn't use exception is I still have some following logic after assigning the v_column, and I have to use goto in the exception section to jump back to the following code. I'm kind of hesitate of goto lines.
You can simply handle the NO_DATA_FOUND exception by setting your variable to NULL. This way, only one query is required.
v_column my_table.column%TYPE;
BEGIN
BEGIN
select column into v_column from my_table where ...;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_column := NULL;
END;
... use v_column here
END;
I know it's an old thread, but I still think it's worth to answer it.
select (
SELECT COLUMN FROM MY_TABLE WHERE ....
) into v_column
from dual;
Example of use:
declare v_column VARCHAR2(100);
begin
select (SELECT TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME = 'DOES NOT EXIST')
into v_column
from dual;
DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
end;
What about using MAX?
That way if no data is found the variable is set to NULL, otherwise the maximum value.
Since you expect either 0 or 1 value, MAX should be OK to use.
v_column my_table.column%TYPE;
select MAX(column) into v_column from my_table where ...;
Using an Cursor FOR LOOP Statement is my favourite way to do this.
It is safer than using an explicit cursor, because you don't need to remember to close it, so you can't "leak" cursors.
You don't need "into" variables, you don't need to "FETCH", you don't need to catch and handle "NO DATA FOUND" exceptions.
Try it, you'll never go back.
v_column my_table.column%TYPE;
v_column := null;
FOR rMyTable IN (SELECT COLUMN FROM MY_TABLE WHERE ....) LOOP
v_column := rMyTable.COLUMN;
EXIT; -- Exit the loop if you only want the first result.
END LOOP;
From all the answers above, Björn's answer seems to be the most elegant and short. I personally used this approach many times. MAX or MIN function will do the job equally well. Complete PL/SQL follows, just the where clause should be specified.
declare v_column my_table.column%TYPE;
begin
select MIN(column) into v_column from my_table where ...;
DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
end;
I would recommend using a cursor. A cursor fetch is always a single row (unless you use a bulk collection), and cursors do not automatically throw no_data_found or too_many_rows exceptions; although you may inspect the cursor attribute once opened to determine if you have a row and how many.
declare
v_column my_table.column%type;
l_count pls_integer;
cursor my_cursor is
select count(*) from my_table where ...;
begin
open my_cursor;
fetch my_cursor into l_count;
close my_cursor;
if l_count = 1 then
select whse_code into v_column from my_table where ...;
else
v_column := null;
end if;
end;
Or, even more simple:
declare
v_column my_table.column%type;
cursor my_cursor is
select column from my_table where ...;
begin
open my_cursor;
fetch my_cursor into v_column;
-- Optional IF .. THEN based on FOUND or NOTFOUND
-- Not really needed if v_column is not set
if my_cursor%notfound then
v_column := null;
end if;
close my_cursor;
end;
I use this syntax for flexibility and speed -
begin
--
with KLUJ as
( select 0 ROES from dual
union
select count(*) from MY_TABLE where rownum = 1
) select max(ROES) into has_rows from KLUJ;
--
end;
Dual returns 1 row, rownum adds 0 or 1 rows, and max() groups to exactly 1. This gives 0 for no rows in a table and 1 for any other number of rows.
I extend the where clause to count rows by condition, remove rownum to count rows meeting a condition, and increase rownum to count rows meeting the condition up to a limit.
COALESCE will always return the first non-null result. By doing this, you will get the count that you want or 0:
select coalesce(count(column) ,0) into v_counter from my_table where ...;