【问题标题】:calculate sum for values in SQL for display per month name计算 SQL 中值的总和以显示每月名称
【发布时间】:2011-08-25 15:22:24
【问题描述】:

我有一个布局如下的表格。

Email Blast Table

EmailBlastId |  FrequencyId | UserId
---------------------------------
1            |   5          |   1
2            |   2          |   1
3            |   4          |   1


Frequency Table

Id | Frequency 
------------
 1 |  Daily
 2 |  Weekly
 3 |  Monthly
 4 |  Quarterly
 5 |  Bi-weekly

我需要在我的 asp.net 页面上提供一个网格显示,如下所示。

Email blasts per month.

UserId | Jan | Feb | Mar | Apr |..... Dec | Cumulative
-----------------------------------------------------
1        7      6     6     7          6     #xx

我能想到的唯一方法如下,每个月都有一个案例声明。

select SUM(
        CASE WHEN FrequencyId = 1 THEN 31 
        WHEN FrequencyId = 2 THEN 4
        WHEN FrequencyId = 3 THEN 1
        WHEN FrequencyId = 4 THEN 1
        WHEN FrequencyId = 5 THEN 2 END) AS Jan, 
      SUM(
        CASE WHEN FrequencyId = 1 THEN 28 (29 - leap year)
        WHEN FrequencyId = 2 THEN 4
        WHEN FrequencyId = 3 THEN 1
        WHEN FrequencyId = 4 THEN 0
        WHEN FrequencyId = 5 THEN 2 END) AS Feb, etc etc
FROM EmailBlast 
Group BY UserId

还有其他更好的方法来达到同样的效果吗?

【问题讨论】:

  • 您确定显示的数据正确吗?仅仅因为某些东西被配置为在一段时间内发出特定时间并不意味着它确实如此。我可能对你在做什么是错误的,但我认为最好计算每月实际完成的爆炸次数......
  • 你的最后一个案例不应该是 WHEN FrequencyId = 5 THEN 2
  • @chris,这些爆炸实际上是可以安排的。显示屏显示电子邮件将以当前频率发送多少次。相信我,这是一个要求,所以没有必要去看看这是对还是错。去过那里,做到了。
  • @Lost,你是对的。问题已更新。
  • @Alex J:只是确保。有时很难说。 ;)

标签: asp.net sql sql-server-2008


【解决方案1】:

我认为你最终会得到更复杂的逻辑。当然一月有 31 天……但二月没有……二月会根据年份而变化。接下来,是在周末和节假日发送电子邮件,还是由于各种原因跳过某些日子...如果是这种情况,那么给定月份的工作日数每年都会发生变化。

接下来,给定月份的完整周数也会逐年变化。这额外的 4 个半周会发生什么?他们是在当前还是下个月继续?你用什么方法来确定?举个例子说明这个问题有多复杂:http://en.wikipedia.org/wiki/ISO_week_date 特别是它谈论第一周的部分,实际上有 9 个不同的定义。

我通常不会这么说,但您最好使用常规代码而不是 sql 查询来编写它。只需发出 'select * from emailblast where userid = xxx' 并使用各种代码方法对其进行转换。

【讨论】:

  • 老实说,我在这里也不知所措。也许最好在代码中做到这一点,但是如何,它不会有同样的问题。刚看的时候没想到会这么复杂。
  • @Alex J:是的,你也有同样的问题。只是更好/更快的工具来处理它。
【解决方案2】:

取决于您要查找的内容。建议 1 是跟踪您的实际电子邮件爆炸(带有日期:-)。

如果没有实际的日期,你一个月想出的每个月都是一样的。

无论如何,如果您要进行概括,那么我建议您使用整数以外的东西——比如浮点数或小数。由于您基于帖子中列出的表格的输出只能近似实际发生的情况(例如,1 月实际上有 4-1/2 周,而不是 4 周),因此您在任何月份范围内都会有一个复合误差范围 - - 越来越糟,你推断得越远。例如,如果您输出整整 12 个月,您的推断将被低估超过 4 周。

