如何根据特定的复杂规则计算行数?

时间:2016-05-09 14:23:07

标签: sql sql-server count rows

我有下表:

custid  custname  channelid  channel  dateViewed
--------------------------------------------------------------
1          A       1         ABSS     2016-01-09  
2          B       2         STHHG    2016-01-19 
3          C       4         XGGTS    2016-01-09 
6          D       4         XGGTS    2016-01-09 
2          B       2         STHHG    2016-01-26 
2          B       2         STHHG    2016-01-28 
1          A       3         SSJ      2016-01-28 
1          A       1         ABSS     2016-01-28 
2          B       2         STHHG    2016-02-02
2          B       7         UUJKS    2016-02-10
2          B       8         AKKDC    2016-02-10
2          B       9         GGSK     2016-02-10
2          B       9         GGSK     2016-02-11
2          B       7         UUJKS    2016-02-27

我希望结果如下:

custid  custname  month count  
------------------------------
1          A       1     1
2          B       1     1      
2          B       2     4     
3          C       1     1
6          D       1     1

根据以下规则:

  • 所有频道观看订阅每15天收费一次。如果 客户在15天内查看同一频道,他只会 为该频道收费一次。例如,custid 2,custname B他的计费周期是1月19日 - 2月3日(一个计费周期),2月4日 - 2月20日(一个计费周期),依此类推。因此,自从他在整个计费周期内观看同一频道以来,他只在1月份被收费一次;并且他在2月份收到4次观看(channelid 7,8,9)和2月27日观看的7频道(由于这是另一个结算周期,客户B也在这里收费)。 2月2日,客户B因收看2月19日 - 2月3日的结算周期而被收费,因此不会在2月2日收取费用。
  • 每个月为每位客户生成一张发票,因此, 结果应显示频道的“月份”和“计数” 为每个客户查看。

可以在SQL服务器上完成吗?

5 个答案:

答案 0 :(得分:0)

每当我试图用复杂的标准计算事物时,我都会使用sum和case语句。如下所示:

SELECT custid, custname, 
    SUM(CASE WHEN somecriteria
        THEN 1
        ELSE 0
        END) As CriteriaCount
FROM whateverTable
GROUP BY custid, custname

您可以将somecriteria变量视为一个复杂的语句,只要它返回一个布尔值即可。如果它通过,则该行返回1.如果失败,该行将返回0,然后我们总结返回的值以获得计数。

答案 1 :(得分:0)

  

@Sturgus如果我想在代码中定义它怎么办?任何其他   除了在表中定义它之外的其他选择?如何编写查询   可以每月运行以生成月度发票。 -   星期六15分钟前

嗯,不管怎样,您必须保存每个客户的结算开始日期(最低限度)。如果你想在没有“编辑数据库”的情况下完全在SQL中执行此操作,那么类似下面的内容应该可行。这种方法的缺点是您需要每月手动编辑“INSERT INTO”语句以满足您的需求。如果您被允许编辑已经存在的客户表或创建一个新表,那么它将减少这种手动操作。

DECLARE @CustomerBillingPeriodsTVP AS Table(
   custID int UNIQUE,
   BillingCycleID int,
   BillingStartDate Date,
   BillingEndDate Date
);
INSERT INTO @CustomerBillingPeriodsTVP (custID, BillingCycleID, BillingStartDate, BillingEndDate) VALUES
 (1, 1, '2016-01-03', '2016-01-18'), (2, 1, '2016-01-18', '2016-02-03'), (3, 1, '2016-01-15', '2016-01-30'), (6, 1, '2016-01-14', '2016-01-29');

SELECT A.custid, A.custname, B.BillingCycleID AS [month], COUNT(DISTINCT A.channelid) AS [count]
FROM dbo.tblCustomerChannelViews AS A INNER JOIN @CustomerBillingPeriodsTVP AS B ON A.custid = B.CustID
GROUP BY A.custid, A.custname, B.BillingCycleID;
GO

您从哪里获得客户的结算开始日期?

答案 2 :(得分:0)

