Convert tax like IRS function to SQL - php

Trying to convert this tax-like IRS function Calculating revenue share at different tiers to SQL. The assumption is the table would be one column (price) and the tiers will be added dynamically.
My first attempt has some case statements that didn't work out well. I have since scrapped it :) Thanks for the help!

SQL isn't really suited to iterative loops. I'd
select * from `brackets` where `tier` < $income
From there I'd... well, you linked to some sample code in another language yourself. You should really do it that way.
If you insist otherwise, SQL can do it:
DELIMITER $$
create procedure `calcTax`(IN v_param1 int)
begin
declare v_tier int;
declare v_rate decimal(3,3);
declare v_untaxed int default 0;
declare v_taxTotal float default 0;
set v_untaxed = v_param1;
set v_taxTotal = 0;
while v_untaxed > 0 do
select max(`tier`), max(`rate`) into v_tier, v_rate
from `brackets` where tier < v_untaxed order by `tier` desc limit 1;
set v_taxTotal = v_taxTotal + (v_untaxed - v_tier) * v_rate;
set v_untaxed = v_tier;
end while;
select v_taxTotal;
end;
$$
Correct datatypes are up to you. You'll need a tax brackets table that works out. I made one for testing below (with poorly advised data types).
create table brackets (tier int primary key, rate float);
insert into brackets values (0, 0), (10000, 0.1), (50000, 0.2);

You could create a MYSQL user defined function.
http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html

Related

Sql correct filter for floats values in string column

