【问题标题】:n number of sql intersectionsn个sql交集
【发布时间】:2018-07-31 22:29:38
【问题描述】:

给定下表:

MeasureID FilterID
--------- --------
3         1
3         2
4         1
4         2
4         3
4         4
4         5
5         1

我希望能够从 n 个度量的交集中选择过滤器 ID。

Example:
MeasureID 3, 4 results in FilterID 1, 2
MeasureID 3, 4, 5 results in FilterID 1
MeasureID 4 results in FilterID 1, 2, 3, 4, 5

SQL 查询会很好,我知道这可以通过以下方式完成:

select m.FilterID from MeasureFilter m
where m.MeasureID = 3

intersect

select m.FilterID from MeasureFilter m
where m.MeasureID = 4

但有可能选择 10 或 100 种措施。

最终这将使用 LINQ 和实体框架在 C# 中生成。我知道这可以在某种类型的 C# 循环中完成,但如果想出一些声明性 LINQ 会很好。

想法?

【问题讨论】:

  • 如果你使用 EF,我相信它会在你的 Measure 类上生成一个 ICollection<Filter> Filters 属性,所以你可以这样做:var filters = measures.SelectMany(m => m.Filters).Distinct();
  • 就 SQL 而言,您需要像 SELECT MeasureID From Table Where FilterID IN (1,2,3,4,5) GROUP BY MeasureID HAVING COUNT(*) == 5 这样的东西 - 根据场景调整 IN5。对吗?

标签: c# sql entity-framework linq


【解决方案1】:

您可以将Aggregate 与种子一起使用。

例如

var measures = new List<MeasureFilter>{
    new MeasureFilter(3,1),
    new MeasureFilter(3,2),
    new MeasureFilter(4,1),
    new MeasureFilter(4,2),
    new MeasureFilter(4,3),
    new MeasureFilter(4,4),
    new MeasureFilter(4,5),
    new MeasureFilter(5,1),
};


// all your MeasureIds that you need
var mIds = new List<int> { 3, 4, 5 };

var res = mIds.Aggregate(new List<int>(), (list, next) =>
{
    if (!list.Any()) //need to initially fill list otherwise no intersection
        return measures
            .Where(m => m.MeasureId == mIds.First())
            .Select(m => m.FilterId)
            .ToList();
    return list
        .Intersect(measures.Where(m => m.MeasureId == next)
        .Select(m => m.FilterId))
        .ToList();
});

