mysql function variable set not working - mysql

I am writing a function that should return a floating value.
BEGIN
DECLARE due_amount DECIMAL(9,2);
SET due_amount = (SELECT due_amount FROM (
SELECT id, MAX(date), due_amount, user_id
FROM lunch_transaction
GROUP BY user_id
HAVING user_id = user) l);
IF due_amount IS NULL THEN
SET due_amount = 0.00;
END IF;
RETURN due_amount;
END
The function only returns value 0.00 even though the value should be something else.
Running only this query :
(SELECT due_amount FROM (
SELECT id, MAX(date), due_amount, user_id
FROM lunch_transaction
GROUP BY user_id
HAVING user_id = user) l);
is giving the correct output though.
How should I set the query's output to the variable?

It is really bad practice to use variable name that can conflict with column names. Also, the subquery seems very unnecessary. I would try something more like this:
BEGIN
DECLARE v_due_amount DECIMAL(9,2);
SELECT v_due_amount := l.due_amount
FROM lunch_transaction l
WHERE l.user_id = in_user; -- I'm guessing `user` is also a parameter
IF v_due_amount IS NULL THEN
SET v_due_amount = 0.00;
END IF;
RETURN v_due_amount;
END;
Your version has an aggregation function in the subquery. This makes no sense, because due_amount is not the argument of an aggregation function. This logic should perhaps be:
SELECT v_due_amount := SUM(l.due_amount)
FROM lunch_transaction l
WHERE l.user_id = in_user; -- I'm guessing `user` is also a variable

Related

Using WHERE clause dynamically in select statement