I have table invoices and there is column 'total' varchar(255). There are values like these: "500.00", "5'199.00", "129.60", "1.00" and others.
I need select records and filter by total column. For example, find records where total is not more than 180.
I tried this:
SELECT total from invoices WHERE invoices.total <= '180'
But in result there are :
125.25
100.50
1593.55 - not correct
4'799.00 - not correct
1.00
-99.00
2406.52 -not correct
How can I fix it and write correct filter for this column? Thanks!
You can use cast() function to convert it in float
SELECT total from invoices WHERE cast(invoices.total as decimal(16,2)) <= 180
Why are you storing numbers as strings? That is a fundamental problem with your data model, and you should fix it.
Sometimes, we are stuck with other people's really, really, really bad decisions. If that is the case, you can attempt to solve this with explicit conversion:
SELECT i.total
FROM invoices i
WHERE CAST(REPLACE(i.total, '''', '') as DECIMAL(20, 4)) <= 180;
Note that this will return an error if you have other unexpected characters in your totals.
If the string starts with a number, then contains non-numeric characters, you can use the CAST() function or convert it to a numeric implicitly by adding a 0:
SELECT CAST('1234abc' AS UNSIGNED); -- 1234
SELECT '1234abc'+0; -- 1234
To extract numbers out of an arbitrary string you could add a custom function like this:
DELIMITER $$
CREATE FUNCTION `ExtractNumber`(in_string VARCHAR(50))
RETURNS INT
NO SQL
BEGIN
DECLARE ctrNumber VARCHAR(50);
DECLARE finNumber VARCHAR(50) DEFAULT '';
DECLARE sChar VARCHAR(1);
DECLARE inti INTEGER DEFAULT 1;
IF LENGTH(in_string) > 0 THEN
WHILE(inti <= LENGTH(in_string)) DO
SET sChar = SUBSTRING(in_string, inti, 1);
SET ctrNumber = FIND_IN_SET(sChar, '0,1,2,3,4,5,6,7,8,9');
IF ctrNumber > 0 THEN
SET finNumber = CONCAT(finNumber, sChar);
END IF;
SET inti = inti + 1;
END WHILE;
RETURN CAST(finNumber AS UNSIGNED);
ELSE
RETURN 0;
END IF;
END$$
DELIMITER ;
Once the function is defined, you can use it in your query:
SELECT total from invoices WHERE ExtractNumber(invoices.total) <= 180

MySQL trigger: convert text to numbers

I have a table with these fields:
users {id,name,week1,week2,week3,overall_score}
The weeks are TEXT and the overall_score is an INT
In week one either Absent or Present is put into it. Absent is 0 points and Present is 10. I would like to create a trigger to run automatically after each update to calculate the overall score and convert the text to a number.
I would go about solving this problem using the following 2 steps:
Create a function to determine the score for a particular week...
DELIMITER //
CREATE FUNCTION `GetWeekScore`(`WeekAttendance` TEXT) RETURNS INT
BEGIN
IF WeekAttendance = 'Present' THEN
RETURN 10;
ELSE
RETURN 0;
END IF;
END//
DELIMITER ;
Use that function in a trigger...
DELIMITER //
CREATE TRIGGER `CalculateOverallScore` BEFORE UPDATE ON `users`
FOR EACH ROW
BEGIN
SET NEW.overall_score = (GetWeekScore(NEW.week1) +
GetWeekScore(NEW.week2) +
GetWeekScore(NEW.week3));
END//
DELIMITER ;

Getting the MySQL WHERE IN delimited string

I've been recently stuck on an issue I've been having involving sending a string of comma separated values into a stored procedure. My issue is that when I execute my stored procedure in PHP it uploads the values with quotes around it like so;
CALL `rankingInformation`('145', '5', '', '37,38,39,40,41')
Failing to add the quotes would make MySQL interpret them as extra parameters.
However it's mean't to be like in the WHERE IN on the query side it's meant to be formatted like so
'37', '38', '39', '40', '41'
Here is the query below, can anyone spot anything I can do? Here is what I've got up to now.
CREATE DEFINER = `root`#` localhost` PROCEDURE` rankingInformation`(IN` surveyId` INT, IN` filterCounting` INT, IN` survey_filter_id` INT, IN` question_limit` TEXT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Gathers all the ranking information for a given ID'
BEGIN
DECLARE sfi int(2);
DECLARE ql TEXT;
IF(survey_filter_id = '') THEN
SET sfi = (SELECT sf2.survey_filter_id FROM survey_filters AS sf2 WHERE sf2.survey_id = 145 AND sf2.survey_filter_id IS NOT NULL LIMIT 1);
ELSE
SET sfi = survey_filter_id;
END IF;
SELECT
COUNT( * ) AS total, CONCAT(su.first_name, ' ', su.last_name) as full_name, sf.survey_filter_id, sf.survey_filter_name, qa.question_id, su.temp_group_1 AS department
FROM questions_answers AS qa
INNER JOIN survey_users AS su ON su.survey_users_id = qa.survey_users_id_answer
INNER JOIN survey_filters AS sf ON sf.survey_id = surveyId
WHERE qa.survey_id = surveyId
AND qa.question_id IN (splitAndTranslate(question_limit, ','))
AND sf.survey_filter_id = sfi
GROUP BY qa.survey_users_id_answer
HAVING total > filterCounting
ORDER BY total DESC;
END
splitAndTranslate
Here is a function I found which is mean't to do the job, I am not sure I am far away.
CREATE DEFINER=`root`#`localhost` FUNCTION `splitAndTranslate`(`str` TEXT, `delim` VARCHAR(1))
RETURNS text CHARSET utf8
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Fixes all Where IN issues'
BEGIN
DECLARE i INT DEFAULT 0; -- total number of delimiters
DECLARE ctr INT DEFAULT 0; -- counter for the loop
DECLARE str_len INT; -- string length,self explanatory
DECLARE out_str text DEFAULT ''; -- return string holder
DECLARE temp_str text DEFAULT ''; -- temporary string holder
DECLARE temp_val VARCHAR(255) DEFAULT ''; -- temporary string holder for query
-- get length
SET str_len=LENGTH(str);
SET i = (LENGTH(str)-LENGTH(REPLACE(str, delim, '')))/LENGTH(delim) + 1;
-- get total number delimeters and add 1
-- add 1 since total separated values are 1 more than the number of delimiters
-- start of while loop
WHILE(ctr<i) DO
-- add 1 to the counter, which will also be used to get the value of the string
SET ctr=ctr+1;
-- get value separated by delimiter using ctr as the index
SET temp_str = REPLACE(SUBSTRING(SUBSTRING_INDEX(str, delim, ctr), LENGTH(SUBSTRING_INDEX(str, delim,ctr - 1)) + 1), delim, '');
-- query real value and insert into temporary value holder, temp_str contains the exploded ID
#SELECT ImageFileName INTO temp_val FROM custombu_roomassets_images WHERE ImageID=temp_str;
-- concat real value into output string separated by delimiter
SET out_str=CONCAT(out_str, temp_val, ',');
END WHILE;
-- end of while loop
-- trim delimiter from end of string
SET out_str=TRIM(TRAILING delim FROM out_str);
RETURN(out_str); -- return
END
What did you do with FIND_IN_SET? Building on spencer7593's answer, it should work if you replace:
AND qa.question_id IN (splitAndTranslate(question_limit, ','))
with
AND FIND_IN_SET(qa.question_id, question_limit)>0
The commas within a string value are not interpreted as SQL text in the context of a SQL IN comparison. Your query is essentially of the form:
AND qa.question_id IN ('some,long,string,value')
And any comma characters within the string are just data; just characters that are part of the string. This is effectively the same as an equals comparison.
The MySQL FIND_IN_SET function might be a way for you to perform the comparison you want.
http://dev.mysql.com/doc/refman/5.5/en/string-functions.html#function_find-in-set
AND qa.question_id IN (splitAndTranslate(question_limit, ','))
Put these lines instead of the above line
AND (qa.question_id = question_limit
OR qa.question_id LIKE CONCAT(question_limit,',%')
OR qa.question_id LIKE CONCAT('%,',question_limit,',%')
OR qa.question_id LIKE CONCAT('%,',question_limit))
Then you don't need the splitAndTranslate Function anymore . .

Creating MySQL function requires SUPER privileges

I have a simple MySQL function for comparing versions:
CREATE FUNCTION `compareVersions` (
versionA VARCHAR(50),
versionB VARCHAR(50)) RETURNS INT DETERMINISTIC NO SQL
BEGIN
DECLARE a1 INT;
DECLARE b1 INT;
DECLARE c1 INT;
DECLARE d1 INT;
DECLARE a2 INT;
DECLARE b2 INT;
DECLARE c2 INT;
DECLARE d2 INT; SET a1 = SUBSTRING_INDEX( `versionA` , '.', 1 );
SET b1 = SUBSTRING_INDEX(SUBSTRING_INDEX( `versionA` , '.', 2 ),'.',-1);
SET c1 = SUBSTRING_INDEX(SUBSTRING_INDEX( `versionA` , '.', -2 ),'.',1);
SET d1 = SUBSTRING_INDEX( `versionA` , '.', -1 );
SET a2 = SUBSTRING_INDEX( `versionB` , '.', 1 );
SET b2 = SUBSTRING_INDEX(SUBSTRING_INDEX( `versionB` , '.', 2 ),'.',-1);
SET c2 = SUBSTRING_INDEX(SUBSTRING_INDEX( `versionB` , '.', -2 ),'.',1);
SET d2 = SUBSTRING_INDEX( `versionB` , '.', -1 );
IF (a1 > a2) THEN
RETURN -1;
ELSEIF ((a1 = a2) AND (b1 > b2)) THEN
RETURN -1;
ELSEIF ((a1 = a2) AND (b1 = b2) AND (c1 > c2)) THEN
RETURN -1;
ELSEIF ((a1 = a2) AND (b1 = b2) AND (c1 = c2) AND (d1 > d2)) THEN
RETURN -1;
ELSEIF ((a1 = a2) AND (b1 = b2) AND (c1 = c2) AND (d1 = d2)) THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
END $$
and its creation fails with
You do not have the SUPER privilege and binary logging is enabled (you
might want to use the less safe log_bin_trust_function_creators variable)
This is almost the same as this question, but my function does not read any SQL data, it is simple, deterministic and I see no reason why it should require any extra privileges. It is not clear to me from the documentation if the SUPER privilege is required for creating all functions (which would be ridiculous, making stored functions unavailable to many users, everyone who does not have access to their database configuration). I do not even know if the function works, this was the first thing that came to mind, but the syntax should be correct (the delimiter is set in PHPMyAdmin). Getting all data from database and comparing them in the PHP application can be done, but I think it is easiest done this way. Is it possible? Does anybody have a better solution for comparing versions?
You can adjust the global variable for that:
/* allows to create functions as not root */
SET GLOBAL log_bin_trust_function_creators = 1;
I use this in setup-sql files for initializing a database for restoring a dump.
I have circumvented the problem by making a long and therefore ugly database query in PHP. Maybe using stored procedure instead of function would be better. After searching I found in the documentation here this statement:
The current conditions on the use of stored functions in MySQL 5.5 can
be summarized as follows. [...] To create or alter a stored function,
you must have the SUPER privilege, in addition to the CREATE ROUTINE
or ALTER ROUTINE privilege that is normally required.
So indeed, if binary logging is on, you really need SUPER privilege to create stored functions. This severely limits the use of stored functions on bigger servers and in my opinion decreases the value of the whole DBMS. Looks like the classical "it is not a bug, it is a feature". I fear to think about what happens to stored functions if the database server is restarted after changing binary logging to on.

Get Updated Value in MySQL instead of affected rows

I've been trying to find an answer to this question, but haven't found any definitive "yes" or "no" in all my research.
I'm running a simple MySQL query like this:
UPDATE item SET `score`=`score`+1 WHERE `id`=1
Is there a way for that query to return the updated value, instead of the number of rows affected? Just as a reference, I'm doing this in PHP, so the actual code looks like:
$sql = "UPDATE item SET `score`=`score`+1 WHERE `id`=1";
$new_value = mysql_query($sql);
//Unfortunately this does not return the new value
I know I could do a second query and just SELECT the value, but I'm trying to cut down on queries as much as possible. Is there a way?
You can do it with a stored procedure that updates, and then selects the new value into an output parameter.
The following returns one column new_score with the new value.
DELIMITER $$ -- Change DELIMITER in order to use ; withn the procedure
CREATE PROCEDURE increment_score
(
IN id_in INT
)
BEGIN
UPDATE item SET score = score + 1 WHERE id = id_in;
SELECT score AS new_score FROM item WHERE id = id_in;
END
$$ -- Finish CREATE PROCEDURE statement
DELIMITER ; -- Reset DELIMITER to standard ;
In PHP:
$result = mysql_query("CALL increment_score($id)");
$row = mysql_fetch_array($result);
echo $row['new_score'];
No, there's nothing like postgresql's UPDATE ... RETURNING output_expression in MySQL (yet?).
If you don't want to run another Query SELECT then here is another way to do it. I have modified Mr. Berkowski code for reference:
DELIMITER $$
CREATE PROCEDURE increment_score
(
IN id_in INT
)
BEGIN
set #newScore := null;
UPDATE item SET score = IF((#newScore := score+1) <> NULL IS NULL, #newScore, NULL) WHERE id = id_in;
SELECT #newScore;
END
DELIMITER ;
No you cant. You could make a function or stored procedure that could do the insert and return the updated value but that would still require you to execute two queries from within the function or stored procedure.
You can create a trigger, and you will know everything about the modifications.

Categories