如果您使用浮点数或小数,那么您将能够更接近实际发生的情况。对于初学者:找到一个通用的计量单位(我建议使用“天”)例如,1 个月 = 365/12 天; 1 个季度 = 365/4 天; 1 2 周 = 14 天;等等

如果您这样做,那么您每季度拥有 1 个 1 的用户实际上每 91.25 天拥有 1 个;每周 1 次变为每 7 天 1 次;每双周 1 次变成每 14 天 1 次。

**EDIT** -- Incidentally, you could store the per-day value in your reference table, so you didn't have to calculate it each time.  For example:
Frequency Table

Id | Frequency         | Value
-------------------------------
 1 |  Daily            | 1.0
 2 |  Weekly           | .14286
 3 |  Monthly          | .03288
 4 |  Quarterly        | .01096
 5 |  Once in 2 weeks  | .07143

现在做数学 - (1/91.25 + 1/7 + 1/14) 需要一个共同的面额(可能是 91.25 * 14),所以它变成了 (14/1277.5 + 182.5/1277.5 + 91.25/1277.5)。

每天总计 287.75/1277.5 或 0.225 封电子邮件。

由于每个月有 365/12 天,多个 .225 * (365/12) 可每月收到 6.85 封电子邮件。

您的输出将如下所示:

Email blasts per month.

UserId | Jan | Feb | Mar | Apr |..... Dec | Cumulative
-----------------------------------------------------
1        6.85  6.85  6.85  6.85     6.85     #xx

数学运算可能看起来有些乏味,但一旦您在代码中使用它,就再也不必重复了。你的结果会更准确(我四舍五入到小数点后两位,但如果你愿意,你可以走得更远)。如果您的公司正在使用这些数据来确定来年的预算/潜在收入,那可能是值得的。

还值得一提的是,在您完成外推(以及由此产生的误差范围)之后,您的此输出的消费者将自己进行外推,而不是在原始数据上,而是在您的输出上。所以这是错误界限的双重打击。您在早期越准确,这些数字在每个后续级别中的可靠性就越高。

【讨论】:

  • 为什么要使用FLOAT 而不是DECIMAL?我认为你不需要那种程度的近似。 :-)
  • @Aaron -- 好点 -- 我想如果你需要将计算值转换回频率,小数会​​更好。但无论哪种方式......只要它不是一个 int :-)。
【解决方案3】:

这是针对任何给定年份的吗?我假设你想要今年的时间表。如果您想要未来的一年,您可以随时更改 DECLARE @now 以指定任何未来的日期。

“两周一次”(通常称为“双周一次”)不太适合每月存储(非闰年的 2 月除外)。是否应该将其更改为“每月两次”?

另外,为什么不将系数存储在频率表中,添加一个名为“PerMonth”的列?那么您只需要处理每日和每季度的案例(仅在 1 月、4 月等发生这种情况是否是任意选择?)。

假设其中一些是灵活的,这就是我的建议,假设对表架构进行了这个非常小的更改:

USE tempdb;
GO

CREATE TABLE dbo.Frequency 
(
    Id INT PRIMARY KEY,
    Frequency VARCHAR(32),
    PerMonth TINYINT
);

CREATE TABLE dbo.EmailBlast 
(
    Id INT,
    FrequencyId INT,
    UserId INT
);

还有这个样本数据:

INSERT dbo.Frequency(Id, Frequency, PerMonth)
  SELECT 1, 'Daily', NULL
  UNION ALL SELECT 2, 'Weekly', 4
  UNION ALL SELECT 3, 'Monthly', 1
  UNION ALL SELECT 4, 'Quarterly', NULL
  UNION ALL SELECT 5, 'Twice a month', 2;

INSERT dbo.EmailBlast(Id, FrequencyId, UserId)
  SELECT 1, 5, 1
  UNION ALL SELECT 2, 2, 1
  UNION ALL SELECT 3, 4, 1;

我们可以使用非常复杂的查询来完成此操作(但我们不必对这些月份数字进行硬编码):