通常,您可以从给定日期(本示例中为@dd)获取固定15天间隔的任何数字(本例中为10)。

DECLARE @dd date = CAST('2016-01-19 17:30' AS DATE);

WITH E1(N) AS (
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), 
E4(N) AS (SELECT 1 FROM E2 a, E2 b),  --10,000 rows max
tally(N) AS (SELECT TOP (10) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4)
SELECT
    startd = DATEADD(D,(N-1)*15, @dd),
    endd = DATEADD(D, N*15-1, @dd)    
FROM tally

使其适应定义必须如何为用户(可能是chanel)计算开始日期的规则。

答案 3 :(得分:0)

我不确定这个解决方案会如何扩展 - 但是如果有一些优秀的索引候选人和体面的数据管理,它会起作用..

您需要为初学者提供一些额外的信息,并规范您的数据。您需要知道每个客户的第一个充电期开始日期。所以将它存储在客户表中。

以下是我使用的表格:

create table #channelViews
(
    custId int, channelId int, viewDate datetime
)
create table #channel 
(
    channelId int, channelName varchar(max)
)
create table #customer 
(
    custId int, custname varchar(max), chargingStartDate datetime
)

我会填充一些数据。我不会得到与您的示例输出相同的结果,因为我没有为每个客户提供适当的开始日期。客户2可以。但

insert into #channel (channelId, channelName)
select 1, 'ABSS'
union select 2, 'STHHG'
union select 4, 'XGGTS'
union select 3, 'SSJ'
union select 7, 'UUJKS'
union select 8, 'AKKDC'
union select 9, 'GGSK'

insert into #customer (custId, custname, chargingStartDate)
select 1, 'A', '4 Jan 2016'
union select 2, 'B', '19 Jan 2016'
union select 3, 'C', '5 Jan 2016'
union select 6, 'D', '5 Jan 2016'

insert into #channelViews (custId, channelId, viewDate)
select 1,1,'2016-01-09'  
union select 2,2,'2016-01-19' 
union select 3,4,'2016-01-09' 
union select 6,4,'2016-01-09' 
union select 2,2,'2016-01-26' 
union select 2,2,'2016-01-28' 
union select 1,3,'2016-01-28' 
union select 1,1,'2016-01-28' 
union select 2,2,'2016-02-02'
union select 2,7,'2016-02-10'
union select 2,8,'2016-02-10'
union select 2,9,'2016-02-10'
union select 2,9,'2016-02-11'
union select 2,7,'2016-02-27'

这是一个有点不自由的查询,在一个语句中。 两个底层子查询实际上是相同的数据,因此可能有更合适/有效的方法来生成这些。

我们需要在上一个月的同一充电时段C内从任何收费渠道中扣除费用。这是加入的本质。我使用了右连接,以便我可以从结果中排除所有这些匹配(使用old.custId is null)。

select c.custId, c.[custname], [month], count(*) [count] from 
(
    select new.custId, new.channelId, new.month, new.chargingPeriod
    from 
        (
        select distinct cv.custId, cv.channelId, month(viewdate) [month], (convert(int, cv.viewDate) - convert(int, c.chargingStartDate))/15 chargingPeriod
        from #channelViews cv join #customer c on cv.custId = c.custId
        ) old
    right join 
        (
        select distinct cv.custId, cv.channelId, month(viewdate) [month], (convert(int, cv.viewDate) - convert(int, c.chargingStartDate))/15 chargingPeriod
        from #channelViews cv join #customer c on cv.custId = c.custId
        )  new
    on old.custId = new.custId 
        and old.channelId = new.channelId 
        and old.month = new.Month -1
        and old.chargingPeriod = new.chargingPeriod

    where old.custId is null
    group by new.custId, new.month, new.chargingPeriod, new.channelId

) filteredResults
join #customer c on c.custId = filteredResults.custId
group by c.custId, [month], c.custname
order by c.custId, [month], c.custname

最后我的结果:

custId custname month count
1      A        1     3
2      B        1     1
2      B        2     4
3      C        1     1
6      D        1     1

此查询执行相同的操作:

