我有几个用R
编写的程序,现在我需要在T-SQL中进行翻译以将它们传递给客户端。我是T-SQL的新手,我在翻译所有R
函数方面遇到了一些困难。
一个例子是数值导数函数,对于两个输入列(值和时间),它将返回具有计算导数的另一列(具有相同长度)。
我目前的理解是:
我不能使用SP,因为我需要使用此函数内联
select
声明,如:
SELECT Customer_ID, Date, Amount, derivative(Amount, Date) FROM Customer_Detail
我不能使用UDF,因为它们只能作为输入参数使用标量。由于速度的原因,我需要矢量化函数,因为对于我所拥有的某些函数,如上所述,逐行运行将没有意义(对于每个值,它需要下一个和前一个)
sum
或avg
那样汇总列。 如果以上是正确的,哪些其他技术可以让我创建我需要的函数类型? SQL
内置函数的一个示例,类似于我所追求的是square()
(显然)取一列并返回^ 2。我的目标是创建一个函数库,其行为类似于square
,power
等。但在内部,它将是不同的,因为square
采用并返回每个标量通过行读取。我想知道是否可以让用户违反累积方法(如UDA),能够对导入结束时的所有数据进行操作,然后返回相同长度的列?
注意:目前我正在使用SQL-Server 2005,但我们很快就会切换到2012年(或者可能是几个月内的2014年),所以基于任何2005+版本的SQL-Server的答案都可以。
编辑:为R开发人员添加了R
标签,希望这些开发人员已经遇到过这样的困难。
EDIT2:添加了CLR
标记:我按照Pro t-sql 2005程序员指南中的定义浏览了CLR
用户定义的聚合。我上面已经说过,这种功能不适合我的需要,但值得深入研究。 UDA所需的4种方法是:Init
,Accumulate
,Merge
和Terminate
。我的请求需要由UDA
的同一个实例一起分析所有数据。因此,包括merge
方法的选项将多核处理的部分结果组合在一起将不起作用。
答案 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
还检查输出,如下所示。
<强> 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)
答案 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以获取示例