Comparing column values using array - PostgreSQL - arrays

I'm trying to find the best solution for this problem. I have a table (I simplified here with only two columns) in which I have to check if the 'customer_id' is the same for each 'service_id' that I can have in the table.
If the customer_id for the same service_id is different, the service_id is raised as warning. I created a loop to pick each single service_id and I tried to save the results of the select query as an array. The problem is the array I created seems not multidimensional (I can't get the 'customer_id' to make comparison).
I don't have a clear picture of how arrays work, so probably I'm doing something wrong. The table services is this one:
The code I have written so far:
CREATE OR REPLACE PROCEDURE process_services()
LANGUAGE plpgsql
AS $$
DECLARE r RECORD;
DECLARE cur_row REFCURSOR;
DECLARE services varchar array;
begin
OPEN cur_row FOR EXECUTE 'select distinct(service_id)
from services';
loop
FETCH NEXT FROM cur_row INTO r;
-- exit when no more row to fetch
EXIT WHEN NOT FOUND;
SELECT array(select row(service_id, customer_id) FROM services WHERE service_id = r.service_id) INTO services;
IF array_length(services, 1) < 2 THEN
raise notice 'ERROR singleton, service_id =% ',r.service_id;
-- check if there are more than 2 endpoints
ELSEIF array_length(services, 1) > 2 THEN
raise notice 'ERROR too many , service_id =%' , r.service_id;
ELSE
raise notice 'Value: %', r.service_id ;
raise notice 'Value: %', services[:1];
raise notice 'Value: %', services[2];
END IF;
end loop;
CLOSE cur_row;
END;
$$;

Related

Getting error in EXECUTE statement when a null value is encountered

I have created following stored procedure to update recon_dashboard table.
create or replace procedure ca_adhoc_view.sp_count_recon_refresh()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select alvid from ca_adhoc_view.recon_dashboard
loop
raise notice '% alv',f.alv;
execute 'update ca_adhoc_view.recon_dashboard set alv_count =
(select count(*) from ' || f.alv || ')
where id='|| f.id;
end loop;
END;
$$;
It works fine unless there is a null value in the alv, then it throws an error.
I tried to check for null like:
create or replace procedure ca_adhoc_view.sp_count_recon_refresh()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select alv,id from ca_adhoc_view.recon_dashboard
loop
raise notice '% alv',f.alv;
execute 'update ca_adhoc_view.recon_dashboard set alv_count =
(IF '||f.alv||' is not null
then (select count(*) from ' || f.alv || ')
END IF;)
where id='|| f.id;
end loop;
END;
$$;
But it throws error when I try to call the SP.
Error: SQL Error [42601]: ERROR: syntax error at or near "ca_view"
Where: PL/pgSQL function "sp_count_recon_refresh" line 7 at execute statement
In error "ca_view" is a recod. alv column has values like 'ca_view.table1', 'ca_view.table2' and so on.
This probably fixes your problems:
CREATE OR REPLACE PROCEDURE ca_adhoc_view.sp_count_recon_refresh()
LANGUAGE plpgsql AS
$func$
DECLARE
f record;
BEGIN
FOR f IN
SELECT alv, id FROM ca_adhoc_view.recon_dashboard
LOOP
RAISE NOTICE '% alv', f.alv;
IF f.alv IS NOT NULL THEN
EXECUTE format(
'UPDATE ca_adhoc_view.recon_dashboard
SET alv_count = (SELECT count(*) FROM %I)
WHERE id = $1', f.alv)
USING f.id;
END IF;
END LOOP;
END
$func$;
"Problems" (plural). Besides defending against a NULL value for the table name with proper syntax, this also prevents SQL injection. Your original is wide open there. Related:
PostgreSQL - Writing dynamic sql in stored procedure that returns a result set
Table name as a PostgreSQL function parameter
That said, it would be more efficient to run a single UPDATE instead of updating one row per loop iteration. Consider building and executing a single statement.

FEtching into an object throws inconsistent datatypes: expected - got -

Is it possible to iterate over a cursor without the need for an intermediate TABLE as type of Object?
I have the following EVENT_TABLE setup in my schema
create TABLE EVENT_TABLE (
UUID INTEGER,
EVENT_TYPE VARCHAR2(10)
);
INSERT INTO EVENT_TABLE values( 1, 'START');
INSERT INTO EVENT_TABLE values( 2, 'RUNNING');
INSERT INTO EVENT_TABLE values( 2, 'COMPLETE');
CREATE OR REPLACE TYPE EVENT_OBJ AS OBJECT (
UUID INTEGER,
EVENT_TYPE VARCHAR2(10)
);
/
COMMIT;
declare
r EVENT_OBJ;
TYPE cur_typ IS REF CURSOR;
EVENT cur_typ;
BEGIN
OPEN event FOR 'select * from EVENT_TABLE';
LOOP
FETCH event INTO r;
EXIT WHEN event%NOTFOUND;
DBMS_OUTPUT.put_line('NEW_UID:-' || R.UUID);
END LOOP;
END;
/
when I execute the anonymous block at the end I get the following exception
ORA-00932 inconsistent datatypes: expected - got -
I've seen examples where a query is fetched into an object before but I can't seem to get it to work.
By creating a temporary table as follows, the anonymous block completes and the DBMS output as expected is produced?
declare
r EVENT_OBJ;
TYPE cur_typ IS REF CURSOR;
EVENT cur_typ;
-- Temp table
TYPE EVENT_TBL IS TABLE OF EVENT_OBJ;
lt_evt_tbl EVENT_TBL;
BEGIN
select EVENT_OBJ (UUID,EVENT_TYPE ) bulk collect into lt_evt_tbl from EVENT_TABLE;
FOR indx IN 1..lt_evt_tbl.COUNT
LOOP
DBMS_OUTPUT.put_line('NEW_UID:-' || lt_evt_tbl(indx).UUID);
END LOOP;
END;
/
You need to construct an event_obj object for each row returned from the table:
declare
r event_obj;
event sys_refcursor;
begin
open event for
select event_obj(uuid, event_type) from event_table;
loop
fetch event into r;
exit when event%notfound;
dbms_output.put_line('NEW_UID: ' || r.uuid);
end loop;
end;
When you just select plain columns from a plain relational table, you get back a plain record type, not an object, and fetch into won't implicitly it cast to one.
By the way, your lt_evt_tbl in the second example is a collection, not a temp table.
Also, there is no need to define your own weak ref cursor type, as sys_refcursor is already provided; and although you can certainly use native dynamic SQL for your ref cursor, you don't need to, and static code is easier to work with, so I have made it static in the example above.
Even simpler version:
begin
for r in (
select event_obj(uuid, event_type) as event from event_table
)
loop
dbms_output.put_line('NEW_UID: ' || r.event.uuid);
end loop;
end;

Implementing IF statement on Oracle Database

Im trying to implement an IF statement to run a query on my Oracle Database.
The expectation:
If the employee is called John, display the limited items(item1 and item2), if it's not John, display the complete list.
DECLARE
employeeName STRING := 'John';
BEGIN
IF (employeeName = 'John') THEN
       (SELECT TableA.Table from Table where TableA.Table = 'Item1' OR TableA.Table = 'Item2');
ELSE 
       (SELECT TableA.Table from Table);
END IF;
END;
Here's the error output:
Error report: ORA-06550: line 6, column 2: PLS-00103: Encountered the symbol " " when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma raise
return select update while with 'an identifier' 'a double-quoted
delimited-identifier' 'a bind variable' ""continue close current
delete fetch lock insert open rollback savepoint set sql execute
commit forall merge pipe purge
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
What am I doing wrong? Forgive my ignorance on IF statements implemented on databases.
In your code,
table is a reserved keyword. Use double quotes or a different identifier
select is missing into.
String is missing length
You can apply your condition in a single SQL and then, use a for loop to loop over the result set.
DECLARE
employeeName varchar2(100) := 'John';
BEGIN
for t in (select "table" from tablea
where "table" in ('item1', 'item2')
or employeename = 'john') loop
dbms_output.put_line(t."table");
end loop;
END;
/

How to insert rows into table using cursor in Firebird?

Collegues, I am new in Firebird. I'am trying to call procedure (p_procedure) in cursor cycle and insert result into table (tmp_port).
execute block
as
declare contr integer;
declare IN$DT date;
declare cur_list_of_cont cursor for (select first 100 contracts.doc from TABLE_1);
begin
delete from tmp_port;
IN$DT = getdate()-2;
open cur_list_of_cont;
while (ROW_COUNT > 0) do
begin
fetch cur_list_of_cont into contr;
insert into tmp_port (
DT,
....)
select
:IN$DT as DT,
...
from p_procedure (0, :contr , :IN$DT);
if (ROW_COUNT = 0) then leave;
suspend;
end
close cur_list_of_cont;
end;
The problem is that only single, fisrt row processed from cur_list_of_cont.
Why other 99 rows are not processed?
UPDATE
FireBird Server version is 2.5
UPDATE
In this realization it works fine:
begin
IN$DT = getdate()-2;
FOR select first 100 contracts.doc from TABLE_1
INTO :contr
DO
BEGIN
insert into tmp_port (
DT,
....)
select
:IN$DT as DT,
...
from p_procedure (0, :contr , :IN$DT);
END
SUSPEND;
end;
It will be better if the first example works too. How to do it?
The problem is that you are using ROW_COUNT incorrectly. As documented in the Firebird 2.5 language reference:
Description: The ROW_COUNT context variable contains the number of rows affected by the most recent DML statement (INSERT, UPDATE, DELETE, SELECT or FETCH) in the current trigger, stored procedure or executable block.
...
After a FETCH from a cursor, ROW_COUNT is 1 if a data row was retrieved and 0 otherwise. Fetching more records from the same cursor does not increment ROW_COUNT beyond 1.
The while (ROW_COUNT > 0) might be false if nothing was deleted, you might also exit the loop if your procedure returns no rows and therefor nothing was inserted.
If you look at the example of using FETCH, you can modify your code to:
open cur_list_of_cont;
fetch cur_list_of_cont into contr;
while (row_count > 0) do
begin
insert into tmp_port (
DT,
....)
select
:IN$DT as DT,
...
from p_procedure (0, :contr , :IN$DT);
fetch cur_list_of_cont into contr;
end
close cur_list_of_cont;
However, given your code, you should consider using FOR SELECT as it is easier in most cases.

Using a query in a conditional statement in PLSQL?

Can I do something like the following in PLSQL?
if (some_query) then
dbms_output.put_line('Your query returned at least 1 row.');
else
dbms_output.put_line('Your query returned no rows.');
end if;
My use case is I want to check if a value already exists in my database. If the value already exists then I will do something different than if the value doesn't exist at all.
If you a checking for existence of a record, then you can select COUNT(*) from the source table based on the key as it will always return a value you can check:
SQL> set serverout on
SQL> DECLARE
2 v_check NUMBER;
3 BEGIN
4 SELECT COUNT(*)
5 INTO v_check
6 FROM DUAL
7 WHERE DUMMY = 'X'
8 AND ROWNUM = 1;
9
10 if (v_check = 0) then
11 dbms_output.put_line('Your query returned no rows.');
12 else
13 dbms_output.put_line('Your query returned at least 1 row.');
14 end if;
15 END;
16 /
Your query returned at least 1 row.
PL/SQL procedure successfully completed.
SQL>
You'd have to do something like
BEGIN
SELECT 1
INTO l_foo
FROM dual
WHERE EXISTS (<<some query>>);
dbms_output.put_line( 'Your query returned at least 1 row' );
EXCEPTION
WHEN no_data_found
THEN
dbms_output.put_line( 'Your query returned 0 rows' );
END;
If the query isn't too expensive, it will be almost as efficient and probably a bit easier to maintain if you do something simpler
SELECT COUNT(*)
INTO l_foo
FROM (<<some query>>)
WHERE rownum = 1;
IF( l_foo = 1 )
THEN
dbms_output.put_line( 'Your query returned at least row.' );
ELSE
dbms_output.put_line( 'Your query returned 0 rows.' );
END IF;
Exactly the same, no. But there are several ways you can fake it:
If you only need to do one thing do it inside an implicit cursor that confirms whether your value exists.
for i in ( some_query ) loop
do_something;
end loop;
You can also set a value in here and use it in an if
for i in ( some_query ) loop
result := True;
end loop;
if result then
do_something;
else
do_something_else;
end if;
Or you can use an explicit cursor and catch the no_data_found error that'll be raised
declare
cursor c_blah is
select my_value
from my_table
where id = my_id
;
my_value varchar2(4000);
begin
open c_blah(my_id);
fetch c_blah into my_value;
close c_blah;
do_something;
exception when no_data_found then
do_something_else;
end;
Since your use case behaves identically whether the query returns one row or a thousand, use EXISTS:
DECLARE
l_dummy VARCHAR2(1);
BEGIN
SELECT NULL
INTO l_dummy
FROM DUAL
WHERE EXISTS (SELECT NULL
FROM <some_query>);
DBMS_OUTPUT.PUT_LINE('Your query returned at least one row.');
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Your query returned no rows.');
END;

Resources