Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Microsoft SQL Server Новый топик    Ответить
 Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
Решил поделиться функцией арифметического вычисления строкового выражения. Необходимо было сделать по работе. Может кому будет интересно.

--=========================================================================
--арифметическое вычисление строки с помощью обратной польской нотации
--=========================================================================
CREATE FUNCTION fn_Calculate(@Expression VARCHAR(8000))
RETURNS FLOAT
AS
BEGIN

  IF @Expression IS NULL
    RETURN NULL
	
	SET @Expression = REPLACE(@Expression,' ','')
	
	DECLARE @Stack TABLE (Id INT IDENTITY, Item VARCHAR(8000))
	
	DECLARE @StackTmp TABLE (Id INT IDENTITY, Item FLOAT)
	
	DECLARE @OutExpression VARCHAR(8000), @PrevChar VARCHAR(8000), @Err BIT, @Result FLOAT
	
	
	SET @Err = 0
	SET @OutExpression = ''
  SET @PrevChar = ' '+@Expression
  
	WHILE 1=1 AND LEN(@Expression) > 0
	BEGIN
		IF LEFT(@Expression,1) = '-' AND (LEFT(@PrevChar,1) = '-' OR LEFT(@PrevChar,1) = '+' OR LEFT(@PrevChar,1) = '*' OR LEFT(@PrevChar,1) = '/' OR LEFT(@PrevChar,1) = '(' OR LEFT(@PrevChar,1) = ' ')
		BEGIN
			SET @OutExpression = @OutExpression + LEFT(@Expression,1)
			SET @Expression = RIGHT(@Expression, LEN(@Expression)-1)
		END ELSE
		IF ISNUMERIC(LEFT(@Expression,1)) = 1 AND LEFT(@Expression,1) != '-' AND LEFT(@Expression,1) != '+'
		BEGIN
			IF (ISNUMERIC(SUBSTRING(@Expression,2,1)) = 1 AND SUBSTRING(@Expression,2,1) != '-' AND SUBSTRING(@Expression,2,1) != '+') OR LEN(@Expression) = 1
			  SET @OutExpression = @OutExpression + LEFT(@Expression,1)
			ELSE
		    SET @OutExpression = @OutExpression + LEFT(@Expression,1) + ';'
		  SET @Expression = RIGHT(@Expression, LEN(@Expression)-1)
		END ELSE 
		IF LEFT(@Expression,1) = '('
		BEGIN
			INSERT INTO @Stack(Item)
			VALUES(LEFT(@Expression,1))
			SET @Expression = RIGHT(@Expression, LEN(@Expression)-1)
		END ELSE
	  IF LEFT(@Expression,1) = ')'
	  BEGIN
	  	WHILE 1=1
	  	BEGIN
	  		IF (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) != '(' AND (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) IS NOT NULL
	  		  SET @OutExpression = @OutExpression + (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) + ';'
	  		ELSE BREAK
	  		DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id DESC)
	  	END--while
	  	DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id DESC)
	  	SET @Expression = RIGHT(@Expression, LEN(@Expression)-1)
	  END ELSE
	  IF LEFT(@Expression,1) = '+' OR LEFT(@Expression,1) = '-'
	  BEGIN
	  	WHILE 1=1
	  	BEGIN
	  		IF (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) = '+' OR
	  		   (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) = '-' OR
	  		   (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) = '*' OR
	  		   (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) = '/'
	  		BEGIN
	  		  SET @OutExpression = @OutExpression +(SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC)+ ';'
	  		  DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id DESC)
	  		END ELSE
	  		  BREAK
	  	END--while
	  	INSERT INTO @Stack(Item) VALUES (LEFT(@Expression,1))
 	    SET @Expression = RIGHT(@Expression, LEN(@Expression)-1)	  			
	  END ELSE
	  IF LEFT(@Expression,1) = '*' OR LEFT(@Expression,1) = '/'
	  BEGIN
	  	WHILE 1=1
	  	BEGIN
	  		IF (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) = '*' OR
	  		   (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) = '/'
	  		BEGIN
	  		  SET @OutExpression = @OutExpression+(SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC) + ';'
	  		  DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id DESC)
	  		END ELSE 
    	    BREAK		  		
	  	END--while
	  	INSERT INTO @Stack(Item) VALUES (LEFT(@Expression,1))
      SET @Expression = RIGHT(@Expression, LEN(@Expression)-1)
	  END
	  SET @PrevChar = RIGHT(@PrevChar,LEN(@PrevChar)-1)
	END--while
	
	WHILE (SELECT COUNT(1) FROM @Stack) > 0
	BEGIN
	  SET @OutExpression = @OutExpression + ';' + (SELECT TOP 1 Item FROM @Stack ORDER BY Id DESC)
	  DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id DESC)
	END
	
	SET @OutExpression = REPLACE(@OutExpression,'--','')
	
	DELETE FROM @Stack
	DECLARE @fItem FLOAT, @vItem VARCHAR(100), @Sign VARCHAR(10), @Res FLOAT
	WHILE 1=1
	BEGIN
		IF CHARINDEX(';',@OutExpression) > 0
		BEGIN
		  SET @vItem = SUBSTRING(@OutExpression,1,CHARINDEX(';',@OutExpression)-1)
		  SET @OutExpression = RIGHT(@OutExpression,LEN(@OutExpression)-CHARINDEX(';',@OutExpression))
		END ELSE
		BEGIN 
			SET @vItem = @OutExpression
			SET @OutExpression = ''
		END
    
    INSERT INTO @Stack(Item)
    SELECT @vItem
    
    IF CHARINDEX(';',@OutExpression) = 0 AND LEN(@OutExpression) > 0
    BEGIN
      INSERT INTO @Stack(Item)
      SELECT @OutExpression      
      BREAK
    END ELSE IF LEN(RTRIM(LTRIM(@OutExpression))) = 0
      BREAK
	END
	
	IF (SELECT COUNT(1) FROM @Stack) > 0
	  WHILE 1=1
	  BEGIN
		  IF ISNUMERIC((SELECT TOP 1 Item FROM @Stack s ORDER BY s.Id)) = 1 AND (SELECT TOP 1 Item FROM @Stack s ORDER BY s.Id) != '-' AND (SELECT TOP 1 Item FROM @Stack s ORDER BY s.Id) != '+'
		  BEGIN
		    INSERT INTO @StackTmp(Item)
		    SELECT TOP 1 Item FROM @Stack s ORDER BY s.Id
		    DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id)
		  END ELSE
		  BEGIN
			  SET @Sign = (SELECT TOP 1 Item FROM @Stack s ORDER BY s.Id)
			  DELETE FROM @Stack WHERE Id = (SELECT TOP 1 Id FROM @Stack ORDER BY Id)
        IF @Sign = '+'
        BEGIN
      	  SET @fItem = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC)
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC)
      	  SET @Res = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC)+ @fItem
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC)
      	  INSERT INTO @StackTmp(Item)
      	  SELECT @Res
        END		
        IF @Sign = '-'
        BEGIN
      	  SET @fItem = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC)
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC)
      	  SET @Res = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC) - @fItem
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC)      	
      	  INSERT INTO @StackTmp(Item)
      	  SELECT @Res
        END		
        IF @Sign = '*'
        BEGIN
      	  SET @fItem = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC)
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC)
      	  SET @Res = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC) * @fItem
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC) 
      	  INSERT INTO @StackTmp(Item)
      	  SELECT @Res      	      	
        END		
        IF @Sign = '/'
        BEGIN
      	  SET @fItem = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC)
      	  IF ISNULL(@fItem,0) = 0
      	  BEGIN
      	  	SET @Err = 1
      	  	BREAK
      	  END
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC)
      	  SET @Res = (SELECT TOP 1 Item FROM @StackTmp st ORDER BY st.Id DESC) / @fItem
      	  DELETE FROM @StackTmp WHERE Id = (SELECT TOP 1 Id FROM @StackTmp ORDER BY Id DESC) 
      	  INSERT INTO @StackTmp(Item)
      	  SELECT @Res        	
        END		
		  END
		  IF (SELECT COUNT(1) FROM @Stack) = 0
		    BREAK
	  END
	  
	IF @Err = 1
	  SET @Result = NULL
  ELSE
  	SET @Result = (SELECT Item FROM @StackTmp) 
	
	RETURN @Result
