Oracle sql : expression is of wrong type - plsql

I'm trying to write a PL/SQL to convert comma separated string into an array and iterate through that.
for that I created a datatype as follows:
"CODE_TABLE_TYPE" AS TABLE OF VARCHAR2(500)
crated a function - STR_TO_CODE_TABLE to convert the comma separated string in to the table of CODE_TABLE_TYPE.
and the PL/SQL looks like this:
FOR DEP IN ( SELECT * FROM TABLE ( CAST( STR_TO_CODE_TABLE( IN_DES_AIRPORTS ) AS CODE_TABLE_TYPE ) ) ) LOOP
SELECT * INTO RESULTS FROM MY_TABLE
WHERE IN_ID = MY_TABLE.ID
AND ( SELECT 1 FROM TEMP_TABLE WHERE DEPARTURE LIKE '%' || DEP || '%' )= 1;
END LOOP;
But it gives an error saying "expression is of wrong type". But the datatype is varchar2.
Can anyone please suggest what's the possible cause for this. What should I do to avoid this issue?

Related

Create a table as, where 'date condition' in dynamic PL/SQL

I got assigned the following task.
Assume we have a table A structured with an id column and a date column.
Write a procedure in PL/SQL that: takes as parameters the table name (in our case A) and a date D, creates a backup table named A_bck containing only the records of A with dates < D and removes from the table A all the records inserted in A_bck.
Here there is my code.
Unluckily I get this error:
Error report -
ORA-00904: "MAY": invalid identifier
ORA-06512: at line 41
ORA-06512: at line 80
00904. 00000 - "%s: invalid identifier"
If I try to achieve the same result using a where condition on the id column instead that on the date one, I have no problems.
Where is the mistake? Am I implementing it completely in the wrong way?
The problem you have is that as you're executing dynamic sql you're query is built up as a string. Oracle does not know that the date you've given is actually a date, it is simply being treated as part of the string. To solve this you should be able to do the following:
my_query := 'CREATE TABLE ' || table_name_backup || ' AS (SELECT * FROM ' || table_name || ' WHERE table_date < to_date(''' || backup_date || '''))';
This should sort out your issue for you. As a side note, you will probably want to change your "table_exists" query, as table names are all stored in upper case, e.g.
SELECT COUNT(*) INTO table_exists FROM USER_TABLES WHERE TABLE_NAME = upper(my_table);
Edit: Further explanation following comment
To explain why you don't have the above problem when using integers, it is important to remember that using execute immediate simply executes the given string as an SQL query.
For example:
declare
x INTEGER := 1;
i integer;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where 1 = ' || x;
EXECUTE IMMEDIATE my_query INTO i;
end;
my_query in the above example would be executed as:
select 1 from dual where 1 = 1
which is perfectly valid sql. In your example however, you were ending up with something like this:
CREATE TABLE abaco_bck AS (SELECT * FROM abaco WHERE table_date < 27-MAY-17)
As it isn't wrapped in quotes, or explicitly converted to a date, the SQL engine is trying to subtract "MAY" from 27, but it doesn't know what "MAY" is.
One other thing to mention, is that for some operations you could use bind variables instead of quotes (although you can't for DDL) e.g.
declare
lToday DATE := SYSDATE;
i INTEGER;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where sysdate = :1';
EXECUTE IMMEDIATE my_query INTO i USING lToday;
end;

Inserting into table using data from two other tables, using PL/SQL on SQLDeveloper

I'm writing a PL/SQL procedure, and I need to insert into a Table, based on an equality of two columns from two differents tables.
Here is my code:
create or replace PROCEDURE insertSomething
IS
BEGIN
INSERT INTO MYDBP ( ZIP )
SELECT POSTCODE
FROM ZIPDBP
WHERE ZIPDBP.ZIP = OTHERDBP.ZIP;
COMMIT;
END;
I'm getting an error saying OTHERDBP.ZIP is an invalid identifier. What is the issue?
EDIT:
To get the output I expected I need another equality statement between two of the tables ID, but again I'm getting invalid identifier again, this time for DBP_CLIENTS.ID. Here is the code
INSERT INTO DBP_CLIENTS ( POSTCODE )
SELECT POSTCODE
FROM DBP_POSTCODE, HELENS_DATA
WHERE DBP_POSTCODE.LOCALITY = HELENS_DATA.SUBURB
AND DBP_POSTCODE.STATE = 'NSW'
AND DBP_CLIENTS.ID = HELENS_DATA.ID;
COMMIT;
Try this:
create or replace PROCEDURE insertSomething
IS
BEGIN
INSERT INTO MYDBP ( ZIP )
SELECT POSTCODE
FROM ZIPDBP, OTHERDBP
WHERE ZIPDBP.ZIP = OTHERDBP.ZIP;
COMMIT;
END;
You have to add otherdbp to from section. And you don't need to use () in procedure declaration.
Moreover, insert is reserverd word in pl/sql, so procedure must have different name
You have to add DBP_CLIENTS in the FROM clause:
INSERT INTO DBP_CLIENTS ( POSTCODE )
SELECT POSTCODE
FROM DBP_POSTCODE, HELENS_DATA, DBP_CLIENTS
WHERE DBP_POSTCODE.LOCALITY = HELENS_DATA.SUBURB
AND DBP_CLIENTS.ID = HELENS_DATA.ID
AND DBP_POSTCODE.STATE = 'NSW';
COMMIT;

Using a CASE with the IN clause in T-SQL

In My WHERE Clause I am using a CASE that will return all rows if parameter is blank or null. This works fine with single valuses. However not so well when using the IN clause. For example:
This works very well, if there is not match then ALL records are returned!
AND
Name = CASE WHEN #Name_ != '' THEN #Name_ ELSE Name END
AND
This works also, but there is no way to use the CASE expression, so if i do not provide some value i will not see any records:
AND
Name IN (Select Names FROM Person Where Names like #Name_)
AND
Can I use a CASE with the 2nd example? So if there is not a match all Names will be returned?
Maybe coalesce will resolve your problem
AND
Name IN (Select Names FROM Person Where Names like coalesce(#Name,Name))
AND
As Mattfew Lake said and used, you can also use isnull function
Name IN (Select Names FROM Person Where Names like isnull(#Name,Name))
No need for a CASE statement, just use a nested OR condition.
AND ( Name IN (Select Names FROM Person Where Names like #Name_)
OR
#Name_ IS NULL
)
AND
Perhaps something like this?
AND
Name IN (Select Names FROM Person Where Names LIKE
CASE WHEN #Name_ = '' OR #Name_ IS NULL THEN '%' ELSE #Name_ END)
AND
This will use the pattern '%' (which will match everything) only when a null or blank #Name_ parameter is provided, otherwise it will use the pattern specified by #Name_.
Alternatively, something like this should work:
AND
Name IN (Select Names FROM Person Where Names LIKE
ISNULL( NULLIF( #Name_, '' ), '%' ))
AND
This works
DECLARE #Name NVARCHAR(100)
SET #Name = ''
DECLARE #Person TABLE ( NAME NVARCHAR(100) )
INSERT INTO #Person VALUES ( 'fred' )
INSERT INTO #Person VALUES ( 'jo' )
DECLARE #Temp TABLE
(
id INT ,
NAME NVARCHAR(100)
)
INSERT INTO #Temp ( id, NAME ) VALUES ( 1, N'' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 5, N'jo' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 2, N'fred' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 3, N'bob' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 4, N'' )
SELECT * FROM #Temp
WHERE name IN ( SELECT name
FROM #Person
WHERE name = CASE WHEN #name != '' THEN #Name
ELSE name
END )
You should almost definitely use an IF statement with two selects. e.g.
IF #Name IS NULL
BEGIN
SELECT *
FROM Person
END
ELSE
BEGIN
SELECT *
FROM Person
--WHERE Name LIKE '%' + #Name + '%'
WHERE Name = #Name
END
N.B. I've changed like to equals since LIKE without wildcards it is no different to equals,
, it shouldn't make any difference in terms of performance, but it stops ambiguity for the next person that will read your query. If you do want non exact
matches then use the commented out WHERE and remove wildcards as required.
The reason for the IF is that the two queries may have very different execution plans, but by combining them into one query you are forcing the optimiser to pick one plan or the other. Imagine this schema:
CREATE TABLE Person
( PersonID INT IDENTITY(1, 1) NOT NULL,
Name VARCHAR(255) NOT NULL,
DateOfBirth DATE NULL
CONSTRAINT PK_Person_PersonID PRIMARY KEY (PersonID)
);
GO
CREATE NONCLUSTERED INDEX IX_Person_Name ON Person (Name) INCLUDE (DateOfBirth);
GO
INSERT Person (Name)
SELECT DISTINCT LEFT(Name, 50)
FROM sys.all_objects;
GO
CREATE PROCEDURE GetPeopleByName1 #Name VARCHAR(50)
AS
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE Name IN (SELECT Name FROM Person WHERE Name LIKE ISNULL(#Name, Name));
GO
CREATE PROCEDURE GetPeopleByName2 #Name VARCHAR(50)
AS
IF #Name IS NULL
SELECT PersonID, Name, DateOfBirth
FROM Person
ELSE
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE Name = #Name;
GO
Now If I run the both procedures both with a value and without:
EXECUTE GetPeopleByName1 'asymmetric_keys';
EXECUTE GetPeopleByName1 NULL;
EXECUTE GetPeopleByName2 'asymmetric_keys';
EXECUTE GetPeopleByName2 NULL;
The results are the same for both procedures, however, I get the same plan each time for the first procedure, but two different plans for the second, both of which are much more efficient that the first.
If you can't use an IF (e.g if you are using an inline table valued function) then you can get a similar result by using UNION ALL:
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE #Name IS NULL
UNION ALL
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE Name = #Name;
This is not as efficient as using IF, but still more efficient than your first query. The bottom line is that less is not always more, yes using IF is more verbose and may look like it is doing more work, but it is in fact doing a lot less work, and can be much more efficient.

PLS-00103: Encountered the symbol ","

This procedure is getting following error.
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE
'CREATE TABLE COLUMN_NAMES AS (
SELECT LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS
FROM
(SELECT DISTINCT COLUMN_NAME
FROM BW_COLUMN_ROW_CELL_JOIN)
)';
END;
/
gives:
PLS-00103: Encountered the symbol "," when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem return
returning <an exponent (**)> <> or != or ~= >= <= <> and or
like like2 like4 likec between into using || multiset bulk member submultiset
Can any one say what is wrong in this?
Thanks.
Another way (in Oracle 10g and later) is to use the alternative string literal notation - this means you don't need to worry about correctly escaping all the single quotes in the string, e.g. q'{my string's got embedded quotes}':
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE q'[
CREATE TABLE COLUMN_NAMES AS (
SELECT LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS
FROM
(SELECT DISTINCT COLUMN_NAME
FROM BW_COLUMN_ROW_CELL_JOIN)
)]';
END;
/
The problem I think is you have single quotes within single quotes. I cant test this at the moment, but I'd suggest you try the following (note the inner quotes are double quotes '', which escapes them:
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE COLUMN_NAMES AS ( SELECT LISTAGG(COLUMN_NAME, '','') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS FROM (SELECT DISTINCT COLUMN_NAME FROM BW_COLUMN_ROW_CELL_JOIN) )';
END;
/
I'd also try the create table part of the code standalone first just to make sure its valid before wrapping it in a proc.
You can't use single quotes directly in select statement of Execute Immediate it need to be coded using CHR(39)
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE
'CREATE TABLE COLUMN_NAMES AS (
SELECT LISTAGG(COLUMN_NAME,'||chr(39)||','||chr(39)||') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS
FROM
(SELECT DISTINCT COLUMN_NAME FROM BW_COLUMN_ROW_CELL_JOIN))';
END;

PL/SQL - comma separated list within IN CLAUSE

I am having trouble getting a block of pl/sql code to work. In the top of my procedure I get some data from my oracle apex application on what checkboxes are checked. Because the report that contains the checkboxes is generated dynamically I have to loop through the
APEX_APPLICATION.G_F01
list and generate a comma separated string which looks like this
v_list VARCHAR2(255) := (1,3,5,9,10);
I want to then query on that list later and place the v_list on an IN clause like so
SELECT * FROM users
WHERE user_id IN (v_list);
This of course throws an error. My question is what can I convert the v_list to in order to be able to insert it into a IN clause in a query within a pl/sql procedure?
If users is small and user_id doesn't contain commas, you could use:
SELECT * FROM users WHERE ',' || v_list || ',' LIKE '%,'||user_id||',%'
This query is not optimal though because it can't use indexes on user_id.
I advise you to use a pipelined function that returns a table of NUMBER that you can query directly. For example:
CREATE TYPE tab_number IS TABLE OF NUMBER;
/
CREATE OR REPLACE FUNCTION string_to_table_num(p VARCHAR2)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(cc.res);
END LOOP;
END;
/
You would then be able to build queries such as:
SELECT *
FROM users
WHERE user_id IN (SELECT *
FROM TABLE(string_to_table_num('1,2,3,4,5'));
You can use XMLTABLE as follows
SELECT * FROM users
WHERE user_id IN (SELECT to_number(column_value) FROM XMLTABLE(v_list));
I have tried to find a solution for that too but never succeeded. You can build the query as a string and then run EXECUTE IMMEDIATE, see http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#i14500.
That said, it just occurred to me that the argument of an IN clause can be a sub-select:
SELECT * FROM users
WHERE user_id IN (SELECT something FROM somewhere)
so, is it possible to expose the checkbox values as a stored function? Then you might be able to do something like
SELECT * FROM users
WHERE user_id IN (SELECT my_package.checkbox_func FROM dual)
Personally, i like this approach:
with t as (select 'a,b,c,d,e' str from dual)
--
select val
from t, xmltable('/root/e/text()'
passing xmltype('<root><e>' || replace(t.str,',','</e><e>')|| '</e></root>')
columns val varchar2(10) path '/'
)
Which can be found among other examples in Thread: Split Comma Delimited String Oracle
If you feel like swamping in even more options, visit the OTN plsql forums.

Resources