【问题标题】:Count Response once in 30 days SQL每 30 天统计一次响应 SQL
【发布时间】:2013-07-10 20:08:25
【问题描述】:

如果我有客户在 30 天内多次回复同一项调查,我只想计算一次。有人可以给我看一下代码吗?

创建表#Something ( 客户 ID Char(10), SurveyId char(5), 响应日期日期时间 ) 插入#某事 选择“Cust1”、“100”、“5/6/13”联合所有 选择“Cust1”、“100”、“5/13/13”联合所有 选择“Cust2”、“100”、“13 年 4 月 20 日”联合所有 选择“Cust2”、“100”、“13 年 5 月 22 日” 从#Something 中选择不同的 custid、SurveyId、Count(custid) 作为 CountResponse 按客户 ID、SurveyId 分组

上面的代码只给了我响应的总计数,不知道如何编码每 30 天只计数一次。

我要找的输出应该是这样的:

CustomerID SurveyId CountResponse 客户1 100 1 客户 2 100 2

【问题讨论】:

  • “30 天期限”是指滚动的 30 天窗口,还是在同一个月内?
  • 这听起来像是滚动 30 天的窗口,这使得解决关系变得极其困难。
  • 这是一个很难以关系方式解决的问题,因为必须维护有关哪些调查已被计算在内的状态。不过,这是一个有趣且具有挑战性的问题,您应该得到更多的赞成票。

标签: sql sql-server-2008-r2


【解决方案1】:

根据您希望从首次提交调查开始计算您的月经周期为 30 天的理论,这是一个(总)解决方案。

declare @Something table
(
    CustID Char(10),
    SurveyId char(5),
    ResponseDate datetime
)

insert @Something
select 'Cust1', '100', '5/6/13' union all
select 'Cust1', '100', '5/13/13' union all
select 'Cust1', '100', '7/13/13' union all
select 'Cust2', '100', '4/20/13' union all
select 'Cust2', '100', '5/22/13' union all
select 'Cust2', '100', '7/20/13' union all
select 'Cust2', '100', '7/24/13' union all
select 'Cust2', '100', '9/28/13' 

--SELECT CustID,SurveyId,COUNT(*) FROM (

select a.CustID,a.SurveyId,b.ResponseStart,--CONVERT(int,a.ResponseDate-b.ResponseStart),
CASE 
    WHEN CONVERT(int,a.ResponseDate-b.ResponseStart) > 30 
    THEN ((CONVERT(int,a.ResponseDate-b.ResponseStart))-(CONVERT(int,a.ResponseDate-b.ResponseStart) % 30))/30+1
    ELSE 1
END CustomPeriod -- defines periods 30 days out from first entry of survey
from @Something a
inner join
(select CustID,SurveyId,MIN(ResponseDate) ResponseStart
from @Something
group by CustID,SurveyId) b
on a.SurveyId=b.SurveyId
and a.CustID=b.CustID
group by a.CustID,a.SurveyId,b.ResponseStart,
CASE 
    WHEN CONVERT(int,a.ResponseDate-b.ResponseStart) > 30 
    THEN ((CONVERT(int,a.ResponseDate-b.ResponseStart))-(CONVERT(int,a.ResponseDate-b.ResponseStart) % 30))/30+1
    ELSE 1
END

--) x GROUP BY CustID,SurveyId

至少您可能想让 CASE 语句成为一个函数,这样它的读起来会更简洁一些。最好在单独的表中定义显式窗口。如果您想避免在第一个周期结束时返回调查,几天后在第二个周期返回另一个调查这样的情况,这可能不可行。

如果可能,您应该考虑在输入时处理此问题。例如,如果您要在在线调查中识别客户,请拒绝填写调查的尝试。或者如果有人邮寄这些,如果有人在 30 天内寄来,让数据输入人员拒绝它。

或者,与“wild and crazy”一样,添加一个位和一个 INSERT 触发器。仅当在该时间段内没有为该客户找到该类型的调查时才打开该位。

总体而言,更完整地表述问题会有所帮助。不过,我确实很欣赏实际的编码示例。

