Why PHPMyAdmin throws an error in my SQL syntax? - mysql

It's again PHPMyAdmin... I try to run query on two tables - cards and ownerships (which list all players with the cards they own). And I'd like to create a function which will return (if given the name) the total amount of that card on the market. I defined this in the following way:
CREATE FUNCTION get_card_overall_count (_name TEXT)
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE res INT;
SET res = SELECT SUM(`ownerships`.`amount`)
FROM `ownerships` JOIN `cards` ON `ownerships`.`card_ID` = `cards`.`ID`
WHERE `cards`.`name` = _name
GROUP BY `name`;
RETURN res;
END//

The SET syntax wants the right hand side to be a scalar, not a query, but you can convince it to run a scalar subquery:
SET res = (SELECT SUM(`ownerships`.`amount`)
FROM `ownerships` JOIN `cards` ON `ownerships`.`card_ID` = `cards`.`ID`
WHERE `cards`.`name` = _name
GROUP BY `name`);

Related

How to return boolean based on number of records in database?

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 error in mysql function? ERROR is Not allowed to return a result set from a function

I have error in mysql function. I attached my code below here.
DELIMITER $$
CREATE FUNCTION CheckAccount6(ids INT)
RETURNS integer
BEGIN
DECLARE opening INT;
DECLARE a INT;
SET a = 0;
select q.invoice, q.timestamp,
(select sum(o.totprice) from sp_orderdetails o where o.quotation_no = q.id) as amount
from sp_quotation q where q.cid = ids and q.invoice != '0'
UNION
select 0, t.timestamp, t.amount from sp_transactions t where t.cid = ids and t.status like 'Approved';
IF(invoice = 0) THEN
a = a - amount;
ELSE
a = a + amount;
return a;
END $$
If you write a SELECT query in a function or procedure without assigning the result to a variable, it tries to return it. This is allowed for procedures, because they can be used as a query, and the result of the SELECT query becomes the result of CALL procedurename.
But a function can just return a single value, not a query result set. If you use a query in the function, it has to store the results in variables and then use those to compute the function's return value. If you need to process all the rows of a result set, it needs to use a cursor.
Since you just want to return a sum, you can use SUM() in the query, and return that. There's no need for any variables or looping.
DELIMITER $$
CREATE FUNCTION CheckAccount6(ids INT)
RETURNS integer
BEGIN
RETURN (SELECT SUM(o.totprice)
FROM sp_orderdetails AS o
JOIN sp_quotation AS q ON o.quotation_no = q.id
WHERE q.cid = ids AND q.invoice != '0')
-
(SELECT SUM(t.amount)
FROM sp_transactions t
WHERE t.cid = ids and t.status like 'Approved');
END $$

MySQL Use table name for function

When we use a statement like select count(*) from TABLE, the function count() automatically knows which table it is counting. Is it possible to grab the table and use it in a user defined function.
drop function if exists related_count;
create function related_count(parent int(11)) returns int(11) deterministic
begin
declare count int(11) default 0;
set count=(select count(*) from TABLENAME where id=parent);
return count;
end;
So that I can use it like this:
select count(*),related_count(id) from TABLENAME
So that I can use the same function regardless of table instead of defining multiple functions because of multiple tables.
Is there a way to switch between select count(*) from TABLENAME1 where id=parent or select count(*) from TABLENAME2 where id=parent dependent on a variable related_count('TABLE1',id)
The comment above from #RajeevRanjan mentions using dynamic SQL. This won't work, but if it did it would look like this:
create function related_count(tablename varchar(64), parent int) returns int reads sql data
begin
declare count int default 0;
set #sql = concat('select count(*) into count from `', tablename, '` where id = ', parent);
prepare stmt from #sql;
execute stmt;
return count;
end
However, this is not allowed:
ERROR 1336 (0A000): Dynamic SQL is not allowed in stored function or trigger
The reason it doesn't work is that your stored function could be called by an expression in an SQL statement that is itself a dynamic SQL execution. I guess MySQL only allows one level "deep" of prepare/execute. You can't make a prepared query run another prepared query.
To do this, you'd have to hard-code each table name like the following:
create function related_count(tablename varchar(64), parent int) returns int reads sql data
begin
declare count int default null;
if tablename = 'foo' then set count = (select count(*) from foo where id = parent);
elseif tablename = 'bar' then set count = (select count(*) from bar where id = parent);
elseif tablename = 'baz' then set count = (select count(*) from baz where id = parent);
end if;
return count;
end
This also has an advantage that it isn't an SQL injection vulnerability, whereas the PREPARE/EXECUTE solution (if it had worked) would be.
PS: A function that reads from other tables is not deterministic.

