SQLServerCentral Article

A simple math expression solver

,

Introduction

I ran into a problem at work.  I had a need for SQL Server  to be able to process a dynamic simple math expression string in a SELECT statement.  These are the math operations that were needed: adding, subtracting, multiplying and dividing.

Problem

A customer had a table that contained a string column with simple math expressions.  For example the column could contain something like this:

3.5 * 2.2

3 + 8

5

3 / 4 + 3.2

I needed to execute these expressions and find the result.

Solution

My first idea was to use the EXEC command since SQL Server already knows how to solve these simple math problems.  My issue with using EXEC is that it can’t be used in a SELECT statement or in a function.  It appeared I was going to have to write a custom function that would know how to process certain math operations.  I decided to do a recursive function using charindex to find my predetermined math operators. 

As I started to code the function I ran into a few problems.  First, recursion doesn’t work if you don’t process the operations in order. This makes sense when you think about it, but my first pass at the function didn’t have any logic to figure out which math operator should be processed first.  After I added code to determine which math operator was first, the recursive function worked fine.

The next issue I ran into was charindex returns the first occurrence of the character you are looking for.  When you process the first math operation with a recursive function you are actually processing the math expression from right to left.  Although this worked fine, it gave a different answer to the math expression. 

For example, take this math expression 3 * 4 + 8.  When you process this left to right you do the multiplication first so the answer is 12 + 8 = 20.  When it is processes from right to left you get 3 * 12 = 36.  For western thinkers we think left to right since that in how we read so the first answer makes more logical sense.

I will pause here and acknowledge that I could have set a precedence to process certain operators first.  Or I could have used brackets to control which operations should go together.  I decided I didn’t really want to go there.  Instead I decided I just needed a charindex that would give me the last occurrence of the operator.

I was surprised to not find a lot of great solutions for doing a charindex that provided the last occurrence of a character.  So I wrote my own function.  It is a simple while loop that puts the charindex returned into the start_location optional parameter.

CREATE function [dbo].[fn_reverse_charIndex]
/*********************************************************//* CREATE fn_reverse_charIndex                           *//* ---------------- COMMENTS    ------------------------ *//* Give the last occurrence of the character passed in   *//* ---------------- HISTORY LOG ------------------------ *//*********************************************************/( @char char(1), @expression varchar(255))
returns int
as
begin
  declare @charIdx int, @returnIdx int
  set @returnIdx = 0
    select @charIdx = charindex(@char, @expression)
    while @charIdx <> 0
    begin
        set @returnIdx = @charIdx
        select @charIdx = charindex(@char, @expression,@charIdx+1)
    end
    return @returnIdx
end

Now that I had a custom function that would return the last occurrence of the charindex, I used that in my recursive function.  Now the recursive function properly processes the math expression from left to right as you would expect.

CREATE function [dbo].[fn_simplemath]
( @expression varchar(255))
returns numeric(18,6)
as
begin
declare  @result numeric(18,6), @opr1 varchar(50), @opr2 varchar(50),
@nopr1 numeric(18,6), @nopr2 numeric(18,6),
@multIdx int, @divIdx int, @addIdx int, @subIdx int, @modIdx int,
@firstIdx int
--first get the last index in the expression passed in
select @multIdx = dbo.fn_reverse_charIndex('*',@expression),
       @divIdx = dbo.fn_reverse_charIndex('/',@expression),
       @addIdx = dbo.fn_reverse_charIndex('+',@expression),
       @subIdx = dbo.fn_reverse_charIndex('-',@expression),
       @modIdx = dbo.fn_reverse_charIndex('%',@expression),
       @firstIdx = 1
       
--figure out which operation is last       
if @multIdx > @firstIdx
begin
  set @firstIdx = @multIdx
end
if @divIdx > @firstIdx
begin
  set @firstIdx = @divIdx
end
if @addIdx > @firstIdx
begin
  set @firstIdx = @addIdx
end
if @subIdx > @firstIdx
begin
  set @firstIdx = @subIdx
end
if @modIdx > @firstIdx
begin
  set @firstIdx = @modidx
end
       
--process the last operation and then recursively call this function to process other math operations
if @multIdx = @firstIdx
begin
 
   select @opr1 = SUBSTRING(@expression,0,@multidx)
  select @opr2 = SUBSTRING(@expression,@multidx+1, LEN(@expression) - @multIdx)
 
  select @nopr2 = CAST(@opr2 as numeric(18,6))
  select @nopr1 = dbo.fn_simplemath(@opr1)
  select @result = @nopr1 * @nopr2
 
end
else if @divIdx = @firstIdx
begin
      
   select @opr1 = SUBSTRING(@expression,0,@dividx-1)
  select @opr2 = SUBSTRING(@expression,@dividx+1, LEN(@expression) - @divIdx)
 
  select @nopr2 = CAST(@opr2 as numeric(18,6))
  select @nopr1 = dbo.fn_simplemath(@opr1)
  select @result = @nopr1 / @nopr2
end
else if @addIdx = @firstIdx
begin
      
   select @opr1 = SUBSTRING(@expression,0,@addidx-1)
  select @opr2 = SUBSTRING(@expression,@addidx+1, LEN(@expression) - @addIdx)
 
  select @nopr2 = CAST(@opr2 as numeric(18,6))
  select @nopr1 = dbo.fn_simplemath(@opr1 )
  select @result = @nopr1 + @nopr2
end
else if @subIdx = @firstIdx
begin
      
   select @opr1 = SUBSTRING(@expression,0,@subidx-1)
  select @opr2 = SUBSTRING(@expression,@subidx+1, LEN(@expression) - @subIdx)
 
  select @nopr2 = CAST(@opr2 as numeric(18,6))
  select @nopr1 = dbo.fn_simplemath(@opr1 )
  select @result = @nopr1 - @nopr2
end
else if @modIdx = @firstIdx
begin
      
   select @opr1 = SUBSTRING(@expression,0,@modidx-1)
  select @opr2 = SUBSTRING(@expression,@modidx+1, LEN(@expression) - @modIdx)
 
  select @nopr2 = CAST(@opr2 as numeric(18,6))
  select @nopr1 = dbo.fn_simplemath(@opr1)
  select @result = @nopr1 % @nopr2
end
else begin
    --No operations left so pass back a number
      select @result = CAST(@expression as numeric(18,6))
end
return @result
end

Note: I am sure I will hear some comments about only allowing varchar(255) as the input for the math expression.  Clearly that 255 could be changed to a larger number, or perhaps even just varchar(max).  For my usage varchar(255) worked for me.  Also there are many more math functions that sql server supports.  Clearly they could have been added to this function as well.  Again they were not needed, so I didn’t spend the time to add them.

Conclusion

I was able to write a recursive function that processes simple math expressions and is able to return the answer in a timely fashion in a select statement.   I also wrote a reverse charindex function so that my recursive function would process the simple math expressions from left to right as would be expected.  I hope you enjoy these functions and find them useful in whatever problems you are facing.

Resources

Rate

2.48 (23)

You rated this post out of 5. Change rating

Share

Share

Rate

2.48 (23)

You rated this post out of 5. Change rating