我必须在数据上实现一个算法,这个算法存储在SQL服务器中(有充分理由)。该算法不适合SQL,因此我想将其实现为CLR函数或过程。这就是我想要做的事情:
执行多个查询(通常为20-50,但最多为100-200),这些查询都具有select a,b,... from some_table order by xyz
形式。有一个符合该查询的索引,因此无需任何计算即可获得或多或少的结果。
逐步消耗结果。确切的步进取决于结果,因此它不是完全可预测的。
通过逐步调整结果来汇总一些结果。我只会消耗结果的第一部分,但无法预测我需要多少。停止标准取决于算法中的某个阈值。
我的想法是打开几个SqlDataReader,但我遇到了两个问题:
每个连接只能有一个SqlDataReader,在CLR方法中我只有一个连接 - 据我所知。
我不知道如何告诉SqlDataReader如何以块的形式读取数据。我找不到SqlDataReader应该如何表现的文档。据我所知,它正在准备整个结果集,并将整个结果加载到内存中。即使我只消耗它的一小部分。
任何提示如何解决这个问题作为CLR方法?或者是否有更低级别的SQL服务器接口,这更适合我的问题?
更新:我应该更清楚地说明两点:
我在谈论大数据集,因此查询可能会产生1 mio记录,但我的算法只会消耗前100-200个记录。但正如我之前所说:我事先并不知道确切的数字。
我知道SQL可能不是那种算法的最佳选择。但由于其他限制,它必须是SQL服务器。所以我正在寻找最好的解决方案。
答案 0 :(得分:4)
SqlDataReader
不会读取整个数据集,您会将其与Dataset
类混淆。它正在逐行读取,因为正在调用.Read()
方法。如果客户端不使用结果集,则服务器将暂停查询执行,因为它没有空间将输出写入(所选行)。当客户端消耗更多行(正在调用SqlDataReader.Read)时,执行将恢复。甚至还有一个特殊的命令行为标志SequentialAccess
,它指示ADO.Net不要在内存中预加载整行,这对于以流方式访问大型BLOB列非常有用(请参阅Download and Upload images from SQL Server via ASP.Net MVC了解实际情况例子)。
当MARS处于活动状态时,您可以在单个连接上激活多个活动结果集(SqlDataReader)。但是,MARS与SQLCLR上下文连接不兼容。
因此,您可以创建一个CLR streaming TVF来完成CLR中所需的一些操作,但前提是您只有一个SQL查询源。要求您放弃上下文连接和使用的多个查询并不是完全成熟的连接,即。在回送中连接回同一个实例,这将允许MARS,从而消耗多个结果集。但是loopback有自己的问题,因为它打破了你从上下文连接中获得的事务边界。特别是使用环回连接,您的TVF将无法读取由调用TVF的同一事务所做的更改,因为不同的连接上的不同的事务。
答案 1 :(得分:1)
SQL旨在对付大量数据集,并且功能非常强大。使用基于集合的逻辑,通常不需要迭代数据来执行操作,并且在SQL本身中有许多内置方法可以执行此操作。
1)基于写集的逻辑来更新没有游标的数据
2)将确定性用户定义函数与基于集合的逻辑一起使用(您可以使用SqlFunction attribute in CLR code执行此操作)。非确定性会产生内部将查询转换为游标的影响,这意味着在给定相同输入的情况下,值输出并不总是相同。
[SqlFunction(IsDeterministic = true, IsPrecise = true)]
public static int algorithm(int value1, int value2)
{
int value3 = ... ;
return value3;
}
3)使用游标作为最后的手段。这是在数据库上每行执行逻辑但是会对性能产生影响的强大方法。它出现在this article CLR可以执行SQL游标(感谢Martin)。
我看到你的评论说使用基于集合的逻辑的复杂性太多了。你能提供一个例子吗?有许多SQL方法可以解决复杂问题 - CTE,视图,分区等。
当然,你可能在你的方法中是正确的,我不知道你想做什么,但我的直觉说利用SQL的工具。产生多个读取器不是接近数据库实现的正确方法。您可能需要多个线程调用SP来运行并发处理,但不要在CLR中执行此操作。
要回答您的问题,使用CLR实现(和IDataReader
),您实际上不需要以块的形式分页结果,因为您没有将数据加载到内存或通过网络传输数据。 IDataReader
使您可以逐行访问数据流。根据声音,您的算法会确定需要更新的记录数量,因此当发生这种情况时,只需停止调用Read()
并在此时结束。
SqlMetaData[] columns = new SqlMetaData[3];
columns[0] = new SqlMetaData("Value1", SqlDbType.Int);
columns[1] = new SqlMetaData("Value2", SqlDbType.Int);
columns[2] = new SqlMetaData("Value3", SqlDbType.Int);
SqlDataRecord record = new SqlDataRecord(columns);
SqlContext.Pipe.SendResultsStart(record);
SqlDataReader reader = comm.ExecuteReader();
bool flag = true;
while (reader.Read() && flag)
{
int value1 = Convert.ToInt32(reader[0]);
int value2 = Convert.ToInt32(reader[1]);
// some algorithm
int newValue = ...;
reader.SetInt32(3, newValue);
SqlContext.Pipe.SendResultsRow(record);
// keep going?
flag = newValue < 100;
}
答案 2 :(得分:0)
游标是仅限SQL的函数。如果您想一次读取数据块,则需要进行某种分页,以便只返回一定数量的记录。如果使用Linq,
.Skip(Skip)
.Take(PageSize)
Skips and takes可用于限制返回的结果。
您可以通过执行以下操作简单地遍历DataReader:
using (IDataReader reader = Command.ExecuteReader())
{
while (reader.Read())
{
//Do something with this record
}
}
这将一次迭代一次结果,类似于SQL Server中的游标。
对于多个记录集,请尝试MARS (如果是SQL Server)