2016-05-09 3 views
0

Ich habe die folgende Tabelle:Wie COUNT Zeilen nach bestimmten komplizierten Regeln?

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 

Und ich die Ergebnisse sein wollen:

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

Nach den folgenden Regeln:

  • Alle Kanalaufrufe Abonnement alle 15 in Rechnung gestellt wird Tage. Wenn der Kunde den gleichen Kanal innerhalb der 15 Tage angesehen hat, wird er nur für diesen Kanal abgerechnet. Zum Beispiel ist custid 2, custname B sein Abrechnungszyklus vom 19. Januar bis 3. Februar (ein Abrechnungszyklus), vom 4. Februar bis zum 20. Februar (ein Abrechnungszyklus) und so weiter. Daher wird er nur einmal im Januar abgerechnet, da er während des Abrechnungszyklus den gleichen Kanal sieht; und er wird 4 mal im Februar zum Anschauen (channelid 7, 8, 9) und channelid 7 am 27. Februar (da dies in einem anderen Abrechnungszyklus fällt, wird auch hier Kunde B berechnet). Kunde B wird am 2. Februar für das Ansehen von Kanal 2 nicht belastet, da er bereits vom 19. Januar bis 3. Februar abgerechnet wurde.
  • Für jeden Kunden wird jeden Monat eine Rechnung erstellt. Daher sollten die Ergebnisse die Anzahl der Monate " " anzeigen, die für jeden Kunden angezeigt werden.

Kann dies in SQL Server getan werden?

+0

Keine Daten für 2015 in der Tabelle? – jarlh

+0

Wie lauten die Regeln für den Abrechnungszyklus? sind sie auf festen Daten oder ändern sie sich? –

+0

Es kann getan werden, aber die Abrechnungszyklen müssen irgendwo definiert werden (entweder in Code oder in einer Tabelle). Gibt es für jeden Kunden eindeutige Zeiträume oder sind die Abrechnungszyklen für alle gleich? – Sturgus

Antwort

0
;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) 

Ausgang:

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) 

Ich weiß nicht, warum Sie 1 in count Feld für Kunden A erhalten. Er bekam:

ABSS  2016-01-09 +1 to count (+15 days = 2016-01-24) 
SSJ  2016-01-28 +1 to count 
ABSS  2016-01-28 +1 to count (28-01 > 24.01) 

So im Januar muss count = 3 sein.

+0

ja, du hast recht, der januar sollte = 3 sein – saturday

0

Immer, wenn ich versuche, Dinge mit komplexen Kriterien zu zählen, verwende ich eine Summe und eine case-Anweisung. So etwas wie unten:

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

können Sie machen, dass somecriteria Variable als eine Erklärung kompliziert, wie Sie möchten, solange es einen Booleschen Wert zurück. Wenn es übergeben wird, gibt diese Zeile eine 1 zurück. Wenn es fehlschlägt, wird die Zeile wieder eine 0 zurückgegeben, dann addieren wir die zurückgegebenen Werte, um die Anzahl zu erhalten.

0

@Sturgus was ist, wenn ich es im Code definieren möchte? Irgendwelche anderen Alternativen neben der Definition in der Tabelle? Wie schreibe ich eine Abfrage , die jeden Monat ausgeführt werden kann, um die monatliche Rechnung zu generieren. - Samstag 15 Minuten vor

Nun, eine oder andere Weise, werden Sie jeden Kunden die Abrechnung Startdatum speichern müssen (minimal). Wenn Sie dies vollständig in SQL ohne "Bearbeiten der Datenbank" tun möchten, sollte etwas wie das Folgende funktionieren. Der Nachteil dieses Ansatzes besteht darin, dass Sie die Anweisung "INSERT INTO" jeden Monat manuell bearbeiten müssen, um sie Ihren Anforderungen anzupassen. Wenn Sie die bereits vorhandene Kundentabelle bearbeiten oder eine neue erstellen könnten, würde dies den manuellen Aufwand reduzieren.

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 

Woher bekommen Sie die Abrechnungsdaten Ihrer Kunden, wie sie sind?

0

Im Allgemeinen erhalten Sie eine beliebige Anzahl (10 in diesem Beispiel) mit festen 15-Tage-Intervallen ab dem angegebenen Datum (@dd in diesem Beispiel).

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 

es Passen Sie die Regeln zu definieren, wie das Startdatum für den Benutzer berechnet werden müssen (und wahrscheinlich chanel).

0

Ich bin nicht sicher, wie diese Lösung skaliert - aber mit einigen guten Indexkandidaten und menschenwürdige Daten Housekeeping, wird es funktionieren ..

Sie werden einige zusätzliche Informationen für den Anfang brauchen, und normalisieren Sie Ihre Daten. Sie müssen das Startdatum der ersten Ladeperiode für jeden Kunden kennen. Speichern Sie das also in einer Kundentabelle.

Hier sind die Tabellen I verwendet:

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 
) 

ich einige Daten füllen werde. Ich erhalte nicht die gleichen Ergebnisse wie Ihre Beispielausgabe, da ich für jeden Kunden nicht die entsprechenden Starttermine habe. Kunde 2 wird jedoch OK sein.

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' 

Und hier ist die etwas unleidy Abfrage, in einer einzigen Aussage. Die beiden zugrunde liegenden Unterabfragen sind tatsächlich die gleichen Daten, daher könnte es geeignetere/effizientere Möglichkeiten geben, diese zu generieren.

Wir müssen jeden Kanal, der in derselben Ladeperiode C für den vorherigen Monat berechnet wurde, von der Abrechnung ausschließen. Dies ist die Essenz des Beitritts. Ich habe einen Right-Join verwendet, so dass ich alle derartigen Übereinstimmungen von den Ergebnissen ausschließen konnte (unter Verwendung von 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 

Und schließlich meine Ergebnisse:

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

Diese Abfrage macht das Gleiche:

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] 
+0

Was für ein Unterschied eine gute Nachtruhe macht. Habe eine übersichtlichere Abfrage hinzugefügt, die die gleichen Ergebnisse liefert. – Brett