How to get table name as a parameter in Oracle? - sql

I have a query:
SELECT
q1.table_name,
q1.column_name,
q1.data_type,
q1.nullable,
q2.comments
FROM
(
SELECT
table_name,
column_name,
data_type,
nullable
FROM
USER_TAB_COLUMNS
WHERE TABLE_NAME = 'EMPLOYEES'
) q1
JOIN
(
SELECT
column_name,
comments
FROM
USER_COL_Comments
WHERE TABLE_NAME = 'EMPLOYEES'
) q2 ON q1.column_name = q2.column_name;
It works fine, but I need to get my table name as a parameter. And I just got stucked. How can I do this? What is the difference between function and stored procedure in Oracle? What is better to use in this case? Will be very grateful for any help.

I don't think you need subqueries here: just join the two views on table name and column name. This makes it easy to parameterise the query, because you only need populate the one instance of table_name.
I have used an outer join in this query because in my experience developers aren't very disciplined about writing column comments ;)
SELECT
q1.table_name,
q1.column_name,
q1.data_type,
q1.nullable,
q2.comments
FROM USER_TAB_COLUMNS q1
left outer JOIN USER_COL_Comments q2
ON q1.table_name = q2.table_name
and ON q1.column_name = q2.column_name
WHERE q1.TABLE_NAME = 'EMPLOYEES'
;
If you need to put this in a function then it's quite straightforward: you just need to decide what return type you want. Will the function be called by other programs or used in queries? If just SQL queries, probably you should write a view instead (without the WHERE clause).
For use by programs the function should return a ref cursor, which can be mapped to a JDBC or ODBC result set.
create or replace function get_table_details
(p_table_name in user_tables.table_name%type)
return sys_refcursor
as
rc sys_refcursor;
begin
open rc for
SELECT
q1.table_name,
q1.column_name,
q1.data_type,
q1.nullable,
q2.comments
FROM USER_TAB_COLUMNS q1
left outer JOIN USER_COL_Comments q2
ON q1.table_name = q2.table_name
and ON q1.column_name = q2.column_name
WHERE q1.TABLE_NAME = p_table_name
;
return rc;
end;
"What is the difference between function and stored procedure in Oracle?"
A function returns something whereas a procedure doesn't. The convention is that a function is used for read-only features and procedures are used for changing database state. However, procedures can have OUT parameters so they can return values, and some people are ill-mannered enough to execute DML in functions. But if you stick to the convention you'll be fine.

Related

How to retrieve method type inside a package?

How can I get the type of the method (whether it's a FUNCTION or a PROCEDURE) inside a package?
CREATE OR REPLACE PACKAGE SAMPLE_PACKAGE IS
PROCEDURE procedure1;
PROCEDURE procedure2;
FUNCTION function1(
key_ IN VARCHAR2) RETURN BOOLEAN;
END SAMPLE_PACKAGE;
My package will be like this, it will have multiple Functions and Procedures.
I tried examples like How do I differentiate between Procedures and Functions in Oracle's Metadata? using user_arguments and user_procedures tables but it seems to be costly when it comes to a large data set
SELECT up.object_name, up.procedure_name, up.overload,
CASE WHEN ua.object_id IS NULL THEN 'PROCEDURE' ELSE 'FUNCTION' END AS method_type
FROM user_procedures up LEFT JOIN user_arguments ua ON ( ua.object_id = up.object_id
AND ua.subprogram_id = up.subprogram_id AND ua.position = 0 )
WHERE up.object_name = 'MY_OBJECT';
select object_name, object_type, name, type
from user_identifiers
where object_type like 'PACKAGE%'
and usage in ('DECLARATION', 'DEFINITION')
and type in ('FUNCTION', 'PROCEDURE');
This simple query returns all user defined packages. Feel free to filter out what you need.

Can I transfer SQL Query into a FUNCTION?

Currently, I have some SQL queries which looks like this:
Drop Table X;
Create Table X(id INTEGER);
Insert Into X
select ..
from..
where a.name = GIVENNAME;
Select SUM(..)
from ..
..
order by date desc;
And I want to put all these into a SQL Function, where I can choose the Parameter "GIVENNAME" when I call the function.
Is there a way to make this possible?
I would know how to do it in JSON/Java, but I have really no clue how to make it as a Function in SQL (using Oracle).
Edit:
After pointing out some things, I want to add my current code:
DROP TABLE TEMPTABLE;
CREATE TABLE TEMPTABLE
(mitID INTEGER);
INSERT INTO TEMPTABLE
select m.mitid
from mitarbeiter m
inner join abteilungen a on m.abt = a.abtid
where a.abtname = #GIVENNAME;
select SUM(g.kosten)
from gehaelter g
left outer join gehaelter k
on g.mitarbeiter = k.mitarbeiter
and g.vondatum < k.vondatum
where k.mitarbeiter is null AND g.mitarbeiter in (select * from TEMPTABLE)
order by g.vondatum desc;
I'm currently more interested in a working solution than a nice & clean one
Fortunately you can have both:
create or replace function get_sum_kosten
( p_givenname in abteilungen.abtname%type )
return number
as
return_value number;
begin
select SUM(g.kosten)
into return_value
from gehaelter g
left outer join gehaelter k
on g.mitarbeiter = k.mitarbeiter
and g.vondatum < k.vondatum
where k.mitarbeiter is null
AND g.mitarbeiter in (select m.mitid
from mitarbeiter m
inner join abteilungen a on m.abt = a.abtid
where a.abtname = P_GIVENNAME
)
;
return return_value;
end;
Possible? Yes. Recommended? No.
For any DDL, you'd have to use dynamic SQL (EXECUTE IMMEDIATE). If queries are complex, those commands will be difficult to maintain.
INSERT is a DML, but you can't use it in a function, unless it is an autonomous transaction (and you'll have to commit (or rollback) within the function).
If it were a procedure, you'd - at least - avoid the last problem I mentioned. If you're returning something, use an OUT parameter.
Can't you use a (global) temporary table, instead? Create it once, use it many times. I understand that your code might be very complex and maybe it really can't fit into a single SELECT statement, but you should - at least - try to do that job in an Oracle spirit (i.e. it is not MS SQL Server).
example of procedure
https://www.sitepoint.com/stored-procedures-mysql-php/
like this
DELIMITER $$
CREATE PROCEDURE `avg_sal`(out avg_sal decimal)
BEGIN
select avg(sal) into avg_sal from salary;
END

