How to resolve error: return columns doesn't match expected columns? - postgresql

I write function in PostgreSQL. I am trying to fetch data from db by passing parameters in function .But i am getting error" ERROR: structure of query does not match function result type".
Here i created a function in which i am using union to get unique values.In first select stat. i amsetting value for account total that is used in below code.
Function:
CREATE OR REPLACE FUNCTION GetDeptListForViewModifyJointUsePercentages ( p_nInstID numeric,p_nDeptID numeric)
RETURNS Table(
res_ndept_id numeric,
res_salaryPercent character varying,
res_nclient_cpc_mapping_id numeric,
res_CPCCODE character varying,
res_sdept_name character varying,
res_sclient_dept_id character varying,
res_sAlternateJointUsePercentage character varying
)
AS $$
declare v_AccountTotal numeric(18,2);
BEGIN
RETURN QUERY
select SUM(CAST (coalesce(eam.npayroll_amt, null) AS numeric(18,2))) as AccountTotal
FROM Account acct
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = acct.naccount_id
AND acct.ninst_id =p_nInstID
where acct.ndept_id =p_nDeptID ;
SELECT *
FROM
(select dep.ndept_id ,( CASE
WHEN v_AccountTotal =0 THEN 0
ELSE Round(SUM(CAST(coalesce(eam.npayroll_amt, null) AS numeric(18,2)) * cac.npercentage_assigned/100) / v_AccountTotal *100,null)
END
) as salaryPercent
,cac.nclient_cpc_mapping_id,client.sclient_cpc AS CPCCODE,
dep.sdept_name,dep.sclient_dept_id,'NO' ASsAlternateJointUsePercentage
FROM account
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = account .naccount_id AND account .ninst_id=p_nInstID
INNER JOIN accountcpcmapping acm
ON acm.naccount_cpc_mapping_id = account.naccount_cpc_mapping_id
INNER JOIN cpcaccountcpcmapping as cac
ON cac.naccount_cpc_mapping_id=acm.naccount_cpc_mapping_id
INNER JOIN clientcostpoolcodes as client
ON client.nclient_cpc_mapping_id=cac.nclient_cpc_mapping_id
INNER JOIN mastercostpoolcodes
ON mastercostpoolcodes .nmaster_cpc_id= client.nmaster_cpc_id
INNER JOIN department as dep
ON account.ndept_id=dep.ndept_id and dep.balternate_jointuse_percentage=FALSE
where account.ndept_id =p_nDeptID
Group by dep.ndept_id,cac.nclient_cpc_mapping_id,client.sclient_cpc
,dep.sdept_name,dep.sclient_dept_id, dep.balternate_jointuse_percentage
UNION
SELECT Department_1.ndept_id, a_1.SumByCPC, clientcostpoolcodes.nclient_cpc_mapping_id, clientcostpoolcodes .sclient_cpc, Department_1.sdept_name, Department_1.sclient_dept_id , 'YES' AS sAlternateJointUsePercentage
FROM department AS Department_1 LEFT OUTER JOIN
mastercostpoolcodes RIGHT OUTER JOIN
clientcostpoolcodes RIGHT OUTER JOIN
(SELECT JointUseStatistics_1.nccp_code, SUM(JointUseStatistics_1.npercent) /
(SELECT SUM(jointusestatistics.npercent) AS SumByCPC
FROM jointusestatistics INNER JOIN
roomdepartmentmapping ON jointusestatistics.nroom_allocation_id = roomdepartmentmapping .nroom_allocation_id
WHERE (roomdepartmentmapping .ndept_id = Room_1.ndept_id)) * 100 AS SumByCPC,
Room_1.ndept_id
FROM jointusestatistics JointUseStatistics_1 INNER JOIN roomdepartmentmapping AS Room_1 ON JointUseStatistics_1.nroom_allocation_id = Room_1.nroom_allocation_id
GROUP BY JointUseStatistics_1.nccp_code, JointUseStatistics_1.npercent, Room_1.ndept_id) AS a_1 ON
clientcostpoolcodes .nclient_cpc_mapping_id = a_1.nccp_code ON mastercostpoolcodes .nmaster_cpc_id = clientcostpoolcodes .nmaster_cpc_id ON
Department_1.ndept_id = a_1.ndept_id
WHERE (Department_1.balternate_jointuse_percentage = 'TRUE')
AND (Department_1.ninst_id= p_nInstID)
)AS My
where (ndept_id= p_nDeptID )
ORDER BY My.sdept_name,My.CPCCODE ;
END;
$$ LANGUAGE plpgsql;

