Concatenate multiple fields in query string in plpgsql - sql

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

Related

Query table within variable schemanames

I want to run a query of a table within variable schemas.
I have a postgresql db that I've written the following query for:
SELECT
project_id, project_name, schema_p
FROM public.projects
JOIN public.schema_ps
ON public.schema_ps.root_id = public.projects.project_id
ORDER BY project_id ASC
The schema_p column contains the names of schemas that all have a table named com_set that I need to query as follows:
SELECT dev_id, dev_name FROM [variable_schama_name].com_set
What I'd like to do is write a query to generate a single combined result, where my resulting dataset would contain the columns of project_id, project_name, schema_p, dev_id, and dev_name. This would mean I'd have repeats in the first three columns and unique entries in the second last two columns. One way to think of it is that I'd like to run the first query to get the names of the schemas, then run the second query on each of the schemas.
Hopefully that all makes sense. Thanks for the help!
A possible solution relies on a dynamic query without guarantee about the performance :
CREATE OR REPLACE FUNCTION my_query(OUT project_id int, OUT project_name text, OUT schema_p text, OUT dev_id int, OUT dev_name text)
RETURNS setof record LANGUAGE plpgsql AS $$
DECLARE
txt text ;
BEGIN
SELECT string_agg(
'SELECT p.project_id, p.project_name, s.schema_p, c.dev_id, c.dev_name
FROM public.projects AS p
INNER JOIN public.schema_ps AS s
ON s.root_id = p.project_id
CROSS JOIN ' || ps.schema_p || '.com_set AS c', ' UNION ALL ')
|| ' ORDER BY project_id ASC'
FROM public.schema_ps AS ps
INTO txt ;
RETURN QUERY EXECUTE txt ;
END ; $$ ;
and then you just call my_query :
SELECT my_query() ;

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

Syntax error in dynamic SQL in pl/pgsql function

I am using pl/pgsql in PostgreSQL 10, to create complex queries. I am testing a query with a couple of JOINs and ANDs. This is what I have so far:
DROP FUNCTION IF EXISTS search_person(name text);
CREATE FUNCTION search_person(name text) RETURNS TABLE(address_id integer, address_geom text, event_name text) AS $$
--DECLARE
BEGIN
RETURN QUERY EXECUTE
'SELECT address.id, event.name, address.geom
FROM event JOIN person JOIN address JOIN person_address JOIN event_person
WHERE
person_address.event_id = event.id AND
event_person.event_id = event.id AND
person.id = event_person.person_id AND
person.name like
$1'
USING name;
END;
$$
LANGUAGE plpgsql;
I get no errors while creating this function. I call it like so select search_person('nick'); and I get:
ERROR: syntax error at or near "WHERE"
LINE 3: WHERE
^
QUERY: SELECT address.id, event.name, address.geom
FROM event JOIN person JOIN address JOIN person_address JOIN event_person
WHERE
person_address.event_id = event.id AND
event_person.event_id = event.id AND
person.id = event_person.person_id AND
person.name like
$1
CONTEXT: PL/pgSQL function search_creator(text) line 5 at RETURN QUERY
SQL state: 42601
I cannot see or fix the problem. I tried replacing AND with || in the WHERE clause, but nothing changed.
What should I do?
EDIT
This is the code I have now and I get an empty table, even though I should get results, according to my database data that I checked.
CREATE FUNCTION search_person(name character(600)) RETURNS TABLE(address_id bigint, address_geom geometry, event_name character(200)) AS $$
BEGIN
RETURN QUERY EXECUTE
'SELECT address.id, address.geom, event.name
FROM
person
JOIN event_creator ON event_person.person_id = person.id
JOIN event ON event.id = event_person.event_id
JOIN person_address ON person_address.event_id = event.id
JOIN address ON address.id = cep.address_id
WHERE person.name LIKE $1'
USING name;
END;
$$
LANGUAGE plpgsql;
When creating a PL/pgSQL function, the function body is saved as string literal as is. Only superficial syntax checks are applied. Contained statements are not actually executed or tested on a deeper level.
However, basic syntax errors like you have in your query string would still be detected in actual SQL statements. But you are using dynamic SQL with EXECUTE. The statement is contained in a nested string literal and is your responsibility alone.
This seems to be misguided to begin with. There is no apparent reason for dynamic SQL. (Unless you have very uneven data distribution and want to force Postgres to generate a custom plan for each input value.)
If you had used a plain SQL statement, you would have gotten the error message at creation time:
CREATE OR REPLACE FUNCTION search_person(name text) -- still incorrect!
RETURNS TABLE(address_id integer, address_geom text, event_name text) AS
$func$
BEGIN
RETURN QUERY
SELECT address.id, event.name, address.geom
FROM event JOIN person JOIN address JOIN person_address JOIN event_person
WHERE
person_address.event_id = event.id AND
event_person.event_id = event.id AND
person.id = event_person.person_id AND
person.name like $1; -- still $1, but refers to func param now!
END
$func$ LANGUAGE plpgsql;
The SQL statement is still invalid. [INNER] JOIN requires a join condition - like Nick commented. And I don't see the need for PL/pgSQL at all. A simple SQL function should serve well:
CREATE FUNCTION search_person(name text)
RETURNS TABLE(address_id integer, address_geom text, event_name text) AS
$func$
SELECT a.id, a.geom, e.name -- also fixed column order to match return type
FROM person AS p
JOIN event_person AS ep ON ep.person_id = p.id
JOIN event AS e ON e.id = ep.event_id
JOIN person_address AS pa ON pa.event_id = e.id
JOIN address AS a ON a.id = pa.address_id -- missing join condition !!
WHERE p.name LIKE $1;
$func$ LANGUAGE sql;
I rewrote the query to fix syntax error, using table aliases for better readability. Finally, I also added one more missing condition based on an educated guess: a.id = pa.address_id.
Now it should work.
Related:
plpgsql function not inserting data as intended
Difference between language sql and language plpgsql in PostgreSQL functions
Or no function at all, just use a prepared statement instead. Example:
Split given string and prepare case statement
If you need dynamic SQL after all, pass values with the USING clause like you had it and make sure to defend against SQL injection when concatenating queries. Postgres provides various tools:
SQL injection in Postgres functions vs prepared queries
Define table and column names as arguments in a plpgsql function?
Table name as a PostgreSQL function parameter

Oracle SQL Create function or procedure returning a table

In SQL Server, I can just use 'RETURNS TABLE' and it will do the job. But I can't find how to do the same in Oracle SQL
I have the following SELECT statement that needs to be put in a function or procedure:
SELECT a.CodAcord, a.Descr
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.codSoce = codSoce;
codSoce is an INTEGER IN parameter, and I need to return a.CodAcord and a.Descr as result from my function/procedure.
Is there a simple way to do this? Without having to deal with temp variables and/or advanced content...
EDIT: Aditional info:
- I need to return a.CodAcord and a.Descr, but when I did some research to know how to return more than one variable using SQL Functions or Procedures, all I could find was that this was only possible by returning a TABLE. If there's a way to return more than one item from a Function or Procedure, please let me know.
- The use of Functions or Procedures is strictly required.
- I'm using SQL Developer.
You can achieve a table as a return value from function by using a pipelined table function. Please see the example below:
-- Create synthetic case
CREATE TABLE Acord AS
SELECT rownum CodAcord, 'Description ' || rownum Descr
FROM dual CONNECT BY LEVEL <= 5;
CREATE TABLE FreqSoce AS
SELECT rownum CodSoce, rownum CodAcord
FROM dual CONNECT BY LEVEL <= 10;
-- Test dataset
SELECT a.CodAcord, a.Descr
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.CodSoce = 10;
-- Here begins actual code
-- Create an object type to hold each table row
CREATE OR REPLACE TYPE typ_acord AS OBJECT(
CodAcord NUMBER,
Descr VARCHAR2(40)
);
/
-- Create a collection type to hold all result set rows
CREATE OR REPLACE TYPE tab_acord AS TABLE OF typ_acord;
/
-- Our function that returns a table
CREATE OR REPLACE FUNCTION getAcord(pCodSoce IN NUMBER)
RETURN tab_acord PIPELINED
AS
BEGIN
FOR x IN (SELECT a.CodAcord, a.Descr
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.CodSoce = pCodSoce)
LOOP
PIPE ROW (typ_acord(x.CodAcord, x.Descr));
END LOOP;
END;
/
-- Testing the function (please note the TABLE operator)
SELECT * FROM TABLE(getAcord(5));
Take the following as a code template:
CREATE OR REPLACE PACKAGE tacord AS
TYPE ttabAcord IS TABLE OF ACord%ROWTYPE;
END tacord;
/
show err
CREATE OR REPLACE PACKAGE BODY tacord AS
BEGIN
NULL;
END tacord;
/
show err
CREATE OR REPLACE FUNCTION demo RETURN tacord.ttabAcord AS
to_return tacord.ttabAcord;
BEGIN
SELECT a.*
BULK COLLECT INTO to_return
FROM FreqSoce f
LEFT JOIN Acord a ON a.CodAcord = f.CodAcord
WHERE f.codSoce = codSoce
;
RETURN to_return;
END demo;
/
show err
Key points:
%ROWTYPE represents the datatype of a database table's record
BULK COLLECT INTO inserts a complete result set into a plsql data structure
Iteration over the table contents is availabel by the plsql collection methods, namely .FIRST, .LAST, .NEXT.

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;