This question already has answers here:
Sql Server deterministic user-defined function
(2 answers)
Closed 9 years ago.
SQL Server 10.50.1600
I am trying to use a scalar valued function in a computed column. I then wish to create an index off of this.
ALTER TABLE [dbo].[Modified]
ADD [StartQDate] AS ([dbo].[QDay]([StartDT])) PERSISTED,
[EndQDate] AS ([dbo].[QDay]([EndDT])) PERSISTED;
I am receiving the following error when I try to create the computed column.
Computed column 'StartQDate' in table 'Modified' cannot be persisted because the column is non-deterministic.
Except for the fact that my scalar function QDay is defined as
FUNCTION [dbo].[QDay]
(
#Date DATETIME
)
RETURNS INT
AS
BEGIN
RETURN YEAR(#Date)*10000+MONTH(#Date)*100+DAY(#Date)
END
Which according to Microsoft should be deterministic.
Even if I change the function to
FUNCTION [dbo].[QDay]
(
#Date DATETIME
)
RETURNS INT
AS
BEGIN
RETURN 1
END
I still get the Non-Deterministic error message.
I have this working on another server. I'm at a loss on what to do.
The solution was to add
WITH SCHEMABINDING
as suggested in this question Sql Server deterministic user-defined function
FUNCTION [dbo].[QDay]
(
#Date DATETIME
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN YEAR(#Date)*10000+MONTH(#Date)*100+DAY(#Date)
END
Related
CREATE TABLE Person (
id serial primary key,
accNum text UNIQUE GENERATED ALWAYS AS (
concat(right(cast(extract year from current_date) as text), 2), cast(id as text)) STORED
);
Error: generation expression is not immutable
The goal is to populate the accNum field with YYid where YY is the last two letters of the year when the person was added.
I also tried the '||' operator but it was unsuccessful.
As you don't expect the column to be updated, when the row is changed, you can define your own function that generates the number:
create function generate_acc_num(id int)
returns text
as
$$
select to_char(current_date, 'YY')||id::text;
$$
language sql
immutable; --<< this is lying to Postgres!
Note that you should never use this function for any other purpose. Especially not as an index expression.
Then you can use that in a generated column:
CREATE TABLE Person
(
id integer generated always as identity primary key,
acc_num text UNIQUE GENERATED ALWAYS AS (generate_acc_num(id)) STORED
);
As #ScottNeville correctly mentioned:
CURRENT_DATE is not immutable. So it cannot be used int a GENERATED ALWAYS AS expression.
However, you can achieve this using a trigger nevertheless:
demo:db<>fiddle
CREATE FUNCTION accnum_trigger_function()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
NEW.accNum := right(extract(year from current_date)::text, 2) || NEW.id::text;
RETURN NEW;
END
$$;
CREATE TRIGGER tr_accnum
BEFORE INSERT
ON person
FOR EACH ROW
EXECUTE PROCEDURE accnum_trigger_function();
As #a_horse_with_no_name mentioned correctly in the comments: You can simplify the expression to:
NEW.accNum := to_char(current_date, 'YY') || NEW.id;
I am not exactly sure how to solve this problem (maybe a trigger), but current_date is a stable function not an immutable one. For the generated IDs I believe all function calls must be immutable. You can read more here https://www.postgresql.org/docs/current/xfunc-volatility.html
I dont think any function that gets the date can be immutable as Postgres defines this as "An IMMUTABLE function cannot modify the database and is guaranteed to return the same results given the same arguments forever." This will not be true for anything that returns the current date.
I think your best bet would be to do this with a trigger so on insert it sets the value.
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)]);
This question already has answers here:
Error: query has no destination for result data while using a cursor
(2 answers)
Function with SQL query has no destination for result data
(3 answers)
Closed 8 years ago.
i have table like below with name student
S.No Name
1. Ramesh
2. Raju
3. SOmu
-------------
------------- etc
My requirement is when i am passing the S.no i will get the Name by using function
My function like below:
CREATE OR REPLACE FUNCTION fn_name(v_sno bigint)
RETURNS TEXT
AS
$BODY$
DECLARE RESULT VARCHAR(5000);
BEGIN
SELECT RESULT "Name" FROM "Student" WHERE "s.no" = v_sno;
RETURN RESULT;
END;
$BODY$
LANGUAGE plpgsql
COST 100;
ALTER FUNCTION fn_name(bigint)
OWNER TO postgresql
but i am getting the following error .
ERROR: query has no destination for result data
SQL state: 42601
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Context: PL/pgSQL function fn_name(bigint) line 6 at SQL statement
please help me how to resolve the above issue.
The immediate question is easily answered by the manual (see SELECT INTO).
However, all that is a rather overcomplicated way to do what you want. Things notably wrong:
varchar(5000) is weird and unnecessary. Use text or unqualified varchar.
SELECT RESULT "Name" doesn't do what you probably think it does. That's trying to SELECT a column named result and alias it as Name. I think you were looking for SELECT INTO.
"s.no" means the column named s.no. Is that what you meant? Because that's a horrid name for a column; without the quotes it'd be interpreted as the column no from the table s. Don't use periods in column names or table names.
All you really need is:
CREATE OR REPLACE FUNCTION fn_name(bigint)
RETURNS TEXT AS
$BODY$
SELECT "Name" FROM "Student" WHERE "s.no" = $1;
$BODY$
LANGUAGE sql;
(assuming "s.no" really is your column name, not a mangled attempt at an alias).
If you had to use PL/PgSQL for this simple job for some reason (maybe you intend to make it more complex later), you'd write:
CREATE OR REPLACE FUNCTION fn_name(v_sno bigint)
RETURNS TEXT AS
$BODY$
DECLARE
result text;
BEGIN
SELECT INTO result "Name" FROM "Student" WHERE "s.no" = $1;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql;
The following syntax successfully creates a user defined function, but does not drop it. Can anyone identify where my error is?
-- Example 1 - scalar function
USE AdventureWorks2012
GO
CREATE FUNCTION Sales.uf_MostRecentCustomerOrderDate (#CustomerID int)
RETURNS
DATETIME
AS
BEGIN;
DECLARE #MostRecentOrderDate datetime;
SELECT #MostRecentOrderDate = MAX(OrderDate)
FROM Sales.SalesOrderHeader as soh
Where CustomerID = #CustomerID
RETURN #MostRecentOrderDate
END;
GO
-- Using user defined scalar function
SELECT Sales.uf_MostRecentCustomerOrderDate(29825); -- returns 2008-04-01 00:00:00.000
-- Delete existing scalar valued function
USE AdventureWorks2012;
GO
-- determines if function exists in database
IF OBJECT_ID (N'Sales.uf_MostRecentCustomerOrderDate', N'IF') IS NOT NULL
-- deletes function
DROP FUNCTION Sales.uf_MostRecentCustomerOrderDate;
GO
That function gets created with a type of FN (not IF as you've used).
Try this code to drop it:
-- determines if function exists in database
IF OBJECT_ID (N'Sales.uf_MostRecentCustomerOrderDate', N'FN') IS NOT NULL
-- deletes function
DROP FUNCTION Sales.uf_MostRecentCustomerOrderDate;
GO
Type IF stands for an inline table-valued function - this is not the case here.
Type FN stands for a scalar function - which this is.
See the TechNet docs on sys.objects which also lists all defined types in SQL Server catalog views
How do I convert a simple select query like select * from customers into a stored procedure / function in pg?
I'm new to Postgres and create function customers() as returns table/setof just didn't feel right and thus the question here.
I understand procs are called "functions" in pg land. Thus create procedure does not exist and my only options are to either create a view or a function. The issue is create function x() returns setof y returns a paren'd comma separated row of values which can't be used without further processing (at least that's what I'm seeing in pgAdmin and Ruby/Sequel).
create function x() returns table(...) requires I embed the row definition which I don't want to.
I'm sure there's a reason behind all this but I'm surprised that the most common use case is this tricky.
Untested but should be about right:
CREATE OR REPLACE FUNCTION getcustomers() RETURNS SETOF customers AS $$
SELECT * FROM customers;
$$ LANGUAGE sql;
The issue is "create function x() returns setof y" returns a paren'd
comma separated row values which can't be used without further processing
The function returns a row. To decompose into individual columns, call it with:
SELECT * FROM getcustomers();
That's assuming the function defines a proper return type. See:
How to return multiple rows from PL/pgSQL function?
The manual on CREATE FUNCTION should be a good starting point. The example section covers this topic.