Why am I getting result consisted of more than one row error, when finally one row is returned?

Upon running the following procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS `portaldb`.`is_optional_type_assigned`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `is_optional_type_assigned`(userId int, optionalPlanId int)
begin
if userId is not null and optionalPlanId is not null then
// Will return multiple rows
select conv.FeatureName from planallocation as pa
left join subscriptioninfo as si
on si.SubscriptionId = pa.SubscriptionId
left join plans as pl
on pl.PlanId = pa.CurrentPlanId
right join conversiontable as conv
on conv.ConversionId = pl.OptionalFeatureId
where si.UserId = userId and
conv.FeatureType = 'optional' into #featureList;
// Will return single row
select conv.FeatureName from conversiontable as conv
right join plans as pl
on conv.ConversionId = pl.OptionalFeatureId
where conv.FeatureType = 'optional' and
pl.PlanId = optionalPlanId into #featureName;
if #featureName in (#featureList) then
select true as isAssigned;
else
select false as isAssigned;
end if;
end if;
end$$
DELIMITER ;
I am getting:
Error Code : 1172
Result consisted of more than one row
error. What could be the reason for this? The result from the first two select statement is assigned to the variable and then compared if one set contains another.
You are getting the error because MySQL requires that a query being selected into a variable must return exactly one row - return zero or more than one row results in an error. So your first query, which you comment returns multiple rows, will cause the error.
http://dev.mysql.com/doc/refman/5.7/en/select-into.html
you could possibly change the query to something like :
select GROUP_CONCAT(conv.FeatureName) from .....
to get a single comma separated list result & then search for #featureName in that list - but that may depend on the number of results returned. Otherwise you need to restructure your two queries - possibly something like this (note I have incorporated Gordon's suggestion on parameter naming) :
/ Will return single row
select conv.FeatureName from conversiontable as conv
right join plans as pl
on conv.ConversionId = pl.OptionalFeatureId
where conv.FeatureType = 'optional' and
pl.PlanId = in_optionalPlanId into #featureName;
select IF(count(conv.FeatureName)>0,true,false) from planallocation as pa
left join subscriptioninfo as si
on si.SubscriptionId = pa.SubscriptionId
left join plans as pl
on pl.PlanId = pa.CurrentPlanId
right join conversiontable as conv
on conv.ConversionId = pl.OptionalFeatureId
where si.UserId = in_userId and
conv.FeatureType = 'optional' and
conv.FeatureName = #featureName;
It may be possible to reorganise it into a more efficient query or even a single query.
Your code does not do what you think it does. A cardinal rule when using parameters: Name them differently, so they are obvious in the code.
When you write:
where si.UserId = userId and
This is interpreted as:
where si.UserId = si.userId and
I would suggest that you start with a more readable and useful:
DELIMITER $$
DROP PROCEDURE IF EXISTS portaldb.is_optional_type_assigned$$
CREATE DEFINER = root#localhost PROCEDURE is_optional_type_assigned (
in_userId int,
in_optionalPlanId int
)
BEGIN
if in_userId is not null and in_optionalPlanId is not null then
. . .
END;$$
DELIMITER ;

MySQL stored function SELECT INTO unexpected results

I am trying to write a stored function in mysql 5.1 that returns the value 'AccountIDref' for a given room. If I only query the inner SELECT statement this works (returns the value for room). But invoking the function I get the response:
'#1172 - Result consisted of more than one row'
CREATE FUNCTION getAccountId (room INT) RETURNS INT
BEGIN
DECLARE refID INT DEFAULT NULL;
SELECT AccountIDref INTO refID FROM Allocation
WHERE Room = room;
RETURN refID;
END
What am I doing wrong here?
Field name and parameter name must be different -
CREATE FUNCTION getAccountId (room_param INT) RETURNS INT
BEGIN
DECLARE refID INT DEFAULT NULL;
SELECT AccountIDref INTO refID FROM Allocation
WHERE Room = room_param;
RETURN refID;
END
In your function you were getting all tables records.
What I am going to suggest isn't going to be much different from what you have, but I am skeptical about the where clause being in the next line and also let's use limit 1 to explicitly set the limit.
Try this :
CREATE FUNCTION getAccountId (room INT) RETURNS INT
BEGIN
DECLARE refID INT DEFAULT NULL;
SELECT AccountIDref INTO refID FROM Allocation WHERE Room = room LIMIT 1;
RETURN refID;
END