Good day Stackoverflow!
I have a query that is giving me an error: "Missing Right Parenthesis", at least, so says SQL Developer.
My query has a CASE statement within the WHERE clause that takes a parameter, and then executing a condition based on the value entered.
I've read that when using a CASE statement within a WHERE clause you have to surround the statement with parenthesis and assign it to a numeric value, e.g. "1", however doing so does not accomplish my goal.
My goal is to execute a condition in the CASE statement if that condition is met.
Would you mind taking a look and giving me some input please?
Thanks!
SELECT ...
FROM ....
WHERE 1 = 1
AND (
CASE :P_REPORT_PERIOD
WHEN 'SPRING'
THEN ((fiscal_year = (EXTRACT(YEAR FROM (SYSDATE))-1) AND period >=10) OR (fiscal_year = (EXTRACT(YEAR FROM (SYSDATE))) AND period < 4))
WHEN 'FALL'
THEN ((fiscal_year = (EXTRACT(YEAR FROM (SYSDATE))) AND period >=4) OR (fiscal_year = (EXTRACT(YEAR FROM (SYSDATE))) AND period < 10))
END
) = 1
Problem Solved, THANKS to all those that attempted finding a solution.
Solution: Rather than using a CASE statement, I just created a stored procedure, replaced the CASE with IF and built a VSQL string from my query.
Example:
VSQL := 'SELECT.....'
IF (v_rpt_pd = 'SPRING') THEN
VSQL := VSQL || '( ( AND EXTRACT(YEAR FROM (SYSDATE))-1 = fiscal_year and period >=10) or ';
VSQL := VSQL || ' ( AND EXTRACT(YEAR FROM (SYSDATE)) = fiscal_year and period <=3) )';
ELSE
VSQL := VSQL || '( ( AND EXTRACT(YEAR FROM (SYSDATE)) = fiscal_year and period >=4) or ';
VSQL := VSQL || ' ( AND EXTRACT(YEAR FROM (SYSDATE)) = fiscal_year and period <=9) )';
END IF;
VSQL := VSQL ||' GROUP BY fiscal_year, period
and so on, if you want the entire solution, DM me and I'll send you the code.
Cheers!
As per Tom the CASE syntax in WHERE CLAUSE is -
--Syntax
select * from <table name>
where 1=1
and
(case
when <BOOLEAN_EXPRESSION>
then <SCALAR_RETURN_VALUE>
...
ELSE <SCALAR_RETURN_VALUE>
end) = <SCALAR_VALUE> ;
Example:
--Query
WITH SAMPLE_DATA AS
(select 100 COL1,999 COL2 from DUAL UNION ALL
select 200 COL1,888 COL2 from DUAL
)
SELECT * FROM SAMPLE_DATA
WHERE 1=1
AND (
CASE COL2
WHEN 999 THEN 1
ELSE 0
END
) = 1 ;
-- Output:
100 999
Related
I am deleting a massive table and want to delete the table in batches. I am deleting records older than 467 days. I want to insert the dbms_output status in the following procedure to be written to a table that has two columns such as the number of records to be deleted with timestamp columns:
CREATE OR REPLACE PROCEDURE delete_tab (tablename IN VARCHAR2, nrows IN NUMBER ) IS
sSQL1 VARCHAR2(2000);
sSQL2 VARCHAR2(2000);
nCount NUMBER;
BEGIN
DBMS_OUTPUT.enable (100000);
nCount := 0;
sSQL1:='delete from '|| tablename ||
' where ROWNUM < ' || nrows || ' and where cast(time_stamp as date) < sysdate - 467';
sSQL2:='select count(ROWID) from ' || tablename ||
' where cast(time_stamp as date) < sysdate - 467';
EXECUTE IMMEDIATE sSQL2 INTO nCount;
LOOP
EXECUTE IMMEDIATE sSQL1;
nCount := nCount-nrows;
DBMS_OUTPUT.PUT_LINE('Existing records to be deleted: ' || to_char(nCount));
commit;
EXIT WHEN nCount = 0;
END LOOP;
END delete_tab;
/
Let me know how I can add an insert statement within the block to write the progress.
Besides the missing Insert there are a couple other minor issues and a major issue with this code.
ssql1:='delete from '|| tablename ||
' where ROWNUM < ' || nrows || ' and where cast(time_stamp as date) < sysdate - 467';
If the above were valid then when executed it nrows-1 (not nrows as I suspect you are thinking) every time as long as that many rows still exist. However, it is not valid; the where clause is invalid. The proper format for the where clause is
"Where <condition> and <condition> ..."; do not repeat where.
ssql2:='select count(ROWID) from ' || tablename ||
' where cast(time_stamp as date) < sysdate - 467';
There is no reason to count the rowids, every row has exactly 1. ROWID is pseudo column telling Oracle basically where on the disk the row is located. You have probably heard that it is the fastest way to retrieve a specific - that is true - but it is not true selecting rowid itself is faster, it will if anything, be slower. It would require a full table scan and calculation of its value for every row in the table. The optimizer may notice what is happening and change this to select count() but why hope for that just start with count().
Now we get to the worst issue. Unless the total number of rows to delete is an exact multiple of the parameter nrows then the procedure becomes a never ending loop until it throws the exception ORA-22054: underflow error.
This is because result as the only exit condition is "exit when ncount = 0". Suppose you have 1002 rows to delete and nrows is 1000. The first iteration deletes 999 then reduces ncount to 2 (1002-1000). Then the second iteration deletes another 999 and reduces ncount to -998 (2-1000). The procedure continues iterating until eventually ncount gets so small it cannot be held any longer (something like -1*10^39 - 1).
You could change the exit condition to "exit when nCount < 1". But even that is not necessary, no need for the code to calculate the exit condition at all. Let Oracle tell it. The delete statement returns SQL%rowcount that contains the number of rows processed by the last DML statement. If no are processed it returns 0. So the exist condition becomes a simple "exit when sql%rowcount = 0". Lesson: always be careful with equal 0 conditions.
Taking all this into account the procedure becomes. (Also changed variable names as I do not like ssql1, ssql2 and reusing a variable for 2 things. When using such I get confused as to which is which, esp on large procedures.):
create or replace procedure delete_tab
( tablename in varchar2
, nrows in number
)
is
delete_sql varchar2(2000);
count_sql varchar2(2000);
deleted_count number;
expect_delete number;
begin
dbms_output.enable (100000);
count_sql := 'select count(*) from ' || tablename ||
' where time_stamp < current_timestamp - 467';
dbms_output.put_line('COUNT_SQL: ' || count_sql
);
execute immediate count_sql into expect_delete;
dbms_output.put_line('Existing records to be deleted: ' || to_char(expect_delete));
delete_sql := 'delete from '|| tablename ||
' where rownum <= ' || nrows ||
' and cast(time_stamp as date) < sysdate - 467';
dbms_output.put_line('DELETE_SQL: '
|| delete_sql
);
deleted_count := 0;
loop
execute immediate delete_sql;
exit when sql%rowcount = 0;
deleted_count := deleted_count+sql%rowcount;
commit;
end loop;
insert into delete_log( table_name, date_deleted, rows_deleted)
values (tablename, sysdate, deleted_count) ;
commit; -- commit the deletes in final loop and insert
end delete_tab;
See demo (includes example of danger with ncount = 0 and procedure has additional trace information). BTW in a production run my commit interval (nRows) would something like 500000 or larger.
I have below Two Strings
Src Id = 1:2:3:4
Src Qty = 2:4:7:9
So I want to Insert into a Table like
Insert into tbl (Src_id,qty) values (1,2);
Insert into tbl (Src_id,qty) values (2,4);
Insert into tbl (Src_id,qty) values (3,7);
and so on for all
So How i can use a loop by using APEX_UTIL.STRING_TO_TABLE or something else to align the Src id and Qty to insert them as single row in a table
You don't need a loop (i.e. PL/SQL); everything can be done in SQL. Here's how:
SQL> with test (src_id, src_qty) as
2 (select '1:2:3:4', '2:4:7:9' from dual)
3 select regexp_substr(src_id, '[^:]+', 1, level) ||','||
4 regexp_substr(src_qty, '[^:]+', 1, level) result
5 from test
6 connect by level <= regexp_count(src_id, ':') + 1;
RESULT
---------------------------------------------------------
1,2
2,4
3,7
4,9
SQL>
I have done the Solution as below
DECLARE
l_src_arr2 APEX_APPLICATION_GLOBAL.VC_ARR2;
l_qty_arr2 APEX_APPLICATION_GLOBAL.VC_ARR2;
BEGIN
l_src_arr2 := APEX_UTIL.STRING_TO_TABLE ('1:2:3:4');
l_qty_arr2 := APEX_UTIL.STRING_TO_TABLE ('2:4:7:9');
FOR i IN 1 .. l_src_arr2.COUNT LOOP
DBMS_OUTPUT.PUT_LINE ('Src = ' || l_src_arr2 (i) || ' qty = ' || l_qty_arr2 (i));
END LOOP;
END;
I'm trying to use "EXECUTE IMMEDIATE", when I run the procedure I keep getting -
ORA-00904: "NOV": invalid identifier.
I checked the column like 20 times, the name is right. the table has only 2 columns - acc, run1.
also, in the output I get [using the dbms_output.put_line (v_stat)]:
update LAHAD2
set run1 = 19-NOV-17
am I missing something?
create or replace procedure lahad is
v_sec_t varchar2 (100) := 'LAHAD2';
v_date date := trunc (sysdate+10);
v_stat varchar2 (500);
begin
v_stat :=
'update '|| v_sec_t|| '
set run1 = ' || v_date ;
dbms_output.put_line (v_stat);
EXECUTE IMMEDIATE v_stat;
end Lahad;
You must have this:
update LAHAD2 set run1 = '19-NOV-17'
So for this above, you must have
v_stat := 'update '|| v_sec_t|| ' set run1 = ''' || v_date ||'''';
It might also be safer to have a to_date around this. To expect:
update LAHAD2 set run1 = to_date('19-NOV-17', 'DD-MON-YY')
You need
v_stat := 'update '|| v_sec_t|| ' set run1 = to_date(''' || v_date ||''') , ''DD-MON-YY'')';
Hope you get the thing, especially this '' to input single ' in the output.
I have a SEARCH package with several functions within that each return a pipelined table.
I am getting the following error:
ORA-29902: error in executing ODCIIndexStart() routine
ORA-06512: at "<SCHEMA_NAME>.SEARCH", line 955
ORA-06512: at "<SCHEMA_NAME>.SEARCH", line 216
ORA-06512: at line 1
ORA-06512: at "<SCHEMA_NAME>.SEARCH", line 1143
ORA-06512: at line 1
29902. 00000 - "error in executing ODCIIndexStart() routine"
*Cause: The execution of ODCIIndexStart routine caused an error.
*Action: Examine the error messages produced by the indextype code and
take appropriate action.
The command that I use to get this error is:
select * from table(search.user_search(10000003, '', 1, 20, 5, '', '', ''));
Here is the code around line 1143 of the SEARCH body:
FUNCTION USER_SEARCH(p_user_id number, p_search varchar2, p_page number, p_num_results number, p_concept_type_id number := null, p_standard_org varchar2 := null, p_standard_title varchar2 := null, p_standard_number varchar2 := null) RETURN SEARCH_RESULTS_TABLE AS
v_sub char(1);
v_rtn search_results_table := search_results_table();
v_user_id number;
v_page number;
v_num_results number;
v_query varchar2(4000);
cursor c_concept is
select * from table(search.concept_new(v_user_id, v_query, v_page, v_num_results, p_concept_type_id, p_standard_org, p_standard_title, p_standard_number));
BEGIN
v_page := p_page;
v_num_results := p_num_results;
v_query := p_search;
if (v_query = '') then
v_query := null;
end if;
-- ONLY DURING DEVELOPMENT
v_user_id := p_user_id;
if v_user_id = 0 then
v_user_id := 2;
end if;
if v_page = null then
v_page := 1;
end if;
if v_num_results = null then
v_num_results := -1;
end if;
select security.has_permission(p_user_id, 'UNLIMITED_RESULTS') into v_sub from dual;
case v_sub
when 'N' then
v_rtn := search.term(v_user_id, p_search, 1, 20, P_concept_type_id, p_standard_org, p_standard_title, p_standard_number);
when 'Y' then
for r_concept in c_concept loop -- line 1143
v_rtn.extend;
v_rtn(v_rtn.last) := search_results_row(r_concept.SCORE, r_concept.FROM_TABLE, r_concept.ID, r_concept.IN_DICTIONARY);
end loop;
end case;
return v_rtn;
END USER_SEARCH;
The code around line 216:
FUNCTION CONCEPT_NEW(p_user_id number, p_search varchar2, p_page number := 1, p_num_results number := -1, p_concept_type_id number := null, p_standard_org varchar2 := null, p_standard_title varchar2 := null, p_standard_number varchar2 := null) RETURN SEARCH_RESULTS_TABLE PIPELINED AS
out_rec search_results_row := search_results_row(null,null,null,null);
v_num_pages number;
v_num_results number;
cursor c_count is
select count(*) cnt, ceil(count(*) / p_num_results) num_pages from (
select * from (
select rownum rn, bb.* from (
select * from (
select sum(score) score, concept_id from
(
(
select
sum(score) score,
concept_id
from
(
select
s.score + (case
when c.eotd = 'N' then
200
else
0
end) score,
ct.concept_id
from
table(search.synonym_search(p_search)) s,
term_synonym ts,
concept_term ct,
concept c
where
c.eotd = 'N' and
s.table_id = ts.synonym_id and
ts.term_id = ct.term_id and
c.concept_id = ct.concept_id
)
group by
concept_id
)
union all
(
select
sum(score) score,
concept_id
from
(
select
s.score + (case
when c.eotd = 'N' then
200
else
0
end) score,
cd.concept_id
from
table(search.definition_search(p_search)) s,
concept_definition cd,
concept c
where
c.eotd = 'N' and
s.table_id = cd.definition_id and
c.concept_id = cd.concept_id
)
group by
concept_id
)
)
group by concept_id
) order by score desc
) bb) where score > 100) where rn < 1000;
-- another cursor here called c_search (removed for cleanliness)
v_added boolean;
v_in_dict varchar(1);
v_concept_id number;
cursor c_in_dict is
select * from dictionary_concept where concept_id = v_concept_id and dictionary_id in (select dictionary_id from company_dictionary where client_company_id in (select client_company_id from users where users_id = p_user_id));
v_in_dict_result c_in_dict%rowtype;
BEGIN
open c_count;
fetch c_count into v_num_results, v_num_pages; -- line 216
close c_count;
out_rec.SCORE := v_num_results;
out_rec.FROM_TABLE := 'HEADER';
out_rec.ID := v_num_pages;
out_rec.IN_DICTIONARY := 'N';
pipe row(out_rec);
-- more code here but been removed
RETURN;
END CONCEPT_NEW;
The code around line 995:
FUNCTION STANDARD_SEARCH(p_search varchar2) RETURN RESULT_TABLE
PIPELINED
AS
out_rec result_type := result_type(null, null);
cursor c_search is
select
45 score,
source_id
from
source
where
source_title = p_search or
standard_number = p_search
union all
select
40,
source_id
from
source
where
lower(source_title) = lower(p_search) or
lower(standard_number) = lower(p_search)
union all
select
35,
source_id
from
source
where
lower(source_title) like lower(p_search) || '%' or
lower(standard_number) like lower(p_search) || '%'
union all
select
30,
source_id
from
source
where
lower(source_title) like '%' || lower(p_search) || '%' or
lower(standard_number) like '%' || lower(p_search) || '%'
union all
select
10 * contains(source_title, lower(p_search)),
source_id
from
source
where
contains(source_title, lower(p_search)) > 1
union all
select
10 * contains(standard_number, lower(p_search)),
source_id
from
source
where
contains(standard_number, lower(p_search)) > 1;
BEGIN
for r_search in c_search loop -- line 995
out_rec.SCORE := r_search.SCORE;
out_rec.TABLE_ID := r_search.SOURCE_ID;
pipe row(out_rec);
end loop;
return;
END;
Change = null to is null. Zero-length strings in Oracle are already NULL so = '' is also incorrect.
Those are some obvious bugs but the code is so complex I'm not sure if they are directly related to the errors. You may need to shrink your code down until it can fit inside a small, reproducible test case. That can take hours of work sometimes.
Another suggestion is to rethink whether you need to use pipelined functions. In my experience pipelined functions are buggy and over-used. Sometimes they can be replaced by regular SQL statements. Sometimes they can be replaced by a simpler function that returns a complete collection, instead of returning it one row at a time. Unless the collection is so ginormous that it can't reasonably fit in memory, or you need the function to return the first N results so they can be processed immediately, the pipelined feature doesn't help.
Oracle SQL Developer complains about next SQL though I can't seem to find the reason:
IF to_number(to_char(sysdate, 'HH24')) > 6 THEN
IF to_number(to_char(sysdate, 'HH24')) < 9 THEN
SELECT 1 FROM dual;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
What's the mistake in provided query?
Error report:
Error starting at line 7 in command:
ELSE
Error report:
Unknown Command
(CASEWHENRESULT='SUCCESS'THEN1ELSE0END)
---------------------------------------
1
Error starting at line 9 in command:
END IF
Error report:
Unknown Command
There are a couple of problems with your code (which is PL/SQL, not just SQL):
1) You are missing the begin and end around the block.
2) Your selects need an into clause
try:
DECLARE
l_result number;
BEGIN
IF to_number(to_char(sysdate, 'HH24')) > 6 THEN
IF to_number(to_char(sysdate, 'HH24')) < 9 THEN
SELECT 1 INTO l_result FROM dual;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END)
INTO l_result
FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END)
INTO l_result
FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
dbms_output.put_line('result is '||l_result);
END;
Since this is a PL/SQL block, your SELECT statements would need to select the data into some local variable or they would need to be used in a cursor. Which approach you want would depend on how many rows in T_JOB could potentially match the criteria you're specifying. Assuming all three statements would return exactly 1 row, you could do something like this (code simplified to avoid repeating the same query twice)
DECLARE
l_some_local_variable PLS_INTEGER;
BEGIN
IF( to_number( to_char( sysdate, 'HH24' ) ) > 6 and
to_number( to_char( sysdate, 'HH24' ) ) < 9 )
THEN
SELECT 1
INTO l_some_local_variable
FROM dual;
ELSE
SELECT (CASE WHEN result = 'SUCCESS'
THEN 1
ELSE 0
END)
INTO l_some_local_variable
FROM t_job
WHERE trunc( start_time ) = trunc( sysdate );
END IF;
END;
Of course, once you populate the data in your local variable, you would need to actually do something with the value. Potentially, you may want to create a function that returns the local variable rather than using an anonymous PL/SQL block as I have done here.