【问题标题】:SQL: Perform arithmetic operations on values in a columnSQL:对列中的值执行算术运算
【发布时间】:2019-12-23 19:10:04
【问题描述】:

我有一个包含公式的varchar

declare @formula varchar(50) = 'X + Y + Z'

我还有一张桌子:

+---+---+
| A | B |
+---+---+
| X | 1 |
+---+---+
| Y | 2 |
+---+---+
| Z | 3 |
+---+---+

A 列的值是唯一的,公式可能会发生变化。例如,如果公式设置为'X + Y + Z',则结果将为6。如果公式设置为'Z - X + Y',则结果为4。运算仅包括加法和减法。我怎样才能做到这一点?很难寻找从哪里开始。

【问题讨论】:

  • 我在您的数据中没有看到任何公式。只是参数/变量的名称。
  • 我认为他正在尝试将公式写入一个单独的字段并动态计算该公式,就像 Excel 会做的那样。但我无法完全理解它的行为或执行方式等。问题在书面上没有多大意义。
  • @GordonLinoff 嗨,我添加了一个“公式”变量。
  • @Marc 会有一个包含公式的 varchar 变量。它会根据该公式计算值。没错,它就像 Excel。
  • 一种方法是动态旋转数据,使 X、Y 和 Z 成为列,然后应用公式。没有办法将公式应用于标准 SQL 中的基于行的数据。

标签: sql sql-server


【解决方案1】:

SQL Server 不支持宏替换,也没有 Eval()...这就留下了动态 SQL

示例

Declare @YourTable Table ([A] varchar(50),[B] varchar(50))
Insert Into @YourTable Values 
 ('X',1)
,('Y',2)
,('Z',3)

Declare @formula varchar(50) = 'X + Y + Z'
Select @formula=replace(@formula,[A],[B])
 From  @YourTable

Exec('Select NewValue='+@formula)

退货

NewValue
6

【讨论】:

  • +1,优雅的解决方案。请注意,与大多数编程语言相比,-(负数) 的优先顺序不同,-1.0/-2.0*3.0 被评估为0.166666,而我们大多数人认为结果应该是1.5(如评论here)。
  • @Zhorov 好吧,根据“我亲爱的莎莉阿姨”的说法,那将是 0.166,以防万一我和自己约会。我亲爱的莎莉阿姨是乘法、除法、加法、减法的助记符 :) 当前是 PEMDAS
  • 这对我帮助很大。非常感谢!
  • @Wabbage 总是乐于提供帮助
  • @Wabbage 只是一个评论。当我做这些事情时,我倾向于用括号“标记化”,例如 ( [CurrentAssets] - [Inventory] ​​)/( [CurrentLiability] )
【解决方案2】:

只是为了好玩,这是一个修改后的选项,它将支持TABLE

示例

Declare @YourValues Table ([A] varchar(50),[B] varchar(50))
Insert Into @YourValues Values 
 ('X',1)
,('Y',2)
,('Z',3)

Declare @YourFormula Table (ID int,Formula varchar(50))
Insert Into @YourFormula Values
(1,'X + Y + Z'),
(2,'X - Y + Z')

Declare @SQL varchar(max) = stuff((Select concat(',(',ID,',',Formula,')') From @YourFormula For XML Path ('')),1,1,'') 
Select  @SQL=replace(@SQL,[A],[B])
  From  @YourValues

Create Table #TempResults (ID int,Calc money)
Exec('Insert Into #TempResults Select * from (values '+@SQL+')A(ID,Calc)')

Select * from #TempResults

退货

ID  Calc
1   6.00
2   2.00

【讨论】:

    【解决方案3】:

    这是相当粗略的,仅适用于 +/- 操作数,但是,我相信它可以满足问题。

    DECLARE  @Formulas TABLE (Formula NVARCHAR(MAX))
    INSERT INTO @Formulas SELECT 'Z-X+Y'
    
    DECLARE @Values TABLE(Name NVARCHAR(50), Value DECIMAL(18,2))
    INSERT @Values VALUES ('X',1),('Y',2),('Z',3)
    
    ;WITH MySplitFormula AS
    (
        SELECT Value = SUBSTRING(Formula,Number,1) FROM @Formulas
        CROSS APPLY (SELECT DISTINCT number FROM master..spt_values WHERE number > 0 AND number <= LEN(Formula))V
    )
    
    ,NormalizedFormula AS
    (
        SELECT 
            DerivedOperations   = CASE WHEN F.Value IN('+','-') THEN F.Value ELSE NULL END,
            IsOperator          = CASE WHEN F.Value IN('+','-') THEN 1 ELSE 0 END,
            DerivedValues       = CASE WHEN F.Value IN('+','-') THEN NULL ELSE V.Value END
        FROM 
            MySplitFormula F
            LEFT OUTER JOIN @Values V ON V.Name = F.Value
        WHERE
            NOT F.Value IS NULL
    ),
    ValidatedFormula AS
    (
        SELECT DerivedOperations,DerivedValues FROM NormalizedFormula WHERE NOT((DerivedOperations IS NULL) AND (DerivedValues IS NULL))
    ),
    Operators AS
    (
        SELECT 
            OrderIndex=ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
            Operator=DerivedOperations FROM ValidatedFormula WHERE NOT DerivedOperations IS NULL
    ),
    Operands AS
    (
        SELECT 
            OrderIndex=ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
            Operand=DerivedValues FROM ValidatedFormula WHERE NOT DerivedValues IS NULL
    )
    ,Marked AS
    (
        SELECT 
            OP.OrderIndex,
            DoOperation = CASE WHEN OP.OrderIndex % 2 = 1 THEN  1 ELSE  0 END,
            Operand1 = Operand,
            Operator,
            Operand2 = LEAD(Operand) OVER(ORDER BY OP.OrderIndex) 
        FROM
            Operands OP
            LEFT OUTER JOIN Operators OPR ON OPR.OrderIndex = OP.OrderIndex
    )
    ,MarkedAgain AS
    (
        SELECT 
            *,
            CalculatedValue = CASE WHEN DoOperation = 1 THEN  
                CASE
                    WHEN Operator = '+' THEN Operand1 + Operand2 
                    WHEN Operator = '-' THEN Operand1 - Operand2 
                    WHEN Operator IS NULL THEN 
                        CASE WHEN LAG(Operator) OVER(ORDER BY OrderIndex) ='+' THEN Operand1 ELSE -Operand1  END
                ELSE NULL
                END
            END
        FROM 
            Marked  
    )
    SELECT SUM(CalculatedValue) FROM MarkedAgain
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-02-06
      • 2017-01-10
      • 1970-01-01
      • 2022-06-14
      • 2021-06-23
      • 2013-09-06
      • 1970-01-01
      相关资源
      最近更新 更多