You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core中多SQL查询生成矩阵结果的性能优化与格式选型

Optimizing 10x10 Matrix Generation from Database (Reduce 100 Queries & Return Format Best Practices)

The Problem

I'm trying to generate a 10×10 matrix for a frontend, where both X and Y axes are split into 10 quantile-based ranges. Each cell in the matrix needs to count the number of distinct UserIds that fall into the corresponding X-Y range. Right now, my code runs 100 separate SQL queries (one per cell), which is terrible for performance. I'm also stuck deciding whether returning a Dictionary with 100 entries or a multi-dimensional array is better practice.

Here's my current code:

public async Task GenerateMatrix(List<double> x, string xAxis, List<double> y, string yAxis, Parameters parameters) {
    IDictionary<string, string> xDict = GenerateRanges(x, parameters.XAxis);
    IDictionary<string, string> yDict = GenerateRanges(y, parameters.YAxis);
    var innerJoin = GenerateInnerJoin(parameters);
    var whereClauses = GenerateWhereClause(parameters);
    var sql = $@"SELECT COUNT(DISTINCT [dbo].[{nameof(Table)}].[{nameof(Table.UserId)}]) FROM [dbo].[{nameof(Table)}] {innerJoin} ";
    if (whereClauses.Any()) {
        sql += " WHERE " + string.Join(" AND ", whereClauses);
    }
    for (int i = 0; i < x.Count; i++) {
        var queryToExecute = "";
        for (int j = 0; j < y.Count; j++) {
            queryToExecute = sql + " AND " + xDict.Values.ElementAt(i) + " AND " + yDict.Values.ElementAt(j);
            var userCount = await Store().QueryScalar<int>(queryToExecute);
        }
    }
    return null;
}

private IDictionary<string, string> GenerateRanges(List<double> axis, string columnTitle) {
    IDictionary<string, string> d = new Dictionary<string, string>();
    for (int i = 0; i < axis.Count; i++) {
        var rangeSql = $@" [dbo].[{nameof(Table)}].[{columnTitle}]";
        if (i == 0) {
            d.Add(axis[i].ToString(), rangeSql + " < " + axis[i]);
        } else if (i == axis.Count - 1) {
            d.Add(axis[i] + "+", rangeSql + " > " + axis[i]);
        } else {
            d.Add(axis[i-1] + "-" + axis[i], rangeSql + " > " + axis[i-1] + " AND " + rangeSql + " < " + axis[i]);
        }
    }
    return d;
}

Example single query:

SELECT COUNT(DISTINCT [dbo].[Table].[UserId]) FROM [Table] WHERE Table.[ClientId] = '2' AND [dbo].[Table].[ProbabilityAlive] < 0.1 AND [dbo].[Table].[SpendAverage] < 24.86

Solution 1: Fix Performance with a Single Query

Running 100 queries is a huge waste of database resources (round-trips, query parsing, etc.). Instead, we can calculate all 100 cell counts in one query using CASE WHEN to map rows to their X/Y segments, then group by those segments.

Step 1: Generate Segment Mapping Logic

Modify your range generation to output CASE WHEN clauses that assign each row to a segment number (1-10) for X and Y axes:

private string GenerateSegmentCase(List<double> axis, string columnTitle) {
    var caseClauses = new List<string>();
    for (int i = 0; i < axis.Count; i++) {
        string condition;
        if (i == 0) {
            condition = $"{columnTitle} < {axis[i]}";
        } else if (i == axis.Count - 1) {
            condition = $"{columnTitle} > {axis[i]}";
        } else {
            condition = $"{columnTitle} > {axis[i-1]} AND {columnTitle} < {axis[i]}";
        }
        caseClauses.Add($"WHEN {condition} THEN {i+1}"); // Use 1-based segments for clarity
    }
    // Catch edge cases (e.g., values exactly equal to boundaries)
    caseClauses.Add("ELSE 0");
    return $"CASE {string.Join(" ", caseClauses)} END";
}

Step 2: Rewrite the Matrix Generation Method

Now build a single SQL query that groups by X and Y segments, then populate your matrix from the results:

public async Task<int[,]> GenerateMatrix(List<double> x, string xAxis, List<double> y, string yAxis, Parameters parameters) {
    // Initialize 10x10 matrix with default 0s
    var matrix = new int[10, 10];
    
    var innerJoin = GenerateInnerJoin(parameters);
    var whereClauses = GenerateWhereClause(parameters);
    
    // Generate segment case logic for X and Y axes
    var xSegmentCase = GenerateSegmentCase(x, $"[dbo].[{nameof(Table)}].[{xAxis}]");
    var ySegmentCase = GenerateSegmentCase(y, $"[dbo].[{nameof(Table)}].[{yAxis}]");
    
    // Build the optimized query
    var sqlBuilder = new StringBuilder();
    sqlBuilder.Append($@"
        SELECT 
            {xSegmentCase} AS X_Segment,
            {ySegmentCase} AS Y_Segment,
            COUNT(DISTINCT [dbo].[{nameof(Table)}].[{nameof(Table.UserId)}]) AS UserCount
        FROM [dbo].[{nameof(Table)}] {innerJoin}");
    
    if (whereClauses.Any()) {
        sqlBuilder.Append($" WHERE {string.Join(" AND ", whereClauses)}");
    }
    
    sqlBuilder.Append(" GROUP BY X_Segment, Y_Segment");
    
    // Execute once and fetch all results
    var results = await Store().QueryAsync<(int X_Segment, int Y_Segment, int UserCount)>(sqlBuilder.ToString());
    
    // Populate the matrix with results
    foreach (var (xSeg, ySeg, count) in results) {
        // Skip invalid segments from the ELSE clause
        if (xSeg < 1 || xSeg > 10 || ySeg < 1 || ySeg > 10) continue;
        // Convert to 0-based index for array access
        matrix[xSeg - 1, ySeg - 1] = count;
    }
    
    return matrix;
}

This cuts your database round-trips from 100 to 1, which will drastically improve performance.


Solution 2: Return Format Best Practices

Let's break down the two options you're considering:

Option A: Multi-Dimensional Array (int[,])

  • Pros:
    • Directly mirrors the 10×10 grid structure—intuitive for both backend code and frontend rendering.
    • Faster index-based access compared to dictionary lookups.
    • Frontend can easily iterate over rows/columns without parsing keys.
  • Cons:
    • Doesn't include explicit range labels (but you can send these separately if the frontend needs them).

Option B: Dictionary (Dictionary<string, int> with keys like "1-1")

  • Pros:
    • Explicitly ties counts to their segment pairs, which can help with debugging or logging.
  • Cons:
    • Requires frontend code to parse keys (split on "-") to map to grid positions.
    • Less efficient than array access, and the structure doesn't visually reflect the grid layout.

Recommendation

Go with the multi-dimensional array for this use case. It's the most efficient and natural fit for a fixed-size 10×10 matrix. If the frontend needs to display the actual range values (e.g., "0-0.1" for X segment 1), you can return a separate object with axis range labels alongside the matrix.

Alternatively, if you want to bundle range info with each count, you could return a list of objects like:

public class MatrixCell {
    public string XRange { get; set; }
    public string YRange { get; set; }
    public int UserCount { get; set; }
}

But this adds extra data transfer compared to a raw array, so only use this if the frontend needs those labels directly.


内容的提问来源于stack exchange,提问作者user122222

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 11:22:32