DECLARE @now DATE = CURRENT_TIMESTAMP;
DECLARE @Jan1 DATE = DATEADD(MONTH, 1-MONTH(@now), DATEADD(DAY, 1-DAY(@now), @now));

WITH n(m) AS 
(
    SELECT TOP 12 m = number
        FROM master.dbo.spt_values
        WHERE number > 0 GROUP BY number
),
months(MNum, MName, StartDate, NumDays) AS
(    SELECT m, mn = CONVERT(CHAR(3), DATENAME(MONTH, DATEADD(MONTH, m-1, @Jan1))),
        DATEADD(MONTH, m-1, @Jan1), 
        DATEDIFF(DAY, DATEADD(MONTH, m-1, @Jan1), DATEADD(MONTH, m, @Jan1))
    FROM n
),
grp AS
(
    SELECT UserId, MName, c = SUM (
        CASE x.Id WHEN 1 THEN NumDays
            WHEN 4 THEN CASE WHEN MNum % 3 = 1 THEN 1 ELSE 0 END
            ELSE x.PerMonth END )
    FROM months CROSS JOIN (SELECT e.UserId, f.* 
        FROM EmailBlast AS e 
        INNER JOIN Frequency AS f
        ON e.FrequencyId = f.Id) AS x
    GROUP BY UserId, MName
),
cumulative(UserId, total) AS
(
    SELECT UserId, SUM(c)
      FROM grp GROUP BY UserID
),
pivoted AS
(
    SELECT * FROM (SELECT UserId, c, MName FROM grp) AS grp 
    PIVOT(MAX(c) FOR MName IN (
        [Jan],[Feb],[Mar],[Apr],[May],[Jun],[Jul],[Aug],[Sep],[Oct],[Nov],[Dec])
    ) AS pvt
)
SELECT p.*, c.total 
    FROM pivoted AS p
    LEFT OUTER JOIN cumulative AS c
    ON p.UserId = c.UserId;

结果:

UserId  Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec total
1       7   6   6   7   6   6   7   6   6   7   6   6   76

清理:

DROP TABLE dbo.EmailBlast, dbo.Frequency;
GO

事实上,我建议的架构更改并没有给你带来太多好处,它只是在grp CTE 中为你节省了两个额外的CASE 分支。总体来说是花生。

【讨论】:

  • 我的脑子里充满了我需要投入的所有想法:).. 谢谢,我今天晚些时候要休息一下。
  • 我现在要接受这个答案。它看起来是最接近的解决方案。尽管我将尝试将要求更改为估计的数字,但可能不是 100% 准确。不错的 sql,包含我没见过的大多数东西。
  • +1。我同意这是获得估计的好方法。只要每个人都可以接受它,你就应该很好。虽然我可能误读了这个,但它似乎完全忽略了季度。
  • @Chris 季刊在那里。看看 1 月、4 月、7 月和 10 月有 7 个吗?那几个月的额外邮件是按季度发送的(请参阅我检查MNum % 3 = 1的案例部分)。
【解决方案4】:

您可能需要考虑添加一个名为 Schedule 之类的第三个表。

你可以这样组织它:

MONTH_NAME
DAILY_COUNT
WEEKLY_COUNT
MONTHLY_COUNT
QUARTERLY_COUNT
BIWEEKLY_COUNT

JAN 的记录是

JAN
31
4
1
1
2

或者你可以这样构造它:

MONTH_NAME 
FREQUENCY_ID 
EMAIL_COUNT

并且每个月都有多条记录:

JAN   1   31
JAN   2    4
JAN   3    1
JAN   4    1
JAN   5    2

我让你弄清楚检索它的逻辑是否比你的 CASE 结构更好。

【讨论】:

  • 您需要为每年计算 2 月,以及从技术上讲,每年每个月的周数。
  • 是的。大量维护基于日历的计数变化,但最好在表格中而不是代码中执行此操作。
  • 好吧,代码总是可以告诉它是哪一年,或者如果你不希望它基于当年的二月计数,你可以告诉它使用哪一年。
猜你喜欢
  • 2021-07-17
  • 2017-06-18
  • 2018-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多