优化SQL Server/C#查询与自定义分页:交易数据分组技术问询
Hey there! Let's tackle your requirements step by step, covering grouped queries, SQL Server optimizations, and custom pagination in C# based on your table structure.
First, here's a sample SQL query to group transactions by ItemID (and other relevant fields) while calculating key metrics like total sales quantity, revenue, and transaction count:
SELECT ItemID, MAX(Title) AS ItemTitle, -- Use MAX since Title should be consistent per ItemID; adjust if needed SUM(QuantitySoldTransaction) AS TotalQuantitySold, SUM(TransactionPrice * QuantitySoldTransaction) AS TotalRevenue, COUNT_BIG(TransactionID) AS TotalTransactions FROM YourTableName -- Optional: Filter by date or other criteria to reduce data before grouping WHERE TransactionDate >= '2023-01-01' AND TransactionDate < '2024-01-01' GROUP BY ItemID ORDER BY TotalRevenue DESC;
If you need to group by additional fields (like CountryName), just add them to the GROUP BY clause and adjust the SELECT accordingly.
To make your grouped queries run efficiently, focus on these key areas:
- Create Covering Indexes: Build indexes that include all fields used in filtering, grouping, and aggregation to avoid expensive table lookups. For the query above, this index would work well:
CREATE NONCLUSTERED INDEX IX_Transactions_ItemID_Date ON YourTableName (ItemID, TransactionDate) INCLUDE (Title, QuantitySoldTransaction, TransactionPrice, TransactionID); - Filter Early: Use
WHEREclauses to exclude irrelevant data before grouping—this reduces the number of rows the database needs to process during aggregation. - Avoid Unnecessary Data: Never use
SELECT *; only fetch the fields you need. For counts, useCOUNT_BIG(TransactionID)instead ofCOUNT(*)sinceTransactionIDis non-null, which is slightly more efficient. - Analyze Execution Plans: Use SQL Server Management Studio's execution plan feature to spot bottlenecks like full table scans or key lookups. Adjust indexes or query logic based on what you see.
There are two main approaches to pagination—database-side pagination (recommended for large datasets) and in-memory pagination (only for small datasets).
1. Database-Side Pagination (SQL Server 2012+)
Leverage SQL Server's OFFSET FETCH syntax to let the database handle pagination, which is far more efficient than pulling all data into memory first. Here's a C# example using Dapper (you can adapt this for Entity Framework Core too):
using System.Data.SqlClient; using Dapper; using System.Threading.Tasks; using System.Collections.Generic; public class TransactionService { private readonly string _connectionString; public TransactionService(string connectionString) { _connectionString = connectionString; } public async Task<PagedResult<TransactionGroupDto>> GetGroupedTransactionsAsync( int pageNumber, int pageSize, DateTime? startDate = null) { // Query to get paginated grouped data const string dataSql = @" SELECT ItemID, MAX(Title) AS ItemTitle, SUM(QuantitySoldTransaction) AS TotalQuantitySold, SUM(TransactionPrice * QuantitySoldTransaction) AS TotalRevenue, COUNT_BIG(TransactionID) AS TotalTransactions FROM YourTableName WHERE (@StartDate IS NULL OR TransactionDate >= @StartDate) GROUP BY ItemID ORDER BY TotalRevenue DESC OFFSET (@PageNumber - 1) * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY; "; // Query to get total number of groups for pagination metadata const string countSql = @" SELECT COUNT_BIG(DISTINCT ItemID) AS TotalGroups FROM YourTableName WHERE (@StartDate IS NULL OR TransactionDate >= @StartDate); "; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); var groupedData = (await connection.QueryAsync<TransactionGroupDto>(dataSql, new { PageNumber = pageNumber, PageSize = pageSize, StartDate = startDate })).ToList(); var totalGroups = await connection.ExecuteScalarAsync<long>(countSql, new { StartDate = startDate }); var totalPages = (int)Math.Ceiling(totalGroups / (double)pageSize); return new PagedResult<TransactionGroupDto> { Data = groupedData, PageNumber = pageNumber, PageSize = pageSize, TotalItems = totalGroups, TotalPages = totalPages }; } } // DTO for grouped transaction data public class TransactionGroupDto { public long ItemID { get; set; } public string ItemTitle { get; set; } public int TotalQuantitySold { get; set; } public double TotalRevenue { get; set; } public long TotalTransactions { get; set; } } // Pagination result wrapper public class PagedResult<T> { public List<T> Data { get; set; } public int PageNumber { get; set; } public int PageSize { get; set; } public long TotalItems { get; set; } public int TotalPages { get; set; } }
2. In-Memory Pagination (Small Datasets Only)
If you already have all grouped data in memory (e.g., for small datasets), use LINQ to handle pagination:
public PagedResult<TransactionGroupDto> GetPagedGroupedData( List<TransactionGroupDto> allGroupedData, int pageNumber, int pageSize) { var pagedData = allGroupedData .OrderByDescending(g => g.TotalRevenue) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList(); return new PagedResult<TransactionGroupDto> { Data = pagedData, PageNumber = pageNumber, PageSize = pageSize, TotalItems = allGroupedData.Count, TotalPages = (int)Math.Ceiling(allGroupedData.Count / (double)pageSize) }; }
⚠️ Important: Avoid this approach for large datasets—it will consume excessive memory and slow down your application. Always prefer database-side pagination when dealing with large amounts of data.
内容的提问来源于stack exchange,提问作者User987




