I am running same query for different values. Such as I am querying the table for aa, bb, cc, dd, ee ... Is there any way like as function and use parameters rather than duplication my codes 10 times only for one variable changes.
I am pretty new, and don't know what to name of my solution. I do appreciate any ideas, or let me know if you need more details.
I am using toad for oracle, and need oracle sql solution.
You can write a simple query like this
select * from table where value in ('aa','bb','cc','dd','ee')
IF need to function you can use below sample :
FUNCTION GET_values( Any arguments to that query)
RETURN VARCHAR2
IS
BEGIN
SELECT value
INTO v_value
FROM table
WHERE condition;
RETURN v_value;
END GET_values;
You can use a cursor for the solution.
declare
cursor c1(value1 varchar) is
select columns from tab1
where column1= value1;
l_columns varchar2;
begin
OPEN c1(aa);
fetch c1 into l_columns ;
close c1;
end;
Related
I would like to know if it's possible to write a sql query that returns a set of columns based on a condition.
Like for example:
If (id=='A')
{
Select id,name
From Table A
}
Else If(Condition=B)
{
Select Column1, Column3
From Table A
}
If yes please help me write it
You can do switch-like statements with CASE expressions in plain SQL. See the following example:
SELECT some_other_field,
id,
CASE ID
WHEN A THEN columnA
WHEN B THEN columnB
ELSE 'Unknown'
END genericvalue
FROM customers;
There are some limitations of course. For example the type of the return values in the THEN clause need to match, so you may need to convert for example all to char, or to int, etc.
Syntax (IF-THEN-ELSE)
The syntax is for IF-THEN-ELSE in Oracle/PLSQL is:
IF condition THEN
{...statements to execute when condition is TRUE...}
ELSE
{...statements to execute when condition is FALSE...}
END IF;
http://www.techonthenet.com/oracle/loops/if_then.php
SQL itself isn't turing-complete and it doesn't have syntax for loops and conditions: you can perform a query with it, no matter how complex it is, but you can't decide which query to execute depending on a condition or perform a query a number of times, which is what you are trying to do here.
In order to provide such functionality each database developer typically provides an additional language that includes variable declaration, loops, conditionals, etc. For Oracle this language is PL/SQL.
What you need to do in SQL Developer to solve your issue and see how PL/SQL works is create an empty script, then write something like this:
--Enabling output to the console
SET SERVEROUTPUT ON;
DECLARE
--Variable declaration block; can initialize variables here too
test_var varchar2(10);
test_result varchar2(10);
BEGIN
--Initializing variables, the first one we will check in the IF statement, the second one is just for transparency
test_var := 'test';
test_result := '';
--IF block: check some condition, perform a select based on the value, save result into a variable
IF test_var = 'test' THEN
SELECT '1' INTO test_result FROM dual;
ELSE
SELECT '2' INTO test_result FROM dual;
END IF;
--Output the result to console
DBMS_OUTPUT.PUT_LINE(test_result);
END;
Then run it with 'Run script'/F5. You will get '1' as output as you would expect. Change test_var to something else and run it again, you will get '2'.
If you have questions of this kind it might be useful to read about what exactly SQL and PL/SQL are. PL/SQL is quite efficient and versatile and can be used for anything from automating SQL scripts to implementing complex optimisation algorithms.
Of course, PL/SQL has similar constructs for FOR and WHILE loops, CASE checks, etc.
I guess this is what you are looking at.
It is not possible to do such a selection in SQL even by using CASE and DECODE.
But the best we can do is we can create a function which returns a ref_cursor and use the function in the SQL stetement to fit your requirement.
Below is an example for it:
CREATE OR REPLACE
FUNCTION test1(
id1 VARCHAR)
RETURN sys_refcursor
AS
v_ref sys_refcursor;
BEGIN
IF(id1='A') THEN
OPEN v_ref FOR SELECT id,name FROM TABLE A;
ElsIf(id1='B') THEN
OPEN v_ref FOR SELECT Column1, Column3 FROM TABLE A;
END IF;
RETURN v_ref;
END;
select test1(A) from dual;
Above will display only the columns id and name.
I have a SQL sentence where I make a minus of two tables to search the differences. As I use frequently I would like to create a function or procedure to make these and get output by screen. Someone could explain me how is the best way to make these, could you put me some example?
If you frequently use the MINUS query, then it is better to create a view on the query. To fetch the resultset, you just need to select from the view.
For example,
CREATE OR REPLACE VIEW my_view AS
SELECT column_list FROM table1
MINUS
SELECT column_list FROM table2
And to fetch the result,
SELECT * FROM my_view;
Read the documentation for more details on CREATE VIEW
Maybe this is the what you're looking for, if you're using Oracle 11g Release 2:
create or replace procedure prnt_my_view(my_view in varchar2, separator in varchar2 default ',') is
type myrefcur is ref cursor;
type rowtext is table of varchar2(256);
rowdef varchar2(256);
rows_cv myrefcur;
text rowtext;
begin
select listagg(column_name,'||'''||separator||'''||') within group (order by column_id) into rowdef from user_tab_columns where lower(table_name) = lower(my_view);
open rows_cv for 'select '||rowdef||' from '||my_view;
fetch rows_cv bulk collect into text;
for i in text.first..text.last loop
dbms_output.put_line(text(i));
end loop;
close rows_cv;
exception when others then
dbms_output.put_line('something is wrong:'||sqlerrm);
end;
edit:
if you can't use listagg, check other solutions for example here: alternative to listagg in Oracle?
I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/
I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.
I wrote a package to query rows from a table. This select query will call other functions and returns all the rows from table. But when i write a package with all functions and sprocs , my sproc with select statement gives me an error saying i cannot execute without into statement. But if i use into then it will return only one row. How can i retrieve all rows using oracle sp?
Procedure GetData As
BEGIN
Select Jobid, JobName, JobLocation, JobCompany, X(jobid) FROM jobsTable; END GetData;
END;
I had to change it to following make the error go away:
Procedure GetData As
r_Jobid jobsTable.jobid%type;
r_JobName jobsTable.jobName%type;
r_JobLocation jobsTable.jobLocation%type;
r_temp varhar2(10);
BEGIN
Select Jobid, JobName, JobLocation, JobCompany, X(jobid)
INTO r_jobid, r_jobName, r_jobLocation, r_temp
FROM jobsTable;
END GetData;
END;
This is a better approach to returning multiple rows from a function:
FUNCTION GET_DATA()
RETURN SYS_REFCURSOR IS
results_cursor SYS_REFCURSOR;
BEGIN
OPEN results_cursor FOR
SELECT t.jobid,
t.jobName,
t.joblocation,
t.jobcompany,
X(t.jobid)
FROM JOBSTABLE t;
RETURN results_cursor;
END;
I agree with afk though that this doesn't appear to be what you really need to be using. Here's my recommendation for using a cursor:
CURSOR jobs IS
SELECT t.jobid,
t.jobName,
t.joblocation,
t.jobcompany,
X(t.jobid)
FROM JOBSTABLE t;
v_row jobs%ROWTYPE; --has to be declared AFTER the cursor to be able to reference the row type
BEGIN
OPEN jobs;
FETCH jobs INTO v_row;
IF jobs%FOUND THEN
--do stuff here, per row basis
--access columns in the row using: v_row.jobid/etc
END IF;
CLOSE jobs;
END;
Are you aware that this:
Procedure GetData As
r_Jobid jobsTable.jobid%type;
r_JobName jobsTable.jobName%type;
r_JobLocation jobsTable.jobLocation%type;
r_temp varhar2(10);
...means you defined local variables? You won't be able to get information out of the procedure. If you do, you'd need parameters, like this:
Procedure GetData(IO_R_JOBID IN OUT JOBSTABLE.JOBID%TYPE,
IO_R_JOBNAME IN OUT JOBSTABLE.JOBNAME%TYPE,
IO_R_JOBLOCATION IN OUT JOBSTABLE.JOBLOCATION%TYPE,
IO_R_TEMP IN OUT VARCHAR2(10)) AS
I use the IO_ to note which parameters are IN/OUT. I'd use IN_ or OUT_ where applicable. But the key here is to define OUT if you want to get a parameter back out.
Also - packages are just logical grouping of procedures & functions, with the ability to define constants scoped to the package. The package itself doesn't execute any SQL - it's still a function or procedure that is executing. God how I wish SQL Server had packages...
You could use a pipelined function. For this example I'm only fetching the ID columns, but you just need to add the others.
CREATE PACKAGE jobsPkg AS
TYPE jobsDataRec IS RECORD ( jobid jobsTable.jobid%type );
TYPE jobsDataTab IS TABLE OF jobsDataRec;
FUNCTION getJobsData RETURN jobsDataTab PIPELINED;
END jobsPkg;
/
CREATE PACKAGE BODY jobsPkg AS
FUNCTION getJobsData RETURN jobsDataTab PIPELINED
IS
output_record jobsDataRec;
BEGIN
FOR input_record IN ( SELECT jobid FROM jobsTable ) LOOP
output_record.jobid := input_record.jobid;
PIPE ROW (output_record);
END LOOP;
END getJobsData;
END jobsPkg;
/
This function can then be used as a row source:
SELECT * FROM TABLE( jobsPkg.getJobsData );
You need to use a Cursor for multiple return results. Take a look at this article on using Cursors for some details.
You could do something like:
DECLARE
CURSOR myCur IS
SELECT Jobid, JobName, JobLocation, JobCompany, X(jobid)
FROM jobsTable;
BEGIN
FOR resultRow in myCur
LOOP
--do your stuff here
END LOOP;
END;