【问题标题】:Create a function with whole columns as input and output创建一个以整列作为输入和输出的函数
【发布时间】:2013-10-02 14:41:45
【问题描述】:

我有几个用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 内置函数的示例类似于我所追求的 @ 987654330@ (显然)采用一列并返回自身^2。我的目标是创建一个函数库,其行为类似于squarepower 等。但在内部它会有所不同,因为square 获取并返回通过行读取的每个标量。我想知道是否有可能让 User Defied 使用累积方法(如 UDA)能够在导入结束时对所有数据进行操作,然后返回相同长度的列?

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

编辑:为 R 开发人员添加了 R 标签,希望他们已经面临这样的困难。

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

【问题讨论】:

  • 为了澄清,如果您的表中有 100 行,并且列有数量,那么返回的结果是取决于单行的数量还是所有行的数量?您的函数 square 示例采用单个值并返回单个值。像 sum 这样的函数处理多行。你想要一个像 sum 这样处理多行来产生结果的函数,还是像 square 这样只需要一行来产生输出的函数?
  • @Vulcronos 我已经为此使用了衍生示例。是的,结果取决于提供给函数的变量中的所有值。 is possible to have User Defied with an accumulate method (like the UDA) able to operates ... ? (我只使用正方形作为可见结构/内联使用的示例,我提到过)。
  • 对于您的“EDIT2”:您可以以相同的方式实现合并和累积:将数据存储在一些大的并行数据结构中(例如danieltao.com/ConcurrentList)。当您拥有所有数据后,您可以在 Terminate 中进行真正的聚合。
  • 根据文档,UDA 应该与 UDT 一起使用:" ::= system_scalar_type | { [ udt_schema_name.] udt_type_name }" (technet.microsoft.com/en-us/library/ms182741.aspx)
  • 我认为您可以通过使用技巧来做到这一点。您返回系统标量类型(例如整数),但这只是一个句柄。在后台,您将整个结果存储在 .NET 堆中。然后,您使用 UDF,为其提供句柄和可选的原始输入的一部分。然后,此函数将其映射到实际返回值。换句话说:UDF 将列返回值映射到结果集的每一行。但这是一个相当黑客,这应该是一种最后的解决方案。尤其是资源管理会很困难(释放中间列)。

标签: sql sql-server r clr


【解决方案1】:

我想你可以考虑改变一下主意。 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 过程可能并不难。

希望对你有帮助。

【讨论】:

  • 嗨,内置的任何东西都可能不符合我的需要。我需要通过 CLR 创建自己的函数库,我要问的是:如果给定一个 c# 类采用多个值,如 UDA,但不只返回一个标量,sql 可以接受这样的函数吗?我认为我的问题很清楚,导数函数是我需要使用的函数类型的一个(非常简单的)示例:select value, deriv(value, time) from customer。当然,正如您所建议的,我需要以“sql 方式”思考,所以在这里我问你们上述是否可能。后者也可以是一个有效的答案。
  • 顺便说一句,您可以在R:setkey(test, GROUP_ID, TEST_VALUE); test[, rn := 1:.N, by=GROUP_ID]; test[, cnt := .N, by=GROUP_ID]; test[ cnt &lt;= 3 | (rn &gt;= cnt / 2 - 1 &amp; rn &lt;= cnt / 2 + 1), mean(TEST_VALUE), by=GROUP_ID] 中复制您的解决方案的确切方法。无论如何,它超出了范围。
  • 看起来确实 SQL 窗口函数(SQL 2005 及更高版本可用)应该满足@Michele 提到的需求:“我想知道是否有可能拥有用户是否使用累积方法(如 UDA)能够在导入结束时对所有数据进行操作,然后返回相同长度的列?”。
  • 再次嗨...刚刚发现在R 中,内置函数mean 本身已经做到了`mean(test_value, trim=1/10). the parameter trim`现在将排除顶部和底部的 10%。 :)
【解决方案2】:

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

您可以在此处找到表函数参考: 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 并不适合“函数式编程”。处理数据和数据集非常棒,但是您将其用作“应用程序服务器”的次数越多,您就越会意识到它并非为此而生。

【讨论】:

  • 以上内容是否会以一整列作为输入? @storeid 对我来说似乎是一个标量。
  • 不,不是完整的列,但 StoreID 将是您要处理的列的主键。然后,您将在函数内部查询该行...您可以使用保存记录的 XML 来解决它。但是为什么不在函数中传递对记录的引用和查询呢?使用 XML 进行了描述,例如这里:stackoverflow.com/questions/1609115/…
  • 理解您的评论,谢谢,但您的回答似乎仍然超出主题,因为我说我需要将此函数调用到针对 customer_detail 表的 select 语句中。如果此表有 1,000 行,使用您的方法,服务器将必须执行 1,000,000 次迭代(即为每一行调用函数)。如果我错了,请告诉我。另外,对于我上面提到的例子,导数,函数必须同时具有所有值,因为你不能做一个点的导数......
  • 无论如何,再次感谢您的回答,顺便说一句,我同意SQL-Server is not really good for "functional-programming",但不幸的是,这似乎是我必须使用的...R 规则:-) 我孩子...
  • 它被称为 1.000 次,而不是 100 万次。我更新了上面的示例以包含 OUTER APPLY / CROSS APPLY 以将函数加入表。这样做的性能非常好(虽然使用 SQL-Server 2008 或更高版本而不是 2005 会变得更好),但可以肯定的是,使用函数总是相当大的开销。
