SELECT Value1 INTO lValue
FROM Table1
WHERE Field1 = lTempValue;
This works fine when the match is true. But if the match isn't true, I receive an error.
ORA-01403: no data found
Ideally, that's fine with me because I'm going to check that value next to see if it's above 0 and if it is, use that value in an insert query. I don't want to check for the value and then have to run the same query to retrieve it essentially, I want to do it in one query if possible, but I can't figure out how that is done.
If there's a value, then I want that value to go into lValue. If there is no value, then I want 0 to go into lValue. Anyone got any ideas? I've only done a quick google check, but it came up dry. Figured I'd post this while looking. Thanks for the help.
Normally, you'd simply catch the exception
BEGIN
SELECT value1
INTO lValue
FROM table1
WHERE field1 = lTempValue;
EXCEPTION
WHEN no_data_found
THEN
lvalue := 0;
END;
You can write less code by using NVL and an aggregate function (either MIN or MAX) but that tends to be a bit less obvious (note, for example, that those answers had to get revised a couple of times). And it requires whoever comes after you to pause for a moment to understand what you are doing (and whether you are doing it correctly or not). A simple nested PL/SQL block is pretty common and pretty self-explanatory.
More than that, however, it doesn't hide bugs due to duplicate rows. If you happen to get two rows in table1 where field1 is lTempValue, catching just the no_data_found exception allows the unexpected too_many_rows exception to propagate up to the caller. Since you don't expect to have multiple rows, that is exactly the behavior that you want. Using aggregate functions hides the fact that the underlying data has problems causing you to return potentially incorrect results and making it impossible to detect that there is a problem. I would always rather get an error as soon as something is causing duplicate rows to appear-- allowing me to fix the problem before it gets out of hand-- rather than finding out years later that we've got millions of duplicate rows, that the code has been occasionally returning incorrect results, and that we have a huge data cleansing effort after addressing the root cause.
As an alternative to Justin Cave's suggestion, you can rewrite the query slightly so that it always returns a row — something like this:
SELECT NVL(Value1, 0) INTO lValue
FROM Table1
RIGHT
JOIN dual
ON Field1 = lTempValue
It seems that everyone is really overcomplicating this, do this assuming your values aren't weird types like record or clobs:
SELECT NVL(MIN(Value1), 0) INTO lValue
FROM Table1
WHERE Field1 = lTempValue;
I would do it as a cursor- just to be safe (since, I don't like the idea of stray PLSQL blocks like BEGIN ....END; inside my stored procedure), something like
CREATE OR REPLACE .....
...
CURSOR c_get_val IS
SELECT Value1
FROM Table1
WHERE Field1 = lTempValue;
lValue Table1.Value1%TYPE;
lTempValue Table1.Table1%TYPE;
and then,
BEGIN
...
....
/* populate lTempValue */
OPEN c_get_val;
FETCH c_get_val INTO lValue;
if c_get_val%NOTFOUND --this is where you handle ORA-01403: no data found
then
lValue := 0;
/*or call a function, do some other stuff*/
end if;
CLOSE c_get_val;
...
...
EXCEPTION
/*do some smart exception handling here*/
END;
Some info on cursors, and more, and some more.
Related
set serveroutput on;
declare
v_emp_first_name US_EMPLOYEES.FIRST_NAME%TYPE;
BEGIN
select us_employees.first_name into v_emp_first_name from us_employees where email = 'jbutt#gmail.com';
dbms_output.put_line('v_emp_first_name --> ' || v_emp_first_name);
v_emp_first_name := select first_name from us_employees where email = 'kris#gmail.com';
dbms_output.put_line('v_emp_first_name --> ' || v_emp_first_name);
END;
When I compile above code the below statement throws error
v_emp_first_name := select first_name from us_employees where email = 'kris#gmail.com';
Is it not possible to read a column value via a sql query and map that value using assignment operator ?
If i have the below code alone then it compile and works fine
select us_employees.first_name into v_emp_first_name from us_employees where email = 'jbutt#gmail.com';
The short answer is NO. You can't assign the output of a scalar query to a variable. The proper syntax for your "assignment" is the one you are already aware of.
I am sure that you will then ask WHY. Why did Oracle choose that "weird" syntax for assignment, and not the simpler one you tried?
The answer is that in more general cases a select statement may return multiple columns, not just one; and the values from multiple columns can be "assigned" to multiple variables (local to the PL/SQL block) simultaneously. Which, by the way, is the more common usage - people extract one "record" at a time, rather than a single value. So, how would you re-write the "select into" operation as an "assignment" (or multiple "assignments")? There is no natural way to do that.
You may ask why Oracle doesn't allow "your" assignment syntax, in the case of a single column selected, in addition to the select into syntax (which is needed for multi-column rows anyway). The answer is that would be wasteful. We already have a syntax - that we need for more general cases anyway; we don't need one more.
You might say that we could put all the receiving local variables into a record type, and do a single assignment (to a record) even for general "rows" returned by a select. Alas, SQL statements return rows, not records; rows are a SQL concept, and specifically they are not a data type. Perhaps Oracle could do further development along those lines (to add functionality that doesn't exist today), but why bother - they already have a perfectly fine syntax for what you need, and you already know what that syntax is - without needing to define a record type to hold all your local variables, then define a "record type" for rows coming from a SQL select statement, then .....
You have issue with this code:
v_emp_first_name := select first_name from us_employees where email = 'kris#gmail.com';
You can not use the assignment operator against the query as you have used.
Replace this assignment := with INTO as follows:
select first_name INTO v_emp_first_name from us_employees where email = 'kris#gmail.com';
First off, my background is in SQL Server. Using CTEs (Common Table Expressions) is a breeze and converting it to a stored procedure with variables doesn't require any changes to the structure of the SQL other than replacing entered values with variable names.
In Oracle PL/SQL however, it is a completely different matter. My CTEs work fine as straight SQL, but once I try to wrap them as PL/SQL I run into a host of issues. From my understanding, a SELECT now needs an INTO which will only hold the results of a single record. However, I am wanting the entire recordset of multiple values.
My apologies if I am missing the obvious here. I'm thinking that 99% of my problem is the paradigm shift I need to make.
Given the following example:
NOTE: I am greatly over simplifying the SQL here. I do know the below example can be done in a single SQL statement. The actual SQL is much more complex. It's the fundamentals I am looking for here.
WITH A as (SELECT * FROM EMPLOYEES WHERE DEPARTMENT = 200),
B as (SELECT * FROM A WHERE EMPLOYEE_START_DATE > date '2014-02-01'),
C as (SELECT * FROM B WHERE EMPLOYEE_TYPE = 'SALARY')
SELECT 'COUNTS' as Total,
(SELECT COUNT(*) FROM A) as 'DEPT_TOTAL',
(SELECT COUNT(*) FROM B) as 'NEW_EMPLOYEES',
(SELECT COUNT(*) FROM C) as 'NEW_SALARIED'
FROM A
WHERE rowcount = 1;
Now if I want to make this into PL/SQL with variables that are passed in or predefined at the top, it's not a simple matter of declaring the variables, popping values into them, and changing my hard-coded values into variables and running it. NOTE: I do know that I can simply change the hard-coded values to variables like :Department, :StartDate, and :Type, but again, I am oversimplifying the example.
There are three issues I am facing here that I am trying to wrap my head around:
1) What would be the best way to rewrite this using PL/SQL with declared variables? The CTEs now have to go INTO something. But then I am dealing with one row at a time as opposed to the entire table. So CTE 'A' is a single row at a time, and CTE B will only see the single row as opposed to all of the data results of A, etc. I do know that I will most likely have to use CURSORS to traverse the records, which somehow seems to over complicate this.
2) The output now has to use DBMS_OUTPUT. For multiple records, I will have to use a CURSOR with FETCH (or a FOR...LOOP). Yes?
3) Is there going to a big performance issue with this vs. straight SQL in regards to speed and resources used?
Thanks in advance and again, my apologies if I am missing something really obvious here!
First, this has nothing to do with CTEs. This behavior would be the same with a simple select * from table query. The difference is that with T-SQL, the query goes into an implicit cursor which is returned to the caller. When executing the SP from Management Studio this is convenient. The result set appears in the data window as if we had executed the query directly. But this is actually non-standard behavior. Oracle has the more standard behavior which might be stated as "the result set of any query that isn't directed into a cursor must be directed to variables." When directed into variables, then the query must return only one row.
To duplicate the behavior of T-SQL, you just have to explicitly declare and return the cursor. Then the calling code fetches from the cursor the entire result set but one row at a time. You don't get the convenience of Sql Developer or PL/SQL Developer diverting the result set to the data display window, but you can't have everything.
However, as we don't generally write SPs just to be called from the IDE, it is easier to work with Oracle's explicit cursors than SQL Server's implicit ones. Just google "oracle return ref cursor to caller" to get a whole lot of good material.
Simplest way is to wrap it into an implicit for loop
begin
for i in (select object_id, object_name
from user_objects
where rownum = 1) loop
-- Do something with the resultset
dbms_output.put_line (i.object_id || ' ' || i.object_name);
end loop;
end;
Single row query without the need to predefine the variables.
PROCEDURE ORG_spGetType
(
v_TypeId IN NUMBER DEFAULT NULL
)
AS
BEGIN
SELECT *
FROM ORG_Type
WHERE TypeId = v_TypeId ;
END;
While Running above proceedure in oracle10g usinq eclipse platform sql editor getting error like "ORA-0675:package or function ORG_SPGETTYPE is in an invalid state"
That's not much of a surprise; that procedure is invalid. When compiling a procedure or any other PL/SQL block you should check whether it's compiled correctly.
If you're compiling on the command line, i.e. in SQL*Plus you can use the SHOW command and use the ERRORS variable, which:
displays the line and column number of the error (LINE/COL) as well as the error itself (ERROR).
After you've compiled the procedure type the following:
show errors
Alternatively, you can use the USER_ERRORS system view which will show you a list of all errors, which the schema you're in can see.
select *
from user_errors
where name = 'ORG_SPGETTYPE'
The actual reason for your error is that you cannot simply SELECT in PL/SQL. If you do you're not giving PL/SQL the ability to use the results of your SELECT statement in any way and so it won't allow you to do so. If you're selecting something you need to do something with it; what that something is is up to you.
There are numerous ways of doing this but let's say the table ORG_TYPE is unique on the column TYPEID and you want to return one column as an OUT parameter. Your procedure would then look like this:
create or replace procedure org_spgettype (
P_typeid in org_type.type_id%type
, P_some_column out org_type.some_column%type
) is
begin
select some_column into P_some_column
from org_type
where typeid = P_typeid;
end;
/
show errors
Please note a number of things:
There doesn't seem to be any need for the DEFAULT NULL if this is your primary key; it can't be NULL so you don't want to allow this possibility.
I've declared your parameters as the type of the column; this enables you to change the column without having to recode everything.
There is so much more to explain about all of this (exceptions for a start) so I would highly recommend taking some basic tutorials first or asking a colleague/friend for help.
You would have a 'compiled with warnings' message when creating the procedure. You can query the user_errors view to see what problems are reported for your peocedure. You will see something like 'PLS-00428: an INTO clause is expected in this SELECT statement', because you are not selecting into something.
The documentation shows how to do that. You need to declare variables to select into. In this case you could declare a rowtype variable:
CREATE OR REPLACE PROCEDURE ORG_spGetType (v_TypeId IN NUMBER DEFAULT NULL) AS
l_org_type org_type%rowtype;
BEGIN
SELECT *
INTO l_org_type
FROM ORG_Type
WHERE TypeId = v_TypeId ;
-- do something with l_org_type
END;
/
Note that this can get a no_data_found exception if there are no matching rows, or too_many_rows if there are multiple matches for the passed ID.
But it isn't clear what you're planning to do with the retrieved data. This currently does nothing at all with it, it's just selected and then forgotten. The name of the procedure suggests you want to return all or part of the data from the table to the caller. If it's only one field value then this should probably be a function rather than a procedure. Or you could add out parameters to put the values into (as Ben shows), or return a refcursor. Depends what you need.
The default null, however, maybe suggests you sometimes expect more than one result, maybe the whole table if no value is passed - though your where clause won't find anything if v_typeid is null.
Scenario: we have flashback set up on certain tables in a Oracle database. Every now and then, we want to see what fields changed from one row to another. We can inspect visually of course but that is error-prone.
So I had the "brilliant" idea to try to step through the rows, store the current record into one record variable, and the prior record into another one. Then, field-by-field, compare each field, and if different, print out the field name and the values. Something like this:
DECLARE CURSOR myflash IS SELECT * FROM myflashtable;
OLDRECORD myflashtable%ROWTYPE;
NEWRECORD myflashtable%ROWTYPE;
dynamic_statement varchar2(4000);
cursor colnames is select * from all_tab_columns where table_name = 'myflashtable';
begin
if not myflash%ISOPEN then
open myflash;
end if;
fetch myflash into NEWRECORD;
while myflash%FOUND loop;
for columnnames in colnames loop
/* cobble together dynamic SQL along the lines of
"if oldrecord.column_name != newrecord.column_name
then print some information``....end if;"
*/
execute immediate dynamic_statement;
end loop;
OLDRECORD := NEWRECORD;
fetch myflash into NEWRECORD;
end loop;
end;
Naturally this didn't work. Initially it gave me "invalid SQL statement" and I added begin/end onto the dynamic SQL. When I tried running that version, it gave me an error because it doesn't know about the old/new records. When I run without doing the execute, but just dumping the generated SQL, it is stepping through all the columns on each of the records, so that part of the logic is working.
I'm quite sure there's a better way to do this, or perhaps to make it work. One thought was to do something like declaring old/new value variables, then using dynamic SQL to move the old/new record fields to each of those:
EXECUTE IMMEDIATE 'oldvalue := OLDRECORD.'||columnnames.column_name;
EXECUTE IMMEDIATE 'newvalue := NEWRECORD.'||columnnames.column_name;
IF oldvalue != newvalue then
/* print some stuff */
END IF:
but of course the trick is that the target variable would have to handle columns of a bunch of different types - char, date, etc. So there'd need to be variants of old/newvalue variables, and logic to handle that, and it was turning into not-so-much-fun.
Any suggestions for a more elegant way to do this? I've checked around the site and haven't had much like finding anything that quite seemed like what I'm trying to do.
You are on the right track. But it is quite some more programming work to do. Read the old and new table in a join linking it with the correct primary key and loop through it. You can use DMBS_SQL package to build a dynamic cursor and loop through the tables.
I've heard that it's a good practice to define your records in PL/SQL by using the %ROWTYPE attribute. This saves typing and allows your package to continue functioning even when a column is added or deleted. (Correct me if I'm wrong!)
However, when I am fetching from a cursor that involves a join, I find that I have to fetch into a programmer-defined record that includes a (quite-possibly long) hand-written list of every column returned by the join.
So my question is:
Is it possible to fetch into nested records, or fetch into a list of records, or do something to avoid such an ugly kludge? Everything I've tried leads to an error about the record not matching what's being returned by the cursor.
Returning the result of a join using a cursor seems like such a common use-case to me that it's strange that nothing related to this comes up in a search.
Thank you.
You can user cursor%rowtype.
Sample:
declare
cursor c_c is
select emp.*, dept.* -- use aliasses if columns have same name
from emp
, dept; -- for sample no join condition
r_c c_c%rowtype;
begin
for r_c in c_c loop -- with for loop even the definition of r_c is not needed.
...
end loop;
end;
/
Why even bother with the cursor declaration?
This is equivalent.
begin
for r_c in (select emp.*, dept.* from emp, dept) loop
...
end loop;
end;
I see in your comment you mention this. But I see the explicit cursor syntax used so much, i think it's important to show.