Left join with dynamic table name derived from column

I am new in PostgreSQL and I wonder if it's possible to use number from table tbc as part of the table name in left join 'pa' || number. So for example if number is 456887 I want left join with table pa456887. Something like this:
SELECT tdc.cpa, substring(tdc.ku,'[0-9]+') AS number, paTab.vym
FROM public."table_data_C" AS tdc
LEFT JOIN concat('pa' || number) AS paTab ON (paTab.cpa = tdc.cpa)
And I want to use only PostgreSQL, not additional code in PHP for example.
Either way, you need dynamic SQL.
Table name as given parameter
CREATE OR REPLACE FUNCTION foo(_number int)
RETURNS TABLE (cpa int, nr text, vym text) AS -- adapt to actual data types!
$func$
BEGIN
RETURN QUERY EXECUTE format(
'SELECT t.cpa, substring(t.ku,'[0-9]+'), p.vym
FROM public."table_data_C" t
LEFT JOIN %s p USING (cpa)'
, 'pa' || _number
);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM foo(456887)
Generally, you would sanitize table names with format ( %I ) to avoid SQL injection. With just an integer as dynamic input that's not necessary. More details and links in this related answer:
INSERT with dynamic table name in trigger function
Data model
There may be good reasons for the data model. Like partitioning / sharding or separate privileges ...
If you don't have such a good reason, consider consolidating multiple tables with identical schema into one and add the number as column. Then you don't need dynamic SQL.
Consider inheritance. Then you can add a condition on tableoid to only retrieve rows from a given child table:
SELECT * FROM parent_table
WHERE tableoid = 'pa456887'::regclass
Be aware of limitations for inheritance, though. Related answers:
Get the name of a row's source table when querying the parent it inherits from
Select (retrieve) all records from multiple schemas using Postgres
Name of 2nd table depending on value in 1st table
Deriving the name of the join table from values in the first table dynamically complicates things.
For only a few tables
LEFT JOIN each on tableoid. There is only one match per row, so use COALESCE.
SELECT t.*, t.tbl, COALESCE(p1.vym, p2.vym, p3.vym) AS vym
FROM (
SELECT cpa, ('pa' || substring(ku,'[0-9]+'))::regclass AS tbl
FROM public."table_data_C"
-- WHERE <some condition>
) t
LEFT JOIN pa456887 p1 ON p1.cpa = t.cpa AND p1.tableoid = t.tbl
LEFT JOIN pa456888 p2 ON p2.cpa = t.cpa AND p2.tableoid = t.tbl
LEFT JOIN pa456889 p3 ON p3.cpa = t.cpa AND p3.tableoid = t.tbl
For many tables
Combine a loop with dynamic queries:
CREATE OR REPLACE FUNCTION foo(_number int)
RETURNS TABLE (cpa int, nr text, vym text) AS
$func$
DECLARE
_nr text;
BEGIN
FOR _nr IN
SELECT DISTINCT substring(ku,'[0-9]+')
FROM public."table_data_C"
LOOP
RETURN QUERY EXECUTE format(
'SELECT t.cpa, _nr, p.vym
FROM public."table_data_C" t
LEFT JOIN %I p USING (cpa)
WHERE t.ku LIKE (_nr || '%')'
, 'pa' || _nr
);
END LOOP;
END
$func$ LANGUAGE plpgsql;