select c.custId, c.custname, [month], count(*) from 
(
    select cv.custId, min(month(viewdate)) [month], cv.channelId
    from #channelViews cv join #customer c on cv.custId = c.custId
    group by cv.custId, cv.channelId, (convert(int, cv.viewDate) - convert(int, c.chargingStartDate))/15
) x
join #customer c 
 on c.custId = x.custId
group by c.custId, c.custname, x.[month]
order by custId, [month]

答案 4 :(得分:0)

.ageSelectImg {
  height: 25px;
  width: 25px;
}

输出:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav-justified">
  <li>
    <label>
      <input class="ageSelect" type="radio" id="timeSpan" name="timeSpan" value="0" />
      <img class="ageSelectImg" src="https://www.windmobile.ca/images/default-source/icons/icons---koray/power-on-off.jpg" />
      <br />0-2år</label>
  </li>
  <li>
    <label>
      <input class="ageSelect" type="radio" id="timeSpan" name="timeSpan" value="2" />
      <img class="ageSelectImg" src="https://www.windmobile.ca/images/default-source/icons/icons---koray/power-on-off.jpg" />
      <br />2-10 år</label>
  </li>
  <li>
    <label>
      <input class="ageSelect" type="radio" id="timeSpan" name="timeSpan" value="10" />
      <img class="ageSelectImg" src="https://www.windmobile.ca/images/default-source/icons/icons---koray/power-on-off.jpg" />
      <br />10+ år</label>
  </li>
</ul>

我不知道为什么您在;WITH cte AS ( SELECT custid, custname, channelid, channel, dateViewed, CAST(DATEADD(day,15,dateViewed) as date) as dateEnd, ROW_NUMBER() OVER (PARTITION BY custid, channelid ORDER BY dateViewed) AS rn FROM (VALUES (1, 'A', 1, 'ABSS', '2016-01-09'),(2, 'B', 2, 'STHHG', '2016-01-19'), (3, 'C', 4, 'XGGTS', '2016-01-09'),(6, 'D', 4, 'XGGTS', '2016-01-09'), (2, 'B', 2, 'STHHG', '2016-01-26'),(2, 'B', 2, 'STHHG', '2016-01-28'), (1, 'A', 3, 'SSJ', '2016-01-28'),(1, 'A', 1, 'ABSS', '2016-01-28'), (2, 'B', 2, 'STHHG', '2016-02-02'),(2, 'B', 7, 'UUJKS', '2016-02-10'), (2, 'B', 8, 'AKKDC', '2016-02-10'),(2, 'B', 9, 'GGSK', '2016-02-10'), (2, 'B', 9, 'GGSK', '2016-02-11'),(2, 'B', 7, 'UUJKS', '2016-02-27') ) as t(custid, custname, channelid, channel, dateViewed) ), res AS ( SELECT custid, channelid, dateViewed, dateEnd, 1 as Lev FROM cte WHERE rn = 1 UNION ALL SELECT c.custid, c.channelid, c.dateViewed, c.dateEnd, lev + 1 FROM res r INNER JOIN cte c ON c.dateViewed > r.dateEnd and c.custid = r.custid and c.channelid = r.channelid ), final AS ( SELECT * , ROW_NUMBER() OVER (PARTITION BY custid, channelid, lev ORDER BY dateViewed) rn, DENSE_RANK() OVER (ORDER BY custid, channelid, dateEnd) dr FROM res ) SELECT b.custid, b.custname, MONTH(f.dateViewed) as [month], COUNT(distinct dr) as [count] FROM cte b LEFT JOIN final f ON b.channelid = f.channelid and b.custid = f.custid and b.dateViewed between f.dateViewed and f.dateEnd WHERE f.rn = 1 GROUP BY b.custid, b.custname, MONTH(f.dateViewed) 字段中获取custid custname month count ----------- -------- ----------- ----------- 1 A 1 3 2 B 1 1 2 B 2 4 3 C 1 1 6 D 1 1 (5 row(s) affected) 客户1。他得到了:

count

所以在1月份必须有A