END
GO
12 окт 11, 10:13    [11424672]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
J.d
Member

Откуда: Москва
Сообщений: 691
о, пригодится спасибо)))
12 окт 11, 10:28    [11424777]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
JeanBatist
Необходимо было сделать по работе. Может кому будет интересно.
Да! Для чего вам на работе понадобилось. Очень интересно.
J.d
пригодится
А можно спросить? В каких случаях на ваш взгляд?

Можно пост поднять как пятничная задачка. Типа кто сможет короче/проще/понятнее записать это ... месиво.

Подсказки (для JeanBatist):
+ 1
Декларативное программирование
+ 2
CTE
13 окт 11, 00:01    [11430659]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
Mnior
Да! Для чего вам на работе понадобилось. Очень интересно.

В базе хранятся формулы, которые пользователь может редактировать, собирать их из различных параметров, которые потом они заменяются на числа и считаются с помощью этой функции. Можно использовать, например, при подсчете зарплаты, динамически задавая формулу мотивации.
Наример: формула [параметр1]/[параметр2]*[параметр3]*100.
13 окт 11, 15:25    [11434710]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
alexeyvg
Member

Откуда: Moscow
Сообщений: 31961
JeanBatist
Mnior
Да! Для чего вам на работе понадобилось. Очень интересно.

