I am creating a csv file from a query using ! as a delimiter (I did not create the query and I cannot modify the delimiter).
I have to modify the select statement to use a between to_date but my procedure is dying. The query is assigned to a variable which is then passed into the procedure that is used to create the csv file.
I believe the error is happening because I'm delimiting the two dates when there is only supposed to be a single date column
This is the original code that works using a single date
CREATE OR REPLACE PROCEDURE my_procedure(
p_column1 IN VARCHAR2
,p_column2 IN VARCHAR2
)AS
v_query VARCHAR2(5000);
v_column1 VARCHAR2(10);
v_column2 VARCHAR2(10);
v_column1 := p_column1;
v_column2 := p_column2;
v_query := q'!
SELECT column1
FROM mytable
WHERE
column1= TO_DATE('!'
|| v_date
|| q'!','yyyy-mm-dd')!';
END;
This is the code I am currently trying to use
CREATE OR REPLACE PROCEDURE my_procedure(
p_column1 IN VARCHAR2
,p_column2 IN VARCHAR2
)AS
v_query VARCHAR2(5000);
v_column1 VARCHAR2(10);
v_column2 VARCHAR2(10);
v_column1 := p_column1;
v_column2 := p_column2;
v_query := q'!
SELECT column1
FROM mytable
where
column1 BETWEEN TO_DATE('!' ||v_date || q'!,'yyyy-mm-dd')
AND TO_DATE( '!' ||v_end_date || q'!,'yyyy-mm-dd')!';
END;
How can I delimit the between to_date to be one column?
You've lost the closing quote after the v_date and v_end_date values:
where
column1 BETWEEN TO_DATE('!' ||v_date || q'!','yyyy-mm-dd')
--------------------------------------------^
AND TO_DATE( '!' ||v_end_date || q'!','yyyy-mm-dd')!';
--------------------------------------------^
You can debug problems like this by doing:
dbms_output.put_line(v_query);
With a random date value that shows your original query as:
SELECT column1
FROM
mytable
where
column1= TO_DATE('2011-12-13','yyyy-mm-dd')
and your modified one as:
SELECT column1
FROM
mytable
where
column1 BETWEEN TO_DATE('2011-12-13,'yyyy-mm-dd')
AND TO_DATE( '2012-01-02,'yyyy-mm-dd')
which makes the missing quotes in both to_date() calls more obvious than they are during construction. (And trying to execute that gets ORA-00907: missing right parenthesis.)
With my modification that now comes out as:
SELECT column1
FROM
mytable
where
column1 BETWEEN TO_DATE('2011-12-13','yyyy-mm-dd')
AND TO_DATE( '2012-01-02','yyyy-mm-dd')
which looks more reasonable.
Hopefully those v_* variables are strings. If they are dates then you should not be relying on NLS sessions to convert them to dates. Either way you should probably be using bind variablesm then supplying the actual values when you execute the dynamic query - though it isn't obvious why this is dynamic, so perhaps that constructed query is being passed back for something else to run.
Related
I get 'expression is of wrong type' error when I'm trying to execute the following sql procedure:
DECLARE
v_date TIMESTAMP(6) := to_date('03-11-2011', 'dd-mm-yyyy');
BEGIN
for newiD in (select tabA.id from tabA where tabA.prop = 1)
loop
insert into tabB values (newId, 'testval', 'testval', newId, v_date);
end loop;
END;
Is there another way I can declare the array I'm trying to store into the newId variable?
Update: The data type of the tabA.id and the next tabB.newId is the same - NUMBER(19,0)
A better way, with no loops and just SQL, could be:
DECLARE
v_date TIMESTAMP(6) := to_date('03-11-2011', 'dd-mm-yyyy');
BEGIN
insert into tabB /* here it would be better to list the columns */
select tabA.id, 'testval', 'testval', tabA.id, v_date
from tabA
where tabA.prop = 1
END;
In this case newId is a record wich has one field: id. That's why you need to put newId.id in your insert clause.
I have a table where i have to update multiple records on one button click. I am trying to update multiple record using below simple query.
UPDATE tablename SET column1=1 WHERE
idcolumn IN ('1','2','3')
where datatype of idcolumn is Number. If i run this query manually its working perfectly. But if i pass these ('1','2','3') parameteres through procedure then it is showing me below error i.e. (ora-01722 invalid number).
I tried to_number() function but still it is showing me above error.
Proc:
CREATE OR REPLACE PROCEDURE procname(idpara VARCHAR2,
RCT_OUT OUT SYS_REFCURSOR) IS
BEGIN
UPDATE tablename SET column1 = 1 WHERE idcolumn IN (idpara);
COMMIT;
OPEN RCT_OUT FOR
SELECT 'RECORD UPDATED SUCCESSFULLY' RESULT FROM DUAL;
END;
The procedure does not understand IN (idpara) with idpara being '1','2','3' as IN ('1','2','3') but as IN (q'!'1','2','3'!'). In other words, it is not searching for '1' and '2' and '3' but for '1,2,3'. But while '1' can be converted to a number '1,2,3' can not.
Here is a test case for you to show you:
select * from dual;
-- X
-- notice I have 'X' in the in list below
set serveroutput on
declare
idpara varchar2(400) := q'!'X','2','3'!';
v_out varchar2(400);
begin
select count(*) into v_out from dual where dummy in (idpara);
dbms_output.put_line(v_out);
end;
/
-- 0
declare
idpara varchar2(400) := q'!'X','2','3'!';
v_out varchar2(400);
sql_stmt VARCHAR2(1000) := NULL;
begin
sql_stmt :='select count(*) from dual where dummy in ('||idpara||')';
execute immediate sql_stmt into v_out;
dbms_output.put_line(v_out);
end;
/
-- 1
One solution inside of procname would be to build a pl/sql object of numbers and use that in the update. There is a lot of info out there on how to do it. E.g. here Convert comma separated string to array in PL/SQL And here is info on how to use the object in the IN-clause Array in IN() clause oracle PLSQL
Here is What i actually wanted to do, Fetch Data From a Table without knowing any columns but i.j.Column_Name gives an error, so i wanted to put it in a variable. After reading the comments i think it's not possible
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
CURSOR C2 IS SELECT Table_Name,Column_Name FROM user_tab_columns
WHERE data_type='VARCHAR2';
v_table Varchar2(256);
v_Col varchar2(200);
BEGIN
FOR i in C1 LOOP
FOR j in (SELECT Column_Name FROM user_tab_columns WHERE
Table_Name='Table_Name') LOOP
dbms_output.put_line(i.j.Column_Name);
END LOOP;
END LOOP;
END;
/
No, There is no Column Named v_Col
You can't refer to a field in a record (which is what the cursor loop is giving you) dynamically. If you need to do flexibly then you can use dbms_sql (possibly adapting this approach), but in the scenario you've shown you could use dynamic SQl to only get the column you want in the cursor:
-- dummy data
create table table_name (id number, column_name varchar2(10), other_col date);
insert into table_name values (1, 'Test 1', sysdate);
insert into table_name values (2, 'Test 2', sysdate);
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
v_Cur sys_refcursor;
v_Col varchar2(200);
v_Val varchar2(4000);
BEGIN
v_Col:= 'Column_Name';
OPEN v_Cur for 'SELECT ' || v_Col || ' FROM Table_Name';
LOOP
FETCH v_Cur INTO v_Val;
EXIT WHEN v_Cur%notfound;
dbms_output.put_line(v_val);
END LOOP;
END;
/
Test 1
Test 2
PL/SQL procedure successfully completed.
The downside of this is that whatever the data type of the target column is, you have to implicitly convert it to a string; but you would be doing that in the dbms_output call anyway. So if you change the column you want to print:
v_Col:= 'Other_Col';
then the output from my dummy data would be:
2018-08-23
2018-08-23
PL/SQL procedure successfully completed.
where the date value is being implicitly formatted as a string using my current NLS session settings.
You could get more advanced by checking the data type in user_tab_columns and changing the dynamic query and/or the fetch and handling, but it isn't clear what you really need to do.
I need to do a FOR EACH loop in a procedure, but I need to pass the table name dynamically.
This is the declaration
CREATE OR REPLACE PROCEDURE MIGRATE_PRIMITIVES_PROPS
(
FromTable IN VARCHAR2,
ToTable IN VARCHAR2
)
When I try and do this
FOR EachRow IN (SELECT * FROM FromTable) It says the table isn't valid
The table coming into the procedure is dynamic, columns are added and deleted all the time so I can't spell out the columns and use a cursor to populate them.
You have to use dynamic SQL to query a table whose name you don't know at compile time. You can do that with a dynamic cursor:
as
l_cursor sys_refcursor;
begin
open l_cursor for 'select * from ' || fromtable;
loop
fetch l_cursor into ...
... but then it breaks down because you can't define a record type to fetch into based on a weak ref cursor; and you don't know the column names or types you're actually interested in - you're using select * and have specific names to exclude, not include. You mentioned an inner loop that works and gets the column names, but there is no way to refer to a field in that cursor variable dynamically either.
So you have to work a bit harder and use the dbms_sql package instead of native dynamic SQL.
Here's a basic version:
create or replace procedure migrate_primitives_props
(
fromtable in varchar2,
totable in varchar2
)
as
l_cursor pls_integer;
l_desc_tab dbms_sql.desc_tab;
l_columns pls_integer;
l_value varchar2(4000);
l_status pls_integer;
begin
l_cursor := dbms_sql.open_cursor;
-- parse the query using the parameter table name
dbms_sql.parse(l_cursor, 'select * from ' || fromtable, dbms_sql.native);
dbms_sql.describe_columns(l_cursor, l_columns, l_desc_tab);
-- define all of the columns
for i in 1..l_columns loop
dbms_sql.define_column(l_cursor, i, l_value, 4000);
end loop;
-- execute the cursor query
l_status := dbms_sql.execute(l_cursor);
-- loop over the rows in the result set
while (dbms_sql.fetch_rows(l_cursor) > 0) loop
-- loop over the columns in each row
for i in 1..l_columns loop
-- skip the columns you aren't interested in
if l_desc_tab(i).col_name in ('COL_NAME', 'LIB_NAME', 'PARTNAME',
'PRIMITIVE', 'PART_ROW')
then
continue;
end if;
-- get the column value for this row
dbms_sql.column_value(l_cursor, i, l_value);
-- insert the key-value pair for this row
execute immediate 'insert into ' || totable
|| '(key, value) values (:key, :value)'
using l_desc_tab(i).col_name, l_value;
end loop;
end loop;
end;
/
I've assumed you know the column names in your ToTable but still used a dynamic insert statement since that table name is unknown. (Which seems strange, but...)
Creating and populating sample tables, and then calling the procedure with their names:
create table source_table (col_name varchar2(30), lib_name varchar2(30),
partname varchar2(30), primitive number, part_row number,
col1 varchar2(10), col2 number, col3 date);
create table target_table (key varchar2(30), value varchar2(30));
insert into source_table (col_name, lib_name, partname, primitive, part_row,
col1, col2, col3)
values ('A', 'B', 'C', 0, 1, 'Test', 42, sysdate);
exec migrate_primitives_props('source_table', 'target_table');
End up with the target table containing:
select * from target_table;
KEY VALUE
------------------------------ ------------------------------
COL1 Test
COL2 42
COL3 2015-05-22 15:29:31
It's basic because it isn't sanitising the inputs (look up the dbms_assert package), and isn't doing any special handling for different data types. In my example my source table had a date column; the target table gets a string representation of that date value based on the calling session's NLS_DATE_FORMAT setting, which isn't ideal. There's a simple but slightly hacky way to get a consistent date format, and a better but more complicated way; but you may not have date values so this might be good enough as it is.
I am trying to run a script that involves an xmltable and I am getting a
PLS-00428: an INTO clause is expected in this SELECT statement
How could I correct my script and get 2 rows from the xml?
Oracle 11g
DECLARE xml_char xmltype;
BEGIN
xml_char := xmltype.createXML('<xml><ROWSET><ROW UNIQUEID="All0" ALLOWCHG="0"/><ROW UNIQUEID="All1" ALLOWCHG="1"/></ROWSET></xml>');
select UNIQUEID, ALLOWCHG from xmltable ( '/xml/ROWSET/ROW' passing xml_char columns "UNIQUEID" varchar(30) path '#UNIQUEID', "ALLOWCHG" varchar(30) path '#ALLOWCHG' ) ;
END;
In SQL, a select statement never contains an INTO clause.
In PL/SQL, a select statement requires an INTO clause unless it's in cursor. I modified your code to use a cursor:
DECLARE
xml_char xmltype;
cursor c_cursor is
select UNIQUEID, ALLOWCHG
from xmltable ( '/xml/ROWSET/ROW'
passing xml_char
columns
"UNIQUEID" varchar(30) path '#UNIQUEID',
"ALLOWCHG" varchar(30) path '#ALLOWCHG'
);
BEGIN
xml_char := xmltype.createXML('<xml><ROWSET><ROW UNIQUEID="All0" ALLOWCHG="0"/><ROW UNIQUEID="All1" ALLOWCHG="1"/></ROWSET></xml>');
for i in c_cursor loop
dbms_output.put_line('UNIQUEID: ' || i.uniqueid || ' ALLOWCHG: '|| i.allowchg);
end loop;
END;
Don't worry, we all make silly mistakes mate, I do despite years of experience.