【讨论】:

  • 我很欣赏您提到的其他选项,但它们不在我的范围内,因为数据来自整个组织的各种大数据存储。所以,不能接触源数据库或输入系统。谢谢,您的代码通过一些测试数据返回了我的预期。我必须为收到的所有回复制作一张表格,然后尝试这个 - 逻辑实际上太复杂了(至少对我来说,因为我认为自己是中级),因为我必须计算一个回复/每个调查/每个客户/每个组/每 30 天!
  • 这是一个大报告的代码的一部分,用户可以选择日期范围,如果他们选择 YTD,那么我必须看到这不会导致性能问题。
【解决方案2】:

我不是 SQL Server 专家,但在 Oacle 中,如果您从“日期”中减去整数值,则实际上是在减去“天”,因此可以使用以下方法:

SELECT custid, surveyid
FROM Something a
WHERE NOT EXISTS (
    SELECT 1
    FROM Something b
    WHERE a.custid = b.custid
    AND a.surveyid = b.surveyid
    AND b.responseDate between a.responseDate AND a.responseDate - 30
    );    

为了得到你的计数(如果我理解你的要求):

-- Count of times custID returned surveyID, not counting same
-- survey within 30 day period.
SELECT custid, surveyid, count(*) countResponse
FROM Something a
WHERE NOT EXISTS (
    SELECT 1
    FROM Something b
    WHERE a.custid = b.custid
    AND a.surveyid = b.surveyid
    AND b.responseDate between a.responseDate AND a.responseDate - 30
    )
GROUP BY custid, surveyid

更新:根据下面提出的情况,这实际上是行不通的。您可能应该做的是遍历您的 something 表并将要保留的调查的行插入到 results 表中,然后与 results 表进行比较以查看是否已经收到了调查您要考虑的最后 30 天。我可以向您展示如何在 oracle PL/SQL 中执行此类操作,但我不知道 SQL Server 的语法。也许知道sql server的其他人想窃取这个策略来为你编写一个答案,或者这足以让你继续。

【讨论】:

  • 这行不通。想象一下一个人在三个月零一天内每天都回复的情况(考虑到二月)。显然,计数应该是三。你的查询会产生什么? 1 个?
【解决方案3】:

称我为疯狂和疯狂,但我会通过在每次调查中存储更多状态来解决这个问题。我将采取的方法是添加一个bit 类型列,指示是否应计算特定调查(即Countable 列)。这解决了状态跟踪问题,该问题固有地解决了这个问题。

如果在前 30 天内找不到具有相同 CustID/SurveyIdCountable 设置为 1 的调查,我会在插入时将 Countable 中的值设置为 1。我会设置它为 0,否则。

然后问题变得很容易解决。只需按CustID/SurveyId 分组,然后将Countable 列中的值相加即可。

这种方法的一个警告是,它强制要求必须按时间顺序添加调查,并且如果不重新计算 Countable 值就不能删除。

【讨论】:

  • 谢谢,我认为这是“tetch”试图在下面编写的代码。在此之后我必须再做一步 - 这将计算他发布的结果集中的行数。我是一个中级 sql 编码器,这对我来说编码非常复杂。
【解决方案4】:

我相信这是处理它的一种方法。我快速测试了,它适用于小样本记录,所以我希望它能帮助你。祝你好运。