【解决方案3】:

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

但是您可以创建自己的聚合函数(有关详细信息,请参阅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 快得多。

【讨论】:

  • 您好,感谢您的回答。可以通过 ODBC 将 R 与 SQL 集成(我目前正在这样做)。但是,由于每个表中的数据量很大(100M+ 行,50+ 列),这个选项对于近乎实时的分析是不可行的(除了部署为 SaaS 时的任何许可问题)。谢谢你的例子,我会读完的。然而,我们似乎仍在总产出上。我的想法是:1)Accumulate 方法存储通过行读取的每个值,2)做某事,3)返回一个长度 = number_of_rows_read 的向量。 c# sice 很好,但是这种类型的程序集可以部署在 SQL 中吗?
  • 关于 R 和 DB 集成,似乎 Teradata 和 Revolution Analytics 完成了唯一(生产)有用的工作。声称 R 解释器在数据库中运行,而无需将数据从数据库复制和移动到 R 服务器(如 OracleSAP Hana 通过 RServe
  • @Michele:看起来我描述的解决方案应该类似于您描述的 Teradata 解决方案:R DLL 应该直接加载到数据库服务器进程中。尚无法对其进行测试,但根据文档,它应该以这种方式工作。
  • 哦,是的 R.NET 很好,但据我了解,它可以链接 c#R。然后您仍然需要将结果发送回查询引擎。我没有提到R.NET,因为我可以很容易地将R 翻译成c#。最重要的是在 Sql 中定义了一个用户,该用户接受整列,发送到 c#,并将整列作为结果。这就是聚合不起作用的原因
【解决方案4】:

原始回复:

如果您已经知道需要哪些函数,我能想到的一种方法是,为每个表应用的每个方法/操作创建一个内联函数。 我的意思是什么?例如,您在选择时提到了 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

还检查输出,如下所示。

EDIT2:

三点平滑算法

    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)

【讨论】:

  • 您好,感谢您提供详细信息,但这与我需要的操作有很大不同。我的函数对 WHOLE 列执行操作,需要返回一个 WHOLE 列。结果取决于所有点,因此您需要一次为函数提​​供所有行。如果您使用内联语法为我提供一阶导数和three point smooothing 的示例,我将接受答案
  • 好的,以前我没有获得全列输入和全列输出。但是当我查看“三点平滑算法”的示例并选择它的最简单版本时,我有点明白了。我通过为“三点平滑算法”附加新代码来更新答案。如果你觉得它很有帮助,那么也让我知道“衍生(金额,日期)”是做什么的?如果我看到示例,我会想到一些事情。
  • 嗨,谢谢,是的,上面的功能可以完成,但它不是我想要的那种功能。它只适用于Test3PointSmoothingAlgo 表(至少它会按照现在定义的方式)。我创建函数以将相同的操作(如平滑)应用于任何表中的任何(数字)列。表名和列名可以作为函数参数提供吗?
  • 不,我不认为我们可以很好地做到这一点。从我发布的第一个答案中,我给人的印象是,您应该可以为每个自定义计算创建单独的函数。通用 InlineFuntion 是不可能的,因为 SQL deos 不支持动态 SQL 调用或列作为其中的参数替换。我对 InlineFunction 的选择是出于性能原因,即使我们在其中可以做的事情很严格。我们可以认为其他类型的对象可以更通用地处理相同的事情吗?可能是的,但我怀疑它也会那么干净和完美。
【解决方案5】:

我认为您可能需要将功能分成两部分 - 借助 OVER (...) 子句和组合结果标量的公式,UDA 可以在作用域上工作。

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

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

【讨论】:

  • 一个可以使用任何表和列名(将它们作为参数)的 SP 将比仅适用于预定义表和列的 in-linr 表值函数更好的解决方案跨度>
  • 是的,因此您可以使用@table nvarchar(128), @param1col nvarchar(128), @param2col nvarchar(128) 生成类似sp_derivative 的内容,然后定义@sql nvarchar(max) 并围绕公式构建查询。可能值得先运行一些聚合,但我认为在许多情况下你可以避免它(尽管值得检查性能)。然后就去exec sp_executesql @sql,你就有了结果。请记住,该过程的结果可以插入到表中,以便稍后它可以成为查询的一部分:insert into ... exec sp_derivative ...
【解决方案6】:

既然您提到您将升级到 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的例子

【讨论】:

  • 好像You cannot pass table-valued parameters to CLR user-defined functions.
猜你喜欢
  • 1970-01-01
  • 2014-03-03
  • 1970-01-01
  • 2012-01-14
  • 1970-01-01
  • 2022-01-01
  • 2020-06-26
  • 2017-10-11
  • 1970-01-01
相关资源
最近更新 更多