I have a Table called TblOrders.the fields are FldSlNo, FldStrategyID, FldTradeServerName, FldBaseDir, FldBinaryStartTime, FldInstrumentID, FldOrderNumber, FldBuySell, FldDisplayQuantity, FldRemainingQuantity, FldTotalTradeQuantity, FldLastTradePrice, FldLastTradeQuantity, FldPrice, FldOrderTime, FldReferenceText and FldOrderStatusID. Now I have a procedure called ProfitCalculation.
the procedure is given below:
delimiter //
CREATE PROCEDURE ProfitCalculation
(
IN instrument INT(20) ,
OUT profit float(10,2)
)
BEGIN
DECLARE buy DECIMAL(10,2);
DECLARE sell DECIMAL(10,2);
DECLARE oprofit DECIMAL(8,2);
SELECT SUM(FldLastTradeQuantity*FldPrice)
FROM TblOrders
WHERE FldInstrumentID = instrument AND FldBuySell = 'b' AND FldLastTradePrice != 0 AND FldLastTradeQuantity != 0 group by FldInstrumentID INTO buy;
SELECT SUM(FldLastTradeQuantity*FldPrice)
FROM TblOrders
WHERE FldInstrumentID = instrument AND FldBuySell = 's' AND FldLastTradePrice != 0 AND FldLastTradeQuantity != 0 group by FldInstrumentID INTO sell;
SELECT (sell-buy) INTO oprofit;
SELECT oprofit INTO profit;
END
//
delimiter ;
It always return null.
Is there have any solution for this problem.
Please help me out..
Thanks in advance
First, on both of the queries, you're specifying a GROUP BY, but expecting a single result. Since you're already including the grouped field in the where clause, this is not necessary.
Secondly, try running the individual statements (without the 'INTO' statement) - my guess is that at least one of them is returning NULL, causing the output to be null. If you would prefer zero to NULL as an output with an empty recordset, wrap the SUM() statement in COALESCE, as in:
SELECT COALESCE(SUM(FldLastTradeQuantity*FldPrice), 0)
This way, if one of the two statements return NULL, the other one will still work.
Specifying the conditions FldLastTradePrice != 0 AND FldLastTradeQuantity != 0 are unnecessary, as they'd be handled within the SUM statement.
Related
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;
Here's what I've tried. My host is returning an error, "Sorry an unexpected error happened!" .
I want it to return true if there is at least 1 record with combination pdriver_id, ptruck_number, and pdate.
DELIMITER %%
CREATE FUNCTION DriverActiveInTruckByDate(
pdriver_id INT,
ptruck_number INT,
pdate DATETIME
)
RETURNS boolean
DETERMINISTIC
BEGIN
DECLARE inDB INT DEFAULT 0;
SET inDB =
SELECT IF(COUNT(*) >= 1,1,0)
FROM
truck_timeline tl
WHERE 1=1
AND tl.driver_id = pdriver_id
AND tl.truck_number = ptruck_number
AND ((pdate BETWEEN tl.begin_date AND tl.end_date) OR (pdate >= tl.begin_date AND tl.end_date IS NULL))
END
%%
DELIMITER ;
Several fixes are needed:
The function is not DETERMINISTIC. This means the result will always be the same given the same inputs. In your case, the result may be different depending on the data in your truck_timeline table. So I would suggest using READS SQL DATA.
If you use SET variable = SELECT... you must put the SELECT in a subquery:
SET inDB = (SELECT ...);
The current manual recommends using SELECT ... INTO variable instead of SET. See https://dev.mysql.com/doc/refman/8.0/en/select-into.html
The INTO position at the end of the statement is supported as of MySQL 8.0.20, and is the preferred position.
SELECT ... INTO inDB;
The function you show doesn't have a RETURN statement. See https://dev.mysql.com/doc/refman/8.0/en/return.html
There must be at least one RETURN statement in a stored function.
Your Full Code could be like this:
DELIMITER %%
CREATE FUNCTION DriverActiveInTruckByDate(
pdriver_id INT,
ptruck_number INT,
pdate DATETIME
)
RETURNS boolean
DETERMINISTIC
BEGIN
DECLARE inDB INT DEFAULT 0;
SET inDB =
(SELECT IF(COUNT(*) >= 1,1,0)
FROM
truck_timeline tl
WHERE 1=1
AND tl.driver_id = pdriver_id
AND tl.truck_number = ptruck_number
AND ((pdate BETWEEN tl.begin_date AND tl.end_date) OR (pdate >= tl.begin_date AND tl.end_date IS NULL))
);
END %%
DELIMITER ;
I have a stored procedure that returns a common query, I need to call it in several functions but some functions may call it through Period Id or others through Header Id, so far I would like to know how can I determine what param to use in order to retrive data properly, I have something like this implemented.
CREATE PROCEDURE dbo.GetTFDRecordInfo
#PeriodId int = null,
#HeaderId int = null
AS
BEGIN
SELECT
-- I have a lot more fields and joins here, that's why I need to get the statement in a single call through either period id or header id
*
From NT_CSRTNVPeriodInfo t
-- how can I make possible something like shown above, can I use a "Case When"?
Where (
/*
if #PeriodId is null
Where t.HeaderId = #HeaderId
if #HeaderId is null
Where t.PeriodId = #PeriodId
*/
)
END
GO
-- swtich between params
Exec NT_CSRTNVPeriodInfo null, 2654
Exec NT_CSRTNVPeriodInfo 196, null
This is the answer:
CREATE PROCEDURE dbo.GetTFDRecordInfo
#PeriodId int = null,
#HeaderId int = null
AS
BEGIN
SELECT
-- I have a lot more fields and joins here, that's why I need to get the statement in a single call through either period id or header id
*
From NT_CSRTNVPeriodInfo t
-- how can I make possible something like shown above, can I use a "Case When"?
Where ((#PeriodId IS NULL) or (t.PeriodId = #PeriodId))
And ((#HeaderId IS NULL) or (t.HeaderId = #HeaderId))
END
GO
You have to use conditional OR to check NULLs, if param is set, the second condition is checked, if not, the procedure will consider always true the statement and go to the next.
I have this trigger. If the incoming log agrees with input filter, than is not saved into database. But, I want to keep number of "hits" of each Primitive_filter. I have a column named hit_rate, which is int(30). Is there some way how to do that? Maybe specific error? Or sth else? Thx for help.
UPDATE Primitive_filters SET hit_rate = hit_rate + 1 where Primitive_filters.id = ???;
trigger
delimiter //
CREATE TRIGGER inputFilter
before insert
on Logs
for each row
begin
declare msg varchar(255);
IF (SELECT COUNT(*) FROM Primitive_filters, Primitive_in_filter, Filters WHERE
Filters.name = "input" AND Filters.id = Primitive_in_filter.id_filter AND Primitive_in_filter.id_primitive = Primitive_filters.id AND
(Primitive_filters.id_host LIKE CONCAT('%',(SELECT host FROM Hosts WHERE id = new.id_host),'%') OR Primitive_filters.id_host IS NULL) AND
(Primitive_filters.facility LIKE CONCAT('%',new.facility,'%') OR Primitive_filters.facility IS NULL) AND
(Primitive_filters.priority LIKE CONCAT('%',new.priority,'%') OR Primitive_filters.priority IS NULL) AND
(Primitive_filters.program LIKE CONCAT('%',new.program,'%') OR Primitive_filters.program IS NULL) AND
(new.msg REGEXP Primitive_filters.msg OR Primitive_filters.msg IS NULL)) > 0 THEN CALL raise_error; END IF;
END //
delimiter ;
This is NOT the answer to your question.
It's only a hint how to fix a potentially serious performance problem in your code.
Don't use this:
IF (SELECT COUNT(*) FROM ... giant query ...) > 0
THEN CALL raise_error;
END IF;
Use this instead:
IF EXISTS (SELECT 1 FROM ... giant query ...)
THEN CALL raise_error;
END IF;
The former condition calculates a count ... it must read all rows returned by the query
If the query returns billion rows, it must reads them all --> because you asked give me a count of rows.
Then, after the query return the count, there is a check: if the query returns at least one row, then do something.
The latter condition stops executing the query when the query returns first row, saving time and resources.
This function:
CREATE FUNCTION `GetCardID`(numId INT) RETURNS int(11)
DETERMINISTIC
BEGIN
DECLARE retcard INT(11);
SELECT id
INTO retcard
FROM cards
WHERE `number` = numId
AND enabled = 1
LIMIT 1;
RETURN retcard;
END
Always returns null even when the query:
SELECT id FROM cards WHERE `number`=<Insert Value Here> AND ENABLED = 1 LIMIT 1;
returns a valid value for the same value used in and the function parameter.
For instance:
SELECT id FROM cards WHERE number=12345 AND ENABLED = 1 LIMIT 1;
-- returns an id, while
GetCardId(12345);
-- returns null
Any ideas what I'm missing here? I consider myself quite skilled at SQL, but a little green on SP's.
How big is the data that you are taking into your function? Is it possible that the number is larger than what will fit into an INT?
Christopher here is your function. Try this and it should work:
CREATE FUNCTION [dbo].[GetCardID]
(
#Num_ID INT
)
RETURNS int
AS
BEGIN
declare #retcard int
select Top 1 #retcard = id
FROM cards
where number = #num_Id
AND enabled = 1
return #retcard
END
Always returns NULL:
Get rid of DETERMINISTIC clause in Procedure definition. MySQL caches the responses from such procedure or functions.
Excerpt from MySQL:
A routine is considered “deterministic” if it always produces the same
result for the same input parameters, and “not deterministic”
otherwise. If neither DETERMINISTIC nor NOT DETERMINISTIC is given in
the routine definition, the default is NOT DETERMINISTIC. To declare
that a function is deterministic, you must specify DETERMINISTIC
explicitly
MySQL 5.5 - Creating Procedure or Function