I want to get a particular user_id and balance using a SELECT statement with a WHERE clause from user input
This is my trigger in SQL Server 2008:
create trigger trig_trans
on user_accnt after update
as
begin
declare #id int
declare #balance int
declare #transdate date
select #id = user_id, #balance = user_bal
from user_accnt
merge user_trans target
using user_accnt source on target.user_id = source.user_id
when matched then
update set trans_date = getdate(),
balance = #balance
when not matched then
insert (user_id, trans_date, balance)
values(#id, getdate(), #balance);
end
In this SELECT statement, I want to use a WHERE clause.
select #id = user_id, #balance = user_bal
from user_accnt
For example : if I am using
select #id = user_id, #balance = user_bal
from user_accnt
where account_num = 123456
I will get the result.
But in my situation my query should get the account_num from the user input.
Is there any way to do this?

wrong result mysql in function only

I have the following function, mysql query:
BEGIN
DECLARE r float(10,2);
DECLARE var_total float(10,2);
DECLARE var_discount float(10,2) DEFAULT null;
SELECT
sum(x.amount)
FROM
(
(SELECT
student_booking_school_course_price as amount
FROM
tbl_student_booking_school_course
WHERE
student_booking_id=par_student_booking_id
)
UNION
(SELECT
student_booking_school_accommodation_price as amount
FROM
tbl_student_booking_school_accommodation
WHERE
student_booking_id=par_student_booking_id
)
UNION
(SELECT
student_booking_school_insurance_price as amount
FROM
tbl_student_booking_school_insurance
WHERE
student_booking_id=par_student_booking_id
)
UNION
(SELECT
student_booking_school_transfer_price as amount
FROM
tbl_student_booking_school_transfer
WHERE
student_booking_id=par_student_booking_id
)
) x
INTO var_total;
IF var_total IS NULL THEN
SET r = 0;
END IF;
-- discount
SET var_discount = (SELECT
sb.student_booking_discount_amount
FROM
tbl_student_booking sb
WHERE
sb.student_booking_id=par_student_booking_id LIMIT 1);
IF var_discount IS NOT NULL THEN
SET r = var_total - var_discount;
end if;
return r;
END
The values are:
9698.88 course
559.55 accommodation
559.55 insurance
145.98 discount
It seems that the first query inside the function, only sums distinct values, as the result with discount is: 10112.45, so is not summing one value of 559.55, I tried to output different things as concat with a string and only see the result as 9698.88course,559.55accommodation, etc.. and it is fine. So I assume the issue is that is not summing if values are equals. The strange thing is that running this from the console, only the query outside the function, it sums ok.
My question is this a normal behaviour of MySql?If so is there a way to prevent this? is this a bug?
What you need here is UNION ALL clause:
SELECT
sum(x.amount)
FROM
(
(SELECT
student_booking_school_course_price as amount
FROM
tbl_student_booking_school_course
WHERE
student_booking_id=par_student_booking_id
)
UNION ALL
(SELECT
student_booking_school_accommodation_price as amount
FROM
tbl_student_booking_school_accommodation
WHERE
student_booking_id=par_student_booking_id
)
UNION ALL
(SELECT
student_booking_school_insurance_price as amount
FROM
tbl_student_booking_school_insurance
WHERE
student_booking_id=par_student_booking_id
)
UNION ALL
(SELECT
student_booking_school_transfer_price as amount
FROM
tbl_student_booking_school_transfer
WHERE
student_booking_id=par_student_booking_id
)
) x
INTO var_total;
The MySQL UNION Documentation says:
A DISTINCT union can be produced explicitly by using UNION DISTINCT or
implicitly by using UNION with no following DISTINCT or ALL keyword.

MySQL - Insert multiple rows based on column value

I have the query working, just wondering if there is a better way to do this without cursors/loops/php side. I've been a DBA for 5+ years and just came across the := statement. Very cool.
Table (tblPeople) with the person ID and the number of tickets they bought.
PersonId NumTickets
1 3
2 1
3 1
I then want to assign individual tickets to each person in a new table (tblTickets), depending on how many tickets they bought. The TicketId is a key, auto increment column.
TicketId PersonId
100 1
101 1
102 1
103 2
104 3
Here is the code. It loops through the whole tblPeople over and over again incrementing a new calculated column called rowID. Then I filter out the rows based on the number of tickets they bought in the WHERE clause. The problem I see is the subquery is huge, the more people I have, the bigger the subquery gets. Just not sure if there is a better way to write this.
INSERT INTO tblTickets (PersonId)
SELECT PersonId
FROM (
SELECT s.PersonId, s.NumTickets,
#rowID := IF(#lastPersonId = s.PersonId and #lastNumTickets = s.NumTickets, #rowID + 1, 0) AS rowID,
#lastPersonId := s.PersonId,
#lastNumTickets := s.NumTickets
FROM tblPeople m,
(SELECT #rowID := 0, #lastPersonId := 0, #lastNumTickets := 0) t
INNER JOIN tblPeople s
) tbl
WHERE rowID < NumTickets
I'd add a utility table Numbers which contains all the numbers from 1 up to the maximal number of tickets a person may buy. Then you can do something like this:
INSERT INTO tblTickets (PersonId)
SELECT s.PersonId
FROM tblPeople s, Numbers n
WHERE n.number <= s.NumTickets
Following Stored procedure will serve your purpose...
DELIMITER $$
USE <your database name> $$
DROP PROCEDURE IF EXISTS `update_ticket_value2`$$
CREATE PROCEDURE `update_ticket_value2`()
BEGIN
DECLARE index_value INT;
DECLARE loop_variable INT;
SET #KeyValue = 100;
SET #LastPersonID = 0;
SET #TicketNum = 0;
SET #PersonIDToHandle = 0;
SELECT #PersonIDToHandle = PersonID, #TicketNum = NumTickets
FROM tblPeople
WHERE PersonId > #LastPersonID
ORDER BY PersonId
LIMIT 0,1;
WHILE #PersonIDToHandle IS NOT NULL
DO
SET loop_variable = 0;
WHILE(loop_variable < #TicketNum) DO
INSERT INTO tblTickets(TicketId, PersonId) VALUES(#KeyValue + loop_variable, #PersonIDToHandle);
SET loop_variable = loop_variable + 1;
END WHILE;
SET #LastPersonID = #PersonIDToHandle;
SET #PersonIDToHandle = NULL;
SET #KeyValue = #KeyValue + #TicketNum;
SELECT #PersonIDToHandle := PersonID, #TicketNum := NumTickets
FROM tblPeople
WHERE PersonId > #LastPersonID
ORDER BY PersonId
LIMIT 0,1;
END WHILE;
END$$
DELIMITER ;
Call the procedure as:
CALL update_ticket_value2();
Hope it helps...

Creating a SQL Function with multiple 'With' Statements returning a boolean

I am attempting to create a function that incorporates multiple 'With' Statements.
My original Query is:
WITH EmpCount as
(
SELECT job.EmployeeID, assign.PropertyID FROM EmployeeJobstatus job
JOIN Assignment assign ON job.JobID = assign.ID GROUP by job.EmployeeID,
assign.PropertyID
),
NoDup as
(
SELECT EmployeeID, Count(employeeID) as NO from EmpCount
Group by EmployeeID HAVING count(Employeeid) > 1
)
SELECT prop.Name, job.EmployeeID, Emp.EmpID, Emp.Name
from EmployeeJobStatus job
JOIN Assignment assign ON assign.id = job.jobid
JOIN Property prop ON prop.ID = assign.PropertyID
JOIN Employee emp on emp.ID = job.EmployeeID
WHERE EmployeeID IN (SELECT EmployeeID from NoDup)
GROUP By prop.Name, EmployeeID, emp.EmpID,Emp.Name
Order BY EmployeeID
This returns:
Name EmployeeID EmpID Name
Property 1 23 1286333 LastNameRemoved1, Rachel A
Property 2 23 1286333 LastNameRemoved1, Rachel A
Property 2 76 1268329 LastNameRemoved2, Tamer A
Property 1 76 1268329 LastNameRemoved2, Tamer A
Property 3 135 1411933 LastNameRemoved3, Sarah E
Property 1 135 1411933 LastNameRemoved2, Sarah E
My function needs to return a 'Y' or a 'N' depending on whether or not they were cross property, utilizing the fields in EmployeeJobStatus StartDate and EndDate.
I have only started my function, as I have no idea what to do next.
CREATE function dbo.IsEmployeeCrossPropertyOnDate
(#EmpID int, #AsOfDate datetime)
RETURNS INT AS
Any help, or a nudge in the right direction would be much appreciated. Thank you.
How about this...
CREATE FUNCTION dbo.IsEmployeeCrossPropertyOnDate
(
#empid int
, #asofdate datetime
)
RETURNS TABLE
AS
RETURN
SELECT
CASE MIN(a.propertyid)
WHEN MAX(a.propertyid)
THEN 'N'
ELSE 'Y'
END CrossProp
FROM
EmployeeJobStatus j
JOIN
Assignment a
ON a.id = j.jobid
WHERE
j.employee = #empid
AND j.startdate <= #AsOfDate
AND j.endDate > #AsOfDate
GROUP BY
j.employeeid;
EDIT:
You'd probably outer apply it, rather than cross. Nulls would then be employees with no assignments at the point in time.
EDIT 2:
Since you asked, below is a scalar function. Just be careful of scalars. Depending on where you use it in a query, the BEGIN END block can fool the optimizer into going RBAR.
CREATE FUNCTION dbo.IsEmployeeCrossPropertyOnDate
(
#empid int
, #asofdate datetime
)
RETURNS CHAR(1)
AS
BEGIN
DECLARE #res CHAR(1) = 'N';
SELECT #res =
CASE MIN(a.propertyid)
WHEN MAX(a.propertyid)
THEN 'N'
ELSE 'Y'
END
FROM
EmployeeJobStatus j
JOIN
Assignment a
ON a.id = j.jobid
WHERE
j.employee = #empid
AND j.startdate <= #AsOfDate
AND j.endDate > #AsOfDate
GROUP BY
j.employeeid;
RETURN #res;
END;

Query with multiple EXIST

I've got a database of rooms and equipments. I want to query the database and return a list of rooms with e.g. tv, radio, sat and fridge (eq1, eq2, eq3, ...., eqN).
I have the following SELECT statement:
select * from rooms r where
exists (select id from equipments where eq_id='eq1' and room_id=r.id)
and
exists (select id from equipments where eq_id='eq2' and room_id=r.id)
and
exists (select id from equipments where eq_id='eq3' and room_id=r.id)
.......
and
exists (select id from equipments where eq_id='eqN' and room_id=r.id)
Is there any way to optimize or making this shorter?
To shorten you could
select *
from rooms r
where #N = (select count(distinct eq_id)
from equipments
where eq_id IN ('eq1','eq2',...,'eqN') and room_id=r.id)
EDIT
but not sure if it will actually make it faster... quite the opposite, the version with EXISTS AND EXISTS has a chance to prune execution branch on the first false, the above must actually count the distinct values (go through all records) and see what that value is.
So you should think what is faster:
going once through all records related to a room (one correlated subquery) or
running N (worst case) correlated (but highly selective subqueries) for each room
It depends on the statistics of your data (I would think that if most rooms don't have all the sought equipment in them then your initial version should be faster, if most rooms have all equipment in them then the proposed version might perform better; also if the EXISTS version is faster make an effort to first the queries that are most likely to fail i.e. first check for rarest equipment)
You can also try a version with GROUP BY
select r.*
from rooms r join
equipments e on r.id = e.room_id
group by r.id
where eg_id in ('eq1','eq2',...,'eqN')
having count(distinct e.eq_id) = #N
(above SQL not tested)
try this (I don't have any DB available to test it, also consider performance )
select * from
rooms r,
(
select count(distinct id) as cnt, id from equipments where eq_id in ('eq1','eq2') group by id
) as sub
where sub.id = r.id
and sub.cnt >= 2 'Options count
Note: 2 - it is the number of options that you need. In example they are: 'eq1','eq2'
select * from rooms r where
(select count(id) from equipments where eq_id='eq1' and room_id=r.id) > 0
and
...
Use an Stored Procedure.
here is the procedure for mysql:
DELIMITER $$
CREATE DEFINER=`root`#`%` PROCEDURE `GetRooms`(IN roomtable TEXT, IN equipmenttable TEXT, IN equipments TEXT )
BEGIN
DECLARE statement text;
DECLARE Pos int;
DECLARE cond text;
DECLARE element text;
DECLARE tmpTxt text;
set tmpTxt = equipments;
set cond = "";
set Pos = instr(tmpTxt,';');
while Pos <> 0 do
set element = substring(tmpTxt, 1, Pos-1);
if cond <> "" then
set cond = concat(cond,' and ');
end if;
set cond = concat(cond,'exists (select id from ' , equipmenttable ,' where eq_id=''' , element ,''' and room_id=r.id) ');
set tmpTxt = replace(tmpTxt, concat(element,';'), '');
set Pos = instr(tmpTxt,';');
end while;
if tmpTxt <> "" then
if cond <> "" then
set cond = concat(cond,' and ');
end if;
set cond = concat(cond,'exists (select id from ' , equipmenttable ,' where eq_id=''' , tmpTxt ,''' and room_id=r.id) ');
end if;
SET #statement = concat('Select * FROM ' , roomtable , " WHERE " , cond , ";");
PREPARE stmt FROM #statement;
EXECUTE stmt;
END
Execute it with: CALL GetRooms('RoomTableName','EquipmentTableName','EquipmentIDs')
Example:
Call GetRooms('rooms','equipemnts','eq1;eq2;eq3');
Hope this helps.
To Execute Query Faster use Exists
select *
from rooms as r
where exists (
select *
from equipments
where eq_id IN ('eq1','eq2',..,'eqN') and r.id= equipments.room_id);