I think what you want is the first query to just retrieve the count, store that in your variable, then use that variable in the second query which is the actual source of the result of the function.
So the first query should not use return query but select .. into .. store put the result into the variable.
Then you can prefix the second query with return query to return its result:
CREATE OR REPLACE FUNCTION GetDeptListForViewModifyJointUsePercentages ( p_nInstID numeric,p_nDeptID numeric)
RETURNS Table(
res_ndept_id numeric,
res_salaryPercent character varying,
res_nclient_cpc_mapping_id numeric,
res_CPCCODE character varying,
res_sdept_name character varying,
res_sclient_dept_id character varying,
res_sAlternateJointUsePercentage character varying
)
AS $$
declare
v_AccountTotal numeric(18,2);
BEGIN
-- no RETURN query here!!
select SUM(CAST (coalesce(eam.npayroll_amt, null) AS numeric(18,2)))
into v_accounttotal --<<< store result in variable
FROM Account acct
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = acct.naccount_id
AND acct.ninst_id =p_nInstID
where acct.ndept_id = p_nDeptID;
-- this is the query that should be returned
RETURN QUERY
SELECT *
FROM (
select dep.ndept_id,
CASE WHEN v_AccountTotal = 0 THEN 0
ELSE Round(SUM(CAST(coalesce(eam.npayroll_amt, null) AS numeric(18,2)) * cac.npercentage_assigned/100) / v_AccountTotal *100,null)
END as salaryPercent,
cac.nclient_cpc_mapping_id,
client.sclient_cpc AS CPCCODE,
dep.sdept_name,
dep.sclient_dept_id,
'NO' AS sAlternateJointUsePercentage
FROM account
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = account .naccount_id AND account .ninst_id=p_nInstID
INNER JOIN accountcpcmapping acm
ON acm.naccount_cpc_mapping_id = account.naccount_cpc_mapping_id
INNER JOIN cpcaccountcpcmapping as cac
ON cac.naccount_cpc_mapping_id=acm.naccount_cpc_mapping_id
INNER JOIN clientcostpoolcodes as client
ON client.nclient_cpc_mapping_id=cac.nclient_cpc_mapping_id
INNER JOIN mastercostpoolcodes
ON mastercostpoolcodes .nmaster_cpc_id= client.nmaster_cpc_id
INNER JOIN department as dep
ON account.ndept_id=dep.ndept_id and dep.balternate_jointuse_percentage=FALSE
where account.ndept_id =p_nDeptID
Group by dep.ndept_id,cac.nclient_cpc_mapping_id,client.sclient_cpc
,dep.sdept_name,dep.sclient_dept_id, dep.balternate_jointuse_percentage
UNION
SELECT Department_1.ndept_id, a_1.SumByCPC, clientcostpoolcodes.nclient_cpc_mapping_id,
clientcostpoolcodes .sclient_cpc, Department_1.sdept_name, Department_1.sclient_dept_id,
'YES' AS sAlternateJointUsePercentage
FROM department AS Department_1
LEFT OUTER JOIN mastercostpoolcodes --<<< missing join condition !!
RIGHT OUTER JOIN clientcostpoolcodes --<<< missing join condition !!
RIGHT OUTER JOIN (
SELECT JointUseStatistics_1.nccp_code,
SUM(JointUseStatistics_1.npercent) /
(SELECT SUM(jointusestatistics.npercent) AS SumByCPC
FROM jointusestatistics
INNER JOIN roomdepartmentmapping ON jointusestatistics.nroom_allocation_id = roomdepartmentmapping .nroom_allocation_id
WHERE (roomdepartmentmapping .ndept_id = Room_1.ndept_id)) * 100 AS SumByCPC,
Room_1.ndept_id
FROM jointusestatistics JointUseStatistics_1
INNER JOIN roomdepartmentmapping AS Room_1 ON JointUseStatistics_1.nroom_allocation_id = Room_1.nroom_allocation_id
GROUP BY JointUseStatistics_1.nccp_code, JointUseStatistics_1.npercent, Room_1.ndept_id
) AS a_1 ON clientcostpoolcodes.nclient_cpc_mapping_id = a_1.nccp_code
ON mastercostpoolcodes.nmaster_cpc_id = clientcostpoolcodes.nmaster_cpc_id --<< that should be somewhere else
ON Department_1.ndept_id = a_1.ndept_id --<< ??????
WHERE (Department_1.balternate_jointuse_percentage = 'TRUE')
AND (Department_1.ninst_id= p_nInstID)
) AS My
where (ndept_id= p_nDeptID )
ORDER BY My.sdept_name,My.CPCCODE;
END;
$$ LANGUAGE plpgsql;
Note that there are several syntax errors in your second query because the join conditions are scattered around the query. I have tried to highlight them.

