How to use one sql parameter to represent input array - sql

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;
/

Related

how to return data from the query select * from table in oracle, without using cursor , with an out parameter

how to return data from the query select * from table in oracle, without using cursor , with an out parameter
You have the following options.
Create and store a TYPE OBJECT EXAMPLE_REC with same structure as that of the table and compatible datatypes.
for eg:
CREATE OR REPLACE TYPE EXAMPLE_REC AS OBJECT (
col1 datatype,
col2 datatype,
col3 datatype
);
You then don't need another local record variable.But, you need to initialize using NEW keyword as shown. You can then simply fetch the values into corresponding record elements.
CREATE OR REPLACE PROCEDURE example_sp(
p_example_rec OUT example_rec
)
IS
BEGIN
p_example_rec := NEW example_rec(NULL,NULL,NULL); --Initialization
SELECT
*
INTO
p_example_rec.col1,p_example_rec.col2,p_example_rec.col3
FROM
table;
END;

PL/SQL Oracle - Selecting from internal table

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);

Oracle SQL Create function or procedure returning a table

In SQL Server, I can just use 'RETURNS TABLE' and it will do the job. But I can't find how to do the same in Oracle SQL
I have the following SELECT statement that needs to be put in a function or procedure:
SELECT a.CodAcord, a.Descr
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.codSoce = codSoce;
codSoce is an INTEGER IN parameter, and I need to return a.CodAcord and a.Descr as result from my function/procedure.
Is there a simple way to do this? Without having to deal with temp variables and/or advanced content...
EDIT: Aditional info:
- I need to return a.CodAcord and a.Descr, but when I did some research to know how to return more than one variable using SQL Functions or Procedures, all I could find was that this was only possible by returning a TABLE. If there's a way to return more than one item from a Function or Procedure, please let me know.
- The use of Functions or Procedures is strictly required.
- I'm using SQL Developer.
You can achieve a table as a return value from function by using a pipelined table function. Please see the example below:
-- Create synthetic case
CREATE TABLE Acord AS
SELECT rownum CodAcord, 'Description ' || rownum Descr
FROM dual CONNECT BY LEVEL <= 5;
CREATE TABLE FreqSoce AS
SELECT rownum CodSoce, rownum CodAcord
FROM dual CONNECT BY LEVEL <= 10;
-- Test dataset
SELECT a.CodAcord, a.Descr
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.CodSoce = 10;
-- Here begins actual code
-- Create an object type to hold each table row
CREATE OR REPLACE TYPE typ_acord AS OBJECT(
CodAcord NUMBER,
Descr VARCHAR2(40)
);
/
-- Create a collection type to hold all result set rows
CREATE OR REPLACE TYPE tab_acord AS TABLE OF typ_acord;
/
-- Our function that returns a table
CREATE OR REPLACE FUNCTION getAcord(pCodSoce IN NUMBER)
RETURN tab_acord PIPELINED
AS
BEGIN
FOR x IN (SELECT a.CodAcord, a.Descr
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.CodSoce = pCodSoce)
LOOP
PIPE ROW (typ_acord(x.CodAcord, x.Descr));
END LOOP;
END;
/
-- Testing the function (please note the TABLE operator)
SELECT * FROM TABLE(getAcord(5));
Take the following as a code template:
CREATE OR REPLACE PACKAGE tacord AS
TYPE ttabAcord IS TABLE OF ACord%ROWTYPE;
END tacord;
/
show err
CREATE OR REPLACE PACKAGE BODY tacord AS
BEGIN
NULL;
END tacord;
/
show err
CREATE OR REPLACE FUNCTION demo RETURN tacord.ttabAcord AS
to_return tacord.ttabAcord;
BEGIN
SELECT a.*
BULK COLLECT INTO to_return
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.codSoce = codSoce
;
RETURN to_return;
END demo;
/
show err
Key points:
%ROWTYPE represents the datatype of a database table's record
BULK COLLECT INTO inserts a complete result set into a plsql data structure
Iteration over the table contents is availabel by the plsql collection methods, namely .FIRST, .LAST, .NEXT.

Where column in <function returns a collection > in oracle

I want to achieve something like this:
Example:
Table Customers has columns customer_no, name , age.
some_package package has the following types defined in its spec:
type cust_type is record (custs Customers.customer_no);
type rec_type is table of cust_type index by binary_integer;
function some_function return rec_type;
I am trying to create a view the goes like this:
select ....
from customers c, tablex, tabley
where c.customer_no in some_function() and
... <<other clauses>>
I cannot avoid using some_function() as the logic uses dynamic SQL statements.
I get invalid data type error when i try to compile the view
Is it possible to achieve this in Oracle sql? I don't want to use another function and loops to do this.
Thanks.
No, type rec_type is declared in a package and can be used in PL/SQL blocks but cannot be used in SQL statements.
If you want to use a type in SQL statements then you will need to declare it using a CREATE TYPE statement like this:
CREATE TYPE customers_tab IS TABLE OF NUMBER;
or you can use an existing type like SYS.ODCINUMBERLIST.
Then change the package to:
CREATE OR REPLACE PACKAGE some_package
AS
FUNCTION some_function RETURN SYS.ODCINUMBERLIST;
END;
/
CREATE OR REPLACE PACKAGE BODY some_package
AS
FUNCTION some_function RETURN SYS.ODCINUMBERLIST
AS
t_customers SYS.ODCINUMBERLIST;
BEGIN
SELECT customer_no
BULK COLLECT INTO t_customers
FROM customers
WHERE MOD( customer_no, 3 ) = 0; -- or whatever your query is.
RETURN t_customers;
END;
END;
/
Then you can do:
SELECT *
FROM Customers c
INNER JOIN
TABLE( some_package.some_function() ) t
ON ( c.customer_no = t.COLUMN_VALUE );

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.