Concatenate multiple fields in query string in plpgsql

I am using plpgsql and hibernate and want to create a function which contains the query string given below. In the select clause I want to concatenate 3 fields but while running this query I am getting error message like:
ERROR: syntax error at or near "' '"
SQL state: 42601
Context: PL/pgSQL function "est_fn_dept_wise_emp_report" line 30 at open
I am new using stored functions, it might be a basic question but somehow I was unable to find a solution.
query1 = 'SELECT est_emp_empmaster.emp_no AS est_emp_empmaster_emp_no,
adm_m_department.dept_name AS adm_m_department_dept_name,
adm_m_subdepartment.sub_dept_id AS adm_m_subdepartment_sub_dept_id,
adm_m_subdepartment.sub_dept_name AS adm_m_subdepartment_sub_dept_name,
est_m_designation.desig_name AS est_m_designation_desig_name,
est_emp_empmaster.first_name'|| ' ' ||'est_emp_empmaster.middle_name'|| ' ' ||'est_emp_empmaster.surname AS empname
FROM public.adm_m_department adm_m_department
INNER JOIN public.adm_m_subdepartment adm_m_subdepartment
ON adm_m_department.dept_id = adm_m_subdepartment.dept_id
INNER JOIN public.est_emp_empmaster est_emp_empmaster
ON adm_m_department.dept_id = est_emp_empmaster.dept_id
AND adm_m_subdepartment.sub_dept_id = est_emp_empmaster.sub_dept_id
INNER JOIN public.est_emp_salary est_emp_salary
ON est_emp_empmaster.emp_no = est_emp_salary.emp_no
INNER JOIN public.est_m_designation est_m_designation
ON est_emp_salary.pre_desig_code = est_m_designation.desig_code
AND est_emp_salary.retired_flag ='|| quote_literal('N') ||'
WHERE est_emp_empmaster.corp_coun_id=0 or est_emp_empmaster.corp_coun_id is null or est_emp_empmaster.corp_coun_id = '|| quote_literal($1) ||'
ORDER BY adm_m_department.dept_id,adm_m_subdepartment.sub_dept_id,est_emp_empmaster.emp_no ASC';
OPEN refcur FOR
EXECUTE query1;
LOOP
FETCH refcur INTO return_record;
EXIT WHEN NOT FOUND;
RETURN NEXT return_record;
END LOOP;
CLOSE refcur;**
The above query runs fine if I execute it without executing through query string. But as I want to use this query for multiple conditions and in those condition I want to modify this query to get different results.
This can be much simpler, safer and faster (assuming at least Postgres 8.4):
CREATE OR REPLACE FUNCTION foo(_corp_coun_id int) -- guessing type
RETURNS TABLE (
emp_no int -- guessing data types ..
,dept_name text -- .. replace with actual types
,sub_dept_id int
,sub_dept_name text
,desig_name text
,empname text) AS
$func$
BEGIN
RETURN QUERY
SELECT em.emp_no
,dp.dept_name
,sb.sub_dept_id
,sb.sub_dept_name
,ds.desig_name
,concat_ws(' ', em.first_name, em.middle_name, em.surname) -- AS empname
FROM adm_m_department dp
JOIN adm_m_subdepartment su ON sb.dept_id = dp.dept_id
JOIN est_emp_empmaster em ON em.dept_id = sb.dept_id
AND em.sub_dept_id = sb.sub_dept_id
JOIN est_emp_salary sl ON sl.emp_no = em.emp_no
AND sl.retired_flag = 'N' -- untangled join cond.
JOIN est_m_designation ds ON ds.desig_code = sl.pre_desig_code
WHERE em.corp_coun_id = 0 OR
em.corp_coun_id IS NULL OR
em.corp_coun_id = $1
ORDER BY dp.dept_id, sb.sub_dept_id, em.emp_no;
END
$func$
LANGUAGE plpgsql SET search_path=public;
To address your primary question: use concat_ws() for simple and secure concatenation of multiple columns (doesn't fail with NULL).
You do not need dynamic SQL here since the variables are only values (not identifiers).
You do not need a CURSOR here.
You do not need a LOOP. RETURN QUERY does the same, simpler and faster.
You do not need column aliases, only the names of the OUT parameters (implicitly the column names in RETURNS TABLE (...)) are relevant.
Replace the multiple schema qualification public. in your query with a single SET search_path = public.
I also untangled your query, used short table aliases and reformatted to make it easier to read.
You don't even need plpgsql at all here. Can be a simpler SQL function:
CREATE OR REPLACE FUNCTION foo(_corp_coun_id int)
RETURNS TABLE (
emp_no int -- guessing data types ..
,dept_name text -- .. replace with actual types!
,sub_dept_id int
,sub_dept_name text
,desig_name text
,empname text) AS
$func$
SELECT em.emp_no
,dp.dept_name
,sb.sub_dept_id
,sb.sub_dept_name
,ds.desig_name
,concat_ws(' ', em.first_name, em.middle_name, em.surname) -- AS empname
FROM adm_m_department dp
JOIN adm_m_subdepartment sb ON sb.dept_id = dp.dept_id
JOIN est_emp_empmaster em ON em.dept_id = sb.dept_id
AND em.sub_dept_id = sb.sub_dept_id
JOIN est_emp_salary sl ON sl.emp_no = em.emp_no
AND sl.retired_flag = 'N' -- untangled join cond.
JOIN est_m_designation ds ON ds.desig_code = sl.pre_desig_code
WHERE em.corp_coun_id = 0 OR
em.corp_coun_id IS NULL OR
em.corp_coun_id = $1
ORDER BY dp.dept_id, sb.sub_dept_id, em.emp_no;
$func$
LANGUAGE sql SET search_path=public;
I found a solution to the above problem,
actually it was well working in normal query but i got problem while running it in dynamic query.
The solution to the above problem is as follows.Thanks again.. :)
est_emp_empmaster.first_name||'' ''||est_emp_empmaster.middle_name||'' ''||est_emp_empmaster.surname AS empname

