I'm trying to take some of our more complicated queries and make them as user friendly as possible. For various reasons, we can't/would prefer not to bake them into the schema. Thus, I'm trying to write a version of a query we have to run regularly to simplify entering some of the parameters. My current option is to find-replace the pertinent values each time I have to run it, which is less than ideal.
I'd like to leverage TOAD's support of substitution variables (where if one of these is present, it prompts the user to enter the intended values then runs the whole query).
I've attempted to set up a test query to familiarize myself with the process, but it's not behaving as expected.
DECLARE
&&v_value VARCHAR2 (200);
v_result VARCHAR2 (200);
BEGIN
select &v_value into v_result from dual;
dbms_output.PUT_LINE('result :'|| to_char(v_result));
END;
The result is
result :
Which is of course less than ideal. My expectation is for it to be
result : some_value
Where 'some_value' was the text I entered into the TOAD prompt window.
The reason I need this to work is so that we can treat this as a procedure and pass it the values it needs once and use it throughout the series of queries we have to run. I'm not very familiar or adept with building these kids of queries, so any help or guidance would be appreciated.
I was planning on running it via TOAD's SQL editor window.
The actual procedure I need to run is actually a series of updates and inserts; I used a select as a test case to ensure I understood how to use them.
It looks like you're having a problem with assigning a value to the parameter. I would use the following syntax as it both avoids doing an unnecessary context switch and allows you to do exception handling on the value passed.
DECLARE
v_value VARCHAR2(200);
v_result VARCHAR2(200);
BEGIN
v_value := '&input';
v_result := v_value;
dbms_output.put_line('result : ' || v_result);
END;
/
Given that you're running your series of select statements in Toad - presumably as a script - I fail to see why you need a procedure at all.
You could just use the parameter in your queries like so (making sure to run as a script):
set verify on;
select '&&v_val' value from dual;
select * from dual
where dummy = '&&v_val';
old: select '&&v_val' value from dual
new: select 'X' value from dual
VALUE
-----
X
1 row selected.
old: select * from dual
where dummy = '&&v_val'
new: select * from dual
where dummy = 'X'
DUMMY
-----
X
1 row selected.
(I deliberately set the verify to be on so that you could see the results of what happens when running the above two statements as a script; you might prefer to switch it off.)
Related
I'm trying to do something that's really simple in TSQL but I'm utterly stuck doing the same in PlSQL.
I want to do the equivalent of this:-
declare #Today Date = GetDate();
declare #FirstDate Date;
declare #LastDate Date;
with cteStartDates as
(
Select START_DATE
From TABLE1
Union All
Select START_DATE
From TABLE2
)
Select #FirstDate = MIN(START_DATE),
#LastDate = DateAdd(Day, 1, #Today)
From cteStartDates;
Basically, I'm trying to get the a start date and end date where start date is the date of a first record in one of two tables and end date is tomorrow. I then need to use #FirstDate and #LastDate as parameters in a whole bunch of subsequent queries.
I'm falling at the first hurdle in PLSQL. I can't even work out how to do the equivalent of this:-
declare #Today Date = GetDate();
I've tried reading around Oracle Variables but I'm not really understanding it. I don't understand the difference between DEFINE, DECLARE and VARIABLE and no matter which I try I can't seem to get it to work and keep getting problems I don't really understand.
For example (based on Declare but I've tried all the following with Define and Variable also), I've tried this as an experiment (assign a value to variable and then issue an otherwise valid query which doesn't even use the variable):-
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
Select ID From atable;
End;
That tells me I need an Into clause on the second select. I don't really understand why, I'm not trying to assign ID to anything, I just want to select it. I've seen some examples that sort of imply that an into will define the column names (I'm not sure I've understood that correctly though). OK, I tried this:-
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
Select ID into IDColumn From atable;
End;
That gives me a error saying identifier IDColumn must be declared so clearly the into can't simply name columns.
From examples I get the impression that perhaps the begin and end surround the bock in which variables are assigned values that can then be used later in the script. So I tried this:-
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
End;
Select v_Today from Dual;
That tells me that it encountered the keyword Select, so it seem I can't just simply follow up the declare begin and end block with a query.
Some example seem to show that you can assign a variable, execute then use the variable. So I tried executing the Declare/Begin/End Block on it's own - that gave me message saying it ran successfully. Then I tried executing the subsequent Select v_Today from Dual, v_Today's not recognised, so clearly I've lost the value of the variable by splitting up the executions.
I feel like this should be trivially easy but I'm clearly not getting it. Can someone point me in the right direction?
Edit> Ah, finally figured it out. I can use the variables within the Begin and end but I can't just issue a select in there.
PL/SQL block is enclosed with BEGIN/END keywords. Once you leave the block you end your PL/SQL context. And enter plain SQL context where PL/SQL variables are not known.
In simple words :-)
This is correct what you have learned:
you need to select values into variables
variables must be declared in DECLARE part of PL/SQL block
if you want to return a variable to the client - e.g. to have it displayed - you need to use Oracle's package dbms_output.
Like this:
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
dbms_output.put_line(v_Today);
End;
You will not see a thing until you issue before PL/SQL block:
SET SERVEROUTPUT ON
This blog post can help: https://blogs.oracle.com/connect/post/building-with-blocks
Steve published whole series of posts for PL/SQL beginners. This is just the first one.
The variables in the Declare section can be used in the Begin and End block
I am having a bizarre problem that seems very specific to CURSOR FOR Loops inside of a stored procedure. For clarity, I am using Oracle within DBeaver and am attempting to loop over all of the columns in a table and print out the results of a select statement.
I don't have access to the exact code but this is functionally approximate:
CREATE OR REPLACE PROCEDURE column_null(table_name_in IN VARCHAR2)
AS
str_query VARCHAR2(1000);
temp_number NUMBER(10);
CURSOR col_cursor IS
SELECT * FROM user_tab_cols
WHERE table_name = table_name_in;
BEGIN
FOR c_id IN col_cursor
LOOP
str_query := 'select COUNT(*) FROM ' || table_name_in ||
' WHERE ' || c_id.column_name || ' IS NOT NULL';
EXECUTE IMMEDIATE str_query INTO temp_number;
DBMS_OUTPUT.PUT_LINE(temp_number);
END LOOP;
END;
Now, the bizarre part is that if I do this exact same code block outside of a stored function (minus an extra DECLARE keyword), it works as expected. Even if I try to just echo out 'Hello' within a loop it works as expected, but as soon as it becomes a stored procedure it stops working. I've been testing this for hours today, and am completely baffled; for reference, I have only recently become acquainted with PL/SQL so its mysteries escape me.
Furthermore, it seems specific to CURSOR FOR loops; if I replace the Cursor For loop with a generic numeric loop (i.e. FOR c_id IN 1 .. 10), a procedure will produce output just fine. And it isn't just DBMS_OUTPUT.PUT_LINE that's affected; pretty much everything that goes on inside the Cursor For loop is ignored in a stored procedure, including variable updates, even though they work fine otherwise in normal PL/SQL blocks.
To summarize: Works fine as a PL/SQL block, works fine in a numeric for loop, but for some reason the exact combination of stored procedure and cursor for loop causes no output to be produced; in fact from my testing it seems like nothing meaningful happens within the cursor for loop of a stored function.
Is this a DBeaver bug? A PL/SQL oddity? I'm posting here because I'm ignorant as to whether this is expected behavior due to how Procedures and/or Cursor For loops work, or if this is a bug of some kind.
What you have done is declaring a procedure. Now that you have declared it, you have to call it using a program like bellow. Most likely it will generate outputs.
Option 01
set serveroutput on;
Declare
v_table_name_in IN VARCHAR2(499);
Begin
v_table_name_in := 'your table name';
column_null(table_name_in => v_table_name_in);
end;
Option 02
Get a return parameter. ideally a table type as out parameter. and inside the above code, loop through it and print the value.
Option 03.
Log the outputs into a log table.
I found the error was solved by simply adding AUTHID current_user to the procedure; apparently the procedure didn't have permission to access the table it was trying to select. Strangely though, no error was produced when trying to run the procedure; it just didn't produce any output.
I'm experimenting with queries in an Oracle database (Using Oracle SQl Developer and PL/SQL Developer)
If I run a simple query: (SELECT * FROM myTable WHERE id = 1234) the results display in a nice grid in a lower pane of the SQL tool.
Now, how do I rewrite that query, using a variable for the 1234, And STILL have the results spill out into the results pane. Everything I've tried either won't compile, or requires me to do a SELECT...INTO and then manually output the results.
I just want to do something along the lines of this, and have it work:
DECLARE p0 = 1234;
SELECT * FROM myTable WHERE id = p0;
UPDATE:
In the actual query I'm working on, the variables will be more like:
DECLARE p0 = to_date('1/15/2014 7:11:05 AM','MM/DD/YYYY HH:MI:SS PM');
p1 = p0 + .0007; -- one minute later.
So being able to write that in code is important.
In Toad (or sqlplus or SQL Developer) you would do:
define x='20140820';
select to_date(&x, 'YYYYMMDD') from dual;
And run as a Script (important).
In Toad, the Output grid would be:
old: select to_date(&x, 'YYYYMMDD') from dual
new: select to_date(20140820, 'YYYYMMDD') from dual
TO_DATE(20140820,'YYYYMMDD')
----------------------------
20-AUG-2014
1 row selected.
And Grid1 would just show the results in table grid format.
Note that you can suppress the old/new in output by doing:
set verify off
at top of script.
One thing you can try is to create a bind variable named cur, for example, of type REFCURSOR, use OPEN :cur FOR SELECT ... in your PL/SQL block and then PRINT the cursor out. Here's an example with a very simple query:
VARIABLE cur REFCURSOR
BEGIN
OPEN :cur FOR SELECT * FROM DUAL;
END;
/
PRINT cur
This works in SQL*Plus and SQL Developer, although in the latter it will only work if you use the 'Run Script (F5)' button, not the 'Run Statement (Ctrl+Enter)' button. Also, you don't get a table with the results in, just the query output in preformatted text. Also, it won't necessarily work in other tools, as VARIABLE and PRINT are not part of SQL nor PL/SQL - SQL*Plus and SQL Developer both understand them and can interpret them.
In PL/SQL Developer you can use a Test Window for this kind of thing. For example, enter into a Test Window a PL/SQL block such as
BEGIN
OPEN :cur FOR SELECT * FROM DUAL;
END;
Then, either add the variable cur of type 'Cursor' to the variables table at bottom of the window, (or choose Scan Variables from the context menu. Note that PL/SQL Developer won't necessarily get the type right; you may well still have to change the type. Once you run the block, the results from the cursor can be obtained in a separate window by clicking the '...' button in the row in the variables table for cur.
As a minor variation on Luke's excellent answer, you don't even need a ref cursor if you just want to display a variable's value. Put this in a Test window:
declare
p0 date := to_date('1/15/2014 7:11:05 AM', 'MM/DD/YYYY HH:MI:SS PM');
begin
:p1 := p0 + .0007;
end;
then set up the p1 bind variable in the lower panel. Executing it will display the value.
The following (highly contrived and simplified) example runs fine in SQLDeveloper, but results in an ORA-01008 error when run through OCI.
declare
CURRENT_LINE_ID NUMBER := 120;
TARGETVAR NUMBER;
begin
SELECT 1 INTO TARGETVAR FROM DUAL WHERE 120 = :CURRENT_LINE_ID;
end;
Is there any way to restructure this so that the bind variable is satisfied in OCI?
I experimented with substitution variables a little (again works in SQL Developer), but DEFINE appear to be completely invalid in OCI.
DEFINE MYSUBST = 120;
DECLARE
TARGETVAR NUMBER;
BEGIN
SELECT 1 INTO TARGETVAR FROM DUAL WHERE 120 = &MYSUBST;
END;
When you use :CURRENT_LINE_ID NUMBER , OCI looks for that bind variable in your host program only. Here C++. So you should have had this variable declare in your c++ program in a exec declare section or wherever it should be. When you run anything in SQL developer, when encountered a :variable, it blindly prompts the user to enter the value for it, so dont mix it up with the way it do and the oci libraries work.
In your case finally, when a PL/SQL is used and variable is declared there, you can always refer it without colon. If you want to bind it from the hostprogram, you have declare it as host variable. PRO*C supports that. not sure about c++. pro*c is nothing but a embedded sql in C provided by oracle.
Maheswaran's answer led me to search for host variable, which led me to the answer: I was a single colon away from success.
DECLARE
CURRENT_LINE_ID NUMBER := 120;
targetVar NUMBER;
begin
SELECT 1 INTO targetVar FROM DUAL WHERE 120 = CURRENT_LINE_ID;
end;
Although now it turns out that I'm going to have to wrap everything in a stored procedure to actually get the rows back.
This question is similar to a couple others I have found on StackOverflow, but the differences are signficant enough to me to warrant a new question, so here it is:
I want to obtain a result set from dynamic SQL in Oracle and then display it as a result set in a SqlDeveloper-like tool, just as if I had executed the dynamic SQL statement directly. This is straightforward in SQL Server, so to be concrete, here is an example from SQL Server that returns a result set in SQL Server Management Studio or Query Explorer:
EXEC sp_executesql N'select * from countries'
Or more properly:
DECLARE #stmt nvarchar(100)
SET #stmt = N'select * from countries'
EXEC sp_executesql #stmt
The question "How to return a resultset / cursor from a Oracle PL/SQL anonymous block that executes Dynamic SQL?" addresses the first half of the problem--executing dynamic SQL into a cursor. The question "How to make Oracle procedure return result sets" provides a similar answer. Web search has revealed many variations of the same theme, all addressing just the first half of my question. I found this post explaining how to do it in SqlDeveloper, but that uses a bit of functionality of SqlDeveloper. I am actually using a custom query tool so I need the solution to be self-contained in the SQL code. This custom query tool similarly does not have the capability to show output of print (dbms_output.put_line) statements; it only displays result sets. Here is yet one more possible avenue using 'execute immediate...bulk collect', but this example again renders the results with a loop of dbms_output.put_line statements. This link attempts to address the topic but the question never quite got answered there either.
Assuming this is possible, I will add one more condition: I would like to do this without having to define a function or procedure (due to limited DB permissions). That is, I would like to execute a self-contained PL/SQL block containing dynamic SQL and return a result set in SqlDeveloper or a similar tool.
So to summarize:
I want to execute an arbitrary SQL statement (hence dynamic SQL).
The platform is Oracle.
The solution must be a PL/SQL block with no procedures or functions.
The output must be generated as a canonical result set; no print statements.
The output must render as a result set in SqlDeveloper without using any SqlDeveloper special functionality.
Any suggestions?
The closest thing I could think of is to create a dynamic view for which permission is required. This will certainly involve using a PL/SQL block and a SQL query and no procedure/function. But, any dynamic query can be converted and viewed from the Result Grid as it's going to be run as a select query.
DEFINE view_name = 'my_results_view';
SET FEEDBACK OFF
SET ECHO OFF
DECLARE
l_view_name VARCHAR2(40) := '&view_name';
l_query VARCHAR2(4000) := 'SELECT 1+level as id,
''TEXT''||level as text FROM DUAL ';
l_where_clause VARCHAR2(4000):=
' WHERE TRUNC(1.0) = 1 CONNECT BY LEVEL < 10';
BEGIN
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW '
|| l_view_name
|| ' AS '
|| l_query
|| l_where_clause;
END;
/
select * from &view_name;
You seem to be asking for a chunk of PL/SQL code that will take an arbitrary query returning result set of undetermined structure and 'forward/restructure' that result set in some way such that is can easily be rendered by some "custom GUI tool".
If so, look into the DBMS_SQL for dynamic SQL. It has a DESCRIBE_COLUMNS procedure which returns the columns from a dynamic SELECT statement. The steps you would need are,
Parse the statement
Describe the result set (column names and data types)
Fetch each row, and for each column, call the datatype dependent function to return that value into a local variable
Place those local variables into a defined structure to return to the calling environment (eg consistent column names [such as col_1, col_2] probably all of VARCHAR2)
As an alternative, you could try building the query into an XMLFOREST statement, and parse the results out of the XML.
Added :
Unlike SQL Server, an Oracle PL/SQL call will not 'naturally' return a single result set. It can open up one or more ref cursors and pass them back to the client. It then becomes the client's responsibility to fetch records and columns from those ref cursors. If your client doesn't/can't deal with that, then you cannot use a PL/SQL call.
A stored function can return a pre-defined collection type, which can allow you to do something like "select * from table(func_name('select * from countries'))". However the function cannot do DML (update/delete/insert/merge) because it blows away any concept of consistency for that query. Plus the structure being returned is fixed so that
select * from table(func_name('select * from countries'))
must return the same set of columns (column names and data types) as
select * from table(func_name('select * from persons'))
It is possible, using DBMS_SQL or XMLFOREST, for such a function to take a dynamic query and restructure it into a pre-defined set of columns (col_1, col_2, etc) so that it can be returned in a consistent manner. But I can't see what the point of it would be.
Try try these.
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- Open cursor & specify bind argument in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
declare
v_rc sys_refcursor;
begin
v_rc := get_dept_emps(10); -- This returns an open cursor
dbms_output.put_line('Rows: '||v_rc%ROWCOUNT);
close v_rc;
end;
Find more examples here. http://forums.oracle.com/forums/thread.jspa?threadID=886365&tstart=0
In TOAD when executing the script below you will be prompted for the type of v_result. From the pick list of types select cursor, the results are then displayed in Toad's data grid (the excel spreadsheet like result). That said, when working with cursors as results you should always write two programs (the client and the server). In this case 'TOAD' will be the client.
DECLARE
v_result sys_refcursor;
v_dynamic_sql VARCHAR2 (4000);
BEGIN
v_dynamic_sql := 'SELECT * FROM user_objects where ' || ' 1 = 1';
OPEN :v_result FOR (v_dynamic_sql);
END;
There may be a similar mechanism in Oracle's SQL Developer to prompt for the binding as well.