我有一张包含以下布局的表格。
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
还有其他更好的方法吗?
答案 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
分支。总的来说,花生。