如何从表中逐块获取数据?
Posted
技术标签:
【中文标题】如何从表中逐块获取数据?【英文标题】:How to fetch chunk by chunk data from table? 【发布时间】:2017-05-05 09:09:07 【问题描述】:我正在尝试从 mysql 表中逐块获取数据。
我有一张如下表:
HistoryLogs = 10986119
现在我想从 MyQSL 中逐块获取数据并将其传递给 sqlbulk 副本进行处理。我已决定批量大小为 1000。
例如,如果我有 10000 条记录,那么我的查询将如下所示:
SELECT * FROM tbl LIMIT 0,1000;
SELECT * FROM tbl LIMIT 1000,2000;
SELECT * FROM tbl LIMIT 2000,3000;
SELECT * FROM tbl LIMIT 9000,10000;
所以首先我将从表中获取总记录,然后尝试如下:
private int FetchCount(string table)
using (MySqlCommand cmd = new MySqlCommand("SELECT COUNT(*) FROM " + table, conn))
cmd.CommandTimeout = 0;
return cmd.ExecuteNonQuery();
string query = string.Empty;
string table ="HistoryLogs";
int records = FetchCount(table);
for (int i = 0; i < records / 1000 ; i++) //
here I would like to create chunk by chunk query and pass it to Process method
private MySqlDataReader Process(MySqlConnection conn, string query)
using (MySqlCommand cmd = new MySqlCommand(query, conn))
cmd.CommandTimeout = 0;
MySqlDataReader reader = cmd.ExecuteReader();
return reader;
所以我不知道如何创建分页查询,并且不确定我是否以正确的方式思考。
【问题讨论】:
【参考方案1】:有点不确定您想使用什么技术从数据库中获取数据。
由于 Linq 关键字,我假设您需要一个 Linq 语句,该语句为您提供具有给定 pageSize 的项目页面。
但是,您不确定如何获取页面 X 的数据。您是否有要划分为页面的记录的 IQueryable(如在 Entity Framework 中 - 强烈推荐),或者您是否想更改您的SQL 语句,以便它会给你页面 X?
IQueryable 方法
假设您想要 T 类型的记录页面,并且您有一个 IQueryable<T>
来获取 T 类型的所有记录。
IQueryable<T> allRecords = ...;
您想将此序列分成页面。每个页面都有一个PageSize
、一个PageNr
和一个记录序列:
class Page<T>
public int PageSize get; set;
public int PageNr get; set;
public IEnumerable<T> Contents get; set;
现在把AllRecords分成一个页面序列,我用了一个扩展方法:
public static class PagingExtensions
public static IQueryable<Page<T>> ToPages<T>(this IQueryable<T> allRecords, int pageSize)
return allRecords.Select( (record, i) => new
PageNr = i / pageSize,
Record = record,
)
.GroupBy(item => item.PageNr)
// intermediate result: sequence of IGrouping<int, T>
// where key is pageNr
// and each element in the group are the records for this page
.Select(group => new Page<T>
PageNr = group.Key,
PageSize = pageSize,
Contents = (IEnumerable<T>) group
);
将 MyRecords 序列划分为页面的代码将是:
const int pageSize = 1000;
IQueryable<MyRecord> allMyRecords = ...
IQueryable<Page<MyRecord>> pages = allMyRecords.ToPages(1000);
// do what you want with the pages, for example:
foreach (Page<MyRecord> page in pages)
Console.WriteLine($"Page page.PageNr");
foreach (MyRecord record in Page.Contents)
Console.WriteLine(record.ToString());
请注意,所有使用的函数都使用延迟执行。在您枚举它们之前不会获取记录。
如果您希望能够在本地内存而不是数据库中的页面中划分集合,请使用 IEnumerable<T>
而不是 IQueryable<T>
。
没有 IQueryable 的方法
如果您没有 IQueryable 来获取所有记录,您要么必须自己创建一个实现此功能的类,要么根据要获取的页面调整 SQL 查询。不过我不推荐第一种方法。
class Page<T>
public Page(SqlConnection conn, int pageNr, int pageSize)
this.PageNr = pageNr;
this.PageSize = pageSize;
private readonly SqlConnection conn;
public int PageSize get; private set;
public int PageNr get; private set;
public IEnumerable<T> ReadContents()
int offset = this.PageNr * this.PageSize;
int fetch = this.PageSize;
string cmdText = "SELECT col1, col2, ..."
+ " FROM ... "
+ " WHERE ... "
+ " ORDER BY -- "
// this is a MUST there must be ORDER BY statement
//-- the paging comes here
+ $" OFFSET offset ROWS"
+ $" FETCH NEXT fetch ROWS ONLY;";
using (SqlCommand cmd = new SqlCommand("cmdText, conn))
using (var sqlDataReader = cmd.ExecuteQuery())
List<T> readItems = sqlDataReader...;
// you know better than I how to use the SqlDataReader
return readItems
Fetch / Offset 而不是 Enumerable Skip / Take 的想法来自 *** 上的 Implement paging in SQL。
【讨论】:
这真是太棒了。非常感谢您提供答案,我非常感谢您的努力 IQueryable 版本存在一些问题。第一个很容易修复;Contents = (IEnumerable<T>) group
应该是 Contents = group.Select(g => g.Record)
。如果您使用的是 EF,则第二个不是那么多;您不能在查询中使用 Select
的 (T, int)
重载。以上是关于如何从表中逐块获取数据?的主要内容,如果未能解决你的问题,请参考以下文章