For the below procedure I am getting the ora error as mentioned in the title while running in Oracle SQL developer
DECLARE
sqlstr VARCHAR2(1000);
CURSOR TabSubPartitions IS
SELECT TABLE_NAME, PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'PART_TABLE'
ORDER BY PARTITION_NAME;
BEGIN
FOR aSubPart IN TabSubPartitions LOOP
IF TRUNC(LAST_DAY(SYSDATE)) = '31-07-2020' THEN
sqlstr := 'ALTER TABLE '||aSubPart.TABLE_NAME||' MODIFY PARTITION '||aSubPart.PARTITION_NAME|| ' ADD SUBPARTITION ' ||aSubPart.PARTITION_NAME||'_'||TO_CHAR(TRUNC(LAST_DAY(SYSDATE))+1, 'MON_YYYY')||' VALUES LESS THAN ( '||TO_DATE(TRUNC(LAST_DAY(SYSDATE))+2, 'SYYYY-MM-DD HH24:MI:SS' , 'NLS_CALENDER=GREGORIAN')||')' ;
EXECUTE IMMEDIATE sqlstr;
END IF;
END LOOP;
END;
Could anyone please help me. Many thanks in advance.
The main issue in the block itself is is here:
'NLS_CALENDER=GREGORIAN'
where it must be
'NLS_CALENDAR=GREGORIAN'
Moreover, you should not rely on implicite date conversions.
But there is also an issue with your resulting ALTER TABLE statement, which looks something like
ALTER TABLE MODIFY PARTITION ... VALUES LESS THAN ( 02.08.2020 00:00:00)
depending on session date settings (again because of implict date conversion). I doubt that Oracle accepts a timestamp like this. Make sure you produce a proper date literal (like DATE '2020-08-02') instead.
The whole corrected code:
DECLARE
sqlstr VARCHAR2(1000);
CURSOR TabSubPartitions IS
SELECT TABLE_NAME, PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'PART_TABLE'
ORDER BY PARTITION_NAME;
BEGIN
FOR aSubPart IN TabSubPartitions LOOP
IF TRUNC(LAST_DAY(SYSDATE)) = DATE '2020-07-31' THEN
sqlstr :=
'ALTER TABLE ' || aSubPart.TABLE_NAME || ' MODIFY PARTITION ' || aSubPart.PARTITION_NAME ||
' ADD SUBPARTITION ' || aSubPart.PARTITION_NAME || '_' || TO_CHAR(TRUNC(LAST_DAY(SYSDATE))+1, 'MON_YYYY') ||
' VALUES LESS THAN (DATE ''' || TRIM(TO_CHAR(TRUNC(LAST_DAY(SYSDATE))+2, 'SYYYY-MM-DD', 'NLS_CALENDAR=GREGORIAN')) || ''')' ;
EXECUTE IMMEDIATE sqlstr;
END IF;
END LOOP;
END;
I have several pieces of advice.
First, modify your code to output the value of sqlstr, so you can see exactly what the SQL statement is that you are trying to execute. This should make it easier to understand where the syntax error is.
Second, change TRUNC(LAST_DAY(SYSDATE)) = '31-07-2020' to use an explicit date format when converting from date to string. Something like TO_CHAR(TRUNC(LAST_DAY(SYSDATE)), 'DD-MM-YYYY') .... I don't think this is related to your error, but it is better practice than relying on the implicit conversion format.
Third, look carefully at the TO_DATE call. As it is you appear to be calling TO_DATE with date parameter, then implicitly converting that back to a string. At best that's not necessary, and at worst it will cause unexpected behavior. I suspect you may simply mean to use TO_CHAR where you currently have TO_DATE.
Related
I am looking for a way to search columns with a LONG datatype.
I know those are deprecated (and I've always hated working with them...), but for some reason Oracle themselves continue to use them in their own tables and views...
Basically I want to build a query on SYS.USER_TAB_SUBPARTITIONS with the WHERE-clause filtering a specific HIGH_VALUE.
HIGH_VALUE is of the LONG datatype and the only way I know to filter those things, is by using the undocumented function dbms_metadata_util.long2varchar
When executing a query with this function however, the returned value is NULL.
select sys.dbms_metadata_util.long2varchar(2000,'SYS.USER_TAB_SUBPARTITIONS','HIGH_VALUE', rowid) from USER_TAB_SUBPARTITIONS;
This is most likely because USER_TAB_SUBPARTITIONS is not actually a table, but a view. And views don't have rowids...
However, it seems to be a strange kind of view, as its definition does not show any underlying base table. Instead it just creates a synonym on itself.
So, to my actual question(s): Is there any other way to query LONG? Does anybody know the "base table" of USER_TAB_SUBPARTITIONS?
Yes, data type LONG in Oracle System-Views is a pain. When I have to use such values I use this one:
DECLARE
high_value INTEGER;
BEGIN
FOR aPart IN (SELECT * FROM USER_TAB_SUBPARTITIONS) LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT high_value;
SELECT ...
WHERE ... = high_value;
end loop;
END;
Note, in this example HIGH_VALUE is an integer value. However, it can be anything else (e.g. a TIMESTAMP), consider this in your procedure. For example like this:
FUNCTION IntervalType(tableName IN VARCHAR2) RETURN VARCHAR2 IS
EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);
ds INTERVAL DAY TO SECOND;
ym INTERVAL YEAR TO MONTH;
str VARCHAR2(1000);
BEGIN
SELECT INTERVAL
INTO str
FROM USER_PART_TABLES
WHERE TABLE_NAME = tableName;
EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT ym;
RETURN 'YEAR TO MONTH Interval of '||ym;
EXCEPTION
WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT ds;
RETURN 'DAY TO SECOND Interval of '||ds;
END IntervalType;
If you query ALL_VIEWS or DBA_VIEWS, you will find the definition of the view USER_TAB_SUBPARTITIONS
SELECT TEXT
FROM all_views
WHERE view_name = 'USER_TAB_SUBPARTITIONS';
You will see that the HIGH_VALUE comes from hiboundval column of sys.tabsubpart$.
There is one other way we use to extract the HIGH_VALUE . You may use SUBSTR() to extract the exact values from the extracted HIGH_VALUE.
DECLARE
v_high_value VARCHAR2(100);
BEGIN
SELECT EXTRACTVALUE (
DBMS_XMLGEN.GETXMLTYPE (
'SELECT high_value
FROM all_tab_partitions
WHERE partition_name='''
|| YOUR_PARTITION_NAME
|| '''
AND table_owner='''
|| YOUR_TABLE_OWNER
|| '''
AND table_name='''
|| YOUR_TABLE
|| ''''),
'ROWSET/ROW/HIGH_VALUE') INTO v_high_value
FROM DUAL;
END;
/
You may refer Ask TOM article here
I need to change the SRID (set it to NULL) in the geometry objects of all tables in a specific schema (for a specific user)
The command:
UPDATE my_table t SET t.geometrie.sdo_srid = null;
works fine for a single table.
When I try to do it in a loop for all tables of a specific owner:
BEGIN
FOR my_tables IN (
SELECT TABLE_NAME from all_tables where OWNER = 'LANDWERTZONEN' AND TABLE_NAME NOT LIKE 'GOOM%' AND TABLE_NAME NOT LIKE '%BKP'
)
LOOP
DBMS_OUTPUT.PUT_LINE('UPDATE ' || my_tables || ' t SET t.geometrie.sdo_srid = null');
END LOOP;
END;
I get the error:
pls-00306 wrong number or types of arguments in call to '||'
What could be the problem here?
Wrong concatenation? Wrong call?
Any suggestions are very welcome.
Besides the syntax error that Littlefoot pointed out, you may extend the logic to actually perform the update rather than printing out the UPDATE statement:
DECLARE
sql_stmt varchar2(256);
BEGIN
FOR st IN (
SELECT OWNER, TABLE_NAME, COLUMN_NAME
FROM all_tab_columns
WHERE OWNER = 'LANDWERTZONEN'
AND TABLE_NAME NOT LIKE 'GOOM%'
AND TABLE_NAME NOT LIKE '%BKP'
AND DATA_TYPE = 'SDO_GEOMETRY'
)
LOOP
sql_stmt := 'UPDATE ' || st.owner ||'.' || st.table_name || ' t SET t.'|| st.column_name ||'.sdo_srid = null';
DBMS_OUTPUT.PUT_LINE('Executing ' || sql_stmt);
execute immediate sql_stmt;
COMMIT;
END LOOP;
END;
/
This actually restricts the change to actual spatial tables / columns.
Note that you must make sure the spatial indexes are dropped before execution (and update metadata and recreate the spatial indexes after execution).
BUT I would question the reason for setting the SRIDs to NULL. This will seriously remove functionality: you will no longer be able to perform any measurements (area, length, distances). Also you will no longer be able to relate the data with data that has explicit SRIDs (like a GPS point). And if the data is actually geodetic (long/lat) then operations like within_distance, buffer generation ... are essentially no longer possible.
Our advice is to always explicitly use the correct SRIDs.
How about this:
DBMS_OUTPUT.PUT_LINE('UPDATE ' || my_tables.table_name || ' t SET t.geometrie.sdo_srid = null');
-----------
You forgot to add table name from a cursor.
I am getting the following error:
00000 - "missing right parenthesis"
when I execute my procedure:
CREATE OR REPLACE PROCEDURE ALTER_TABLE_COLUMN_NOT_NULL(
var_tabname IN VARCHAR2,
var_clname IN VARCHAR2,
var_defvalue IN VARCHAR2 )
IS
l_isnull VARCHAR2(1);
BEGIN
SELECT isnull INTO l_isnull FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = var_tabname AND COLUMN_NAME = var_clname;
IF l_isnull = 'Y' THEN
EXECUTE IMMEDIATE 'ALTER TABLE ' || var_tabname ||
' MODIFY COLUMN (' || var_clname ||
' DEFAULT ' || var_defvalue || ' NOT NULL)';
END IF;
END;
I know that according to the error, the right parenthesis is missing. I tried many ways of rewriting it, but I can't manage to fix it.
I am executing my procedure the following way:
BEGIN
ALTER_TABLE_COLUMN_NOT_NULL('FIRSTNAME', 'PRICE', '-');
END;
Writing dynamic SQL is hard, because compilation errors become runtime errors.
In this case I think the problem is that MODIFY COLUMN is wrong syntax. It's just MODIFY.
You may also run into some problems with your default of '-'. If price is a number that will fail because - is an invalid number. If price is a string you'll need to escape the passed value with additional quotes.
But probably you want to make this generic, so you need to write some more sophisticated handling which tests for datatype of the target column and formats default value appropriately.
"Can u give me a hint or any link how one can determine the datatype of a passed value in plsql?"
It's not the passed value which matters, it's the datatype of the modified column. You can get that from the USER_TAB_COLUMNS view which you're already querying.
Print your query to make sure it written correctly
DBMS_OUTPUT.PUT_LINE('ALTER TABLE ' || var_tabname || ' MODIFY COLUMN (' || var_clname || ' DEFAULT ' || var_defvalue || ' NOT NULL)');
I have a data base with daily tables. I want to write a procedure that will create a view to union all the tables for the days of the month. I want to catch months with 31 days or 30, and FEB with 29 days. Is it efficient to use if statement ?
Can I use if statement with more than one value as below?
if(upper(MON)) in ('JAN','MAR','MAY','JUL','AUG','OCT','DEC') THEN {DO SOMETHING}
Given how convoluted your code is, the efficiency of the IF is probably the least of your problems. I guess your company won't spring for the Partitioning licence. (Author's note: this sentence refers to the code posted in the original version of the query which the OP has since redacted.)
However, your approach won't work. You can't use an IF in DDL like that. You need to do something else.
Your view won't compile unless all the tables already exist. So I'm going to assume you have some form of scheduled job which creates those tables and then creates the view. In which case, you can drive the procedure off the data dictionary (Caution: untested code, you may need to fix typos).
CREATE OR REPLACE PROCEDURE monthly_view (
MON_P VARCHAR2,
YEAR_P VARCHAR2 DEFAULT TO_CHAR (SYSDATE, 'YY'))
IS
l_stmt varchar2(32767);
BEGIN
l_stmt := 'CREATE OR REPLACE VIEW monthly_view_'|| UPPER (MON_P)|| '_'|| YEAR_P||' as ';
for r in ( select table_name from user_tables
where table_name like 'LT_'|| UPPER (MON)||'____'|| YEAR_P||'_BCH'
)
loop
if r.table_name != 'LT_'|| UPPER (MON)||'_01_'|| YEAR_P||'_BCH'
then
l_stmt := l_stmt ||' UNION ALL ';
end if;
l_stmt := l_stmt ||' SELECT * FROM '||r.table_name;
end loop;
execute immediate l_stmt;
END;
/
This approach means only the routine which creates the tables needs to worry about the days of the month and whether it's a leap year.
Even better would be to not have separate tables for each month of each year.
I'm fetching value using cursor:
CURSOR Colcond
IS
SELECT CONDITION
FROM CONDITION_TAB
WHERE PROCEDURE_NAME = 'CALL_VOL';
In first iteration it would fetch "SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END)".
In my program:
OPEN Colcond;
FETCH Colcond INTO cond_val;
SELECT Appnum, customer_num,'"cond_val"'
INTO iappnum, icustnum,icond_val
FROM CALL_DETAILS WHERE APPNUM = val_appl
AND customer_num = val_cust
Group by APPLICATION_NUM,CUST_SGMT_NUM,DNIS;
INSERT INTO S_CALL_VOLUME VALUES (iappnum, icustnum, SYSDATE, icond_val);
The record thRough the variable "icond_val" inserted is SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END) instead of the value (10 or 20 or 50).
How to get the value instead of that Sum case statement?
You need to use dynamic SQL to incorporate the value you selected from the condition_tab table into the next query. Here's an example in an anonymous block rather than a procedure:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
iappnum call_details.appnum%type;
icustnum call_details.customer_num%type;
icond_val number;
cursordyn sys_refcursor;
cursor colcond is
select condition
from condition_tab
where procedure_name = 'CALL_VOL';
begin
open colcond;
fetch colcond into cond_val;
close colcond;
query_string:='select appnum, customer_num, ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
open cursordyn for query_string using val_appl, val_cust;
fetch cursordyn into iappnum, icustnum, icond_val;
close cursordyn;
insert into s_call_volume values (iappnum, icustnum, sysdate, icond_val);
end;
/
Your column names seem to be a bit inconsistent so it probably needs some tweaking.
For both cursors you're only selecting one row, so (a) they don't really need to be cursors, they can just be select into statements; and (b) the second one is selecting the two columns from the where clause which seems a bit pointless - when you use iappnum in the insert, you could just use val_app, etc. So I think you could simplify this to:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
icond_val number;
begin
select condition
into cond_val
from condition_tab
where procedure_name = 'CALL_VOL';
query_string:='select ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
execute immediate query_string into icond_val using val_appl, val_cust;
insert into s_call_volume values (val_appl, val_cust, sysdate, icond_val);
end;
/
This will error if either query doesn't return exactly one row. Your cursor version will error if the condition_tab query finds no data, and will only use one 'condition' if there are multiples; and will only use the first result from the second query if there are multiples. If you're expecting multiples from either (not sure what your actual grouping is supposed to be, it looks inconsistent too) then you need to loop over the cursor, fetching repeatedly.
Hopefully this will get you started though.