Oracle SQL Function using data from another schema says table or view does not exist - sql

Im trying to create a function which takes in 3 parameters and returns one. The value of the returned parameter is obtained by querying two tables, however, one of the tables is on a different schema. The SQL im using is below:
create or replace FUNCTION tester
(
originaltext IN VARCHAR2, lang IN VARCHAR2, category IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2 AS
translatedttextval VARCHAR2(255) := '';
BEGIN
--if category is null, disregard category
if category is null then
SELECT distinct nvl(trans.translatedtext, originaltext)
INTO translatedttext
FROM tbl_translations trans, SECDEVSCHEMA.tbl_instanceids ids
WHERE trans.translatedlang = ids.id_a
AND ids.name = lang
AND trans.originaltext = originaltext;
end if;
RETURN translatedttextval;
END;
I've removed a bit of the query here which searches with category because it does something similar and has the same issue.
So, when I run this and pass in the params, I get an error which reads:
Error(16,46): PL/SQL: ORA-00942: table or view does not exist
If I do the following query it works fine when not on the SECDEVSCHEMA and returns the reults from tbl_instanceids which is on the SECDEVSCHEMA schema:
SELECT * FROM SECDEVSCHEMA.tbl_instanceids ids WHERE ids.id_a = 1234555;
I don't have DBA access to the DB but if I need some select priviledge granted or something I can get it done. Not sure if this the case though as the above query works.
Small additional question also:
where it says in the query
nvl(trans.translatedtext, originaltext)
If i wanted to surround the original text value with brackets when no translatedtext value exists, how would I go about this?
I'm using SQL Developer by the way in case that's important.
Thanks

for your smaller question
nvl(trans.translatedtext, '<'|| originaltext ||'>' )

This was down to a priviledges issue.
In SQL Developer, I expanded the tables package and selected the table I needed access to in the Function, right clicked and selected Priviledges > Grant.
As this is just a Development DB I just assigned all the priviledges to the appropriate schema or User and its working now.
Still unsure about this part though:
Small additional question also: where it says in the query nvl(trans.translatedtext, originaltext)
If i wanted to surround the original text value with brackets when no translatedtext value exists, how would >I go about this?
Any takers for this?
Thanks

Related

Postgres ATOMIC stored procedure INSERT INTO . . . SELECT with one parameter and one set of rows from a table

I am trying to write a stored procedure to let a dev assign new user identities to a specified group when they don't already have one (i.e. insert a parameter and the output of a select statement into a joining table) without hand-writing every pair of foreign keys as values to do so. I know how I'd do it in T-SQL/SQL Server but I'm working with a preexisting/unfamiliar Postgres database. I would strongly prefer to keep my stored procedures as LANGUAGE SQL/BEGIN ATOMIC and this + online examples being simplified and/or using constants has made it difficult for me to get my bearings.
Apologies in advance for length, this is me trying to articulate why I do not believe this question is a duplicate based on what I've been able to find searching on my own but I may have overcorrected.
Schema (abstracted from the most identifying parts; these are not the original table names and I am not in a position to change what anything is called; I am also leaving out indexing for simplicity's sake) is like:
create table IF NOT EXISTS user_identities (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL,
[more columns not relevant to this query)
)
create table IF NOT EXISTS user_groups (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL,
name TEXT NOT NULL
)
create table IF NOT EXISTS group_identities (
user_id BIGINT REFERENCES user_identities(id) ON DELETE RESTRICT NOT NULL,
group_id BIGINT REFERENCES user_groups(id) ON DELETE RESTRICT NOT NULL
)
Expected dev behavior:
Add all predetermined identities intended to belong to a group in a single batch
Add identifying information for the new group (it is going to take a lot of convincing to bring the people involved around to using nested stored procedures for this if I ever can)
Bring the joining table up to date accordingly (what I've been asked to streamline).
If this were SQL Server I would do (error handling omitted for time and putting aside whether EXCEPT or NOT IN would be best for now, please)
create OR alter proc add_identities_to_group
#group_name varchar(50) NULL
as BEGIN
declare #use_group_id int
if #group_name is NULL
set #use_group_id = (select Top 1 id from user_groups where id not in (select group_id from group_identities) order by id asc)
ELSE set #use_group_id = (select id from user_groups where name = #group_name)
insert into group_identities (user_id, group_id)
select #use_group_id, id from user_identities
where id not in (select user_id from group_identities)
END
GO
Obviously this is not going to fly in Postgres; part of why I want to stick with atomic stored procedures is staying in "neutral" SQL, both to be closer to my comfort zone and because I don't know what other languages the database is currently set up for, but my existing education has played kind of fast and loose with differentiating what was T-SQL specific at any point.
I am aware that this is not going to run for a wide variety of reasons because I'm still trying to internalize the syntax, but the bad/conceptual draft I have written so that I have anything to stare at is:
create OR replace procedure add_identities_to_groups(
group_name text default NULL ) language SQL
BEGIN ATOMIC
declare use_group_id integer
if group_name is NULL
set use_group_id = (select Top 1 id from user_groups
where id not in (select user_id from group_identities)
order by id asc)
ELSE set use_group_id = (select id from user_groups where name = group_name) ;
insert into group_identities (group_id, user_id)
select use_group_id, id from user_identities
where id not in (select user_id from group_identities)
END ;
GO ;
Issues:
Have not found either answers for how to do this with the combination of a single variable and a column with BEGIN ATOMIC or hard confirmation that it wouldn't work (e.g. can atomic stored procedures just not accept parameters? I cannot find an answer to this on my own). (This is part of why existing answers that I can find here and elsewhere haven't been clarifying for me.)
~~Don't know how to compensate for Postgres's not differentiating variables and parameters from column names at all. (This is why examples using a hardcoded constant haven't helped, and they make up virtually all of what I can find off StackOverflow itself.)~~ Not a problem if Postgres will handle that intelligently within the atomic block but that's one of the things I hadn't been able to confirm on my own.
Google results for "vanilla" SQL unpredictably saturated with SQL Server anyway, while my lack of familiarity with Postgres is not doing me any favors but I don't know anyone personally who has more experience than I do.
because I don't know what other languages the database is currently set up for
All supported Postgres versions always include PL/pgSQL.
If you want to use procedural elements like variables or conditional statements like IF you need PL/pgSQL. So your procedure has to be defined with language plpgsql - that removes the possibility to use the ANSI standard BEGIN ATOMIC syntax.
Don't know how to compensate for Postgres's not differentiating variables and parameters from column names at all.
You don't. Most people simply using naming conventions to do that. In my environment we use p_ for parameters and l_ for "local" variables. Use whatever you prefer.
Quote from the manual
By default, PL/pgSQL will report an error if a name in an SQL statement could refer to either a variable or a table column. You can fix such a problem by renaming the variable or column, or by qualifying the ambiguous reference, or by telling PL/pgSQL which interpretation to prefer.
The simplest solution is to rename the variable or column. A common coding rule is to use a different naming convention for PL/pgSQL variables than you use for column names. For example, if you consistently name function variables v_something while none of your column names start with v_, no conflicts will occur.
As documented in the manual the body for a procedure written in PL/pgSQL (or any other language that is not SQL) must be provided as a string. This is typically done using dollar quoting to make writing the source easier.
As documented in the manual, if you want to store the result of a single row query in a variable, use select ... into from ....
As documented in the manual an IF statement needs a THEN
As documented in the manual there is no TOP clause in Postgres (or standard SQL). Use limit or the standard compliant fetch first 1 rows only instead.
To avoid a clash between names of variables and column names, most people use some kind of prefix for parameters and variables. This also helps to identify them in the code.
In Postgres it's usually faster to use NOT EXISTS instead of NOT IN.
In Postgres statements are terminated with ;. GO isn't a SQL command in SQL Server either - it's a client side thing supported by SSMS. To my knowledge, there is no SQL tool that works with Postgres that supports the GO "batch terminator" the same way SSMS does.
So a direct translation of your T-SQL code to PL/pgSQL could look like this:
create or replace procedure add_identities_to_groups(p_group_name text default NULL)
language plpgsql
as
$$ --<< start of PL/pgSQL code
declare --<< start a block for all variables
l_use_group_id integer;
begin --<< start the actual code
if p_group_name is NULL THEN --<< then required
select id
into l_use_group_id
from user_groups ug
where not exists (select * from group_identities gi where gi.id = ug.user_id)
order by ug.id asc
limit 1;
ELSE
select id
into l_use_group_id
from user_groups
where name = p_group_name;
end if;
insert into group_identities (group_id, user_id)
select l_use_group_id, id
from user_identities ui
where not exists (select * from group_identities gi where gi.user_id = ui.id);
END;
$$
;

Is it possibile to write a query that returns multiple fields from an object obtained by calling a function in Oracle?

I would like to know if it is possible to write a query that, given an object obtained by calling a function, lists many of that object's fields.
For example if I have this schema:
CREATE OR REPLACE TYPE T1 as OBJECT (
col1 varchar2(10),
col2 varchar2(10),
col3 varchar2(10),
CONSTRUCTOR FUNCTION T1(input varchar ) RETURN SELF AS RESULT
)
CREATE OR REPLACE TYPE BODY T1 IS
CONSTRUCTOR FUNCTION T1(input varchar ) RETURN SELF AS RESULT IS
random_String varchar(5);
BEGIN
random_String:=dbms_random.string(opt => '',len=>'5');
self.col1:='1'||input||random_String;
self.col2:='2'||input||random_String;
self.col3:='3'||input||random_String;
RETURN;
end;
end;
create or replace function GET_T1(input varchar) return T1 is
object_created T1;
BEGIN
RETURN T1(input);
end;
If I run this query:
select * from (
select
get_t1('1').col1 c1,get_t1('1').col2 c2,get_t1('1').col3
from dual
)
I see that get_t1 is called three times, while I would like to write a query that "intuitively" would work like this one below:
select obj.col1,obj.col2,obj.col3 from (
select get_t1('1') obj from dual
)
which unfortunately doesn't parse, and gives ORA 00904 - invalid identifier (Oracle doesn't recognize obj.colx as meaningful).
Please note that I don't want to find a workaround to this specific situation, nor do I want to create other types or modify the function, I would like to know if what I want is possible at all and, if so, how.
Please note also that I would like the function that returns the object to be executed exactly once.
You need to supply a table alias, and then use that when referring to the object, to satisfy the object name resolution rules:
select t.obj.col1,t.obj.col2,t.obj.col3
from (
select get_t1('1') obj
from dual
where rownum = 1
) t;
OBJ.COL1 OBJ.COL2 OBJ.COL3
---------- ---------- ----------
11IQGCV 21IQGCV 31IQGCV
Without that table alias, it doesn't know what obj is referring to. There is no table or alias called obj, so it looks for other objects/schemas/synonyms etc called obj, rather than recognising it as a column from the inline view.
This is actually covered in the object-relational developer's guide, under name resolution:
To avoid inner capture and similar problems resolving references, Oracle Database requires you to use a table alias to qualify any dot-notational reference to subprograms or attributes of objects.
I've also add where rownum = 1 to the inline view, which seems to stop it calling the function multiple times. I'm not sure quite why; I'd wondered if was something to do with fast dual but you need it with a real single-row table too. Using a CTE and/or the undocumented materialize hint doesn't stop the multiple calls either; not does making the function deterministic (which may not be appropriate anyway). This is only thing I've found so far that does - though you found that the RESULT_CACHE hint also works. That's probably worthy of its own question though.

Stored PL/SQL function

I'm trying to implement a stored function in PL/SQL that finds the total number of cities visited by a given driver, return the driving license number.
I tried something like this, but i keep getting "Function created with compilation errors.". The thing is, oracle 11g doesn't show where is the error at?
Here is what i got.
create or replace function driverL return number
as licence L#%TYPE;
begin
select L#, count(T#)
from driver d
join trip t ON d.L# = t.L#
join leg l ON l.t# = l.t#;
return licence;
end;
/
How do i consider multiple visits to the same city as one visit?
If you are using SQL*Plus you can use the statement show errors after the create statement to show the compilation errors for the most recently created or altered stored procedure/function/package.
In your code I see at least two problems:
You are missing an INTO clause in your select
The column L# in your SELECT clause is ambiguously defined since two tables have a column with that name

Getting past an ORA-00904 error with multiple database query

I have some code that runs the same query in 2 different databases.
SELECT
P.MYID,
CASE WHEN
SUBSTR(P.MYID, 1, 1) NOT IN ('W') THEN
'YOUR_ID_IS_NOT_START_WITH_W'
ELSE
(SELECT OTHER_ID FROM PERSON WHERE NUMBER = '2554' )
END AS "ALTERNATE_ID"
FROM
PERS_INFO P
WHERE
P.NUMBER = '2554'
OTHER_ID in this example is a column that only exists in the 2nd database. Thats completely fine because the query will only execute in the 1st database when the id DOES NOT start with 'W'. In otherwords, this query will only ever run in the 1st database, when the MYID does not start with 'W' and will only ever run in the 2nd database when MYID does start with 'W'.
So the query would work in both databases, however, the query fails with an ORA-00904 in the first database because it says OTHER_ID is not legal in the first database (which is true, but i dont care). how do i force oracle to run the query anyways or work around this?
You could create a function in both databases to get the OTHER_ID value. It would just return null in your first database.
For example, in the first database:
create or replace function f_get_other_id(for_no in varchar2) return varchar2 is
begin
return null;
end;
In the second database:
create or replace function f_get_other_id(for_no in varchar2) return varchar2 is
v_other_id varchar2(100);
begin
select other_id into v_other_id from person where number = for_no;
return other_id;
end;
Then, your query can be:
select p.myid,
case
when substr(p.myid, 1, 1) not in ('W') then 'YOUR_ID_IS_NOT_START_WITH_W'
else f_get_other_id('2554')
end as "ALTERNATE_ID"
from pers_info p
where p.number = '2554'
Have you heard about the EXECUTE_IMMEDIATE (http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/executeimmediate_statement.htm) command or about DBMS_SQL (see http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_sql.htm)?
If you are working with scripts on various databases that have different tables definition, this might be the solution for you, though this requires PL/SQL.
I'm not sure what the problem would be with adding this column to the database that it is currently absent from, but adding it seems like a pretty low effort and low risk exercise, and would permanently solve this kind of problem.
Probably you need to add another CASE after else checking the database name. Case db_name = db_name1 then your other_id query else some other query lk select 1 from dual... You can get db_name from v$database view.

Using a function for the table name

I have a set of 4 tables that contain different partitions of some data. I also have a plpgsql function that will take an id and return the name of the table containing that data as a 'character varying'.
However, when I try to use this function to select from the correct table, e.g.
SELECT f.a FROM getTable(someID) AS f;
it doesn't seem to work. It doesn't throw an error on the SELECT, but it doesn't return the fields I'm expecting either (i.e. it says f.a doesn't exist).
How do I select data from a table where the table name is given by the return of a function?
I'm using PostgreSQL 9.1. This is going to be run over millions of records, so I don't want to have to do it as two separate calls.
I think for the problem described in the topic you should use inheritance with tables.
Answering the question - use execute:
execute "SELECT f.a FROM " || getTable(someID) || " AS f";