SELECT s.CustID, COUNT(s.SurveyID) AS SurveyCount
FROM #something s
INNER JOIN (SELECT CustID, SurveyId, ResponseDate
            FROM (SELECT #Something.*,
            ROW_NUMBER() OVER (PARTITION BY custid ORDER BY ResponseDate ASC) AS RN
            FROM #something) AS t
            WHERE RN = 1 ) f ON s.CustID = f.CustID
WHERE s.ResponseDate BETWEEN f.ResponseDate AND f.ResponseDate+30
GROUP BY s.CustID
HAVING COUNT(s.SurveyID) > 1

【讨论】:

  • 但是对于 CustId 1,这个返回计数为 2。抱歉,这不起作用。
  • 也许我当时理解错了。我以为你想计算那些在 30 天内进行了多次调查的人。 CustID 1 在 30 天内进行了两次调查——一次在 5 月 6 日,一次在 5 月 13 日。 CustID 2 在 30 天内没有多次进行调查,因此我排除了该 ID。即使我误解了您的意图,您也应该能够很容易地将其调整为您需要的内容。它为您提供了一个自动的、滚动的 30 天期限,并以您喜欢的任何方式汇总计数以进行过滤。不管怎样,如果你得到了一个你满意的解决方案,干杯。
【解决方案5】:

你的问题模棱两可,这可能是你困难的根源。

insert #Something values
('Cust3', '100', '1/1/13'),
('Cust3', '100', '1/20/13'),
('Cust3', '100', '2/10/13')

Cust3 的计数应该是 1 还是 2? '2/10/13' 响应是否无效,因为它在 '1/20/13' 响应之后不到 30 天?或者“2/10/13”响应是否有效,因为“1/20/13”已被“1/1/13”响应无效,因此比上一个有效响应晚了 30 多天?

【讨论】:

  • 我与客户交谈过,因为这也是我的困惑。他们想要滚动的 30 天期限 - 因此在您的示例中,计数将为 2,因为 1/20 超出了 1/1 的 30 天期限。如果还有另一行('Cust3'、'100'、'2/27/13'),则不应计算在内,因为它在 2/10 的 30 天内。如果还有另一行('Cust3'、'100'、'3/15/13'),那么这将被计算在内,因为它是 2/10 之后的 30 天。有人可以告诉我如何编码吗?
【解决方案6】:

下面的代码是产生示例输出的一种方法。但是,如果您添加 select 'Cust1', '100', '4/20/13',则结果仍将是 Cust1 100 1,因为它们都在每个先前调查响应的 30 天内,因此只会计算第一个。这是期望的行为吗?

SELECT     CustID, SurveyID, COUNT(*) AS CountResponse
FROM         #SurveysTaken
WHERE     (NOT EXISTS
                      (SELECT     1
                        FROM          #SurveysTaken AS PriorSurveys
                        WHERE      (CustID = #SurveysTaken.CustID)
                               AND (SurveyId = #SurveysTaken.SurveyId)
                               AND (ResponseDate >= DATEADD(d, - 30, #SurveysTaken.ResponseDate))
                               AND (ResponseDate < #SurveysTaken.ResponseDate)))
GROUP BY CustID, SurveyID

或者,您可以将一年分成任意 30 天的时段,并在每个新年重置。

SELECT     CustID, SurveyID, COUNT(*) AS CountResponse
FROM         (SELECT DISTINCT CustID, SurveyID, YEAR(ResponseDate) AS RepsonseYear,
                              DATEPART(DAYOFYEAR, ResponseDate) / 30 AS ThirtyDayPeriod
              FROM          #SurveysTaken) AS SurveysByPeriod
GROUP BY CustID, SurveyID

你也可以按月去。

SELECT     CustID, SurveyID, COUNT(*) AS CountResponse
FROM         (SELECT DISTINCT CustID, SurveyID, YEAR(ResponseDate) AS ResponseYear,
                              MONTH(ResponseDate) AS ResponseMonth
              FROM          #SurveysTaken) AS SurveysByMonth
GROUP BY CustID, SurveyID

您可以使用任意纪元日期的 30 天时间段。 (也许通过从另一个查询中提取首次创建调查的日期?)

SELECT     CustID, SurveyID, COUNT(*) AS CountResponse
FROM         (SELECT DISTINCT CustID, SurveyID, DATEDIFF(D, '1/1/2013', ResponseDate) / 30 AS ThirtyDayPeriod
                       FROM          #SurveysTaken) AS SurveysByPeriod
GROUP BY CustID, SurveyID

任意三十个时期的最后一个变体是基于客户第一次对相关调查做出回应。

SELECT     CustID, SurveyID, COUNT(*) AS CountResponse
FROM         (SELECT DISTINCT CustID, SurveyID, DATEDIFF(DAY,
                                                  (SELECT     MIN(ResponseDate)
                                                    FROM          #SurveysTaken AS FirstSurvey
                                                    WHERE      (CustID = #SurveysTaken.CustID)
                                                           AND (SurveyId = #SurveysTaken.SurveyId)), ResponseDate) / 30 AS ThirtyDayPeriod
                       FROM          #SurveysTaken) AS SurveysByPeriod
GROUP BY CustID, SurveyID

您在使用 epoch/period 技巧时会遇到一个问题,即计数的调查每个周期仅发生一次,但不一定相隔 30 天。

【讨论】:

  • 这些都不能正确处理三十天的滚动窗口,这是提出问题的人想要的。
猜你喜欢
  • 2022-01-18
  • 2020-09-04
  • 1970-01-01
  • 2019-07-18
  • 2015-08-31
  • 1970-01-01
  • 2019-03-21
  • 2019-07-24
  • 2014-08-14
相关资源
最近更新 更多