【问题标题】:How do I make this NHibernate QueryOver query return rows for empty groups如何使这个 NHibernate QueryOver 查询返回空组的行
【发布时间】:2012-07-01 13:56:53
【问题描述】:

以下 NHibernate QueryOver 查询正在计算给定日期范围内每个月的应用程序数量。

但是,对于没有任何应用程序的月份,我没有得到任何结果,但我实际上希望这些月份返回 Count = 0。

那么我将如何更改查询以在其中没有任何应用程序的月份也返回一行?

DateTimeOffset endDate = DateTimeOffset.Now;
DateTimeOffset startDate = endDate.AddMonths(-12);

var result = Session.QueryOver<Application>()
    .WhereRestrictionOn(c => c.SubmissionDate).IsBetween(startDate).And(endDate)
    .SelectList(list => list
        .Select(Projections.SqlGroupProjection(
            "YEAR(SubmissionDate) As [Year]",
            "YEAR(SubmissionDate)",
            new[] { "YEAR" },
            new IType[] { NHibernateUtil.Int32 }))
        .Select(Projections.SqlGroupProjection(
            "MONTH(SubmissionDate) As [Month]",
            "MONTH(SubmissionDate)",
            new[] { "MONTH" },
            new IType[] { NHibernateUtil.Int32 }))
        .SelectCount(x => x.Id))
    .OrderBy(Projections.SqlFunction(
        "YEAR",
        NHibernateUtil.Int32,
        Projections.Property<Application>(item => item.SubmissionDate))).Asc
    .ThenBy(Projections.SqlFunction(
        "MONTH",
        NHibernateUtil.Int32,
        Projections.Property<Application>(item => item.SubmissionDate))).Asc
    .List<object[]>()
    .Select(n => new
    {
        Year = n[0],
        Month = n[1],
        Count = (int)n[2]
    }));

