Oracle PL/SQL function to return rows or rowids - oracle

I want to write a function that returns table records that I can show in my app's grid. My app shall be oblivious to the table's structure. I want it to be still working when someone adds columns to the table or removes them.
In my app it shall look something like this:
myQuery = "select * from table(myfunction)";
...
The function should hence be something like:
CREATE OR REPLACE FUNCTION myfunction RETURN TABLE OF mytable%ROWTYPE AS ...
But RETURN TABLE OF mytable%ROWTYPE is not allowed. One has to create an SQL type.
But CREATE TYPE table_of_mytable_rows IS TABLE OF mytable%ROWTYPE is not allowed either.
So, is there a way to achieve exactly what I want?
As an alternative I thought of working with IDs. This would make my app's code look something like this:
myQuery = "select * from mytable where rowid in (select * from table(myfunction))";
...
And the function would then be
CREATE OR REPLACE FUNCTION myfunction RETURN TABLE OF UROWID AS ...
But again, RETURN TABLE OF UROWID is not allowed. Neither is creating the SQL table type with CREATE TYPE table_of_rowids IS TABLE OF UROWID.
I know I could create an object type resembling the table's primary key columns and then create a table type on this object. Then my app would have to know the table's primary key in order to
myQuery = "select * from mytable where (key1, key2) in (select key1, key2 from table(myfunction))";
...
I would much prefer my function to return table rows or rowids. Is this possible?
I'm currently still working on Oracle 11.2, but I would also be interested in solutions for newer versions.

Related

Can a type table of table%rowtype be indexed by some field of table%rowtype?

Why to I asked this question?
I have a table which as key that have a lot of field. Every time, I'm making a jointure, I miss a field. Therefore I have defined a pipelined function that take the key as an argument so that I am sure that I get only one element when I'm doing a jointure.
But the query take more time now. The table a has an index on some fields but not the table type used by pipelined function. I would like to know if it is possible to created a index on some fields of the table%rowtype
code:
create table a ( a1 integer);
create package p_a
as
type t_a iS TABLE of a%ROWTYPE;
function f(i_a1 integer) return t_a pipelined;
end;
CREATE PACKAGE BODY p_a
AS
CURSOR c_A (i_a1 INTEGER)
RETURN a%ROWTYPE
IS
SELECT t.*
FROM a t
WHERE t.a1 = i_a1;
FUNCTION f (i_a1 INTEGER)
RETURN t_a
PIPELINED
IS
BEGIN
FOR c IN c_a (i_a1)
LOOP
PIPE ROW (c);
END LOOP;
END;
END;
with b as( select 1 b1 from dual) select * from b cross apply (table(p_a.f(b.b1)));
the question
I've tried to index the type table by a field of a table like this
create table a ( a1 integer);
create package p_a2
as
type t_a iS TABLE of a%ROWTYPE index by a.a1%type;
function f(i_a1 integer) return t_a pipelined;
end;
PLS-00315: Implementation restriction: unsupported table index type
Is what I want to do possible. If not how to solve the performance problems mentioned in the introduction?
code
A TYPE is NOT a table and cannot be indexed.
When you do:
create package p_a
as
type t_a iS TABLE of a%ROWTYPE;
end;
/
You are defining a type and the type is a collection data type; an instance of that type is NOT a physical table and but is more like an in-memory array.
When you create a PIPELINED function:
function f(i_a1 integer) return t_a pipelined;
It does NOT return a table; it returns the collection data type.
When you do:
type t_a iS TABLE of a%ROWTYPE index by a.a1%type;
You are NOT creating an index on a table; you are changing to a different collection data type that is an associative array (like a JavaScript object or a Python dictionary) that stores key-value pairs.
An associative array is a PL/SQL data type and (with limited exceptions in later versions for insert, update and delete statements) cannot be used in SQL statements.
When you do:
SELECT * FROM TABLE(SYS.ODCIVARCHAR2LIST('a', 'b', 'c'));
or:
SELECT * FROM TABLE(p_a.f(1));
Then you are passing a collection data type to an SQL statement and the table collection expression TABLE() is treating the collection expression as if it was a table. It is still NOT a table.
If you want to use an index on the table then use the table (without a cursor or a pipeline function):
WITH b (b1) AS (
SELECT 1 FROM DUAL
)
SELECT *
FROM b
CROSS APPLY (
SELECT a.*
FROM a
WHERE a.a1 = b.b1;
);
I think the first line of your question says it all: "key that have a lot of field". If I understand correctly, the table has a primary key that consists of a large number of columns and because of that writing queries becomes a challenge.
It sounds like you're trying to do something pretty complex that should not be an issue at all.
Take a step back and ask yourself - does this need to be the primary key of the table ? Or can you use a surrogate key (identity column, sequence), use that as the primary key and just create a unique index on the set of field that currently make up the primary key. It will (1) simplify your data model and (2) make writing the queries a lot easier.

How to do the following query in Oracle NoSQL