В базе хранятся формулы, которые пользователь может редактировать, собирать их из различных параметров, которые потом они заменяются на числа и считаются с помощью этой функции. Можно использовать, например, при подсчете зарплаты, динамически задавая формулу мотивации.
Наример: формула [параметр1]/[параметр2]*[параметр3]*100.
Мне кажется, что для такой задачи больше подходит кодогенерация или динамический SQL

Это будет быстрее, универсальнее (можно будет использовать не только +-*/ но и агрегатные и прочие функции), и удобнее для пользователя (не надо пользоваться обратной польской нотацией)
13 окт 11, 15:32    [11434799]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
Mnior,

Замечания приму к сведению, попробую переписать с помощью CTE как будет время.
13 окт 11, 15:35    [11434844]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
alexeyvg
JeanBatist
пропущено...

В базе хранятся формулы, которые пользователь может редактировать, собирать их из различных параметров, которые потом они заменяются на числа и считаются с помощью этой функции. Можно использовать, например, при подсчете зарплаты, динамически задавая формулу мотивации.
Наример: формула [параметр1]/[параметр2]*[параметр3]*100.
Мне кажется, что для такой задачи больше подходит кодогенерация или динамический SQL

Это будет быстрее, универсальнее (можно будет использовать не только +-*/ но и агрегатные и прочие функции), и удобнее для пользователя (не надо пользоваться обратной польской нотацией)


Как я понимаю, вы предлагаете скомпоновать запрос, а потом выполнить его через exec(@sql_query).
Мне же необходимо было сделать расчет в одном select
например:
select  dbo.fn_Calculate(formula)
from    Formulas
--где formula типа (-6+15)*5
13 окт 11, 15:49    [11435016]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
iljy
Member

Откуда:
Сообщений: 8711
JeanBatist,

вы знаете, такую задачу на порядок проще и эффективнее будет написать на CLR. А в вашу функцию еще и не понятно, как параметры передать, если формула например от других полей зависит.
13 окт 11, 15:58    [11435130]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
alexeyvg
Member

Откуда: Moscow
Сообщений: 31961
JeanBatist
alexeyvg
пропущено...
Мне кажется, что для такой задачи больше подходит кодогенерация или динамический SQL

Это будет быстрее, универсальнее (можно будет использовать не только +-*/ но и агрегатные и прочие функции), и удобнее для пользователя (не надо пользоваться обратной польской нотацией)


Как я понимаю, вы предлагаете скомпоновать запрос, а потом выполнить его через exec(@sql_query).
Мне же необходимо было сделать расчет в одном select
например:
select  dbo.fn_Calculate(formula)
from    Formulas
--где formula типа (-6+15)*5
Вот и генерите эту вашу dbo.fn_Calculate по данным в Formulas
13 окт 11, 16:10    [11435264]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Начинающий SQL 2008
Member