【问题讨论】:

    标签: c# nhibernate group-by queryover


    【解决方案1】:

    更新:用DateTime.AddMonths() 表达你的想法会变得更短

        DateTime lastMonth = startdate;
        var unionresults = result.SelectMany(r =>
        {
            var actualDate = new DateTime(r.Year, r.Month, 1);
    
            var results = Enumerable.Repeat(1, Months)
                .Select(i => lastMonth.AddMonths(i))
                .TakeWhile(date => date < actualDate)
                .Select(date => new { Year = date.Year, Month = date.Month, Count = 0 })
                .Concat(new[] { r });
    
            lastMonth = actualDate;
    
            return results;
        });
    

    原文:

    我认为您必须在查询后添加该数据。这里是一个使用 linq 填写缺失月份的示例

    var result = <query>;
    
    int lastMonth = 1;
    var unionresults = result.SelectMany(r =>
    {
        var results = new[] { r }.AsEnumerable();
    
        if (lastMonth > r.Month)
        {
            results = Enumerable.Range(lastMonth, 12 - lastMonth).Select(month => new { Year = r.Year, Month = month, Count = 0 })
                .Concat(Enumerable.Range(1, r.Month).Select(month => new { Year = r.Year, Month = month, Count = 0 }))
                .Concat(results);
        }
        else if (lastMonth < r.Month)
        {
            results = Enumerable.Range(lastMonth, r.Month - lastMonth)
                .Select(month => new { Year = r.Year, Month = month, Count = 0 })
                .Concat(results);
        }
    
        lastMonth = r.Month + 1;
        if (lastMonth > 12)
        {
            lastMonth = 1;
        }
    
        return results;
    });
    

    【讨论】:

    • 啊当然,我没有想到这一点。我喜欢 Enumerable.Range 方法,但使用 DateTime.AddMonths() 购买,您的解决方案中的条件不再需要。
    【解决方案2】:

    这无法通过一些简单的更改来完成。由 QueryOver() 生成的 SQL 查询首先无法计算不存在的内容。 您可能可以使用虚拟/临时表(取决于 DBMS)使用 UNION 或 JOIN 来执行此操作,但这会使查询过于复杂。

    我建议在您的查询之后添加一个循环,该循环遍历列表、将元素复制到新列表并将任何不存在的月份添加到该新列表中。像这样的:

    class YearMonthCount
    {
        public int Year { get; set; }
        public int Month { get; set; }
        public int Count { get; set; }
    }
    
    // Start and End dates
    DateTime startDate = new DateTime(2011, 9, 1);
    DateTime endDate = new DateTime(2012, 6, 1);
    // this would be a sample of the QueryOver() result
    List<YearMonthCount> result = new List<YearMonthCount>();
    result.Add(new YearMonthCount { Year = 2011, Month = 10, Count = 2 });
    result.Add(new YearMonthCount { Year = 2011, Month = 11, Count = 3 });
    result.Add(new YearMonthCount { Year = 2012, Month = 1, Count = 4 });
    result.Add(new YearMonthCount { Year = 2012, Month = 2, Count = 1 });
    result.Add(new YearMonthCount { Year = 2012, Month = 4, Count = 1 });
    result.Add(new YearMonthCount { Year = 2012, Month = 5, Count = 1 });
    
    int i = 0;
    List<YearMonthCount> result2 = new List<YearMonthCount>();
    // iterate through result list, add any missing entry
    while (startDate <= endDate)
    {
        bool addNewEntry = true;
        // check to avoid OutOfBoundsException
        if (i < result.Count)
        {
            DateTime listDate = new DateTime(result[i].Year, result[i].Month, 1);
            if (startDate == listDate)
            {
                // entry is in the QueryOver result -> add this
                result2.Add(result[i]);
                i++;
                addNewEntry = false;
            }
        }
        if (addNewEntry)
        {
            // entry is not in the QueryOver result -> add a new entry
            result2.Add(new YearMonthCount { 
                Year = startDate.Year, Month = startDate.Month, Count = 0 });
        }
        startDate = startDate.AddMonths(1);
    }
    

    这可能会更优雅地完成,但它可以完成工作。

    【讨论】:

    • 谢谢,这启发了我得出最终的解决方案。
    【解决方案3】:

    感谢所有的答案,这就是我最终这样做的方式:

    DateTime endDate = DateTime.Now;
    DateTime startDate = endDate.AddMonths(-Months);
    
    var result = Session.QueryOver<Application>()
        .WhereRestrictionOn(c => c.SubmissionDate).IsBetween(startDate).And(endDate)
        .SelectList(list => list
            .Select(Projections.SqlGroupProjection(
            "YEAR(SubmissionDate) As [Year]",
            "YEAR(SubmissionDate)",
            new[] { "YEAR" },
            new IType[] { NHibernateUtil.Int32 }))
        .Select(Projections.SqlGroupProjection(
            "MONTH(SubmissionDate) As [Month]",
            "MONTH(SubmissionDate)",
            new[] { "MONTH" },
            new IType[] { NHibernateUtil.Int32 }))
        .SelectCount(x => x.Id))
        .List<object[]>()
        .Select(n => new
        {
            Year = (int)n[0],
            Month = (int)n[1],
            Count = (int)n[2]
        }).ToList();
    
    var finalResult = result
        .Union(
            Enumerable.Range(0, Months - 1).Select(n => new
            {
                Year = startDate.AddMonths(n).Year,
                Month = startDate.AddMonths(n).Month,
                Count = 0
            })
            .Where(n => !result.Any(r => r.Year == n.Year && r.Month == n.Month)))
        .OrderBy(n => n.Year).ThenBy(n => n.Month);
    

    【讨论】:

    • 这看起来更短,但复杂度是 O(n^2) 而我的是 O(n) 并且不需要结果集上的 ToList()
    • @Firo:我的解决方案并不是真正的 O(n^2),因为它不运行多个 SQL 查询。 ToList 对我来说很好,因为我总是控制结果集的大小。
    猜你喜欢
    • 1970-01-01
    • 2011-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    相关资源
    最近更新 更多