PostgreSQL: Getting result of a function in a single column in PostgreSQL - function

Trying to display the result in the form of table with different columns, but getting all result in a single column.
--My Function
create or replace function test1()
returns table ( "Fname" varchar(20),"Lname" varchar(20),"A-B" bigint,"C-D" bigint,
"E-F" bigint ) as
$body$
begin
return query
SELECT tb."Fname",tb."Lname",count(tb."City"='A-B' OR NULL) AS "A-B",
count(tb."City"='C-D' OR NULL) AS "C-D",
count(tb."City"='E-F' OR NULL) AS "E-F"
FROM "Table1" tb
WHERE tb."City" in ('A-B','C-D','E-F')
GROUP BY 1,2
ORDER BY 1,2;
end
$body$
language plpgsql;

In instead of
select test1()
do
select * from test1()

You need no plgpsql for this. This is just a plain query.
But supposing you just want to test it: How do you call the function?
For table returning functions you do: select * from f1();.
For functions returning one value you do select f1();.

Related

PL/SQL Function, how to use SELECT INTO clause to declare variables from an existing table?

I would like to create a PL/SQL Function that calculates the age of any person from an existing table "Family tree" (Familienbaum), based on their Name. The Table has the needed Values Name, BirthYear (Geburtsjahr), YearOfDeath (Sterbejahr), etc.
Now I want to calculate the age of the person in two ways:
If the Person has a YearOfDeath, it should subtract the BirthYear from the YearofDeath to calculate
If the Person has no YearOfDeath, it should subtract the BirthYear from the Current System Year of the Oracle SQL Server
So far I have tried using the SELECT INTO clause to declare the variables needed for the calculation from the table Family Tree (Familienbaum):
CREATE OR REPLACE FUNCTION BerechneAlter(Person VARCHAR2)
RETURN INTEGER IS
BEGIN
SELECT Name, Sterbejahr, Geburtsjahr FROM Familienbaum
WHERE Person = Name;
RETURN (CASE
WHEN Sterbejahr IS NULL THEN (year(curDate()) - Geburtsjahr)
WHEN Sterbejahr IS NOT NULL THEN (Sterbejahr - Geburtsjahr)
END);
END BerechneAlter;
The SELECT INTO clause is giving me a lot of problems, does anyone know what needs to be changed in the syntax?
I also tried using cursors, but it seems more complicated than needed:
create or replace FUNCTION BerechneAlter(Person VARCHAR2)
RETURN INTEGER IS
Sterbejahr INTEGER;
Geburtsjahr INTEGER;
CURSOR SJ IS SELECT familienbaum.sterbejahr FROM familienbaum WHERE familienbaum.name=Person;
CURSOR GJ IS SELECT familienbaum.geburtsjahr FROM familienbaum WHERE familienbaum.name=Person;
BEGIN
OPEN SJ;
FETCH SJ INTO Sterbejahr;
CLOSE SJ;
OPEN GJ;
FETCH GJ INTO Geburtsjahr;
CLOSE GJ;
RETURN (CASE
WHEN Sterbejahr IS NULL THEN (2022 - Geburtsjahr)
WHEN Sterbejahr IS NOT NULL THEN (Sterbejahr - Geburtsjahr)
END);
END BerechneAlter;
If you are using a SQL SELECT statement within an anonymous block or function or procedure, etc (in PL/SQL - between the BEGIN and the END keywords) you must select INTO something so that PL/SQL can utilize a variable to hold your result from the query. It is important to note here that if you are selecting multiple columns, (which you are by "SELECT Name, Sterbejahr, Geburtsjahr"), you must specify multiple variables or a record to insert the results of your query into.
for example:
SELECT 1
INTO v_dummy
FROM dual;
SELECT 1, 2
INTO v_dummy, v_dummy2
FROM dual;
It is also worth pointing out that if your SELECT , ... FROM.... will return multiple rows, PL/SQL will throw an error. You should only expect to retrieve 1 row of data from a SELECT INTO.
In your case, it would look something like this (note - I haven't confirmed that your logic is correct, also note that I don't know your datatypes so you'll have to work on that bit too):
CREATE OR REPLACE FUNCTION BerechneAlter(p_person VARCHAR2)
RETURN INTEGER IS
v_name VARCHAR2(100);
v_sterbejahr VARCHAR2(100);
v_geburtsjahr VARCHAR2(100)
BEGIN
SELECT Name, Sterbejahr, Geburtsjahr
INTO v_name, v_sterbejahr, v_geburtsjahr
FROM Familienbaum
WHERE Name = p_person;
RETURN (CASE
WHEN v_sterbejahr IS NULL THEN (year(curDate()) - v_geburtsjahr)
WHEN v_sterbejahr IS NOT NULL THEN (v_sterbejahr - v_geburtsjahr)
END);
END BerechneAlter;
I think the function's logic is overcomplicated. You can get data and calculate age in an SQL statement, so only one var is needed.
create or replace function get_age(p_name varchar2) return number
is
l_aelter number;
begin
select nvl(sterbejahr, to_char(sysdate, 'YYYY')) - geburtsjahr aelter
into l_aelter
from familienbaum
where name = p_name;
return l_aelter;
end;
If using plsql is not required, you might wanted to use a standalone SQL statement to perform all the calculations you need:
select nvl(sterbejahr, to_char(sysdate, 'YYYY')) - geburtsjahr aelter
from familienbaum
where name = p_name;

Mysql calculate the average of a function's parameter

Here is a function to describe my question:
CREATE DEFINER=`root`#`localhost` FUNCTION `Demo`(`data` Integer) RETURNS int(11)
BEGIN
declare result integer;
set result = avg(`data`);
return result;
RETURN 1;
END
And the parameter data is a whole column from other select result, just looks like:
10
12
15
...
I want to use this function to calculate the average of the data, but it just shows:
Error Code: 1111. Invalid use of group function
It seems that was a wrong way to use the avg function, and other function like count() has the same problem too, but I can't find a way to achieve my purpose, is there a way to do that?
It makes no sense to calculate the average of a single value. The average of a single value is the same as the min and the same as the max, and these are all equal to the single value itself.
It's hard to tell what you intend this function to do. If you want to use it in a query, you can calculate the average of its result as you use it on each row of a query:
SELECT AVG(Demo(data)) FROM MyTable;
Or you can use the function to query some table and return the average of many rows. But then you don't need to pass any function argument.
CREATE FUNCTION Demo() RETURNS INT
BEGIN
DECLARE result INT;
SET result = (SELECT AVG(`data`) FROM MyTable);
RETURN result;
END
You would have to pass it an array.
CREATE DEFINER=`root`#`localhost` FUNCTION `Demo`(a_array IN my_integer_array) RETURNS int(11)
BEGIN
FOR i IN a_array.first..a_array.last LOOP
set result = a_array(i);
return result;
END LOOP;
RETURN 1;
END

GROUP_CONCAT as input of MySQL function

Is it possible to use a GROUP_CONCAT in a SELECT as the input of a MySQL function? I cannot figure out how to cast the variable it seems. I've tried blob. I've tried text (then using another function to break it up into a result set, here) but I haven't had any success.
I want to use it like this:
SELECT
newCustomerCount(GROUP_CONCAT(DISTINCT items.invoicenumber)) AS new_customers
FROM items;
Here is the function:
DROP FUNCTION IF EXISTS newCustomerCount;
DELIMITER $$
CREATE FUNCTION newCustomerCount(invoicenumbers BLOB)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE new_customers INT;
SET new_customers = 0;
SELECT
SUM(nc.record) INTO new_customers
FROM (
SELECT
1 AS customer,
(SELECT COUNT(*) FROM person_to_invoice ps2 WHERE person_id = ps1.person_id AND invoice < ps1.invoice) AS previous_invoices
FROM person_to_invoice ps1
WHERE invoice IN(invoicenumbers)
HAVING previous_invoices = 0
) nc;
RETURN new_customers;
END$$
DELIMITER ;
Because Mysql functions do not support dynamic queries, I recommend you re-think your basic strategy to pass in a list of invoice numbers to your function. Instead, you could modify your function to accept a single invoice number and return the number of new customers just for the one invoice number.
Also, there are some optimizations you can make in your query for finding the number of new customers.
DROP FUNCTION IF EXISTS newCustomerCount;
DELIMITER $$
CREATE FUNCTION newCustomerCount(p_invoice INT)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE new_customers INT;
SET new_customers = 0;
SELECT
COUNT(DISTINCT ps1.person_id) INTO new_customers
FROM
person_to_invoice ps1
WHERE
ps1.invoice = p_invoice
AND NOT EXISTS (
SELECT 1
FROM person_to_invoice ps2
WHERE ps1.person_id = ps2.person_id
AND ps2.invoice < ps1.invoice
);
RETURN new_customers;
END$$
DELIMITER ;
Then you can still get the total number of new customers for a given list of invoice numbers like this:
SELECT
SUM(newCustomerCount(invoice)) as total_new_customers
FROM items
WHERE ...
You could try FIND_IN_SET() instead of IN(). The performance will probably be horrible when passing in a long list of invoice numbers. But it should work.
WHERE FIND_IN_SET(invoice, invoicenumbers)
You are looking in the wrong place.
WHERE invoice IN(invoicenumbers) will not do the desired substitution. Instead you need to use CONCAT to construct the SQL, then prepare and execute it.

how to combine two sql statements into a function

I am trying to combine the following two sql statements in my application code into a function in postgresql, but I'm having some trouble.
Here are the two sql queries I'd like to combine:
UPDATE userrange f
SET UsedYesNo = true, user_id=user_id, updateddatetime=now()
WHERE f.uservalue IN(
SELECT a.uservalue FROM userrange a WHERE UsedYesNo=false Order By id ASC Limit 1)
RETURNING a.uservalue;
The results from the above statement are used in this query:
INSERT INTO widget
VALUES(DEFAULT, uservalue, 'test','123456778',1,"testservername", now(), Null)
So far, I've built function that just does the first update statement, like so:
CREATE or REPlACE FUNCTION create_widget(IN user_id integer, IN password character varying DEFAULT NULL::character varying)
RETURNS TABLE(uservalue integer) AS
$BODY$
BEGIN
RETURN QUERY
UPDATE userrange f SET UsedYesNo = true, user_id=user_id, updateddatetime=now()
WHERE f.uservalue IN(
SELECT a.uservalue FROM userrange a WHERE UsedYesNo=false Order By id ASC Limit 1)
RETURNING a.uservalue;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
It compiles but when I execute it, it fails with the error:
ERROR: missing FROM-clause entry for table "a" LINE 4: RETURNING a.uservalue
I'm just googling this error to see how I can fix it... but could I just create a variable
called uservalue in a DECLARE section and use it in the secondary query? Or can i combine the sql into one
thanks.
If you can forego the function and you have PostgreSQL 9.2+, you can do the update and insert in a single query. It should be straightforward to port this to a function if necessary.
WITH f AS (
UPDATE userrange f
SET UsedYesNo = true, user_id=user_id, updateddatetime=now()
WHERE UsedYesNo IS FALSE
RETURNING f.uservalue)
INSERT INTO widget (<column list>)
SELECT f.uservalue, 'test','123456778',1,"testservername", now(), NULL
FROM f;
-- [edit: added function]
-- Note: the following function is untested
CREATE OR REPLACE FUNCTION create_widget(IN p_user_id INTEGER, IN p_password VARCHAR DEFAULT NULL::VARCHAR)
RETURNS TABLE(uservalue integer) AS
$BODY$
BEGIN
RETURN QUERY
WITH f AS (
UPDATE userrange f
SET UsedYesNo = true,
user_id = p_user_id,
updateddatetime = now()
WHERE UsedYesNo IS FALSE
RETURNING f.uservalue)
INSERT INTO widget (<column_list>)
/* Omit the DEFAULT, by not including it in the column list above,
* the DEFAULT already defined on the column will be used.
*/
SELECT f.uservalue, 'test','123456778',1,"testservername", now(), NULL
FROM f;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

PostgreSQL: Call a Function More Than Once in FROM Clause

How can I call a function which returns records more than once in FROM clause? I understand that I have to specify a 'column definition list' when using a function that returns records. But how can I then use aliases for that function?
Example:
CREATE OR REPLACE FUNCTION foo(which_foo int) RETURNS SETOF RECORD AS
$BODY$BEGIN
IF which_foo=0 THEN
RETURN QUERY EXECUTE 'SELECT 1::int,2::int;';
ELSE
RETURN QUERY EXECUTE 'SELECT 1::int,2::int;';
END IF;
END$BODY$
LANGUAGE plpgsql;
SELECT * FROM foo(0) AS (a int, b int);;
SELECT * FROM foo(1) AS (c int, d int);
SELECT * FROM foo(0) AS (a int, b int), foo(1) AS (c int, d int);
The last select statement will fail with:
ERROR: table name "foo" specified more than once
I want to keep using the column definition list, because the function I want to use in the end has to be as generic as possible.
SELECT f0.*, f1.*
FROM
foo(0) AS f0 (a int, b int),
foo(1) AS f1 (c int, d int);
I understand that I have to specify a 'column definition list' when
using a function that returns records.
No, you do not. I wouldn't operate with anonymous records. Declare the return type, since you already know it:
CREATE OR REPLACE FUNCTION foo(which_foo int)
RETURNS TABLE (a int, b int) AS
$func$
BEGIN
IF which_foo = 0 THEN
RETURN QUERY SELECT 1,2;
ELSE
RETURN QUERY SELECT 1,2;
END IF;
END
$func$ LANGUAGE plpgsql;
And assuming you do not want to combine multiple calls into one row, you should use UNION ALL:
SELECT * FROM foo(0)
UNION ALL
SELECT * FROM foo(1);