创建一个以整列作为输入和输出的函数

时间:2013-09-26 14:47:47

标签: sql sql-server r clr

我有几个用R编写的程序,现在我需要在T-SQL中进行翻译以将它们传递给客户端。我是T-SQL的新手,我在翻译所有R函数方面遇到了一些困难。

一个例子是数值导数函数,对于两个输入列(值和时间),它将返回具有计算导数的另一列(具有相同长度)。

我目前的理解是:

  1. 我不能使用SP,因为我需要使用此函数内联 select声明,如: SELECT Customer_ID, Date, Amount, derivative(Amount, Date) FROM Customer_Detail

  2. 我不能使用UDF,因为它们只能作为输入参数使用标量。由于速度的原因,我需要矢量化函数,因为对于我所拥有的某些函数,如上所述,逐行运行将没有意义(对于每个值,它需要下一个和前一个)

  3. UDA采用整列,但正如名称所示......,他们会像sumavg那样汇总列。
  4. 如果以上是正确的,哪些其他技术可以让我创建我需要的函数类型? SQL内置函数的一个示例,类似于我所追求的是square()(显然)取一列并返回^ 2。我的目标是创建一个函数库,其行为类似于squarepower等。但在内部,它将是不同的,因为square采用并返回每个标量通过行读取。我想知道是否可以让用户违反累积方法(如UDA),能够对导入结束时的所有数据进行操作,然后返回相同长度的列?

    注意:目前我正在使用SQL-Server 2005,但我们很快就会切换到2012年(或者可能是几个月内的2014年),所以基于任何2005+版本的SQL-Server的答案都可以。

    编辑:为R开发人员添加了R标签,希望这些开发人员已经遇到过这样的困难。

    EDIT2:添加了CLR标记:我按照Pro t-sql 2005程序员指南中的定义浏览了CLR用户定义的聚合。我上面已经说过,这种功能不适合我的需要,但值得深入研究。 UDA所需的4种方法是:InitAccumulateMergeTerminate。我的请求需要由UDA的同一个实例一起分析所有数据。因此,包括merge方法的选项将多核处理的部分结果组合在一起将不起作用。

6 个答案:

答案 0 :(得分:4)

我想你可能会考虑改变一下你的想法。在处理数据集时,SQL语言非常好,尤其是现代RDBMS实现(如SQL Server 2012),但您必须在集合中思考,而不是在行或列中思考。虽然我不知道你的具体任务,但是让我们看一下 - SQL Server 2012有一套很好的window functions + ranking functions + analytic functions + common table expressions,所以你几乎可以写任何内联查询。您可以使用common table expression链来以您想要的方式转换数据,计算运行总计,计算平均值或窗口上的其他聚合数等。

实际上,我一直很喜欢SQL,当我学习函数式语言(ML和Scala)时,我的想法是我的SQL方法与函数式语言范式非常相似 - 只是切片和切割数据而不保存任何变成变量的东西,直到你得到你需要的结果集。

只是快速示例,这是来自SO的问题 - How to get average of the 'middle' values in a group?。目标是获得每组中间3值的平均值:

TEST_ID TEST_VALUE  GROUP_ID
1       5           1       -+
2       10          1        +- these values for group_id = 1
3       15          1       -+
4       25          2       -+
5       35          2        +- these values for group_id = 2
6       5           2       -+
7       15          2       
8       25          3
9       45          3       -+
10      55          3        +- these values for group_id = 3
11      15          3       -+
12      5           3
13      25          3
14      45          4       +- this value for group_id = 4

对我来说,在R中执行它并不是一件容易的事,但在SQL中它可能是一个非常简单的查询:

with cte as (
    select
        *,
        row_number() over(partition by group_id order by test_value) as rn,
        count(*) over(partition by group_id) as cnt
    from test
)
select
    group_id, avg(test_value)
from cte
where
    cnt <= 3 or
    (rn >= cnt / 2 - 1 and rn <= cnt / 2 + 1)
group by group_id

您还可以轻松扩展此查询以获得中间的5个值。

仔细看看analytical functions,尝试根据窗口函数重新考虑您的计算,可能在纯SQL中重写R过程并不困难。

希望它有所帮助。

答案 1 :(得分:3)

我将通过传递对您要处理的记录的引用来解决此问题,并使用所谓的“内联表值函数”在处理初始记录后返回记录。