In Postgresql, function returns a single value. But in your example, you are trying to return multiple outputs in terms of number of rows and columns.
You can alternatively use a stored procedure and save the output in a temporary table. Later use that table for your use.

Related

PgSQL function returning table and extra data computed in process

In PgSQL I make huge select, and then I want count it's size and apply some extra filters.
execute it twice sound dumm,
so I wrapped it in function
and then "cache" it and return union of filtered table and extra row at the end where in "id" column store size
with q as (select * from myFunc())
select * from q
where q.distance < 400
union all
select count(*) as id, null,null,null
from q
but it also doesn't look like proper solution...
and so the question: is in pg something like "generator function" or any other stuff that can properly solve this ?
postgreSQL 13
myFunc aka "selectItemsByRootTag"
CREATE OR REPLACE FUNCTION selectItemsByRootTag(
in tag_name VARCHAR(50)
)
RETURNS table(
id BIGINT,
name VARCHAR(50),
description TEXT,
/*info JSON,*/
distance INTEGER
)
AS $$
BEGIN
RETURN QUERY(
WITH RECURSIVE prod AS (
SELECT
tags.name, tags.id, tags.parent_tags
FROM
tags
WHERE tags.name = (tags_name)
UNION
SELECT c.name, c.id , c.parent_tags
FROM
tags as c
INNER JOIN prod as p
ON c.parent_tags = p.id
)
SELECT
points.id,
points.name,
points.description,
/*points.info,*/
points.distance
from points
left join tags on points.tag_id = tags.id
where tags.name in (select prod.name from prod)
);
END;
$$ LANGUAGE plpgsql;
as a result i want see maybe set of 2 table or generator function that yield some intermediate result not shure how exacltly it should look
demo
CREATE OR REPLACE FUNCTION pg_temp.selectitemsbyroottag(tag_name text, _distance numeric)
RETURNS TABLE(id bigint, name text, description text, distance numeric, count bigint)
LANGUAGE plpgsql
AS $function$
DECLARE _sql text;
BEGIN
_sql := $p1$WITH RECURSIVE prod AS (
SELECT
tags.name, tags.id, tags.parent_tags
FROM
tags
WHERE tags.name ilike '%$p1$ || tag_name || $p2$%'
UNION
SELECT c.name, c.id , c.parent_tags
FROM
tags as c
INNER JOIN prod as p
ON c.parent_tags = p.id
)
SELECT
points.id,
points.name,
points.description,
points.distance,
count(*) over ()
from points
left join tags on points.tag_id = tags.id
where tags.name in (select prod.name from prod)
and points.distance > $p2$ || _distance
;
raise notice '_sql: %', _sql;
return query execute _sql;
END;
$function$
You can call it throug following way
select * from pg_temp.selectItemsByRootTag('test',20);
select * from pg_temp.selectItemsByRootTag('test_8',20) with ORDINALITY;
The 1 way to call the function, will have a row of total count total number of rows. Second way call have number of rows plus a serial incremental number.
I also make where q.distance < 400 into function input argument.
selectItemsByRootTag('test',20); means that q.distance > 20 and tags.name ilike '%test%'.

syntax error at or near " " postgresql function