【讨论】:

    【解决方案2】:

    试试this solution:

    var filterIDs = context.MeasureFilters.Where(x => measureIds.Contains(x.MeasureID))
        .Select(x => new { x.FilterID, x.MeasureID }).ToList()
        .GroupBy(x => x.MeasureID).Select(x => x.Select(y => y.FilterID))
        .Aggregate((a, b) => a.Intersect(b)).ToList();
    

    【讨论】:

    • 这非常接近,但 EF 不喜欢 Aggregate 函数,因此需要一个临时的 ToList() after the GroupBy()`。
    • 您不需要在初始 Select() 之后的 ToList() 并且可以在 GroupBy() 之后将其向下移动这将导致基于 SQL 的执行,包括应该更有效和可扩展?
    • @andleer,GroupBy之后的ToList()会更贵,因为没有后续的聚合操作,比如Sum()或者Count(),所以EF还是应该返回整套实体,还执行其他操作
    【解决方案3】:

    如果我理解正确:

    select filterid
    from t
    where measureid in (3, 4, 5)
    group by filterid
    having count(*) = 3;
    

    having 子句中的3where 中列表的长度。

    【讨论】:

    • 我认为这不会返回预期的结果。多个结果可能具有相同的长度。
    • @andleer 。 . .只有在原始数据中有重复项时才会发生这种情况——样本数据中没有重复项,而且对于联结表来说似乎极不可能。
    • 这可能会超过数千条可能的记录。我需要分组集合的交集。对于一个度量,映射表中会有多个过滤器。
    • @andleer 。 . .这可能是在 SQL 中做你想做的最有效的方法。
    • 看起来可行。我要煮一下这个。谢谢,
    【解决方案4】:

    理论

    你请求的函数有点像数学归纳法的证明:如果你已经计算了前 N 个 measureIds 的结果,并称它为 F(N),那么第一个 (N+1) 个 measureIds 的结果是F(N) 与元素 N+1 的“FilterIds”的交集。

    换句话说:一旦你计算了measureIds 3、4、7(假设是 X)的函数,那么 measureIds 3、4、7、10 的结果就是属于 'FilterIds' 的交集measureId' 10 和 X。

    当您在需求中看到计算的输出是下一个计算的输入以及下一个元素时,函数Queryable.Aggregate 是应该立即在您的脑海中弹出的函数。

    回到你的问题

    所以你需要一个函数,将前面计算的结果作为一个参数,将下一个元素作为另一个参数:

    • 'previousResult',是FilterIds 的序列。到目前为止,它是“FilterIds”或所有已处理的measureIds 的交集。
    • 'nextMeasureId' 是下一个要处理的 measureId

    函数的输出应该是'previousResult'和属于'measureId'的所有'FilterIds'的交集

    (previousResult, nextMeasureId) =>
        previousResult.Intersect( "all FilterIds belonging to nextMeasureId")
    

    现在要正式化,我需要一个类。

    class MeasureResult
    {
        public int MeasureId {get; set;}
        public int FilterId {get; set;}
    }
    

    我们创建一个函数,输入IQueryable&lt;MeasureResult&gt;,这是您的原始表格,其他输入:IEnumerable&lt;int&gt; measureIds。

    输出是一个int序列,包含所有FilterIds这些measureIds的交集:IEnumerable&lt;int&gt;

    我会将它实现为扩展函数,因此它可以用作任何 linq 函数。见Extension Methods Demystified

    static IEnumerable<int> IntersectFilterIds(this IQueryable<MeasureResult> measureResults,
        IEnumerable<int> measureIds)
    {
        // Todo: check validity input parameters;
    
        // if no measureIds, return empty sequence:
        if (!measureIds.Any()) return Enumerable.Empty<int>();
    
        // TODO: add some Aggregate function
    }
    

    Aggregate 的输入是 measureId 的序列:取第一个和第二个 measureId 并相交。取结果与第三个 measureId 相交,取结果与第四个 measureId 相交,以此类推。

    聚合的输出是您请求的交集。这与输入序列的元素类型不同:输入元素是整数,输出元素是整数序列。因此,您需要聚合重载,其中结果 (TAccumulate) 与输入序列元素 (TSource) 的类型不同。这个版本需要一个Seed,这是你第一个交集的结果:你的第一个元素。

    我们还需要一个函数来获取measureResults 和一个measureId 的表,并返回此measureId 中的所有FilterIds。我会把它写成一个扩展函数:

    static IEnumerable<int> GetFilterIds(this IEnumerable<MeasureResult> measureResults,
         int measureId)
    {
        return measureResults.Where(measureResult => measureResult.MeasureId == measureId)
            .Select(measureResult.FilterId);
    }
    

    该函数用于仅获取第一个元素的交集结果(即第一个元素的所有FilterId。用作Seed。也用于获取下一个元素的FilterId

    现在我们有一切可以填充聚合:

    • 种子是第一个measureIdFilterIds
    • 聚合的来源是所有其他measureIds
    • 累加函数是previousResult和下一个measureIdFilterIds的交集

      静态 IEnumerable IntersectFilterIds(这个 IEnumerable measureResults, IEnumerable measureIds) { if (!measureIds.Any()) return Enumerable.Empty();

      int firstMeasureId = measureIds.FirstOrDefault();
      IEnumerable<int> seed = measureResults.GetFilterIds(firstMeasureId);
      IEnumerable<int> restOfSequence = measureIds.Skip(1);
      
      // the aggregate:
      var result = restOfSequence.Aggregate(       // aggregate the rest of the measureIds
          seed,                                    // start with the FilterIds of the first measureId
          (previousResult, nextMeasureId) =>       // the next accumulation is
              previousResult.Intersect(            // the intersection of the previous result
                 measureResults.GetFilterId(nextMeasureId) // and all FilterIds of the next measureId
           );
      

      }

    我不确定您的 measureResults 和 measureIds 是在本地内存 (IEnumerable) 中还是在数据库的表中。如果是后者,你不能使用函数,所以你必须编写代码而不是函数,这使得它有点难以阅读,这就是我选择函数解释的原因。

    IQueryable<MeasureResult> measureResults = ...
    IQueryable<int> measureIds = ...
    
    var result = measureIds.Skip(1)        //  = rest of sequence, also: empty if no measureIds
       .Aggregate(
    
        // first parameter: seed, get FilterIds of first measureId
        // note that seed is empty sequence if there is no first measureId
        measureResults                     
            .Where(measureResult.MeasureId == measureIds.FirstOrDefault())
            .Select(measureResult => measureResult.FilterId),
    
        // aggregate function: intersection of the previousResult
        // and all FilterIds for the nextMeasureId
        (previousResult, nextMeasureId) => previourResult.Intersect(
            measureResults.Where(measureResult.MeasureId == nextMeasureId)
            .Select(measureResult => measureResult.FilterId));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-04
      • 2014-11-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多