您可以在此处找到表函数引用: http://technet.microsoft.com/en-en/library/ms186755.aspx

样本:

    CREATE FUNCTION Sales.CustomerExtendedInfo (@CustomerID int)
RETURNS TABLE
AS
RETURN 
(
    SELECT FirstName + LastName AS CompleteName, 
           DATEDIFF(Day,CreateDate,GetDate()) AS DaysSinceCreation
    FROM Customer_Detail
    WHERE CustomerID = @CustomerID

);
GO

StoreID将是您要处理的记录的主键。

如果要一次处理多个记录,表函数可以随后加入其他查询结果。

这是一个示例:

SELECT  * FROM Customer_Detail
CROSS APPLY Sales.CustomerExtendedInfo (CustomerID) 

使用正常的存储过程会或多或少地做同样的事情,但以编程方式处理结果有点棘手。

但请记住一点:SQL-Server并不适合“功能编程”。使用数据和数据集非常棒,但是你越是将它用作“应用程序服务器”,你就越会意识到它不是为此而做的。

答案 2 :(得分:3)

我不认为在没有使用游标的情况下纯T-SQL是可行的。但是使用游标,东西通常会很慢。游标正在逐行处理表格,有些人称之为“慢速慢”。

但您可以创建自己的聚合函数(有关详细信息,请参阅Technet)。您必须使用.NET CLR实现该功能(例如C#或R.NET)。

有一个很好的例子,请参阅here

我认为将R与SQL连接是一个非常好的解决方案。 Oracle将此组合作为commercial product提供,因此为什么不以与SQL Server相同的方式。

使用自己的聚合函数在代码中集成R时,您只需支付很小的性能损失。根据Microsoft文档:"Managed code generally performs slightly slower than built-in SQL Server aggregate functions",自己的聚合函数非常快。 R.NET solution似乎也很快loading the native R DLL directly in the running process。所以它应该比使用R over ODBC快得多。

答案 3 :(得分:2)

原始回应:

如果您已经知道您需要的功能之一是我能想到的方法之一,那么为每个表格应用的每个方法/操作创建一个内联函数。 我的意思是什么?例如,当您选择FROM Customer_Detail表时,您可能需要一个方法“衍生(金额,日期)”。让我们说你可能需要的第二种方法(我只是为了解释)是“derivative1(Amount1,Date1)”。 我们创建两个内联函数,每个函数都会在预期列的函数内部进行自己的计算,并按原样返回剩余的列。这样,您可以从表中获取所有列,并执行自定义计算作为基于集合的操作而不是标量操作。 以后,如果有意义,可以将相同函数中的列的独立计算组合起来。 如果需要,你仍然可以使用这个所有函数并执行JOIN以获得单个集合中的所有自定义计算,因为所有函数都将按原样使用公共/未处理的列。 见下面的例子。

    IF object_id('Product','u') IS NOT NULL
          DROP TABLE Product
    GO
    CREATE TABLE Product
    (
          pname       sysname NOT NULL
          ,pid        INT         NOT NULL
          ,totalqty   INT         NOT NULL DEFAULT 1
          ,uprice           NUMERIC(28,10)    NOT NULL DEFAULT 0
    )
    GO
    INSERT INTO Product( pname, pid, totalqty, uprice )
                      SELECT      'pen',1,100,1.2
    UNION ALL   SELECT      'book',2,300,10.00
    UNION ALL   SELECT      'lock',3,500,15.00
    GO

    IF object_id('ufn_Product_totalValue','IF') IS NOT NULL
          DROP FUNCTION ufn_Product_totalValue
    GO
    CREATE FUNCTION ufn_Product_totalValue
    (
          @newqty           int
          ,@newuprice numeric(28,10)
    )
    RETURNS TABLE AS
    RETURN
    (
          SELECT pname,pid,totalqty,uprice,totalqty*uprice AS totalValue
          FROM
          (
                SELECT 
                            pname
                            ,pid
                            ,totalqty+@newqty AS totalqty
                            ,uprice+@newuprice AS uprice
                FROM Product
          )qry
    )
    GO

    IF object_id('ufn_Product_totalValuePct','IF') IS NOT NULL
          DROP FUNCTION ufn_Product_totalValuePct
    GO
    CREATE FUNCTION ufn_Product_totalValuePct
    (
          @newqty           int
          ,@newuprice numeric(28,10)
    )
    RETURNS TABLE AS
    RETURN
    (
          SELECT pname,pid,totalqty,uprice,totalqty*uprice/100 AS totalValuePct
          FROM
          (
                SELECT 
                            pname
                            ,pid
                            ,totalqty+@newqty AS totalqty
                            ,uprice+@newuprice AS uprice
                FROM Product
          )qry
    )
    GO

    SELECT * FROM ufn_Product_totalValue(10,5)

    SELECT * FROM ufn_Product_totalValuepct(10,5)

    select tv.pname,tv.pid,tv.totalValue,pct.totalValuePct
    from ufn_Product_totalValue(10,5) tv
    join ufn_Product_totalValuePct(10,5) pct
        on tv.pid=pct.pid

还检查输出,如下所示。 enter image description here

<强> EDIT2:

  

三点平滑算法

enter image description here

    IF OBJECT_ID('Test3PointSmoothingAlgo','u') IS NOT NULL
        DROP TABLE Test3PointSmoothingAlgo
    GO
    CREATE TABLE Test3PointSmoothingAlgo
    (
        qty INT NOT NULL
        ,id INT IDENTITY NOT NULL
    )
    GO
    INSERT Test3PointSmoothingAlgo( qty ) SELECT 10 UNION SELECT 20 UNION SELECT 30
    GO

    IF object_id('ufn_Test3PointSmoothingAlgo_qty','IF') IS NOT NULL
          DROP FUNCTION ufn_Test3PointSmoothingAlgo_qty
    GO
    CREATE FUNCTION ufn_Test3PointSmoothingAlgo_qty
    (
        @ID INT --this is a dummy parameter
    )
    RETURNS TABLE AS
    RETURN
    (
        WITH CTE_3PSA(SmoothingPoint,Coefficients)
        AS --finding the ID of adjacent points
        (
            SELECT id,id
            FROM Test3PointSmoothingAlgo
            UNION
            SELECT id,id-1
            FROM Test3PointSmoothingAlgo
            UNION
            SELECT id,id+1
            FROM Test3PointSmoothingAlgo 
        )
        --Apply 3 point Smoothing algorithms formula
        SELECT a.SmoothingPoint,SUM(ISNULL(b.qty,0))/3 AS Qty_Smoothed--this is a using 3 point smoothing algoritham formula
        FROM CTE_3PSA a
        LEFT JOIN Test3PointSmoothingAlgo b
        ON a.Coefficients=b.id
        GROUP BY a.SmoothingPoint
    )
    GO

    SELECT SmoothingPoint,Qty_Smoothed FROM dbo.ufn_Test3PointSmoothingAlgo_qty(NULL)

enter image description here

答案 4 :(得分:1)

我认为您可能需要将功能分为两部分 - 转换为可以使用范围的UDA,感谢OVER (...)子句和结合结果标量的公式。

您要求的是 - 以使其成为聚合/标量组合的方式定义对象 - 可能超出了常规SQL Server的功能范围,除非您重新使用CLR代码,实际上等效于光标在性能方面还是更差。

你最好的拍摄可能是定义SP(我知道你不是那个)会产生整个结果。就像创建[衍生]存储过程一样,它会将带有表名和列名的参数作为参数。你甚至可以扩展这个想法,但最终并不是你想要的。

答案 5 :(得分:1)

由于您提到您将升级到SQL Server 2012 - SQL Server 2008引入了Table Valued Parameters

此功能可以满足您的需求。您必须在数据库中定义用户定义类型(UDT),这类似于具有列和列的表定义。他们各自的类型。

然后,您可以将该UDT用作数据库中任何其他存储过程或函数的参数类型。

您可以将这些UDT与CLR集成相结合,以实现您的需求。

如前所述,当您将行与其他行进行比较时,SQL并不好,在基于集合的操作中,每个行都被视为一个独立的实体,这样做要好得多。 但是,在看游标之前CLR,你应该确保它不能在纯TSQL中完成,它几乎总是更快和更快。随着你的桌子的增长,你的表现会更好。

根据顺序比较行的一种方法是将数据包装在CTE中,添加排名函数(如ROW_NUMBER)来设置行顺序,然后将CTE自行连接到自身。

将在有序字段上执行连接,例如ROW_NUMBER =(ROW_NUMBER-1)

请查看此article以获取示例