Строковое выражение, которое будет оцениваться как число

Мне нужно написать определяемую пользователем функцию TSQL, которая будет принимать строку и возвращать число.

Я буду вызывать функцию типа dbo.EvaluateExpression('10*4.5*0.5'), которая должна возвращать число 22.5

Может ли кто-нибудь помочь мне написать эту функцию EvaluateExpression.

В настоящее время я использую функцию CLR, которую мне нужно избегать.

Изменить1

Я знаю, что это можно сделать с помощью хранимой процедуры, но я хочу вызвать эту функцию в некоторых операторах, например: select 10* dbo.EvaluateExpression('10*4.5*0.5')

Также у меня есть около 400 000 таких формул для оценки.

Изменить2

Я знаю, что мы можем сделать это, используя внутреннюю функцию osql.exe, как описано здесь . Но из-за настроек разрешений я также не могу использовать это.


person PraveenVenu    schedule 24.03.2012    source источник
comment
comment
Вы говорите, что у вас есть работающая функция CLR, но вам нужно ее избегать. Было бы полезно, если бы вы могли объяснить, почему; TSQL просто не подходит для этого, и у вас, вероятно, будет больше проблем при таком подходе, чем при простом использовании CLR.   -  person Pondlife    schedule 26.03.2012
comment
я пытаюсь избежать CLR для повышения производительности, а также из соображений безопасности   -  person PraveenVenu    schedule 27.03.2012
comment
Какие причины безопасности существуют, чтобы избежать CLR? Производительность — это другой вопрос, но если ваше текущее решение слишком медленное, пытались ли вы оптимизировать его перед переходом на совершенно другой язык?   -  person Pondlife    schedule 27.03.2012
comment
Моя идея состояла в том, чтобы иметь что-то в родном sql   -  person PraveenVenu    schedule 27.03.2012


Ответы (4)


Я не думаю, что это возможно в пользовательской функции.

Вы можете сделать это в хранимой процедуре, например:

declare @calc varchar(max)
set @calc = '10*4.5*0.5'

declare @sql nvarchar(max)
declare @result float
set @sql = N'set @result = ' + @calc
exec sp_executesql @sql, N'@result float output', @result out
select @result

Но динамический SQL, такой как exec или sp_executesql, не разрешен в пользовательских функциях.

person Andomar    schedule 24.03.2012
comment
Ях. Я уже пробовал procs, но я хочу вызвать эту функцию в некоторых операторах, например: select 10* dbo.EvaluateExpression('10*4.5*0.5') - person PraveenVenu; 24.03.2012
comment
Насколько я знаю, это невозможно сделать без поддержки CLR, ограничения на функции достаточно обширны. - person Andomar; 24.03.2012
comment
@PraVn: Возможно, вы захотите подробнее рассказать о том, что вы пытаетесь сделать, и, в частности, откуда берутся эти выражения. Возможно, вы могли бы сохранить выражения во временной таблице вместе с некоторыми ключами, иметь хранимую процедуру для оценки выражений, а затем использовать временную таблицу в запросе/запросах (соединенных с использованием вышеупомянутых ключей), читая/используя результаты по своему усмотрению. . - person Andriy M; 25.03.2012
comment
Спасибо @Андрей. Но это вычислительная машина, которая имеет почти 400 000 подобных формул. Сохранение этого во временной таблице нецелесообразно с точки зрения производительности. - person PraveenVenu; 25.03.2012
comment
@Andomar: Ну, это возможно из функции (см. Здесь: sswug.org /DATABASES/default.aspx?id=22848), но, конечно, было бы ПЛОХО использовать этот материал, если только это не одноразовое упражнение. - person David Brabant; 26.03.2012
comment
С таким подходом следует быть осторожным, потому что он уязвим для SQL-инъекций, когда, например. set @calc = '10*4.5*0.5; DROP TABLE foo' - person natenho; 10.05.2017

Отказ от ответственности: я являюсь владельцем проекта Eval SQL.NET

Для SQL 2012+ вы можете использовать Eval SQL.NET, который можно запускать с разрешением SAFE.

Производительность отличная (лучше, чем UDF) и учитывает приоритет операторов и круглые скобки. На самом деле поддерживаются почти все языки C#.

Вы также можете указать параметры для вашей формулы.

-- SELECT 225.00
SELECT 10 * CAST(SQLNET::New('10*4.5*0.5').Eval() AS DECIMAL(18, 2))

-- SELECT 70
DECLARE @formula VARCHAR(50) = 'a+b*c'
SELECT  10 * SQLNET::New(@formula)
                    .Val('a', 1)
                    .Val('b', 2)
                    .Val('c', 3)
                    .EvalInt()
person Jonathan Magnan    schedule 09.02.2016

Используйте эту функцию, она будет работать.

