计算SQL中值的总和,以显示每月名称

时间:2011-08-25 15:22:24

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

我有一张包含以下布局的表格。

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

还有其他更好的方法吗?

4 个答案:

答案 0 :(得分:3)

我认为你最终会得到更复杂的逻辑。当然Jan有31天..但是2月没有...而且2月会根据年份变化。接下来,即使在周末和节假日也会发送电子邮件群发,或者由于各种原因而跳过某些天......如果是这样的话,那么某个月的工作日数会每年发生变化。

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

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

答案 1 :(得分:3)

取决于你在寻找什么。建议1将跟踪您的实际电子邮件群发(日期:-)。

如果没有实际日期,无论你想出一个月,每个月都会是相同的。

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

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

如果你这样做 - 那么每季度一个1的用户实际上每91.25天有1个;每周1次变为每7天1次;每BiWeek 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)需要一个普通的denom(比如91.25 * 14),所以它变成了(14 / 1277.5 + 182.5 / 1277.5 + 91.25 / 1277.5)。

每天增加287.75 / 1277.5或.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

数学可能看起来有点单调乏味,但是一旦你把代码放在代码上,你就再也不用了。你的结果会更准确(我四舍五入到小数点后两位,但如果你愿意,可以更进一步)。如果贵公司使用这些数据来确定即将到来的一年的预算/潜在收入,那可能是值得的。

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

答案 2 :(得分:2)

您可能需要考虑添加名为Schedule的第3个表。

你可以像这样构建它:

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结构。

答案 3 :(得分:2)

这是否适用于任何一年?我假设你想要当年的时间表。如果您想要未来的一年,您可以随时更改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

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