Order by not working on PostgreSQL function - postgresql

I am new to PostgreSQl and I have created one function which is mentioned below. What I want that when I am running SELECT query in query tool then it working fine and giving result what I want but when I am executing below function then order by not working. Stageno is a "character varying". How can I fix that ?
-- FUNCTION: public.getauditquestion(integer)
-- DROP FUNCTION public.getauditquestion(integer);
CREATE OR REPLACE FUNCTION public.getauditquestion(
auditid integer)
RETURNS TABLE(audit_ques_id bigint, audit_id integer, activityname character varying, question_id integer, question character varying, answer_type integer, answer_lov integer, answertypename character varying, ok_notok integer, oknotokname character varying, answer_min character varying, answer_max character varying, uom integer, uomname character varying, correct_ans integer, created_by integer, created_date timestamp without time zone, stage character varying, stageno character varying, description character varying, severity character varying, who character varying, how character varying, "when" character varying, "where" character varying)
LANGUAGE 'plpgsql'
COST 100.0
VOLATILE
ROWS 1000.0
AS $function$
BEGIN
Return Query
select am.audit_ques_id, am.audit_id ,ddl3.dd_value AS activityName , am.question_id ,
ddl2.question AS question, am.answer_type, am.answer_lov, ddl4.dd_value AS answerTypeName, am.ok_notok ,
ddl5.dd_value AS okNotOkName, am.answer_min, am.answer_max, am.uom, ddl13.dd_value AS uomname, am.correct_ans,
am.created_by, am.created_date,ddl6.dd_value AS stage, ddl14.dd_value AS stage_no, ddl7.description, ddl8.dd_value AS severity,
ddl9.dd_value AS who , ddl10.dd_value AS how , ddl11.dd_value AS "when" , ddl12.dd_value AS "where"
from audit_ques_detail am left join audit_master ddl1 on ddl1.audit_id=am.audit_id
left join dd_type_details ddl3 on ddl3.dd_id=ddl1.activity_id
left join question_master ddl2 on ddl2.question_id=am.question_id
left join dd_type_details ddl4 on ddl4.dd_id = am.answer_type
left join dd_type_details ddl5 on ddl5.dd_id = am.ok_notok
left join dd_type_details ddl6 on ddl6.dd_id = ddl2.stage
left join question_master ddl7 on ddl7.question_id = am.question_id
left join dd_type_details ddl8 on ddl8.dd_id = ddl2.severity
left join dd_type_details ddl9 on ddl9.dd_id = ddl2.who
left join dd_type_details ddl10 on ddl10.dd_id = ddl2.how
left join dd_type_details ddl11 on ddl11.dd_id = ddl2.when
left join dd_type_details ddl12 on ddl12.dd_id = ddl2.where
left join dd_type_details ddl13 on ddl13.dd_id = am.uom
left join dd_type_details ddl14 on ddl14.dd_id = am.stage_no
where am.audit_id = auditid and am.isActive = 1 ORDER BY cast(ddl14.dd_value AS Integer);
END;
$function$;
ALTER FUNCTION public.getauditquestion(integer)
OWNER TO postgres;

Related

In a PostgresSQL function, is it possible to check if a column value matches a given parameter value?