I have an function in postgresql. I don't know whether I made it a bit complicated. I have a select query and I need to append some more expressions into that. I am passing those expressions as input parameters.
CREATE OR REPLACE FUNCTION get_alldocuments(currUser text,
queryExp text,
query0 text,
query1 text)
RETURNS TABLE(fileleafref text,contenttypename text,fsobjtype text,id integer,conttypeid integer,contenttypeid integer,docicon text,encodedabsurl text,refId integer,filesizedisplay integer,created date,
createdby text,version text) AS $$
BEGIN
RETURN QUERY EXECUTE 'select distinct c.fileleafref, ct.contenttypename,c.fsobjtype,c.id,c.conttypeid,d.contenttypeid,c.docicon,c.encodedabsurl,d.ref_id as RefId,c.filesizedisplay,d.created,
d.createdby,c.version from public.documents d inner join public.documentcontent c on d.id=c.documentid inner join conttype ct on ct.id = c.conttypeid
where '
|| quote_literal(queryExp)
|| ' versionflag=true and c.permissiontype ="1" or (c.permissiontype="3" and c.createdby =currUser) order by d.created desc limit 300)
union
(select distinct c.fileleafref, ct.contenttypename,c.fsobjtype,c.id,c.conttypeid,d.contenttypeid,c.docicon,c.encodedabsurl,d.ref_id as RefId,c.filesizedisplay,d.created,
d.createdby,c.version from public.documents d inner join public.documentcontent c on d.id=c.documentid inner join conttype ct on ct.id = c.conttypeid '
|| quote_ident(query0) ||
' where' || quote_ident(queryExp) || 'versionflag=true '|| quote_ident(query1) ||
' order by d.created desc limit 300)';
END
$$ LANGUAGE plpgsql;
in the function I am trying to append different expressions at different places of my select query.
The input variables are
currUser - user1
queryExp - metadata #> ''{"Year":"2011"}'' and metadata #> ''{"Country":"US"}'' and
query0 - ,jsonb_array_elements(c.securitygppermission) as e(users) ,jsonb_array_elements_text(c.userspermission) as p(perm)
query1 - and (c.permissiontype='2' and e.users ->>'Deny'='false' and e.users ->>'Allow'='true' and e.users ->>'GroupName'='Manager' and p.perm ='user1')
The complete select query sample is
select * from get_alldocuments('user1','metadata #> ''{"Year":"2011"}'' and metadata #> ''{"Country":"US"}'' and ',',jsonb_array_elements(c.securitygppermission) as e(users) ,jsonb_array_elements_text(c.userspermission) as p(perm)','and (c.permissiontype="2" and e.users ->>"Deny"="false" and e.users ->>"Allow"="true" and e.users ->>"GroupName"="Manager" and p.perm ="user1")')
While executing the function its showing an error like
ERROR: syntax error at or near "versionflag"
LINE 3: ...11"}'' and metadata #> ''{"Country":"US"}'' and ' versionfla...
^
QUERY: select distinct c.fileleafref, ct.contenttypename,c.fsobjtype,c.id,c.conttypeid,d.contenttypeid,c.docicon,c.encodedabsurl,d.ref_id as RefId,c.filesizedisplay,d.created,
d.createdby,c.version from public.documents d inner join public.documentcontent c on d.id=c.documentid inner join conttype ct on ct.id = c.conttypeid
where 'metadata #> ''{"Year":"2011"}'' and metadata #> ''{"Country":"US"}'' and ' versionflag=true and c.permissiontype ="1" or (c.permissiontype="3" and c.createdby =currUser) order by d.created desc limit 300)
union
(select distinct c.fileleafref, ct.contenttypename,c.fsobjtype,c.id,c.conttypeid,d.contenttypeid,c.docicon,c.encodedabsurl,d.ref_id as RefId,c.filesizedisplay,d.created,
d.createdby,c.version from public.documents d inner join public.documentcontent c on d.id=c.documentid inner join conttype ct on ct.id = c.conttypeid ",jsonb_array_elements(c.securitygppermission) as e(users) ,jsonb_array_elements_text(c.userspermission) as p(perm)" where"metadata #> '{""Year"":""2011""}' and metadata #> '{""Country"":""US""}' and "versionflag=true "and (c.permissiontype=2 and e.users ->>""Deny""=""false"" and e.users ->>""Allow""=""true"" and e.users ->>""GroupName""=""Manager"" and p.perm =""user1"")" order by d.created desc limit 300)
CONTEXT: PL/pgSQL function get_alldocuments(text,text,text,text) line 4 at RETURN QUERY
********** Error **********
What is the issue? Am I concatenated in the correct way?
If I parse your query correctly then this parts looks wrong:
where 'metadata #> ''{"Year":"2011"}'' and metadata #> ''{"Country":"US"}'' and ' versionflag=true
Between the where and versionflag=true you have a string literal (single quoted string). So to postgresql it basically looks like you wrote where 'string' versionflag=true which doesn't make any sense. Looks like a comparison and a logical operator are missing.
I resolve the issue. 'quote_indent' created the issue. The final function is
CREATE OR REPLACE FUNCTION public.get_alldocuments(
curruser text,
queryexp text,
query0 text,
query1 text)
RETURNS TABLE(fileleafref character varying, contenttypename character varying, fsobjtype character varying, id integer, conttypeid integer, contenttypeid character varying, docicon character varying, encodedabsurl character varying, refid character varying, filesizedisplay integer, created date, createdby character varying, version character varying)
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
BEGIN
RETURN QUERY EXECUTE '(select distinct c.fileleafref, ct.contenttypename,c.fsobjtype,c.id,c.conttypeid,cast(d.contenttypeid as varchar) as contenttypeid,c.docicon,c.encodedabsurl,d.ref_id as RefId,c.filesizedisplay,cast(d.created as date) created,
d.createdby,c.version from public.documents d inner join public.documentcontent c on d.id=c.documentid inner join conttype ct on ct.id = c.conttypeid
where '
|| queryExp
|| ' versionflag=true and c.permissiontype=''1'' or (c.permissiontype=''3'' and c.createdby =''|| curruser ||'') order by created desc limit 300)
union
(select distinct c.fileleafref, ct.contenttypename,c.fsobjtype,c.id,c.conttypeid,cast(d.contenttypeid as varchar) as contenttypeid,c.docicon,c.encodedabsurl,d.ref_id as RefId,c.filesizedisplay,cast(d.created as date) created,
d.createdby,c.version from public.documents d inner join public.documentcontent c on d.id=c.documentid inner join conttype ct on ct.id = c.conttypeid '
|| query0 ||
' where ' || queryExp || ' versionflag=true ' || query1 ||
'order by created desc limit 300);';
END
$BODY$;

I am getting Dollar sign unterminated

I want to create a function like below which inserts data as per the input given. But I keep on getting an error about undetermined dollar sign.
CREATE OR REPLACE FUNCTION test_generate
(
ref REFCURSOR,
_id INTEGER
)
RETURNS refcursor AS $$
DECLARE
BEGIN
DROP TABLE IF EXISTS test_1;
CREATE TEMP TABLE test_1
(
id int,
request_id int,
code text
);
IF _id IS NULL THEN
INSERT INTO test_1
SELECT
rd.id,
r.id,
rd.code
FROM
test_2 r
INNER JOIN
raw_table rd
ON
rd.test_2_id = r.id
LEFT JOIN
observe_test o
ON
o.raw_table_id = rd.id
WHERE o.id IS NULL
AND COALESCE(rd.processed, 0) = 0;
ELSE
INSERT INTO test_1
SELECT
rd.id,
r.id,
rd.code
FROM
test_2 r
INNER JOIN
raw_table rd
ON rd.test_2_id = r.id
WHERE r.id = _id;
END IF;
DROP TABLE IF EXISTS tmp_test_2_error;
CREATE TEMP TABLE tmp_test_2_error
(
raw_table_id int,
test_2_id int,
error text,
record_num int
);
INSERT INTO tmp_test_2_error
(
raw_table_id,
test_2_id,
error,
record_num
)
SELECT DISTINCT
test_1.id,
test_1.test_2_id,
'Error found ' || test_1.code,
0
FROM
test_1
WHERE 1 = 1
AND data_origin.id IS NULL;
INSERT INTO tmp_test_2_error
SELECT DISTINCT
test_1.id,
test_1.test_2_id,
'Error found ' || test_1.code,
0
FROM
test_1
INNER JOIN
data_origin
ON
data_origin.code = test_1.code
WHERE dop.id IS NULL;
DROP table IF EXISTS test_latest;
CREATE TEMP TABLE test_latest AS SELECT * FROM observe_test WHERE 1 = 2;
INSERT INTO test_latest
(
raw_table_id,
series_id,
timestamp
)
SELECT
test_1.id,
ds.id AS series_id,
now()
FROM
test_1
INNER JOIN data_origin ON data_origin.code = test_1.code
LEFT JOIN
observe_test o ON o.raw_table_id = test_1.id
WHERE o.id IS NULL;
CREATE TABLE latest_observe_test as Select * from test_latest where 1=0;
INSERT INTO latest_observe_test
(
raw_table_id,
series_id,
timestamp,
time
)
SELECT
t.id,
ds.id AS series_id,
now(),
t.time
FROM
test_latest t
WHERE t.series_id IS DISTINCT FROM observe_test.series_id;
DELETE FROM test_2_error re
USING t
WHERE t.test_2_id = re.test_2_id;
INSERT INTO test_2_error (test_2_id, error, record_num)
SELECT DISTINCT test_2_id, error, record_num FROM tmp_test_2_error ORDER BY error;
UPDATE raw_table AS rd1
SET processed = case WHEN tre.raw_table_id IS null THEN 2 ELSE 1 END
FROM test_1 tr
LEFT JOIN
tmp_test_2_error tre ON tre.raw_table_id = tr.id
WHERE rd1.id = tr.id;
OPEN ref FOR
SELECT 1;
RETURN ref;
OPEN ref for
SELECT o.* from observe_test o
;
RETURN ref;
OPEN ref FOR
SELECT
rd.id,
ds.id AS series_id,
now() AS timestamp,
rd.time
FROM test_2 r
INNER JOIN raw_table rd ON rd.test_2_id = r.id
INNER JOIN data_origin ON data_origin.code = rd.code
WHERE o.id IS NULL AND r.id = _id;
RETURN ref;
END;
$$ LANGUAGE plpgsql VOLATILE COST 100;
I am not able to run this procedure.
Can you please help me where I have done wrong?
I am using squirrel and face the same question as you.
until I found that:
-- Note that if you want to create the function under Squirrel SQL,
-- you must go to Sessions->Session Properties
-- then SQL tab and change the Statement Separator from ';' to something else
-- (for intance //). Otherwise Squirrel SQL sends one piece to the server
-- that stops at the first encountered ';', and the server cannot make
-- sense of it. With the separator changed as suggested, you type everything
-- as above and end with
-- ...
-- end;
-- $$ language plpgsql
-- //
--
-- You can then restore the default separator, or use the new one for
-- all queries ...
--

conditional join with input value in postgreslq function

I have three tables:
create table id_table (
id integer
);
insert into id_table values (1),(2),(3);
create table alphabet_table (
id integer,
letter text
);
insert into alphabet_table values (1,'a'),(2,'b'),(3,'c');
create table greek_table (
id integer,
letter text
);
insert into greek_table values (1,'alpha'),(2,'beta');
I like to create a function that join id_table with either alphabet_table or greek_table on id. The choice of the table depends on an input value specified in the function. I wrote:
CREATE OR REPLACE FUNCTION choose_letters(letter_type text)
RETURNS table (id integer,letter text) AS $$
BEGIN
RETURN QUERY
select t1.id,
case when letter_type = 'alphabet' then t2.letter else t3.letter end as letter
from id_table t1,
alphabet_table t2 ,
greek_table t3
where t1.id = t2.id and t1.id = t3.id;
END;
$$LANGUAGE plpgsql;
I ran select choose_letter('alphabet'). The problem with this code is that when id_table joins with alphabet_table, it does not pick up id, No 3. It seems that inner joins are done for both alphabet_table and greek_table (so it only picks up the common ids, 1 and 2). To avoid this problem, I wrote:
CREATE OR REPLACE FUNCTION choose_letters(letter_type text)
RETURNS table (id integer, letter text) AS $$
BEGIN
RETURN QUERY
select t1.id,
case when letter_type = 'alphabet' then t2.letter else t3.letter end as letter
from id_table t1
left join alphabet_table t2 on t1.id=t2.id
left join greek_table t3 on t1.id=t3.id
where t2.letter is not null or t3.letter is not null;
END;
$$LANGUAGE plpgsql;
Now it pick up all the 3 ids when id_table and alphabet_table join. However, When I ran select choose_letter('greek'). The id no. 3 appears with null value in letter column despite the fact that I specified t3.letter is not null in where clause.
What I'm looking for is that when I ran select choose_letters('alphabet'), the output needs to be (1,'a'), (2,'b'),(3,'c'). select choose_letters('greek') should produce (1,'alpha'),(2,'beta). No missing values nor null. How can I accomplish this?
Learn to use proper, explicit JOIN syntax. Simple rule: Never use commas in the FROM clause.
You can do what you want with LEFT JOIN and some other logic:
select i.id,
coalesce(a.letter, g.letter) as letter
from id_table i left join
alphabet_table a
on i.id = a.id and letter_type = 'alphabet' left join
greek_table g
on i.id = g.id and letter_type <> 'alphabet'
where a.id is not null or g.id is not null;
The condition using letter_type needs to be in the on clauses. Otherwise, alphabet_table will always have a match.
Gordon Linoff's answer above is certainly correct, but here is an alternative way to write the code.
It may or may not be better from a performance perspective, but it is logically equivalent. If performance is a concern you'd need to run EXPLAIN ANALYZE on the query an inspect the plan, and do other profiling.
Some good parts about this are the inner join makes the join clause and where clause simpler and easier to reason about. It's also more straight forward for the execution engine to parallelize the query.
On the downside it looks like code duplication, however, the DRY principle is often misapplied to SQL. Repeating code is less important than repeating data reads. What you're aiming to do is not scan the same data multiple times.
If there is no index on the fields you are joining or the letter_type then this could end up doing a full table scan twice, and be worse. If you do have the indexes then it can do it with index range scans nicely.
SELECT
i.id,
a.letter
FROM id_table i
INNER JOIN alphabet_table a
ON i.id = a.id
WHERE letter_type = 'alphabet'
UNION ALL
SELECT
i.id,
g.letter
FROM id_table i
INNER JOIN greek_table g
ON i.id = g.id
WHERE letter_type <> 'alphabet'
The first problem is your tables or not structured properly, You would have created single table like char_table (id int, letter text, type text) type will specify whether it is alphabet or Greek.
Another solution is you can write two SQL queries one in if condition other one is in else part

PostgreSQL structure of function that return a query

Given a PostgreSQL function that returns a query:
CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
RETURNS TABLE (
txt text -- visible as OUT parameter inside and outside function
, cnt bigint
, ratio bigint) AS
$func$
BEGIN
RETURN QUERY
SELECT t.txt
, count(*) AS cnt -- column alias only visible inside
, (count(*) * 100) / _max_tokens -- I added brackets
FROM (
SELECT t.txt
FROM token t
WHERE t.chartype = 'ALPHABETIC'
LIMIT _max_tokens
) t
GROUP BY t.txt
ORDER BY cnt DESC; -- note the potential ambiguity
END
$func$ LANGUAGE plpgsql;
How can I retrieve the structure of this function? I mean, I know that this function will return the txt, cnt and ratio columns, but how can I make a query that returns these column names? I was trying to find these columns names on information_schema schema, but I couldn't.
The expected result of this hypothetical query would be something like this:
3 results found:
---------------------------------
?column_name? | ?function_name?
---------------------------------
txt word_frequency
cnt word_frequency
ratio word_frequency
This information is stored in pg_proc
SELECT unnest(p.proargnames) as column_name,
p.proname as function_name
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'public'
AND p.proname = 'word_frequency'
Based on the answer of a_horse_with_no_name, I came with this final version:
SELECT
column_name,
function_name
FROM
(
SELECT
unnest(p.proargnames) as column_name,
unnest(p.proargmodes) as column_type,
p.proname as function_name
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'public'
AND p.proname = 'my_function'
) as temp_table
WHERE column_type = 't';
I simply omitted the arguments, returning only the columns that the function returns