Откуда:
Сообщений: 438
Вот мой велосипед:

declare @h int, @obj int, @ErrorSource varchar(max), @ErrorDescription varchar(max), @CalcStr varchar(max)

set @CalcStr = '(-6+15)*5'

exec @h = sp_OACreate 'MSScriptControl.ScriptControl', @obj output
if @h <>  0 goto err

exec @h = sp_OASetProperty @obj, 'Language', 'JScript' 
if @h <>  0 goto err

exec @h = sp_OAMethod @obj, 'Eval', null, @CalcStr
if @h <>  0 goto err


err:
if @h <> 0
begin
  exec sp_OAGetErrorInfo @obj, @ErrorSource output, @ErrorDescription output
  raiserror (@errorDescription, 16, 1)

  exec sp_OADestroy @obj
  return
end
13 окт 11, 19:03    [11437027]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Начинающий SQL 2008
Вот мой велосипед
С треугольными колёсами не беру. Народная примета.
alexeyvg
Вот и генерите замест dbo.fn_Calculate по данным в Formulas
Понятно что это единственный хороший вариант, но как же пятничная задачка? Таки вааще не интересно?
14 окт 11, 01:55    [11438491]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
iljy
JeanBatist,

вы знаете, такую задачу на порядок проще и эффективнее будет написать на CLR. А в вашу функцию еще и не понятно, как параметры передать, если формула например от других полей зависит.

На эффективность не претендую. А функция была написана для конкретной задачи - вычисление арифметической строки типа (8+5)*10 и все.
14 окт 11, 09:19    [11438806]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
Начинающий SQL 2008
Вот мой велосипед:

declare @h int, @obj int, @ErrorSource varchar(max), @ErrorDescription varchar(max), @CalcStr varchar(max)

set @CalcStr = '(-6+15)*5'

exec @h = sp_OACreate 'MSScriptControl.ScriptControl', @obj output
if @h <>  0 goto err

exec @h = sp_OASetProperty @obj, 'Language', 'JScript' 
if @h <>  0 goto err

exec @h = sp_OAMethod @obj, 'Eval', null, @CalcStr
if @h <>  0 goto err


err:
if @h <> 0
begin
  exec sp_OAGetErrorInfo @obj, @ErrorSource output, @ErrorDescription output
  raiserror (@errorDescription, 16, 1)

  exec sp_OADestroy @obj
  return
end

Ваш велосипед не будет работать внутри функции.
14 окт 11, 09:57    [11438938]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Glory
Member

Откуда:
Сообщений: 104751
JeanBatist
Ваш велосипед не будет работать внутри функции.

Будет. Только нужно goto заменить на вложенные IF-ы
Но CLR лучше
14 окт 11, 10:09    [11439003]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
Glory,

Вызов EXEC из функций невозможен.
14 окт 11, 13:14    [11440536]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Glory
Member

Откуда:
Сообщений: 104751
JeanBatist
Вызов EXEC из функций невозможен.

Читайте внимательнее хелп
14 окт 11, 13:28    [11440681]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
J.d
Member

Откуда: Москва
Сообщений: 691
надо через cte переписать, долго работает.

плюс доделаю чтоб вычислял и равенства / неравенства еще.
а пригодится мне в нейронных сетях.
14 окт 11, 16:16    [11442451]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
J.d
надо через cte переписать, долго работает.

плюс доделаю чтоб вычислял и равенства / неравенства еще.
а пригодится мне в нейронных сетях.


Выложишь свой вариант с CTE, когда доделаешь?
15 окт 11, 12:26    [11445478]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Изопропил
Member

Откуда:
Сообщений: 31575
alexeyvg
и удобнее для пользователя (не надо пользоваться обратной польской нотацией)


