Creating a table function in PostgreSQL - postgresql

I'm creating a table function in pgAdmin that returns a table with multiple variables from multiple tables. For simplicity, I will only show the function with one variable since that will answer my question just as well.
I have a patients relation with the following attribute: patient_id, address_id, name, gender, dob. For simplicity purposes, say I want to create a table function that will take in a patient's ID and return a table output with their name.
CREATE OR REPLACE FUNCTION patientname (patient_id char(8))
RETURNS TABLE (
patient_name varchar(250)) AS
$$
BEGIN
RETURN QUERY
SELECT patient_name
FROM patients
WHERE patient_id = patientname.patient_id;
END
$$
In the examples that I have seen, the variables being defined in the RETURNS TABLE() section have the same names as the variables in the tables that the information is being pulled from in the RETURN QUERY section. How would I create this function with variable names in the RETURNS TABLE section that are different from the variable names in the original tables that I will be pulling data from. Like in the example above the table output from the function should return a variable called patient_name but this variable in the patients table is only called name.

You can choose any result column name you want, and it doesn't have to be the same as the alias of the corresponding SELECT list entry of the query. The first column of the query result set will become the first column of the function result set, and so on.

Related

Postgres Functions: Getting the Return Table Column Details

I feel the need to get the column names and data types of the table returned by any function that has a 'record' return data type, because...
A key process in an existing SQL Server-based system makes use of a stored procedure that takes a user-defined function as a parameter. An initial step gets the column names and types of the table returned by the function that was passed as a parameter.
In Postgres 13 I can use pg_proc.prorettype and the corresponding pg_type to find functions that return record types...that's a start. I can also use pg_get_function_result() to get the string containing the information I need. But, it's a string, and while I ultimately will have to assemble a very similar string, this is just one application of the info. Is there a tabular equivalent containing (column_name, data_type, ordinal_position), or do I need to do that myself?
Is there access to a composite data type the system may have created when such a function is created?
One option that I think will work for me, but I think it's a little weird, is to:
> create temp table t as select * from function() limit 0;
then look that table up in info_schema.columns, assemble what I need and drop the temp table...putting all of this into a function.
You can query the catalog table pg_proc, which contains all the required information:
SELECT coalesce(p.na, 'column' || p.i),
p.ty::regtype,
p.i
FROM pg_proc AS f
CROSS JOIN LATERAL unnest(
coalesce(f.proallargtypes, ARRAY[f.prorettype]),
f.proargmodes,
f.proargnames
)
WITH ORDINALITY AS p(ty,mo,na,i)
WHERE f.proname = 'interval_ok'
AND coalesce(p.mo, 'o') IN ('o', 't')
ORDER BY p.i;

How to update column based on column name in postgres?

I've narrowed it down to two possibilities - DynamicSQL and using a case statement.
However, I've failed with both of these.
I simply don't understand dynamicSQL, and how I would use it in my case.
This is my attempt using case statements; one of many failed variations.
SELECT column_name,
CASE WHEN column_name = 'address' THEN (**update statement gives syntax error within here**)
END
FROM information_schema.columns
WHERE table_name = 'employees';
As an overview, I'm using Axios to talk to my Node server, which is making calls to my Heroku database using Massivejs.
Maybe this isn't the way to go - so here's my main problem:
I've ran into troubles because the values I'm planning on using as column names are sent to my server as strings. The exact call that I've been trying to use is
update employees
set $1 = $2
where employee_id = $3;
Once again, I'm passing into those using massive.
I get the error back { error: syntax error at or near "'address'"} because my incoming values are strings. My thought process was that the above statement would allow me to use variables because 'address' is encapsulated by quotes.
But alas, my thought process has failed me.
This seems to be close to answering my question, but I can't seem to figure out what to do in my case if using dynamic SQL.
How to use dynamic column names in an UPDATE or SELECT statement in a function?
Thanks in advance.
I will show you a way to do this by using a function.
First we create the employees table :
CREATE TABLE employees(
id BIGSERIAL PRIMARY KEY,
column1 TEXT,
column2 TEXT
);
Next, we create a function that requires three parameters:
columnName - the name of the column that needs to be updated
columnValue - the new value to which the column needs to be updated
employeeId - the id of the employee that will be updated
By using the format function we generate the update query as a string and use the EXECUTE command to execute the query.
Here is the code of the function.
CREATE OR REPLACE FUNCTION update_columns_on_employee(columnName TEXT, columnValue TEXT, employeeId BIGINT)
RETURNS VOID AS
$$
DECLARE update_statement TEXT := format('UPDATE EMPLOYEES SET %s = ''%s'' WHERE id = %L',columnName, columnValue, employeeId);
BEGIN
EXECUTE update_statement;
end;
$$ LANGUAGE plpgsql;
Now, lets insert some data into the employees table
INSERT INTO employees(column1, column2) VALUES ('column1_start_value','column2_start_value');
So now we currently have an employee with an id value of 1 who has 'column1_start_value' value for the column1, and 'column2_start_value' value for column2.
If we want to update the value of column2 from 'column2_start_value' to 'column2_new_value' all we have to do is execute the following call
SELECT * FROM update_columns_on_employee('column2','column2_new_value',1);