In the WHERE clause of a SQL Server stored procedure, I can do this:
WHERE (*column_value* = #some_parameter OR #some_parameter IS NULL)
When I try to do the same thing in a PostgreSQL function, it throws an error. For example:
WHERE(FRQ.QuoteId = p_FilterQuote OR p_FilterQuote IS NULL)
produces the error: ***column "p_FilterQuote" does not exist.
The input parameter p_FilterQuote is declared and initialized at the top of the function like this:
p_filterquote integer DEFAULT NULL::integer
There must be a way to 'use' input parameters in a WHERE clause.
The entire function code is shown below.
-- FUNCTION: public.postgres_termpositionrawdata_ver3(character varying, date, character, character varying, character varying, character varying, character varying, character varying, integer, integer, character varying, character varying, character varying, character varying, character varying)
-- DROP FUNCTION public.postgres_termpositionrawdata_ver3(character varying, date, character, character varying, character varying, character varying, character varying, character varying, integer, integer, character varying, character varying, character varying, character varying, character varying);
CREATE OR REPLACE FUNCTION public.postgres_termpositionrawdata_ver3(
p_provider character varying,
p_date date,
p_correlationid character DEFAULT NULL::bpchar,
p_type character varying DEFAULT NULL::character varying,
p_productexclusionset character varying DEFAULT 'LevelTermPosition'::character varying,
p_ctmoptions character varying DEFAULT 'WOM'::character varying,
p_test character varying DEFAULT 'No'::character varying,
p_testaccountlogon character varying DEFAULT NULL::character varying,
p_averagetopx integer DEFAULT 5,
p_filterquote integer DEFAULT NULL::integer,
p_includeduplicatequotes character varying DEFAULT 'Yes'::character varying,
p_ignoreglobalpermissions character varying DEFAULT 'Yes'::character varying,
p_ignoredefaultproductexclusions character varying DEFAULT 'No'::character varying,
p_ignorelowstartresponseindicator character varying DEFAULT 'No'::character varying,
p_ignorevariableresponseindicator character varying DEFAULT 'No'::character varying)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
--DECLARE CONSTANTS
DECLARE
--DECLARE VARIABLES
v_Status varchar(10) = 'Success';BEGIN
-- assume success
-- SET CorrelationID
IF p_CorrelationId IS NULL THEN p_CorrelationId := public.swf_newid();
END IF;
DROP TABLE IF EXISTS FilteredRequests;
DROP TABLE IF EXISTS FilteredResponses;
DROP TABLE IF EXISTS Ranks;
CREATE TEMP TABLE FilteredRequests AS
SELECT * FROM public.dblink('srv_exchangemart', '
SELECT
FRQ.QuoteID
,DEO.Name AS OrganisationName
,DEO.Postcode AS OrganisationPostcode
,DEO.FRN AS OrganisationFRN
,DDQ.Date AS RequestDate
,DLB.LifeBasis
,DDC.Date AS CommencementDate
,DQF.QuotationFor
,LCR.LifeCriticalIllnessRiskRelationship
,TDP.TotalPermanentDisabilityCover
,CB.CommissionBasis
,DCT.CommissionType
,DG1.Gender AS Life1Gender
FROM FactRequest FRQ
INNER JOIN dimExchangeOrganisation DEO ON FRQ.ExchangeOrganisationID = DEO.ExchangeOrganisationId
INNER JOIN dimExchangeUser DEU ON FRQ.ExchangeUserId = DEU.ExchangeUserId
INNER JOIN dimLifeBasis DLB ON FRQ.LifeBasisId = DLB.LifeBasisID
INNER JOIN dimTotalPermanentDisabilityCover TDP ON FRQ.TotalPermanentDisabilityCoverId = TDP.TotalPermanentDisabilityCoverID
INNER JOIN dimDate DDQ ON FRQ.QuotationDateID = DDQ.DateID
INNER JOIN dimDate DDC ON FRQ.CommencementDateID = DDC.DateID
INNER JOIN dimExchangePanel DEP ON FRQ.ExchangePanelId = DEP.ExchangePanelId
INNER JOIN dimCommissionBasis CB ON FRQ.CommissionBasisId = CB.CommissionBasisId
INNER JOIN dimCommissionType DCT ON FRQ.CommissionTypeId = DCT.CommissionTypeId
INNER JOIN factClient FC ON FRQ.QuoteID = FC.QuoteID
INNER JOIN dimLifeCriticalIllnessRiskRelationship LCR ON FRQ.LifeCriticalIllnessRiskRelationshipId = LCR.LifeCriticalIllnessRiskRelationshipId
INNER JOIN dimQuotationFor DQF ON FRQ.QuotationForId = DQF.QuotationForId
INNER JOIN dimKeyPerson DKP ON FRQ.KeyPersonId = DKP.KeyPersonId
INNER JOIN dimBenefitBasis DBB ON FRQ.BenefitBasisId = DBB.BenefitBasisId
INNER JOIN dimIntegrator DI ON FRQ.ExchangeIntegratorId = DI.ExchangeIntegratorId
INNER JOIN dimGender DG1 ON FC.Life1GenderID = DG1.GenderID
INNER JOIN dimGender DG2 ON FC.Life2GenderID = DG2.GenderID
INNER JOIN dimSmoker DS1 ON FC.Life1SmokerID = DS1.SmokerID
INNER JOIN dimSmoker DS2 ON FC.Life2SmokerID = DS2.SmokerID
INNER JOIN dimOccupation DO1 ON FC.Life1OccupationId = DO1.OccupationID
INNER JOIN dimOccupation DO2 ON FC.Life2OccupationId = DO2.OccupationID
WHERE DDQ.Date = p_date
AND FRQ.ProductTypeId IN (53) -- TERM ONLY
AND DKP.KeyPerson = ''No'' -- Not Business
AND DBB.BenefitBasis = ''Benefit Led'' -- ONLY RETURNS BENEFIT LED QUOTES
AND (DEU.LogonId = p_TestAccountLogon OR (p_TestAccountLogon IS NULL
AND DI.DefaultExclusion = ''No''
AND DEO.DefaultExclusion = ''No''))
AND ( (p_IncludeDuplicateQuotes = ''No'' AND FRQ.IsDuplicateQuote = 0)
OR (p_IncludeDuplicateQuotes = ''Yes''))
AND (FRQ.QuoteId = p_FilterQuote OR p_FilterQuote IS NULL)
AND ( (p_CTMOptions = ''CTM Only''AND DEU.LogonId = ''CTM000'')
OR (p_CTMOptions = ''Exclude CTM'' AND DEU.LogonId != ''CTM000'')
OR (p_CTMOptions = ''WOM'')
)
')
AS DATA
(QuoteID integer, OrganisationName character varying, OrganisationPostcode character varying, OrganisationFRN character varying, RequestDate date,
LifeBasis character varying, CommencementDate date, QuotationCoverFor character varying,
LifeCIRiskRelationship character varying, TPDOption character varying, CommissionBasis character varying,
CommissionType character varying, Life1Gender character varying);
-- SELECT * FROM FilteredRequests;
END;
$BODY$;
The SQL statement is executed as it is on the remote database, and no parameter substitutions of any kind are performed.
You have to do that yourself:
DECLARE
sql text;
BEGIN
sql := format(
'SELECT ... WHERE(FRQ.QuoteId = %L::integer OR %L::integer IS NULL)',
p_FilterQuote,
p_FilterQuote
);
SELECT * FROM dblink('conn', sql) AS ...;
END;

Concatenating two integer columns works in isolation, but not in a function

Related to my question yesterday which was answered by #a_horse_with_no_name.
The following query works..
SELECT
CAST(FC.Life1monthofbirth AS VARCHAR(2)) || '/' || CAST(FC.Life1yearofbirth AS VARCHAR(4))
FROM
public.factclient FC;
...but when I have essentially the same code in a function...
,CAST(FC.Life1monthofbirth AS VARCHAR(2)) || '/' || CAST(FC.Life1yearofbirth AS VARCHAR(4)) AS Life1DOB
I get this error:
ERROR: operator is not unique: unknown / unknown
LINE 25: ,CAST(FC.Life1monthofbirth AS VARCHAR(2)) || '/' || CAST...
^
HINT: Could not choose a best candidate operator. You might need to add explicit type casts.
I don't understand why the first scenario works, but the second one doesn't. I'm using Postgres version 12 and writing my queries using pgAdmin 4.13
I have added the complete function code below. Apologies if it is not very readable.
CREATE OR REPLACE FUNCTION public.postgres_termpositionrawdata_ver2(
p_provider character varying,
p_date date,
p_correlationid character DEFAULT NULL::bpchar,
p_type character varying DEFAULT NULL::character varying,
p_productexclusionset character varying DEFAULT 'LevelTermPosition'::character varying,
p_ctmoptions character varying DEFAULT 'WOM'::character varying,
p_test character varying DEFAULT 'No'::character varying,
p_testaccountlogon character varying DEFAULT NULL::character varying,
p_averagetopx integer DEFAULT 5,
p_filterquote integer DEFAULT NULL::integer,
p_includeduplicatequotes character varying DEFAULT 'Yes'::character varying,
p_ignoreglobalpermissions character varying DEFAULT 'Yes'::character varying,
p_ignoredefaultproductexclusions character varying DEFAULT 'No'::character varying,
p_ignorelowstartresponseindicator character varying DEFAULT 'No'::character varying,
p_ignorevariableresponseindicator character varying DEFAULT 'No'::character varying)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
--DECLARE CONSTANTS
DECLARE
--DECLARE VARIABLES
v_Status varchar(10) = 'Success';BEGIN
-- assume success
-- SET CorrelationID
IF p_CorrelationId IS NULL THEN p_CorrelationId := public.swf_newid();
END IF;
DROP TABLE IF EXISTS FilteredRequests;
DROP TABLE IF EXISTS FilteredResponses;
DROP TABLE IF EXISTS Ranks;
CREATE TEMP TABLE FilteredRequests AS
SELECT * FROM public.dblink('srv_exchangemart',
'SELECT
FRQ.QuoteID
,DEO.Name AS OrganisationName
,DEO.Postcode AS OrganisationPostcode
,DEO.FRN AS OrganisationFRN
,DDQ.Date AS RequestDate
,DLB.LifeBasis
,DDC.Date AS CommencementDate
,CASE
WHEN FRQ.Term <> 0
THEN CAST (FRQ.Term AS Varchar(5)) || "Years"
WHEN FRQ.TermToAge <> 0
THEN "To Age " || CAST(FRQ.TermToAge AS Varchar(5))
ELSE "Unknown"
END AS PolicyTerm
,DQF.QuotationFor
,LCR.LifeCriticalIllnessRiskRelationship
,TDP.TotalPermanentDisabilityCover
,CB.CommissionBasis
,DCT.CommissionType
,DG1.Gender AS Life1Gender
,CONCAT_WS('|', FC.Life1MonthOfBirth, FC.Life1YearOfBirth) AS Life1DOB
,FC.Life1Age AS Life1Age
,FC.Life1QuarterlyAge
,DS1.Smoker AS Life1Smoker
,DO1.Occupation AS Life1Occupation
,DG2.Gender AS Life2Gender
,CONCAT_WS('|', FC.Life2MonthOfBirth, FC.Life2YearOfBirth) AS Life2DOB
,FC.Life2Age AS Life2Age
,FC.Life2QuarterlyAge
,DS2.Smoker AS Life2Smoker
,DO2.Occupation AS Life2Occupation
,DEP.PanelId
FROM ExchangeMart.dbo.FactRequest FRQ
INNER JOIN ExchangeMart.dbo.dimExchangeOrganisation DEO ON FRQ.ExchangeOrganisationID = DEO.ExchangeOrganisationId
INNER JOIN ExchangeMart.dbo.dimExchangeUser DEU ON FRQ.ExchangeUserId = DEU.ExchangeUserId
INNER JOIN ExchangeMart.dbo.dimLifeBasis DLB ON FRQ.LifeBasisId = DLB.LifeBasisID
INNER JOIN ExchangeMart.dbo.dimTotalPermanentDisabilityCover TDP ON FRQ.TotalPermanentDisabilityCoverId = TDP.TotalPermanentDisabilityCoverID
INNER JOIN ExchangeMart.dbo.dimDate DDQ ON FRQ.QuotationDateID = DDQ.DateID
INNER JOIN ExchangeMart.dbo.dimDate DDC ON FRQ.CommencementDateID = DDC.DateID
INNER JOIN ExchangeMart.dbo.dimExchangePanel DEP ON FRQ.ExchangePanelId = DEP.ExchangePanelId
INNER JOIN ExchangeMart.dbo.dimCommissionBasis CB ON FRQ.CommissionBasisId = CB.CommissionBasisId
INNER JOIN ExchangeMart.dbo.dimCommissionType DCT ON FRQ.CommissionTypeId = DCT.CommissionTypeId
INNER JOIN ExchangeMart.dbo.factClient FC ON FRQ.QuoteID = FC.QuoteID
INNER JOIN ExchangeMart.dbo.dimLifeCriticalIllnessRiskRelationship LCR ON FRQ.LifeCriticalIllnessRiskRelationshipId = LCR.LifeCriticalIllnessRiskRelationshipId
INNER JOIN ExchangeMart.dbo.dimQuotationFor DQF ON FRQ.QuotationForId = DQF.QuotationForId
INNER JOIN ExchangeMart.dbo.dimKeyPerson DKP ON FRQ.KeyPersonId = DKP.KeyPersonId
INNER JOIN ExchangeMart.dbo.dimBenefitBasis DBB ON FRQ.BenefitBasisId = DBB.BenefitBasisId
INNER JOIN ExchangeMart.dbo.dimIntegrator DI ON FRQ.ExchangeIntegratorId = DI.ExchangeIntegratorId
INNER JOIN ExchangeMart.dbo.dimGender DG1 ON FC.Life1GenderID = DG1.GenderID
INNER JOIN ExchangeMart.dbo.dimGender DG2 ON FC.Life2GenderID = DG2.GenderID
INNER JOIN ExchangeMart.dbo.dimSmoker DS1 ON FC.Life1SmokerID = DS1.SmokerID
INNER JOIN ExchangeMart.dbo.dimSmoker DS2 ON FC.Life2SmokerID = DS2.SmokerID
INNER JOIN ExchangeMart.dbo.dimOccupation DO1 ON FC.Life1OccupationId = DO1.OccupationID
INNER JOIN ExchangeMart.dbo.dimOccupation DO2 ON FC.Life2OccupationId = DO2.OccupationID
WHERE DDQ.Date = p_Date
AND FRQ.ProductTypeId IN (53) -- TERM ONLY
AND KeyPerson = "No" -- Not Business
AND DBB.BenefitBasis = "Benefit Led" -- ONLY RETURNS BENEFIT LED QUOTES
AND (DEU.LogonId = p_TestAccountLogon OR (p_TestAccountLogon IS NULL
AND DI.DefaultExclusion = "No"
AND DEO.DefaultExclusion = "No"))
AND ( (p_IncludeDuplicateQuotes = "No" AND FRQ.IsDuplicateQuote = 0)
OR (p_IncludeDuplicateQuotes = "Yes"))
AND (FRQ.QuoteId = p_FilterQuote OR p_FilterQuote IS NULL)
AND ( (p_CTMOptions = "CTM Only" AND DEU.LogonId = "CTM000")
OR (p_CTMOptions = "Exclude CTM" AND DEU.LogonId != "CTM000")
OR (p_CTMOptions = "WOM")
)
')
AS DATA(QuoteID INTEGER, OrganisationName character varying, OrganisationPostcode character varying, OrganisationFRN character varying
, RequestDate date, LifeBasis character varying, CommencementDate date, PolicyTerm character varying, QuotationFor character varying
, LifeCriticalIllnessRiskRelationship character varying, TotalPermanentDisabilityCover character varying, COmmissionBasis character varying
, CommisionType character varying, Life1Gender character varying, Life1DOB character varying, Life1Age INTEGER, Life1QuarterlyAge numeric(12,2)
, Life1Smoker character varying, Life1Occupation character varying, Life2Gender character varying, Life2Age INTEGER
, Life2QuarterlyAge numeric(12,2), Life2Smoker character varying, Life2Occupation character varying);
SELECT * FROM FilteredRequests;
END;
$BODY$;
The error message says you are dividing two items, while your intent is to concatenate them.
It implies that you are running this query using EXECUTE, so the query text is build then run, and the result of the concatenation you wrote is executed, leading to the unexpected division.
You would have to transform the query so that the result of the concatenation - which will be executed - is the desired concatenation. Something like:
EXECUTE 'select fieldA' || '||''/''||' || 'fieldB from myTable';

CREATE TABLE is not allowed in a non-volatile function

I have the following three tables :
create table drugs(
id integer,
name varchar(20),
primary key(id)
);
create table prescription(
id integer,
drug_id integer,
primary key(id),
foreign key(drug_id) references drugs(id)
);
create table visits(
patient_id varchar(10),
prescription_id integer,
primary key( patient_id , prescription_id),
foreign key(prescription_id) references prescription(id)
);
I wrote the following function on these tables to show me a patient's drugs list(the patient id is parameter):
CREATE OR REPLACE FUNCTION public.patients_drugs(
patientid character varying)
RETURNS TABLE(drug_id integer, drug_name character varying)
LANGUAGE 'plpgsql'
COST 100
STABLE STRICT
ROWS 1000
AS $BODY$
begin
create temporary table result_table(
drug_id integer,
drug_name varchar(20)
);
return query select distinct drug.id , drug.name
from visits join prescription
on visits.patient_id = patientID;
end;
$BODY$;
However, it gives me this error:
CREATE TABLE is not allowed in a non-volatile function
You don't need to create a table in order to be able to "return a table". Just get rid of the CREATE TABLE statement.
But your query isn't correct either, as you are selecting columns from the drug table, but you never include that in the FROM clause. You can also get rid of the distinct clause if you don't use a join, but an EXISTS condition:
CREATE OR REPLACE FUNCTION public.patients_drugs(p_patientid character varying)
RETURNS TABLE(drug_id integer, drug_name character varying)
LANGUAGE plpgsql
AS $BODY$
begin
return query
select d.*
from drugs d
where exists (select *
from prescription p
join visits v on v.prescription_id = p.id
where d.id = p.drug_id
and v.patientid = p_patientid);
end;
$BODY$;
Or better, use a simple SQL function:
CREATE OR REPLACE FUNCTION public.patients_drugs(p_patientid character varying)
RETURNS TABLE(drug_id integer, drug_name character varying)
LANGUAGE sql
AS
$BODY$
select d.*
from drugs d
where exists (select *
from prescription p
join visits v on v.prescription_id = p.id
where d.id = p.drug_id
and v.patientid = p_patientid);
$BODY$;

Select [COLUMN_NAME] AS, self referencing table

The following is my function get_reportees performed on the self referencing table emp_tabref1
CREATE OR REPLACE FUNCTION get_reportees4(IN id integer)
RETURNS TABLE(e_id integer, e_name character varying, e_manager integer, e_man_name character varying) AS
$$
BEGIN
RETURN QUERY
WITH RECURSIVE manger_hierarchy(e_id, e_name, m_id, m_name) AS
(
SELECT e.emp_id, e.emp_name, e.mgr_id, e.emp_name AS man_name
FROM emp_tabref1 e WHERE e.emp_id = id
UNION
SELECT rp.emp_id, rp.emp_name, rp.mgr_id, rp.emp_name AS man_name
FROM manger_hierarchy mh INNER JOIN emp_tabref1 rp ON mh.e_id = rp.mgr_id
)
SELECT * from manger_hierarchy;
END;
$$ LANGUAGE plpgsql VOLATILE
Table structure of emp_tabref1:
CREATE TABLE **emp_tabref1**
(
emp_id integer NOT NULL,
emp_name character varying(50) NOT NULL,
mgr_id integer,
CONSTRAINT emp_tabref_pkey PRIMARY KEY (emp_id),
CONSTRAINT emp_tabref_mgr_id_fkey FOREIGN KEY (mgr_id)
REFERENCES emp_tabref (emp_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
What I want returned is the hierarchy (both above and below) of the id that we are passing which will have the emp_name, emp_id, mgr_id and mgr_name.
But my function is returning like this:
select * from get_reportees4(9)
e_id e_name e_manager e_man_name
1 9 "Emp9" 10 "Emp9"
2 5 "Emp5" 9 "Emp5"
3 6 "Emp6" 9 "Emp6"
where my expected output is
e_id e_name e_manager e_man_name
1 9 "Emp9" 10 "Emp10"
2 5 "Emp5" 9 "Emp9"
3 6 "Emp6" 9 "Emp9"
The function should return the manager name and not the employee name. Please help!
Found a solution! By creating a new join between the temporary manger_hierarchy table and the emp_tabref1 table using mgr_id and emp_id
CREATE OR REPLACE FUNCTION get_reportees4(IN id integer)
RETURNS TABLE(e_id integer, e_name character varying, e_manager integer, e_man_name character varying) AS
$$
BEGIN
RETURN QUERY
WITH RECURSIVE manger_hierarchy(e_id, e_name, m_id, m_name) AS
(
SELECT e.emp_id, e.emp_name, e.mgr_id, e.emp_name AS man_name
FROM emp_tabref1 e WHERE e.emp_id = id
UNION
SELECT rp.emp_id, rp.emp_name, rp.mgr_id, rp.emp_name AS man_name
FROM manger_hierarchy mh INNER JOIN emp_tabref1 rp ON mh.e_id = rp.mgr_id
)
SELECT manger_hierarchy.e_id, manger_hierarchy.e_name, manger_hierarchy.m_id, emp_tabref1.emp_name
FROM manger_hierarchy LEFT JOIN emp_tabref1 ON manger_hierarchy.m_id = emp_tabref1.emp_id;
END;
$$ LANGUAGE plpgsql VOLATILE
SELECT manger_hierarchy.e_id, manger_hierarchy.e_name, manger_hierarchy.m_id, emp_tabref1.emp_name
FROM manger_hierarchy LEFT JOIN emp_tabref1 ON manger_hierarchy.m_id = emp_tabref1.emp_id;

How to approach data warehouse (PostgreSQL) documentation?

We do have a small data warehouse in PostgreSQL database and I have to document all the tables.
I thought I can add a comment to every column and table and use pipe "|" separator to add more attributes. Then I can use information schema and array function to get documentation and use any reporting software to create desired output.
select
ordinal_position,
column_name,
data_type,
character_maximum_length,
numeric_precision,
numeric_scale,
is_nullable,
column_default,
(string_to_array(descr.description,'|'))[1] as cs_name,
(string_to_array(descr.description,'|'))[2] as cs_description,
(string_to_array(descr.description,'|'))[3] as en_name,
(string_to_array(descr.description,'|'))[4] as en_description,
(string_to_array(descr.description,'|'))[5] as other
from
information_schema.columns columns
join pg_catalog.pg_class klass on (columns.table_name = klass.relname and klass.relkind = 'r')
left join pg_catalog.pg_description descr on (descr.objoid = klass.oid and descr.objsubid = columns.ordinal_position)
where
columns.table_schema = 'data_warehouse'
order by
columns.ordinal_position;
It is a good idea or is there better approach?
Unless you must include descriptions of the system tables, I wouldn't try to shoehorn your descriptions into pg_catalog.pg_description. Make your own table. That way you get to keep the columns as columns, and not have to use clunky string functions.
Alternatively, consider adding specially formatted comments to your master schema file, along the lines of javadoc. Then write a tool to extract those comments and create a document. That way the comments stay close to the thing they're commenting, and you don't have to mess with the database at all to produce the report. For example:
--* Used for authentication.
create table users
(
--* standard Rails-friendly primary key. Also an example of
--* a long comment placed before the item, rather than on the
--* the same line.
id serial primary key,
name text not null, --* Real name (hopefully)
login text not null, --* Name used for authentication
...
);
Your documentation tool reads the file, looks for the --* comments, figures out what comments go with what things, and produces some kind of report, e.g.:
table users: Used for authentication
id: standard Rails-friendly primary key. Also an example of a
long comment placed before the item, rather than on the same
line.
name: Real name
login: Name used for authentication
You might note that with appropriate comments, the master schema file itself is a pretty good report in its own right, and that perhaps nothing else is needed.
If anyone interested, here is what I've used for initial load for my small documentation project. Documentation is in two tables, one for describing tables and one for describing columns and constraints. I appreciate any feedback.
/* -- Initial Load - Tables */
drop table dw_description_table cascade;
create table dw_description_table (
table_description_key serial primary key,
physical_full_name character varying,
physical_schema_name character varying,
physical_table_name character varying,
Table_Type character varying, -- Fact Dimension ETL Transformation
Logical_Name_CS character varying,
Description_CS character varying,
Logical_Name_EN character varying,
Description_EN character varying,
ToDo character varying,
Table_Load_Type character varying, --Manually TruncateLoad AddNewRows
Known_Exclusions character varying,
Table_Clover_Script character varying
);
insert into dw_description_table (physical_full_name, physical_schema_name, physical_table_name) (
select
table_schema || '.' || table_name as physical_full_name,
table_schema,
table_name
from
information_schema.tables
where
table_name like 'dw%' or table_name like 'etl%'
)
/* -- Initial Load - Columns */
CREATE TABLE dw_description_column (
column_description_key serial,
table_description_key bigint,
physical_full_name text,
physical_schema_name character varying,
physical_table_name character varying,
physical_column_name character varying,
ordinal_position character varying,
column_default character varying,
is_nullable character varying,
data_type character varying,
logical_name_cs character varying,
description_cs character varying,
logical_name_en character varying,
description_en character varying,
derived_rule character varying,
todo character varying,
pk_name character varying,
fk_name character varying,
foreign_table_name character varying,
foreign_column_name character varying,
is_primary_key boolean,
is_foreign_key boolean,
CONSTRAINT dw_description_column_pkey PRIMARY KEY (column_description_key ),
CONSTRAINT fk_dw_description_table_key FOREIGN KEY (table_description_key)
REFERENCES dw_description_table (table_description_key) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
insert into dw_description_column (
table_description_key ,
physical_full_name ,
physical_schema_name ,
physical_table_name ,
physical_column_name ,
ordinal_position ,
column_default ,
is_nullable ,
data_type ,
logical_name_cs ,
description_cs ,
logical_name_en ,
description_en ,
derived_rule ,
todo ,
pk_name ,
fk_name ,
foreign_table_name ,
foreign_column_name ,
is_primary_key ,
is_foreign_key )
(
with
dw_constraints as (
SELECT
tc.constraint_name,
tc.constraint_schema || '.' || tc.table_name || '.' || kcu.column_name as physical_full_name,
tc.constraint_schema,
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name,
TC.constraint_type
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu ON (tc.constraint_name = kcu.constraint_name and tc.table_name = kcu.table_name)
JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
WHERE
constraint_type in ('PRIMARY KEY','FOREIGN KEY')
AND tc.constraint_schema = 'bizdata'
and (tc.table_name like 'dw%' or tc.table_name like 'etl%')
group by
tc.constraint_name,
tc.constraint_schema,
tc.table_name,
kcu.column_name,
ccu.table_name ,
ccu.column_name,
TC.constraint_type
)
select
dwdt.table_description_key,
col.table_schema || '.' || col.table_name || '.' || col.column_name as physical_full_name,
col.table_schema as physical_schema_name,
col.table_name as physical_table_name,
col.column_name as physical_column_name,
col.ordinal_position,
col.column_default,
col.is_nullable,
col.data_type,
null as Logical_Name_CS ,
null as Description_CS ,
null as Logical_Name_EN,
null as Description_EN ,
null as Derived_Rule ,
null as ToDo,
dwc1.constraint_name pk_name,
dwc2.constraint_name as fk_name,
dwc2.foreign_table_name,
dwc2.foreign_column_name,
case when dwc1.constraint_name is not null then true else false end as is_primary_key,
case when dwc2.constraint_name is not null then true else false end as foreign_key
from
information_schema.columns col
join dw_description_table dwdt on (col.table_schema || '.' || col.table_name = dwdt.physical_full_name )
left join dw_constraints dwc1 on ((col.table_schema || '.' || col.table_name || '.' || col.column_name) = dwc1.physical_full_name and dwc1.constraint_type = 'PRIMARY KEY')
left join dw_constraints dwc2 on ((col.table_schema || '.' || col.table_name || '.' || col.column_name) = dwc2.physical_full_name and dwc2.constraint_type = 'FOREIGN KEY')
where
col.table_name like 'dw%' or col.table_name like 'etl%'
)