Пользователь должен писать формулы в инфиксной записи. в польскую можно переводить или перед вычислениями, или при сохранении формулы.
а для ускорения вычислений внутреннее представление можно сделать и посложнее, чем строка в польской записи
15 окт 11, 22:18    [11446887]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Изопропил
Пользователь должен писать формулы в инфиксной записи. в польскую можно переводить или перед вычислениями, или при сохранении формулы.
а для ускорения вычислений внутреннее представление можно сделать и посложнее, чем строка в польской записи

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

+ Вот пример для тех кто плохо догоняет
USE tempdb;
GO
CREATE TABLE [dbo].[Table] (
	 ID	Int IDENTITY
	 CONSTRAINT [PK_Table] PRIMARY KEY
	,A	Int
	,B	Int
	,C	Int
)
GO
CREATE VIEW [dbo].[View] AS
SELECT * FROM dbo.[Table]
GO
CREATE TABLE [dbo].[ComputedColumns] (
	 [Name]		SysName
	 CONSTRAINT [PK_ComputedColumns] PRIMARY KEY
	,Expression	NVarChar(4000)	NOT NULL
)
GO
CREATE TRIGGER [trComputedColumns] ON [dbo].[ComputedColumns]
AFTER INSERT,UPDATE,DELETE AS BEGIN
	SET NOCOUNT ON;

	DECLARE	@Query	NVarChar(max)
	SELECT	@Query = '
ALTER VIEW [dbo].[View] AS
SELECT	 ID
	,A
	,B
	,C' + (	SELECT	 '
	,'		+ Expression
			-- Для красоты
			+ Replicate('	',Ceiling(((Floor((Max(DataLength(Expression))OVER()/2 + 1.)/8) + 1)*8 - DataLength(Expression)/2 - 1.)/8))
			+ 'AS ' + [Name]
		FROM	dbo.ComputedColumns
		ORDER BY [Name]
		FOR XML Path(''),Type).value('text()[1]','NVarChar(max)') + '
FROM	dbo.[Table]
'	PRINT @Query
	EXEC (@Query)
END
GO
INSERT dbo.[Table]		SELECT 1,2,3
UNION ALL			SELECT 6,5,4
UNION ALL			SELECT 7,8,9
UNION ALL			SELECT 2,1,0
GO
INSERT dbo.ComputedColumns	SELECT 'D','A+B'
UNION ALL			SELECT 'E','Power(2,C)'
UNION ALL			SELECT 'F','Power(A,2) + 2*B - C'
UNION ALL			SELECT 'G','Sin(B)/0.3'
GO
SELECT	*
FROM	dbo.[View]
GO
DROP TABLE [dbo].[ComputedColumns]
DROP VIEW  [dbo].[View]
DROP TABLE [dbo].[Table]
ALTER VIEW [dbo].[View] AS
SELECT ID
,A
,B
,C
,A+B AS D
,Power(2,C) AS E
,Power(A,2) + 2*B - C AS F
,Sin(B)/0.3 AS G
FROM dbo.[Table]
IDABCDEFG
11233823,03099142275227
2654111642-3,19641424887713
378915512563,29786082207794
42103162,80490328269299
Ща придёт умник у будет гнать пургу что это не безопасно.

