【问题标题】: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;
            }
        }

所以我不知道如何创建分页查询,并且不确定我是否以正确的方式思考。

【问题讨论】:

    标签: c# mysql linq ado.net


    【解决方案1】:

    有点不确定您想使用什么技术从数据库中获取数据。

    由于 Linq 关键字,我假设您需要一个 Linq 语句,该语句为您提供具有给定 pageSize 的项目页面。

    但是,您不确定如何获取页面 X 的数据。您是否有要划分为页面的记录的 IQueryable(如在 Entity Framework 中 - 强烈推荐),或者您是否想更改您的SQL 语句,以便它会给你页面 X?

    IQueryable 方法

    假设您想要 T 类型的记录页面,并且您有一个 IQueryable&lt;T&gt; 来获取 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&lt;T&gt; 而不是 IQueryable&lt;T&gt;

    没有 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 的想法来自 stackoverflow 上的 Implement paging in SQL

    【讨论】:

    • 这真是太棒了。非常感谢您提供答案,我非常感谢您的努力
    • IQueryable 版本存在一些问题。第一个很容易修复; Contents = (IEnumerable&lt;T&gt;) group 应该是 Contents = group.Select(g =&gt; g.Record)。如果您使用的是 EF,则第二个不是那么多;您不能在查询中使用 Select(T, int) 重载。
    猜你喜欢
    • 2018-11-25
    • 1970-01-01
    • 2018-05-29
    • 1970-01-01
    • 2015-05-29
    • 2021-08-24
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    相关资源
    最近更新 更多