7

我有几个编写的程序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会那样聚合列。

如果以上是正确的,还有哪些其他技术可以让我创建我需要的函数类型?SQL类似于我所追求的内置函数的一个示例是square()(显然)采用一列并返回自身^2。我的目标是创建一个函数库,其行为类似于square,power等。但在内部它会有所不同square,因为每个标量都是通过行读取的。我想知道是否可以让 User Defied 使用累积方法(如 UDA)能够在导入结束时对所有数据进行操作,然后返回相同长度的列?

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

编辑:R为希望已经面临此类困难的 R 开发人员添加了标签。

EDIT2:添加CLR标签:我浏览CLR了 Pro t-sql 2005 程序员指南中定义的用户定义聚合。我在上面已经说过这种类型的功能不适合我的需求,但值得研究一下。UDA 需要的 4 种方法是:InitAccumulate和。我的请求需要由. 因此,包括将多核处理的部分结果组合在一起的方法在内的选项将不起作用。MergeTerminateUDAmerge

4

6 回答 6

4

I think you may consider changing your mind a bit. SQL language is very good when working with sets of data, especially modern RDBMS implementations (like SQL Server 2012), but you have to think in sets, not in rows or columns. While I stilldon't know your exact tasks, let's see - SQL Server 2012 have very nice set of window functions + ranking functions + analytic functions + common table expressions, so you can write almost any query inline. You can use chains of common table expression to turn your data any way you want, to calculate running totals, to calculate averages or other aggregates over window and so on.

Actually, I've always liked SQL and when I've learned functional language (ML and Scala) a bit, my thought was that my approach to SQL is very similar to functional language paradigm - just slicing and dicing data without saving anything into variables, untils you have resultset your need.

Just quick example, here's a question from SO - How to get average of the 'middle' values in a group?. The goal was to get the average for each group of the middle 3 values:

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

For me, it's not an easy task to do in R, but in SQL it could be a really simple query like this:

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

You can also easily expand this query to get 5 values around the middle.

TAke closer look to analytical functions, try to rethink your calculations in terms of window functions, may be it's not so hard to rewrite your R procedures in plain SQL.

Hope it helps.

于 2013-09-30T19:28:23.903 回答
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 对于“函数式编程”并不是很好。处理数据和数据集非常棒,但是您将其用作“应用程序服务器”的次数越多,您就越会意识到它并非为此而生。

于 2013-09-26T16:36:53.587 回答
3

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

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

一个很好的例子见这里

我认为将 R 与 SQL 接口是一个非常好的解决方案。Oracle 将这种组合作为商业产品提供,所以为什么不采用与 SQL Server 相同的方式。

当使用自己的聚合函数将 R 集成到代码中时,您只会付出很小的性能损失。根据 Microsoft 文档,自己的聚合函数非常快:“托管代码的执行速度通常比内置 SQL Server 聚合函数稍慢”。并且R.NET 解决方案似乎也相当快,直接在运行进程中加载​​本机 R DLL。所以它应该比使用 R over ODBC 快得多。

于 2013-10-01T12:44:27.890 回答
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

还要检查输出,如下所示。 在此处输入图像描述

编辑2:

三点平滑算法

在此处输入图像描述

    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)

在此处输入图像描述

于 2013-10-03T04:21:32.323 回答
1

既然您提到您将升级到 SQL Server 2012 - SQL Server 2008 引入了表值参数

这个功能会做你想做的事。您必须在数据库中定义一个用户定义类型 (UDT),这就像一个包含列及其各自类型的表定义。

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

您可以将这些 UDT 与 CLR 集成结合起来以实现您的要求。

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

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

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

看这篇文章的例子

于 2013-10-07T08:50:13.950 回答
1

我认为你可能需要将你的功能分成两部分 - UDA 可以在范围上工作,OVER (...)这要归功于结合了结果标量的子句和公式。

您所要求的 - 以使其成为聚合/标量组合的方式定义对象 - 可能超出常规 SQL Server 功能的范围,除非您退回到 CLR 代码,否则实际上将等同于光标性能或更差。

你最好的办法是定义 SP(我知道你不知道那是什么),它将产生整个结果。就像 create [derivative] 存储过程一样,它将以表名和列名作为参数接收参数。你甚至可以扩展这个想法,但最终这并不是你想要的。

于 2013-10-04T12:33:39.687 回答