А теперь промоем косточки JeanBatist-а:
+
0. Не форматированый код (эта такая же необходимость как дыхание)
1. Опускание схемы в указании объектов и использование системных префиксов (fn_)
2. Заблуждение что Count(*) тормознее чем Count(1), а использование его вместо Exists - глупость
3. Злоупотребление плавающих неточных типов (Float) чревато, особенно если они и не обрабатываются; (на крайняк две функции)
4. Проглатывание ошибок
5. Нарезание строк вместо позиционного пробегания
6. Наваливание подряд идущих SET, вместо одного SELECT @Var1 = ..., @Var2 = ..., ...
7. Повальное использование ветвлений IF вместо одной команды с CASE-ами
8. IDENTITY явно без ключа PK, приводящее к сканированию (не актуально)
9. Незнание:
операторов IN (а также '+='), (написание вместо них километров кода аля <LongExpression> = C1 OR <LongExpression> = C2 ...) (+ инвертные)
логики сравнения со значением NULL
функции Stuff, PatIndex
что Len() не учитывает концевые пробелы
DELETE OUTPUT INTO (вместо INSERT + DELETE)
табличной агрегации строк (через FOR XML) не говоря об (SELECT @Agr1 = @Agr1 + ... FROM ...)
оконных функций с OVER (Row_Number())
параметров WITH для объектов (WITH RETURNS NULL ON NULL INPUT)
10. Море логических тупостей:
(1 = 1) AND <Expression>
WHILE (1 = 1) IF ... ELSE BREAK
Излишние операции:
WHILE Exist(SELECT * FROM @Table) ... END; затем DELETE @Table;
Replace(' ',''); затем Trim()
Неоптимальный порядок обработки условий приводящий к излишним проверкам: IsNumeric AND NOT IN ('+','-') ... ELSE IN ('+','-')
11. Copy/Past вместо подстановки параметра
12. И как следствие зацикливание при попадании неправильного символа (до вылета в ошибку)
13. Забывание прописывать ELSE в END ELSE IF
14. Использование SELECT + DELETE + SELECT + DELETE + INSERT, вместо хотябы UPDATE + DELETE
15. Цикловая обработка вместо одной команды: WHILE by <Exp> + {SELECT DELETE} вместо {SELECT DELETE WHERE ID < (ID by <Exp>)}
16. Проверка на каждой итерации условия для уменьшения на 1цу общего количества итераций
Ещё раз убеждаюсь, что процедурномыслие это признак полной несостоятельности (или школоты).
За разбор ошибок с вас нуль рублей нуль копеек. Если вы не троль.

Изопропил, продолжаем приделывать крылья паровозу.

Вот что получится если чуть причесать:
+ dbo.fnCalculate
CREATE FUNCTION [dbo].[fnCalculate] (	-- арифметическое вычисление строки с помощью обратной польской нотации
	@Expression	VarChar(8000)
) RETURNS Decimal(38,16) WITH RETURNS NULL ON NULL INPUT AS BEGIN	-- SELECT dbo.fnCalculate('12-2+3*(5-3)')
	DECLARE  @Counter	Int
		,@Len		Int
		,@ID		Int
		,@Item		VarChar(100)
		,@Value		Decimal(38,16)
	DECLARE	@StackSign	TABLE (ID Int IDENTITY PRIMARY KEY, [Sign]  VarChar(100)   NOT NULL)
	DECLARE @Buffer		TABLE (ID Int IDENTITY PRIMARY KEY, Item    VarChar(100)   NOT NULL)
	DECLARE @StackValue	TABLE (ID Int IDENTITY PRIMARY KEY, [Value] Decimal(38,16) NOT NULL)

	SELECT	 @Expression	= '(' + Replace(@Expression,' ','') + ')'
		,@Counter	= 0
		,@Len		= Len(@Expression)

	-- Вывёртывание в постфиксную нотацию
	WHILE (@Counter < @Len) BEGIN
		SELECT	 @Counter	= @Counter + 1
			,@Item		= SubString(@Expression,@Counter,1)
		IF (@Item = '(') BEGIN
			INSERT @StackSign VALUES (@Item)
		END ELSE IF (@Item = ')') BEGIN
			SELECT	Top(1)
				@ID = ID
			FROM	@StackSign
			WHERE	[Sign] = '('
			ORDER BY ID DESC

			IF (@@RowCount != 1) GOTO Error;

			INSERT	@Buffer
			SELECT	[Sign]
			FROM	@StackSign
			WHERE	ID > @ID
			ORDER BY ID DESC

			DELETE	@StackSign
			WHERE	ID >= @ID
		END ELSE IF (@Item IN ('+','-','*','/')) AND ((@Item != '-') OR SubString(@Expression,@Counter-1,1) NOT IN ('-','+','*','/','(')) BEGIN
			SELECT	Top(1)
				@ID = ID
			FROM	@StackSign
			WHERE	    [Sign] = '('
				 OR [Sign] IN ('+','-')
				AND @Item  IN ('*','/')
			ORDER BY ID DESC

			IF (@@RowCount != 1) SET @ID = 0

			INSERT	@Buffer
			SELECT	[Sign]
			FROM	@StackSign
			WHERE	ID > @ID
			ORDER BY ID DESC

			DELETE	@StackSign
			WHERE	ID > @ID

			INSERT @StackSign VALUES (@Item)
		END ELSE BEGIN
			SELECT	 @Item		= CASE	WHEN N.I = 0
							THEN Stuff(@Expression,1,@Counter-1,'')
							ELSE SubString(@Expression,@Counter,N.I)
							END
				,@Counter	= CASE	WHEN N.I = 0
							THEN @Len
							ELSE @Counter + N.I - 1
							END
			FROM	(SELECT PatIndex('%[()*/+-]%',Stuff(@Expression,1,@Counter,'')))N(I)	-- У PatIndex нет 3-го аргумента

			INSERT @StackSign VALUES (@Item)
		END
	END

	IF Exists(SELECT * FROM @StackSign) GOTO Error;

	-- Непосрественное вычисление
	WHILE (1 = 1) BEGIN
		SELECT 	Top(1)
			 @ID	= ID
			,@Item	= Item
		FROM	@Buffer
		WHERE	Item IN ('+','-','*','/')
		ORDER BY ID

		IF (@@RowCount = 0) BREAK;

		INSERT	@StackValue
		SELECT	Item
		FROM	@Buffer
		WHERE	ID < @ID
		ORDER BY ID

		DELETE	@Buffer
		WHERE	ID <= @ID

		SELECT	Top(2)
			 @ID	= ID
			,@Value	= CASE	WHEN Row_Number()OVER(ORDER BY ID DESC) = 1
							 THEN [Value]
					WHEN @Item = '+' THEN [Value] + @Value
					WHEN @Item = '-' THEN [Value] - @Value
					WHEN @Item = '*' THEN [Value] * @Value
					WHEN @Item = '/' THEN [Value] / @Value
					END
		FROM	@StackValue
		ORDER BY ID DESC;

		IF (@@RowCount != 2) GOTO Error;

		UPDATE	@StackValue
		SET	[Value]	= @Value
		WHERE	ID = @ID

		DELETE	@StackValue WHERE ID > @ID
	END

	RETURN (SELECT [Value] FROM @StackValue)
