PL/SQL Oracle - Selecting from internal table - sql

EDITED
I have a problem with performing some PL/SQL code.
I have real table a. I want to take only elements with range<=100. I make a collection inside my PL/SQL based on that table. Then I want to perform SELECT operation on that collection. But I got a problem with it.
Prepared table (this is all for example, it's not a real problem. I just would like to know how can I select from collection in PL/SQL code block).
CREATE TABLE a (amount NUMBER);
INSERT INTO a VALUES (50);
INSERT INTO a VALUES (100);
INSERT INTO a VALUES (200);
And then I got this block:
DECLARE
TYPE aTable IS TABLE OF a%ROWTYPE;
aActual aTable;
temp NUMBER;
BEGIN
SELECT * BULK COLLECT INTO aActual
FROM a WHERE amount<=100;
SELECT SUM(amount) INTO temp FROM TABLE(aActual);
DBMS_OUTPUT.PUT_LINE(temp);
END;
But I got eroor PLS-00642 and ORA-22905.
What can I do? Why it doesn't work that way?
I'm on Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production version (according to SELECT * FROM V$VERSION;)

You can't do it because aTable is not a database table. (Yes I know it's defined with table of but that doesn't define a table. One of those things.)
To ask SQL to treat a collection as a database table you would use the table() construction:
select sum(amount) into temp from table(aActual);
although this will fail in your example due to scoping issues and you'll get the self-explanatory
PLS-00642: local collection types not allowed in SQL statements
For it to work, you'd need a schema-level type i.e. one created with create type:
create or replace type xyz as object (a integer, b varchar2(3), c date);
create or replace type xyz_tt as table of xyz;
Now type xyz_tt is in effect published to SQL and it can be used in SQL table() expressions.

As WilliamRobertson showed, you can't use a PL/SQL collection in a SQL query. You can loop over the collection and add each amount to your temp variable, initialising it to zero first:
temp := 0;
for i in 1..aActual.count loop
temp := temp + aActual(i).amount;
end loop;
DBMS_OUTPUT.PUT_LINE(temp);

Related

Reverse a column in postgres

So I have a table in SQL server that is defined as such
create table test(value varchar(200), Reverse(value) as valueReverse);
now when I insert something in this table lets say I insert the string hello, it will store the value in the table as such.
value | valueReverse
--------------------
hello | olleh
I am trying to convert the table into PostgreSQL however the reverse() function is not working and it's giving me errors. What is the correct way to create this table in postgres?
For PostgreSQL 12 and above
If you are using Postgres 12 or higher then you can use GENERATED ALWAYS AS for the column valueReverse like below: Manual
create table test(value varchar(200),
valueReverse varchar(200) generated always as (reverse(value)) STORED );
DEMO
For PostgreSQL 11 or below
For earlier version you can use Triggers like below.
Creating Trigger Function
create or replace function trig_reverse() returns trigger as
$$
begin
new.valueReverse=reverse(new.value);
return new;
end;
$$
language plpgsql
Creating Trigger
create trigger trig_rev
before insert or update on test
for each row
execute procedure trig_reverse();
DEMO
Do not store string twice (redundantly). It will be much cleaner and cheaper overall to store it once and produce the reverted copy on the fly. You can use a VIEW if you need a drop-in replacement for your table:
CREATE TABLE base_test(value varchar(200));
INSERT INTO base_test VALUES ('hello');
CREATE VIEW test AS
SELECT *, reverse(value) AS value_reverse
FROM base_test;
db<>fiddle here
Related:
Computed / calculated / virtual / derived columns in PostgreSQL

How to use one sql parameter to represent input array

Is there a way to write sql for Oracle, MS SQL:
Select * from table where id in(:arr)
Select * from table where id in(#arr)
With one param in sql 'arr' to represent an array of items?
I found examples that explode arr to #arr0,.., #arrn and feed array as n+1 separate parameters, not array, like this
Select * from table where id in(:arr0, :arr1, :arr2)
Select * from table where id in(#arr0, #arr1, #arr2)
Not what i want.
These will cause change in sql query and this creates new execution plans based on number of parameter.
I ask for .net, c# and Oracle and MS SQL.
Thanks for constructive ideas!
/ip/
I believe Table Value Parameter is good option for this case. Have a look at a sample code below in SQL Server.
-- Your table
CREATE TABLE SampleTable
(
ID INT
)
INSERT INTO SampleTable VALUES
(1010),
(2010),
(3010),
(4010),
(5010),
(6010),
(7010),
(8030)
GO
-- Create a TABLE type in SQL database which you can fill from front-end code
CREATE TYPE ParameterTableType AS TABLE
(
ParameterID INT
--, some other columns
)
GO
-- Create a stored proc using table type defined above
CREATE PROCEDURE ParameterArrayProcedure
(
#ParameterTable AS ParameterTableType READONLY
)
AS
BEGIN
SELECT
S.*
FROM SampleTable S
INNER JOIN #ParameterTable P ON S.ID = P.ParameterID
END
GO
-- Populated table type variable
DECLARE #ParameterTable AS ParameterTableType
INSERT INTO #ParameterTable (ParameterID) VALUES (1010), (4010), (7010)
EXECUTE ParameterArrayProcedure #ParameterTable
DROP PROCEDURE ParameterArrayProcedure
DROP TYPE ParameterTableType
DROP TABLE SampleTable
GO
Apart from Table Value Parameter, you can also use Json or XML values as SQL parameter but yes, it will definitely change your execution plan accordingly.
In addition to a Table Valued Parameter as Steve mentioned, there are a couple of other techniques available. For example you can parse a delimited string
Example
Declare #arr varchar(50) = '10,20,35'
Select A.*
From YourTable A
Join string_split(#arr,',') B on A.ID=value
Or even
Select A.*
From YourTable A
Where ID in ( select value from string_split(#arr,',') )
Oracle
In other languages (i.e. Java) you can pass an SQL collection as a bind parameter and directly use it in an SQL statement.
However, C# does not support passing SQL collections and only supports passing OracleCollectionType.PLSQLAssociativeArray (documentation link) which is a PL/SQL only data-type and cannot be used (directly) in SQL statements.
To pass an array, you would need to pass a PLSQLAssociativeArray to a PLSQL stored procedure and use that to convert it to an SQL collection that you can use in an SQL statement. An example of a procedure to convert from a PL/SQL associative array to an SQL collection is:
CREATE TYPE IntList AS TABLE OF INTEGER
/
CREATE PACKAGE tools IS
TYPE IntMap IS TABLE OF INTEGER INDEX BY BINARY_INTEGER;
FUNCTION IntMapToList(
i_map IntMap
) RETURN IntList;
END;
/
CREATE PACKAGE BODY tools IS
FUNCTION IntMapToList(
i_map IntMap
) RETURN IntList
IS
o_list IntList := IntList();
i BINARY_INTEGER;
BEGIN
IF i_map IS NOT NULL THEN
i := o_list.FIRST;
WHILE i IS NOT NULL LOOP
o_list.EXTEND;
o_list( o_list.COUNT ) := i_map( i );
i := i_map.NEXT( i );
END LOOP;
END IF;
RETURN o_list;
END;
END;
/

Oracle Making local tables using dynamic sql?

How to use local temporary table or alternative using dynamic SQL in Oracle?
In SQL Server, to select all the columns from a table called dbo.2019 into a local temporary table called x:
CREATE TABLE #x (a int)
DECLARE #FY varchar(4) = Year(date())
EXEC ('SELECT * into #x FROM dbo.'+#FY)
The reason why I want to do this is because I want to use all kinds of information (besides year, like values from other tables or whatever) to build very complicated queries in a manner that does not require lots of bizarre subqueries.
The limitation of SQL Server is that you must make your temporary tables first, or you have to use global ones, that people could write over.
As you know, Oracle <> MS SQL Server. The latter uses temporary tables a lot. Oracle - usually/mostly - doesn't need them.
But, for your specific problem ("very complicated queries" and stuff), that maybe isn't a bad idea. Depending on your Oracle database version, there are global temporary tables (and - in most recent versions, private ones). How do they work? You create them once and use many times, which means that you should NOT create them dynamically. Data stored within is visible to you only, although many users can use it simultaneously. Furthermore, data is kept during the whole session (if you opt to create them with the on commit preserve rows option) or during transaction (on commit delete rows).
For example:
SQL> create global temporary table gtt_test
2 (id number,
3 name varchar2(20))
4 on commit delete rows;
Table created.
SQL>
You can index them:
SQL> create index i1_gtt on gtt_test (id);
Index created.
SQL>
and do anything you want. Once your session (or transaction) is over, puff! they are empty, not data is permanently stored within. When you have to do the same job tomorrow, the table is still here, waiting for you. No dynamic create/drop/create/drop (or whatever you thought you should do).
So: create it once, use it any time you need.
If it must be dynamically, beware of this:
SQL> declare
2 l_cnt number;
3 begin
4 -- check whether table already exists
5 select count(*)
6 into l_Cnt
7 from user_tables
8 where table_name = 'TEST';
9
10 -- yes, it exists - drop it first
11 if l_cnt = 1 then
12 execute immediate 'drop table test';
13 end if;
14 -- now create it
15 execute immediate 'create table test (id number, name varchar2(20))';
16
17 -- insert some rows
18 insert into test (id, name) values (1, 'Littlefoot');
19 end;
20 /
insert into test (id, name) values (1, 'Littlefoot');
*
ERROR at line 18:
ORA-06550: line 18, column 15:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 18, column 3:
PL/SQL: SQL Statement ignored
SQL>
Although I'm first checking whether the table exists (so that I'd drop it first and then recreate it), the whole PL/SQL block failed because I'm trying to use a table that doesn't exist yet (at compile time).
So, if I'd want to use that test table, any operation - within the same PL/SQL block - should also become dynamic, and that's horrible: ugly to write, difficult to debug, beware of single quote problems ... you'd really want to avoid that.
Once again: I'd suggest you not to do that dynamically.

Error: PLS-00642: local collection types not allowed in SQL statements

I want to retrieve data from a table with multiple values in a variable and with where clause on this variable.
My database version is 11.1
CREATE OR REPLACE PACKAGE BODY pr_retrieve_data as
PROCEDURE FETCH_MYTABLE_DETAILS() is
TYPE ACCNT_NUMBER_TYPE IS TABLE OF MYTABLE.MYCOLUMN%TYPE;
L_ACCNT_NUMBER ACCNT_NUMBER_TYPE;
L_ACCNT_NUMBER.EXTEND(3);
L_ACCNT_NUMBER(1) := 1;
L_ACCNT_NUMBER(2) := 2;
L_ACCNT_NUMBER(3) := 3;
FOR indx in (select column1,
column2
from SOMEOTHERTABLE SOT
WHERE SOT.ACCNT_NUMBER IN (SELECT * FROM TABLE(L_ACCNT_NUMBER))) --The code fails here with PLS-00642 error.
LOOP
...
END LOOP;
end FETCH_MYTABLE_DETAILS;
end pr_retrieve_data;
How can I fetch data from SOMEOTHERTABLE with multiple values in a variable and with where clause on this variable?
Create the type as schema object. However, you cannot inherit data type from table (AS TABLE OF MYTABLE.MYCOLUMN%TYPE):
CREATE OR REPLACE TYPE ACCNT_NUMBER_TYPE AS TABLE OF NUMBER;
Note, in newer Oracle versions you can use local collection types also in SQL. Feature was introduced in version 12.1.
In your particular case you can also use
...
WHERE SOT.ACCNT_NUMBER MEMBER OF L_ACCNT_NUMBER

How to get the objects from an oracle table of row objects

When dealing with an oracle table containing row objects I would expect that each row is an object and I can invoke functions on it or pass it to functions in any context.
As an example if I declare the following:
create type scd_type as object
(
valid_from date,
valid_to date,
member function get_new_valid_to return date
);
create type scd_type_table as table of scd_type;
create table scd_table of scd_type;
create procedure scd_proc (in_table in scd_type_table)
as
begin
... do stuff ...
end;
/
And now I try to call my proc with the table
begin
scd_proc (scd_table);
end;
/
I get an error. Even reading the rows into a nested table is not straight forward. I would expect it to work like this:
declare
temp_table scd_type_table;
begin
select * bulk collect into temp_table from scd_table;
... do stuff ...
end;
/
but instead I have to call the constructor for every line.
And last I cannot invoke functions in a merge statement even though it works in an update statement. Example:
update scd_table st
set st.valid_to = st.get_new_valid_to(); <--- Works.
merge into scd_table st
using (select sysdate as dateCol from dual) M
on (st.valid_from = M.dateCol)
when matched then update set st.valid_to = st.get_new_valid_to(); <--- Does not work.
So I guess there are three sub-questions here:
1) What is the easiest way to pass a table of row objects into a procedure expecting a nested table of the same type?
2) What is the easiest way to convert a table of row objects into a nested table of the same type?
3) Why can't I invoke functions on an object as part of a merge statement (but in an update statement)?
which all come down to the question of "How to extract objects from a table of row objects?".
I can't help but think you need to re-read the documentation on PL/SQL types.
You were close with the bulk collect code. Minor change given below:
declare
plsql_table scd_type_table;
begin
select VALUE(t) bulk collect into plsql_table from scd_table t;
-- do stuff
end;
/
I will admit, I have no idea why the merge fails but the update works.