I am planning to use NoSQL Cloud Service as our datastore. I have question about the MAP data type. Say I have a column “labels” ( labels MAP(RECORD(value STRING, contentType STRING)) in table “myTable”, which the “labels” column is MAP datatype and the value is RECORD data type .
I want to query the table which return all the rows that the key of the “labels” = particular value, what is the sql statement looks like? I tried:
select * from myTable where labels.keys($key=‘xxxx’)
which doesn’t work.
do we need to add the index for the label field in the MAP? any performance improvement? If yes, how to add this index?
Thanks
Please try the following syntax
select * from myTable t
where t.labels.keys() =any "xxx"
Your syntax is good if you add exists
select * from myTable t
where exists t.labels.keys($key= “xxx”)
Concerning your question about performance
there will be significant performance improvement.
If you want to index only the field names (keys) of the map,
you create the index like this:
create index idx_keys on myTable(labels.keys())
If you want to index both they keys and the associated values:
create index idx_keys_values
on myTable(labels.keys(), labels.values())

How alter column type with using toUInt32OrZero function in clickhouse?

I have String column in clickhouse table.
I try alter table with modify type to UInt32:
ALTER TABLE main.abonents
MODIFY COLUMN
device_type UInt32 DEFAULT 0
but have error:
Received exception from server:
Code: 6. DB::Exception: Received from 5.200.55.122:9000. DB::Exception: Cannot parse string 'mo' as UInt32: syntax error at begin of string. Note: there are toUInt32OrZero function, which returns zero instead of throwing exception..
It's clear, clickhouse use toUint32 function on string like 'mobile' and throw exception. And its advice to use function toUInt32OrZero to convert type.
How can i use toUInt32OrZero function with ALTER TABLE??
There's no such way (as far as I know).
You can achieve it with a second table. Let's create one:
CREATE TABLE main.abonents_new AS main.abonents;
Then we have to alter column in that new table.
This table has no data yet, so it won't raise exceptions:
ALTER TABLE main.abonents_new MODIFY COLUMN device_type UInt32 DEFAULT 0;
Then, make sure no new data is written to main.abonents. We'd like to keep everything in place when we'll transfer the data to the new table.
Insert the data using INSERT INTO SELECT query. Make sure to list all the fields with the same order; wrap device_type to the converter function (toUInt32OrZero) :
INSERT INTO main.abonents_new SELECT field1, field2, ..., toUInt32OrZero(device_type) AS device_type, ..., fieldN FROM main.abonents;
Then, make sure that everything's alright (that rows count is the same, device_type was converted as intended, etc), then rename the tables:
RENAME TABLE main.abonents TO main.abonents_old;
RENAME TABLE main.abonents_new TO main.abonents;
(or, you may DROP the older table instead; although I'd keep the old data to be able to restore if things'd go south)

Create Type based on an exiting Table

As the title said : I want to create a type in oracle based on an existing Table.
I did as follow :
create or replace type MY_NEW_TYPE as object( one_row EXISTING_TABLE%rowtype);
The Aim is to be able to use this into a function which will return a table containing sample row of the table EXISTING_TABLE :
create or replace function OUTPUT_FCT() return MY_NEW_TYPE AS
...
If you only need to create a function that returns a row from your table, you could try something like the following, without creating types.
setup:
create table EXISTING_TABLE( a number, b varchar2(100));
insert into EXISTING_TABLE values (1, 'one');
function:
create or replace function OUTPUT_FCT return EXISTING_TABLE%rowtype AS
retVal EXISTING_TABLE%rowType;
begin
select *
into retVal
from EXISTING_TABLE
where rownum = 1;
--
return retVal;
end;
function call
SQL> begin
2 dbms_output.put_line(OUTPUT_FCT().a);
3 dbms_output.put_line(OUTPUT_FCT().b);
4 end;
5 /
1
one
However, I would not recommend such an approach, because things like select * can be really dangerous; I would much prefer defining a type with the fields I need, and then explicitly query my table for the needed columns.
No, you can't do that, you'll get a compilation error:
create or replace type my_new_type as object(one_row t42%rowtype);
/
Type MY_NEW_TYPE compiled
Errors: check compiler log
show errors
Errors for TYPE STACKOVERFLOW.MY_NEW_TYPE:
LINE/COL ERROR
-------- -----------------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
1/36 PLS-00329: schema-level type has illegal reference to MYSCHEMA.T42
You will need to specify each field in the object type, and you will have to specify the data types manually too - you can't use table.column%type either.
You could create the type dynamically based on column and data type information from the data dictionary, but as this will (hopefully) be a one-off task and not something you'd do at runtime, that doesn't really seem worth it.
You can create a PL/SQL table type based on your table's rowtype, but you would only be able to call a function returning that from PL/SQL, not from plain SQL - so you couldn't use it in a table collection expression for example. If you were only returning a single sample row you could return a record rather than a table, but the same applies. You can also have a function that returns a ref cursor which could match the table's structure, but you wouldn't be able to treat that as a table either.
Read more about object type creation in the documentation. Specifically the attribute and datatype sections.

Inserting data from table (SELECT) with REF - Oracle Database

I'm trying to insert all data from Pacient table to Pacient_OR table (Object-Relational). Is there a simple way to do that (one script), if Pacient table has column with Pojistovna_ID (foreign key) and in Pacient_OR table there is REF to the Pojistovna_OR. Both Pojistovna and Pojistovna_OR are populated with the same data, but one is relational, second is based on object type.
I tried this (and more ofc):
INSERT INTO pacient_or
(pacient_or.id,
pacient_or.jmeno,
pacient_or.prijmeni,
pacient_or.datum_narozeni,
pacient_or.rodne_cislo,
pacient_or.telefon,
pacient_or.krevni_skupina,
pacient_or.rodinna_anamneza,
pacient_or.adresa,
pacient_or.pojistovna)
SELECT pacient.id,
pacient.jmeno,
pacient.prijmeni,
pacient.datum_narozeni,
pacient.rodne_cislo,
pacient.telefon,
pacient.krevni_skupina,
pacient.rodinna_anamneza,
Adresa_typ(pacient.ulice, pacient.mesto, pacient.psc),
(SELECT Ref(poj)
FROM pacient pac,
pojistovna_or poj
WHERE pac.pojistovna_id = poj.id)
FROM pacient;
This code throws error:
single-row subquery returns more than one row
Do not use pacient pac in the subquery. Link it to the pacient in your main from-clause. And even better do not use a subquery for this.

Resources