Error:	RETURN 1/0;
END
GO
Там ещё можно сократить (специально так оставил).
Лучше либо отделить функции приведения и польку, либо объеденить циклы, но общего толку от этого мало.
Видно что в алгоритме нет реального понимание математики парсирования и вычисления простых математических выражений. Я чуть позже приведу (для фана) объединённый алгоритм без всяких полек с сутью .

На счёт CTE. - я нагнал. На нём канечно можно, но только если немеряно извратиться (опять нарезка строк). Стек дело императивное. Как по другому сделать - думать нет смысла (см первый вариант).
16 окт 11, 18:48    [11448425]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Mnior
3. Злоупотребление плавающих неточных типов (Float) чревато, особенно если они и не обрабатываются; (на крайняк две функции)
 ... Decimal(38,16) ...
Эпически фейл + фейспалм: Точность, масштаб и длина (см операцию деления). Или верни Float и не выёживайся или хотя бы поправься на:
 ... Decimal(25,13) ...
Конечно Float минное поле, но остальное тоже не подарочек (всё намного сложнее). Это ещё одно доказательство, что императивщина зло, со своими промежуточными вычислениями ломающие всё.
Математические выражения это вам не в носу ковыряться. Надо думать когда их пишешь для вычислений, да и вообще думать надо всегда.

КО
17 окт 11, 00:09    [11448933]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
JeanBatist
Member

Откуда:
Сообщений: 9
Mnior,

огромное спасибо за ликбез. Чувствую, надо было книжки читать... Что, пожалуй, и начну делать.
17 окт 11, 10:06    [11449384]     Ответить | Цитировать Сообщить модератору
 Re: Функция вычисления строкового выражения (обратная польская нотация)  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
JeanBatist, необычайный адекват и точное замечание.
Да, всё должно быть проверено на своём опыте, но заразиться правописанием и стремлением понимать, правильно понимать, можно только у другого человека. Читайте.

КО
18 окт 11, 03:18    [11456332]     Ответить | Цитировать Сообщить модератору
Все форумы / Microsoft SQL Server Ответить