I have a function in the SQL server that calculates the due amount.
I have migrated my database into Postgres but I can't use that function in Postgres because both databases have different schema and syntax.
I am new to Postgres and I don't know how to migrate that function in Postgres from the SQL server.
here is the function, please help me to convert this.
CREATE FUNCTION "CalcDue"(
"#duedate" datetime,
"#latefee" decimal,
"#limit" decimal
)
RETURNS decimal
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
begin
DECLARE #days int
DECLARE #TotalLateFee decimal
SET #days = DateDiff(DAY,#duedate,DATEADD(MINUTE,330,GETUTCDATE()))
if #days < 0 set #days = 0
SET #TotalLateFee = #days * #latefee
if #TotalLateFee > #limit
return #limit
else return #TotalLateFee
return isnull(#TotalLateFee,0)
end
Thank you !!
It's totally unclear how fractional "days" should be treated in the calculation. But a naive implementation in Postgres could look like this:
CREATE FUNCTION calc_due(p_duedate timestamp, p_late_fee decimal, p_limit decimal)
RETURNS decimal
as
$$
declare
l_days int;
l_total_late_fee decimal;
begin
l_days := extract(day from p_duedate - (current_timestamp + interval '5 hours'));
if l_days < 0 then
l_days := 0;
end if;
l_total_late_fee := l_days * p_late_feed;
return least(p_limit, l_total_late_fee);
end;
$$
immutable
language plpgsql;
Or a bit shorter as a SQL function:
CREATE FUNCTION calc_due(p_duedate timestamp, p_late_fee decimal, p_limit decimal)
RETURNS decimal
as
$$
select least(p_limit, greatest(0, extract(day from p_duedate - current_timestamp + interval '5 hours')) * p_late_fee);
$$
immutable
language sql;
Related
I have databse having 2 tables i.e (Installment and InstallmentPlan).
In the installment table there are number of columns. I want to add one new computed column name SurchargeCalculated (SC). The calculation for SC is this
SC = (Installment.Amount * Installment.days * (InstallmentPlan.InstPercentage /365 /100))
I have created user-defined function SurchargeCal having 3 parameters which are Amount, days and InstPercentage. The problem is that when i add computed column in installment table and call scalar function from there, the saclar func needs InstPercetage parameter of 2nd table (InstallmentPlan).
I know recommended ways is to use view but that will complicate my problem as i am using installment table in C#.
Any help will be extremely appreciated.
My scalar function is
USE [myDB]
GO
/****** Object: UserDefinedFunction [dbo].[SurchargeCal] Script Date: 17/02/2020 2:21:15 PM
******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
ALTER FUNCTION [dbo].[SurchargeCal]
(
-- Add the parameters for the function here
#days as int,
#amount as money,
#surchargePerc as decimal
)
RETURNS decimal
AS
BEGIN
-- Declare the return variable here
DECLARE #result as decimal =0;
-- Add the T-SQL statements to compute the return value here
--SELECT <#ResultVar, sysname, #Result> = <#Param1, sysname, #p1>
if #days = 0
set #result = 0
else if (#days > 0 and #amount > 0)
set #result = (#days * #amount * (#surchargePerc/ 365 / 100))
else
set #result = 0
-- Return the result of the function
RETURN #result
END
then below ADD table command XXXX is the problem
USE [myDB]
GO
ALTER TABLE dbo.Installment
ADD SurchargeCalculated AS dbo.SurchargeCalc(days,Amount, XXXX) --where XXX should be InstPercenatage
GO
Since you can't use a subquery directly on the computed column, you will need to do it on the scalar function itself :
CREATE FUNCTION SurchargeCal(#days as integer, #amount as money, #PlanKey as integer)
RETURNS decimal
AS
BEGIN
DECLARE #result as decimal = 0;
SELECT #result = #amount * #days * InstPercentage / 365 / 100
FROM InstallMentPlan
WHERE PlanKey = #PlanKey
RETURN #result
END
Now you can create the computed column, passing the PlanKey instead of its InstPercentage.
ALTER TABLE dbo.Installment
ADD SurchargeCalculated AS dbo.SurchargeCalc(days, Amount, PlanKey)
Is there a way to use the following function from MSSQL in Oracle SQL Developer
create function fnDays2NextEvent(#birthdate datetime, #today datetime)
returns integer
as
begin
declare #birthday datetime
set #birthday = #birthdate
while #birthday < #today set #birthday = dateadd(yy, 1, #birthday)
return datediff(dd,#today,#birthday)
end;
You need to create the function in Oracle similar to this one.
Like the following:
CREATE OR REPLACE FUNCTION FNDAYS2NEXTEVENT (
BIRTHDATE IN DATE,
TODAY IN DATE
) RETURN INT AS
BEGIN
RETURN BIRTHDATE
+ CEIL(MONTHS_BETWEEN(TODAY, BIRTHDATE) / 12)
* INTERVAL '1' YEAR - TODAY + 1;
END;
/
Then, You will be able to use it.
SQL> SELECT FNDAYS2NEXTEVENT(DATE'1991-07-20',SYSDATE) FROM DUAL;
FNDAYS2NEXTEVENT(DATE'1991-07-20',SYSDATE)
------------------------------------------
161
SQL> SELECT SYSDATE + 161 FROM DUAL;
SYSDATE+1
---------
20-JUL-20
SQL>
I don't know if this is what you require, Do comment in case of any discrepancy in the answer and expectation.
Cheers!!
If you are looking for the oracle equivalent, then try this:
create or replace function fnDays2NextEvent(birthdate date, today date)
return number
is
begin
return trunc(birthdate)- trunc(today) ;
end fnDays2NextEvent;
test
select fnDays2NextEvent(to_date('02/14/2020','MM/DD/YYYY'),sysdate) from dual
the function could look like:
CREATE OR REPLACE function fnDays2NextEvent(birthdate DATE, today DATE)
returns NUMBER
AS
v_bd DATE;
begin
v_bd := birthdate;
while v_bd < today
LOOP
v_bd = ADD_MONTHS(v_bd,12);
END LOOP;
RETURN today - v_bd;
end;
this code is not optimized and is 1 to 1 migration from your code
How to convert Epoch to DateTime SQL Server if epoch exceeds the year 2038?
Answer in Convert Epoch to DateTime SQL Server will not work.
Example:
SELECT DATEADD(ss, 2713795200000 / 1000, '19700101')
Thu, 30 Dec 2055 16:00:00 GMT
DATEADD function assumes an INT as an increment to your date, to bypass the limitation of INT you can either reduce the precision of your epoch, or do a slightly complex code to retain the precision of your epoch.
This reduces the precision to minutes:
SELECT DATEADD(MINUTE,#YourEpoch/60/1000, '1/1/1970')
This one splits your epoch to days and milliseconds and then combines them in a datetime
CREATE FUNCTION [dbo].[fn_EpochToDatetime] (#Epoch BIGINT)
RETURNS DATETIME
AS
BEGIN
DECLARE #Days AS INT, #MilliSeconds AS INT
SET #Days = #Epoch / (1000*60*60*24)
SET #MilliSeconds = #Epoch % (1000*60*60*24)
RETURN (SELECT DATEADD(MILLISECOND, #MilliSeconds, DATEADD(DAY, #Days, '1/1/1970')))
END;
However, I'm not quite sure why the 2nd solution is not as precise as I expect it to be.
Building on the response above, the solution provided works but does not protect from trying to convert to a date that is out of bounds for SQL server.
create function dbo.unixTimestampConversion (
#unixTime bigInt
)
returns dateTime2(7)
as
begin
declare
#output dateTime2(7)
, #days int
, #ms int
, #x int = (1000 * 60 * 60 * 24)
;
set #days = #unixTime / #x
;
set #ms = #unixTime % #x
;
if (#unixTime < 32503593600000 and #unixTime > -2208988800000)
begin
set #output = dateAdd (millisecond, #ms, dateAdd (day, #days, '1/1/1970'))
;
end
;
else if (#unixTime <= -2208988800000)
begin
set #output = '1/1/1900'
;
end
;
else if (#unixTime >= 32503593600000)
begin
set #output = '12/31/2999'
;
end
;
return #output
;
end
;
You can assign the epoch time to your datetime directly (I tried this on SQL Server 15.0). Although it considers the number as the number of days since 1900-1-1 00:00:00 so you have to add 2208988800 (the number of seconds in 70 years) and then divide by 86400(number of seconds in a day).
DECLARE #time DATETIME = (2208988800.0 + [your epoch time in seconds])/86400;
However, it seems to be 0.007s or 0.003s behind the given epoch. Also, I'm not sure if this is faster than the DATEADD() function.
create a function to convert epoch to datetime and use them in your query like
below
create FUNCTION [dbo].[from_unixtime] (#Datetime BIGINT)
RETURNS DATETIME
AS
BEGIN
DECLARE #LocalTimeOffset BIGINT
,#AdjustedLocalDatetime BIGINT;
SET #LocalTimeOffset = DATEDIFF(second,GETDATE(),GETUTCDATE())
SET #AdjustedLocalDatetime = #Datetime - #LocalTimeOffset
RETURN (SELECT DATEADD(second,#AdjustedLocalDatetime, CAST('1970-01-01 00:00:00' AS datetime)))
END;
and then use this function in your query
I have a stored procedure writen in T-SQL and I want to make it for PostgreSQL but I'm not so familiar with PostgreSQL.
My stored procedure look like this:
CREATE PROCEDURE usp_insert_allocated_time
#fld_project_id INT,
#fld_allocated_time INT
AS
DECLARE #project int;
SET #project = #fld_project_id;
DECLARE #allocated int;
DECLARE #time int;
BEGIN
SET #time = (SELECT SUM(fld_allocated_time)
FROM dbo.tbl_project_timesheet
WHERE fld_project_id =#project)
SET #allocated = (SELECT fld_allocated_days FROM dbo.tbl_project where fld_id = #project);
IF #allocated > #time
BEGIN
INSERT into dbo.tbl_project_timesheet(fld_project_id,fld_allocated_time)
VALUES(#fld_project_id,#fld_allocated_time);
END
ELSE
PRINT 'Not OK';
END
And I have to do something like this, but on line 10 I get this error:
ERROR: invalid input syntax for integer: "293.00"
SQL state: 22P02
Context: PL/pgSQL function
"SA_PRJ".usp_add_timesheet_record_new(integer,integer,numeric,numeric,character varying,character varying) line 10 at assignment
CREATE OR REPLACE FUNCTION "SA_PRJ".usp_add_timesheet_record_new(p_uid integer, p_project_id integer, p_allocated_time numeric, p_achieved_time numeric, p_task_desc character varying, p_obs character varying)
RETURNS void AS
$BODY$
declare alloc_id integer;
declare project integer;
declare allocated integer;
declare allocated_time integer;
BEGIN
project := p_project_id;
allocated_time := (SELECT SUM(fld_allocated_time)
FROM "SD_PRJ".tbl_project_timesheet
WHERE fld_project_id = project);
allocated := (SELECT fld_allocated_days FROM "SD_PRJ".tbl_project where fld_id = project);
if not "SA_ADM".usp_check_permission(p_uid, 'SA_PRJ', 'usp_add_timesheet_record') then
raise exception 'User ID % no have the permission!', p_uid;
end if;
select fld_id into alloc_id from "SD_PRJ".tbl_project_allocation where fld_emp_id = p_uid and fld_project_id = p_project_id;
BEGIN
IF (allocated > allocated_time) THEN
INSERT INTO "SD_PRJ".tbl_project_timesheet(fld_emp_id, fld_project_id, fld_is_allocated,fld_allocated_time, fld_achieved_time, fld_task_desc, fld_obs)
VALUES (p_uid,p_project_id,coalesce(alloc_id,0), p_allocated_time, p_achieved_time,p_task_desc, p_obs);
ELSE
RAISE NOTICE 'Not OK!!';
END IF;
END;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
It's more complex version in PostgreSQL for what I want.
You don't really give enough information to try and fix your problem, but the error message is pretty descriptive. You are trying to put 293.00 into an integer. Here I can reproduce:
DO
$$
DECLARE
i INT;
BEGIN
i := 293.00;
RAISE NOTICE 'i=%', i;
END
$$;
This will raise:
ERROR: invalid input syntax for integer: "293.00"
SQL state: 22P02
Context: PL/pgSQL function inline_code_block line 6 at assignment
You need to change your variable to the same datatype as the data you are trying to assign to it. For example:
DO
$$
DECLARE
i NUMERIC(5, 2);
BEGIN
i := 293.00;
RAISE NOTICE 'i=%', i;
END
$$;
This works and outputs:
NOTICE: i=293.00
Query returned successfully with no result in 14 ms.
declare #amount float
declare #result char (20)
select #amount = cost from PurchaseDoc where id = 1
if #amount > 0 set #result = 'ok'
else set #result = 'empty'
print #result
Here is one representation of your script which can be executed against an Oracle database:
DECLARE
amount NUMBER;
result varchar2(20);
BEGIN
SELECT SUM(cost) INTO amount
from PurchaseDoc
WHERE id = 1;
if (amount > 0)
then
result := 'ok';
else
result := 'empty';
end if;
dbms_output.put_line(result);
END;
/
See this link for some useful information about dbms_output.
I recommend taking a look at some PL/SQL tutorials, such as the ones located at www.plsql-tutorial.com.
EDIT
Updated select statement based on suggestion by Cade Roux
There is really no need for PL/SQL here, a normal select statement with 'decode' function can do the trick.
SELECT DECODE(SUM(COST),0,'empty','ok') FROM PurchaseDoc where id = 1;