CREATE FUNCTION dbo.EvaluateExpression(@list nvarchar(MAX))

RETURNS Decimal(10,2)
AS

BEGIN
Declare @Result Decimal(10,2)
set @Result=1
 DECLARE @pos        int,
       @nextpos    int,
       @valuelen   int

SELECT @pos = 0, @nextpos = 1


WHILE @nextpos > 0
  BEGIN
     SELECT @nextpos = charindex('*', @list, @pos + 1)
     SELECT @valuelen = CASE WHEN @nextpos > 0
                             THEN @nextpos
                             ELSE len(@list) + 1
                        END - @pos - 1

                        Set @Result=@Result*convert(decimal(10,2),substring(@list, @pos + 1, @valuelen))


     SELECT @pos = @nextpos
  END

RETURN @Result
END

Вы можете использовать это

Select 10* dbo.EvaluateExpression('10*4.5*0.5')
person Balwinder Pal    schedule 24.03.2012
comment
Это будет работать для данного примера. Но он не будет оценивать все выражения. Например: это не удастся для «10 + 20» - person PraveenVenu; 24.03.2012
comment
Было бы трудно соблюдать приоритет оператора или круглые скобки таким образом, например. 10+20*(5+1) - person Andomar; 25.03.2012

Вы можете использовать хранимую процедуру SQL ниже, чтобы вычислить результат любой формулы с любым количеством переменных:

В 2012 году я написал решение, которое может оценивать математические формулы любого типа с использованием SQL SERVER. Решение может обрабатывать любую формулу с N переменными:

Меня попросили найти способ оценить значение, заданное формулой, которая заполняется пользователем. Формула содержит математические операции (сложение, умножение, деление и вычитание). Параметры, используемые для вычисления формулы, хранятся в базе данных SQL сервера.

Решение, которое я нашел для себя, было следующим:

Предположим, у меня есть n параметров, используемых для расчета формулы, каждый из этих параметров хранится в одной строке в одной таблице данных.

  • Таблица данных, содержащая n строк для использования в формуле, называется tab_value.

  • Мне нужно сохранить n значений, найденных в n строках (в tab_values) в одной строке в одной новой таблице, используя курсор SQL,

  • для этого я создаю новую таблицу с именем tab_formula

  • В курсоре я добавлю новый столбец для каждого значения, имя столбца будет Id1, Id2, Id3 и т. д.

  • Затем я создаю сценарий SQL, содержащий формулу для оценки формулы

Здесь, после полного сценария, я надеюсь, вы найдете его полезным, вы можете спросить меня об этом.

Процедура использует в качестве входных данных:

-Формула

-Таблица, содержащая значения, используемые для расчета формулы

if exists(select 1 from sysobjects where name='usp_evaluate_formula' and xtype='p') 

drop proc usp_evaluate_formula 

go

create type type_tab as table(id int identity(1,1),val decimal(10,2)) 

go 
create proc usp_evaluate_formula(@formula as nvarchar(100),@values as type_tab readonly) 

as begin 

declare @tab_values table (id int, val decimal(10,2))

insert into @tab_values(id,val) select * from @values

declare @id as int declare @val as decimal(10,2)
if not exists(select 1 from sysobjects where name ='tab_formula') 
create table tab_formula(id int identity(1,1), formula nvarchar(1000))

if not exists(select 1 from tab_formula where formula=@formula) 
insert into tab_formula(formula) values(@formula)


declare c cursor for select id,val from @tab_values 

declare @script as nvarchar(4000) 

open c 

fetch c into @id,@val 

while @@fetch_status=0 

begin 

set @script = 'if not exists(select 1 from syscolumns c inner join sysobjects o on c.id=o.id where o.name=''tab_formula'' and c.name=''id'+
convert(nvarchar(3),@id)+ ''') 
alter table tab_formula add id'+convert(nvarchar(3),@id)+ ' decimal(10,2)' 

print @script 

exec(@script) 

set @script='update tab_formula set id'+convert(nvarchar(3),@id)+'='+convert(nvarchar(10),@val)+' where formula='''+@formula+'''' print @script exec(@script) fetch c into @id,@val end close c deallocate c

set @script='select *,convert(decimal(10,2),'+@formula+') "Result" from tab_formula where formula='''+@formula+'''' 

print @script 

exec(@script)

end

go

declare @mytab as type_tab 

insert into @mytab(val) values(1.56),(1.5) ,(2.5) ,(32),(1.7) ,(3.3) ,(3.9)

exec usp_evaluate_formula'2*cos(id1)+cos(id2)+cos(id3)+3*cos(id4)+cos(id5)+cos(id6)+cos(id7)/2*cos(Id6)',@mytab

go 
drop proc usp_evaluate_formula 

drop type type_tab 

drop table tab_formula
person Kemal AL GAZZAH    schedule 31.08.2016