PostgreSQL: Creating a function which accepts multiple values

I want to write a function which will add insert record and then insert one or more records in a related table. I think I know what to do inside the function, but I don’t know what the function signature should look like.
Here is a mockup sample:
CREATE TABLE sales(id SERIAL, customer id, sold date);
CREATE TABLE saleitems(SERIAL, sale int, details varchar, price numeric(6,2));
SELECT addSale(42, '2016-01-01',
values ('stuff',13),('more stuff',42),('things',3.14),('etc',0)) items(price,details));
CREATE OR REPLACE FUNCTION addSale(customer,sold,items) RETURNS int AS
$$
-- I think I can handle the rest
$$
LANGUAGE sql;
The salient points:
I would like to be able to use the VALUES (…) name(…) construct as an argument — is this possible?
The real problem, I think, is the last parameter items. What is the appropriate type of this?
I would like the language to be SQL, since my next step is to translate this into other dialects (MySQL & SQL Server). However, I’ll do whatever is needed.
Eventually I will wrap the code body inside a transaction, and return the new sales.id value.
The question is: what is the correct parameter to accept a table expression in the VALUES form?
Your best bet here is to create a new type that holds the details and price of a product:
CREATE TYPE product_details AS (
details varchar,
price numeric(6,2)
);
Then you can define a function parameter of type product_details[], i.e. an array of product details. Since you want to have a SQL function and need to retrieve the value of the serial column of one insert for use in another insert, you need a CTE:
CREATE FUNCTION addSale(_customer int, _sold int, _items product_details[]) RETURNS int AS
$$
WITH s AS (
INSERT INTO sales (customer, sold) VALUES (_customer, _sold) RETURNING id;
)
INSERT INTO saleitems (sale, details, price)
SELECT s.id, i.d, i.p
FROM s, unnest(_items) i(d, p);
$$ LANGUAGE sql;
And then you call the function like so:
SELECT addSale(42, '2016-01-01'::date,
ARRAY[('stuff',13),('more stuff',42),('things',3.14),('etc',0)]);

PLPGSQL function returning trigger AND value

I have written a PL/PGSQL function that returns a trigger, so I can call it before each row insert. I realize now that I would also like that function to return the ID of the newly inserted row. I'm not quite sure how to proceed since my function must return a trigger. Here's some code:
CREATE OR REPLACE FUNCTION f_insert_album() RETURNS TRIGGER AS $$
DECLARE
subj_album_id INTEGER;
BEGIN
-- ... some parts where left out
INSERT INTO t_albums_subjective (user_id, album_id, format_id, location_id, rating)
VALUES (NEW.user_id, obj_album_id, NEW.format_id, NEW.location_id, NEW.rating)
RETURNING id INTO subj_album_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Bind insert function to trigger
DROP TRIGGER IF EXISTS tr_v_albums_insert ON v_albums;
CREATE TRIGGER tr_v_albums_insert INSTEAD OF INSERT ON v_albums
FOR EACH ROW EXECUTE PROCEDURE f_insert_album();
I must keep the return type of my function f_insert_album() to TRIGGER, but I would really like to also return the value in subj_album_id, corresponding to the id of the newly inserted row.
Is there anyway to do this? Is it even possible? Obviously changing the return type didn't work with Postgres. Could you suggest an alternative approach if any?
The crucial question: where to return the ID to?
Assuming you want to return it from the INSERT statement directly, then you are almost there. You already assign the newly generated ID to a function parameter:
...
RETURNING id INTO subj_album_id;
Instead, assign it to a column of the row firing the trigger. The special variable NEW holds this row in a trigger function:
...
RETURNING id INTO NEW.album_id; -- use actual column name in view
Then use the RETURNING clause of the INSERT statement:
INSERT INTO v_albums (user_id, format_id, location_id, rating)
VALUES ( ... )
RETURNING album_id;
Obviously, this is only possible if there is a visible column in the view. It does not have to be assigned in the INSERT command, though. The type of the NEW row variable is defined by the definition of the view, not by the INSERT at hand.
Closely related:
RETURNING data from updatable view not working?
For what you seem to be doing (grant access to certain rows of a table to a certain role) row level security (RLS) in Postgres 9.5 or later might be a more convenient alternative:
CREATE POLICY in the manual
The Postgres Wiki: "What's new in PostgreSQL 9.5"
Review by Depesz

