计算按日期范围拆分的值的先前出现次数

时间:2013-01-26 15:39:26

标签: sql-server-2008-r2 analytic-functions

以下是我们针对营销部门就我们在过去90天内收到的潜在客户提出的临时请求所做的简单查询。

SELECT ID
    ,FIRST_NAME
    ,LAST_NAME
    ,ADDRESS_1
    ,ADDRESS_2
    ,CITY
    ,STATE
    ,ZIP
    ,HOME_PHONE
    ,MOBILE_PHONE
    ,EMAIL_ADDRESS
    ,ROW_ADDED_DTM
FROM WEB_LEADS
WHERE ROW_ADDED_DTM BETWEEN @START AND @END

他们要求添加更多派生列,以显示EMAIL_ADDRESS匹配的ADDRESS_1之前出现的次数。但他们想要的是不同的日期范围。

因此派生列看起来像这样:

,COUNT_ADDRESS_1_LAST_1_DAYS,
,COUNT_ADDRESS_1_LAST_7_DAYS
,COUNT_ADDRESS_1_LAST_14_DAYS
etc.

当只有少数时,我使用update语句手动填充这些派生列。上面的查询实际上只是一个包含更多列的更大查询的示例。实际请求已发展为13列的6个日期范围。我问是否有更好的方法,然后使用78个额外的更新语句。

1 个答案:

答案 0 :(得分:2)

我认为您将很难编写一个查询,其中包含每个电子邮件地址的所有这78个指标,而不会实际创建对不同选项进行硬编码的查询。但是,您可以使用动态SQL生成此类数据透视查询,这将为您节省一些按键,并在您向表中添加更多列时动态调整。

您希望最终得到的结果看起来像这样(但当然您不想输入它):

;WITH y AS
(
  SELECT 
    EMAIL_ADDRESS,

/* aggregation portion */

    [ADDRESS_1] = COUNT(DISTINCT [ADDRESS_1]),
    [ADDRESS_2] = COUNT(DISTINCT [ADDRESS_2]),
    ... other columns

/* end agg portion */

    FROM dbo.WEB_LEADS AS wl 
    WHERE ROW_ADDED_DTM >= /* one of 6 past dates */
    GROUP BY wl.EMAIL_ADDRESS
)
SELECT EMAIL_ADDRESS,

/* pivot portion */

  COUNT_ADDRESS_1_LAST_1_DAYS = *count address 1 from 1 day ago*,
  COUNT_ADDRESS_1_LAST_7_DAYS = *count address 1 from 7 days ago*,
  ... other date ranges ...
  COUNT_ADDRESS_2_LAST_1_DAYS = *count address 2 from 1 day ago*,
  COUNT_ADDRESS_2_LAST_7_DAYS = *count address 2 from 7 days ago*,
  ... other date ranges ...
  ... repeat for 11 more columns ...

/* end pivot portion */
FROM y 
GROUP BY EMAIL_ADDRESS
ORDER BY EMAIL_ADDRESS;

这有点牵扯,它应该全部作为一个脚本运行,但是我将把它分解成块来散布关于如何填充上述部分而不键入它们的注释。 (不久之后@Bluefeet可能会有一个更好的PIVOT替代方案。)我会在/* */中附上我的散布评论,这样你仍然可以将大部分答案复制到Management Studio中并在评论完整的情况下运行它。

要复制的代码/注释如下:


/ * 首先,让我们构建一个日期表,可用于导出旋转标签和协助聚合。我已经添加了你提到的三个范围并在第四个猜测,但希望很清楚如何添加更多: * /

DECLARE @d DATE = SYSDATETIME();

CREATE TABLE #L(label NVARCHAR(15), d DATE);

INSERT #L(label, d) VALUES
(N'LAST_1_DAYS',  DATEADD(DAY,   -1,  @d)),
(N'LAST_7_DAYS',  DATEADD(DAY,   -8,  @d)),
(N'LAST_14_DAYS', DATEADD(DAY,   -15, @d)),
(N'LAST_MONTH',   DATEADD(MONTH, -1,  @d));

/ * 接下来,让我们构建每个列名重复的查询部分。首先,聚合部分的格式为col = COUNT(DISTINCT col)。我们将转到目录视图以动态地派生列名列表(IDEMAIL_ADDRESSROW_ADDED_DTM除外)并将它们填入#temp表中以供重用。 * /

SELECT name INTO #N FROM sys.columns
WHERE [object_id] = OBJECT_ID(N'dbo.WEB_LEADS')
AND name NOT IN (N'ID', N'EMAIL_ADDRESS', N'ROW_ADDED_DTM');

DECLARE @agg NVARCHAR(MAX) = N'', @piv NVARCHAR(MAX) = N'';

SELECT @agg += ',
  ' + QUOTENAME(name) + ' = COUNT(DISTINCT ' 
  + QUOTENAME(name) + ')' FROM #N;

PRINT @agg;

/ * 接下来我们将构建“枢轴”部分(即使我正在寻找穷人的支点 - 一堆CASE表达式)。对于每个列名,我们需要针对每个范围的条件,因此我们可以通过将列名列表与标签表交叉连接来实现此目的。 (我们稍后会在查询中再次使用此精确技术,以使/* one of past 6 dates */部分正常工作。 * /

SELECT @piv += ',
  COUNT_' + n.name + '_' + l.label
  + ' = MAX(CASE WHEN label = N''' + l.label 
  + ''' THEN ' + QUOTENAME(n.name) + ' END)'
FROM #N as n CROSS JOIN #L AS l;

PRINT @piv;

/ * 现在,按照我们喜欢的方式填充这两个部分,我们可以构建一个动态SQL语句来填充其余部分: * /

DECLARE @sql NVARCHAR(MAX) = N';WITH y AS
(
    SELECT 
      EMAIL_ADDRESS, l.label' + @agg + '
      FROM dbo.WEB_LEADS AS wl 
      CROSS JOIN #L AS l
      WHERE wl.ROW_ADDED_DTM >= l.d
      GROUP BY wl.EMAIL_ADDRESS, l.label
)
SELECT EMAIL_ADDRESS' + @piv + '
FROM y 
GROUP BY EMAIL_ADDRESS
ORDER BY EMAIL_ADDRESS;';

PRINT @sql;

EXEC sp_executesql @sql;
GO
DROP TABLE #N, #L;

/ * 再一次,这是一段相当复杂的代码,也许PIVOT可以使代码变得更容易。但我认为即使@Bluefeet也会编写一个使用动态SQL的PIVOT版本,因为在这里硬编码的方法太多了。 * /