Cursor inside SQL query

In Oracle, it's possible to return a cursor inside a SQL query, using the cursor keyword, like this:
select owner, table_name,
cursor (select column_name
from all_tab_columns
where owner = allt.owner
and table_name = allt.table_name) as columns
from all_tables allt
The questions are:
Does anyone know where can I find documentation for this?
Does PortgreSQL (or any other open source DBMS) have a similar feature?
It's called a CURSOR EXPRESSION, and it is documented in the obvious place, the Oracle SQL Reference. Find it here.
As for your second question, the closest thing PostgreSQL offers to match this functionality is "scalar sub-queries". However, as #tbrugz points out, these only return one row and one column, so they aren't much like Cursor Expressions. Read about them in the documentation here. MySQL also has Scalar Sub-queries, again limited to one column and one row. Docs here. Likewise SQL Server and DB2 (not open source but for completeness).
That rules out all the obvious contenders. So, it seems unlikely any other DBMS offers the jagged result set we get from Oracle's cursor expression.
Postgres provides cursor expressions but the syntax is a bit less handy than Oracle's.
First you need to create function for array to refcursor conversion:
create or replace function arr2crs(arr anyarray) returns refcursor as $$
declare crs refcursor;
begin
open crs for select * from unnest(arr);
return crs;
end;
$$ language plpgsql volatile;
Now let's create some test data
create table dep as
select 1 depid, 'Sales' depname
union all
select 2 depid, 'IT' depname;
create table emp as
select 1 empid, 1 depid, 'John' empname union all
select 2 empid, 1 depid, 'James' empname union all
select 3 empid, 2 depid, 'Rob';
You can query it like this
select
dep.*,
arr2crs(array(
select row(emp.*)::emp from emp
where emp.depid = dep.depid
)) emps
from dep
And process in on client side like this (Java)
public static List Rs2List(ResultSet rs) throws SQLException{
List result = new ArrayList();
ResultSetMetaData meta = rs.getMetaData();
while(rs.next()){
Map row = new HashMap();
for (int i = 1; i <= meta.getColumnCount(); i++){
Object o = rs.getObject(i);
row.put(
meta.getColumnName(i),
(o instanceof ResultSet)?Rs2List((ResultSet)o):o);
}
result.add(row);
}
return result;
}
Note that you must explicitly cast row to particular type. You can use CREATE TYPE to create necessary types.