T-SQL Parse text in select statement

I am currently working on a project to import data from one table to another. I am trying to parse a field that contains a FULLNAME into its parts LAST,FIRST,MI. The names are all in the format of "LAST,FIRST MI" I have written a stored procedure that correctly parses and returns the results as neccessary but I am unsure as to how to encorporate the stored procedure into a single select statement. For instance, current I have:
SELECT FULLNAME From UserInfo
and what I would like to have is something like this:
SELECT Last, First, MI from UserInfo
Currently my stored procedure takes the form of ParseName(FULLNAME, Last as OUTPUT, First as OUTPUT, MI as OUTPUT). How can I call this procedure and have the output variables split into 3 different columns?
Replace your stored procedure with table valued function. You can then apply this function to all the rows.
Below is an example - just put your logic for parsing the name
create FUNCTION dbo.f_parseName(#inFullName varchar(255))
RETURNS
#tbl TABLE (lastName varchar(255), firstName varchar(255), middleName varchar(255))
as
BEGIN
-- put your logic here
insert into #tbl(lastName,firstName,middleName)
select substring(#inFullName,0,10),substring(#inFullName,11,10), substring(#inFullName,21,10)
return
end
apply the function
-- sample data
declare #fullNames table (fullName varchar(255))
insert into #fullNames (fullName) values
('111111111122222222223333333333')
,('AAAAAAAAAABBBBBBBBBBCCCCCCCCCC')
select
fn.fullName
,pn.lastName
,pn.firstName
,pn.middleName
from
#fullNames fn
cross apply dbo.f_parseName(fn.fullName) pn
You could put the results of your stored procedure in a (temporary) table, like this (I added the FULLNAME column to provide a join condition, you would have to adapt your stored procedure to do that):
CREATE TABLE #temp (
FULLNAME NVARCHAR(..)
,Last NVARCHAR(..)
,First NVARCHAR(..)
,MI NVARCHAR(..)
);
INSERT INTO #temp (Last, First, MI)
EXECUTE MySproc;
If you want to be able to execute SELECT Last, First, MI from UserInfo structurally, you'd have to first add three columns to UserInfo for your name information, and then insert the parsed data that you got from your stored procedure.
EDIT
You mention that you use a SELECT ... INTO ... to put the data in a new table. I'm guessing that the new table does not have the FULLNAME column, and then you would be better off using a table valued function (as this answer suggests). If you keep the FULLNAME column however, you can use that to join the temp table with your new table to update the new table as follows:
UPDATE NUI
SET NUI.Last = T.Last, NUI.First = T.First, NUI.MI = T.MI
FROM NewUserInfo AS NUI
INNER JOIN #temp AS T ON NUI.FULLNAME = T.FULLNAME;
You could use this UPDATE method also with another join condition if you do not have the FULLNAME column in your new table, but make sure you run a good test beforehand to check if the join holds.
Hope this helps, good luck!
You could add computed columns to table like this:
alter table UserInfo
add firstName as SUBSTRING(fullName, CHARINDEX(',',fullName,0)+2, LEN(fullName)-CHARINDEX(',',fullName,0)-CHARINDEX(' ', REVERSE(fullName),0)-1)
,lastName as SUBSTRING(fullName, 0, CHARINDEX(',',fullName,0))
,middleInitital as REVERSE(SUBSTRING(REVERSE(fullName),0,CHARINDEX(' ', REVERSE(fullName),0)))
But the best solution would be to do the other way around. Normalize the data with real columns for firstName, lastName and middleInitial and do a computed column for the fullName.
The expressions in the code above may need a little more work, as I am sure they can be written more effectivly. I only made them work to show the idea.
After creating the computed columns you may do this:
select firstName
,lastName
,middleInitital
from UserInfo