I've been doing some testing of the other solutions in the article and comments (still have some to go, including another from Peso that might beat the rest).
Peso's version in the body of the article is probably the most clever/elegant to my eyes so far, but is beaten on performance by a couple versions in the comments.
His own #6 solution is significantly faster percentage-wise, and dova's query is faster still, by a narrow margin.
dova's isn't quite to spec, though, since his query only works by getting the MIN and MAX dt from DailyVisits and using that. It can be modified to accept variables for start and end, but that prevents its most efficient query plan, and it then becomes very, very slow.
Just to make it more accessible, here are Peso's solutions from the body of the article and the comments (just the #6), with some formatting applied:to remove all the white space and sprawl in the former case, and to provide any formatting at all in the latter case:
I also have the variables set to the range used in Steve's test data, not the test harness they used to test their solutions, as an FYI. Further, do note that these solutions require the GetDates function they define in the article (just a cascading CTE tally to generate all the dates in the range).
DECLARE
@from AS DATE = '20110101',
@to AS DATE = '20161231';
WITH cteSource(dt, NumVisits, Added, Removed)
AS
(
SELECT dt,
SUM(NumVisits) AS NumVisits,
SUM(Added) AS Added,
SUM(Removed) AS Removed
FROM (
SELECT DATEADD(DAY, f.DayDelta, w.dt) AS dt,
w.Visitor,
f.NumVisits,
f.Added,
f.Removed
FROM dbo.DailyVisits AS w
CROSS JOIN
(
VALUES (0, 1, 1, -1),
(1, 0, -1, 1)
) AS f(DayDelta, NumVisits, Added, Removed)
WHERE w.dt BETWEEN DATEADD(DAY, -1, @From) AND @To
) AS d
WHERE dt BETWEEN @From AND @To
GROUP BY dt,
Visitor
)
SELECT dt,
SUM(NumVisits) AS NumVisits,
SUM(Added) AS Added,
SUM(Removed) AS Removed,
SUM(Remained) AS Remained
FROM (
SELECT dt,
SUM(NumVisits) AS NumVisits,
SUM(CASE WHEN Added = 1 THEN 1 ELSE 0 END) AS Added,
SUM(CASE WHEN Removed = 1 THEN 1 ELSE 0 END) AS Removed,
SUM(CASE WHEN 1 IN (Added, Removed) THEN 0 ELSE 1 END) AS Remained
FROM cteSource
GROUP BY dt
UNION ALL
SELECT dt,
0 AS NumVisits,
0 AS Added,
0 AS Removed,
0 AS Remained
FROM dbo.GetDates(@From, @To)
) AS d
GROUP BY dt;
And the #6:
DECLARE
@from AS DATE = '20110101',
@to AS DATE = '20161231';
WITH cte AS
(
SELECT f.dt,
f.NumVisits,
f.Visitor,
f.Added,
f.Removed,
f.Remained
FROM dbo.GetDates(@From, @To) AS gd
OUTER APPLY
(
SELECT
q.dt,
q.Visitor,
SUM(q.NumVisits) AS NumVisits,
CASE WHEN SUM(q.Added) > 0 THEN 1 ELSE 0 END AS Added,
CASE WHEN SUM(q.Removed) > 0 THEN 1 ELSE 0 END AS Removed,
CASE WHEN COUNT(*) = 2 THEN 1 ELSE 0 END AS Remained
FROM
(
SELECT gd.dt,
d.Visitor,
1 - d.theDiff AS NumVisits,
1 - 2 * d.theDiff AS Added,
2 * d.theDiff - 1 AS Removed
FROM (
SELECT dv.Visitor,
CASE WHEN dv.dt = gd.dt THEN 0 ELSE 1 END AS theDiff
FROM dbo.DailyVisits AS dv
WHERE dv.dt BETWEEN DATEADD(DAY, -1, gd.dt) AND gd.dt
) AS d
) AS q
GROUP BY q.dt,
q.Visitor
) AS f
)
SELECT dt,
SUM(NumVisits) AS NumVisits,
SUM(Added) AS Added,
SUM(Removed) AS Removed,
SUM(Remained) AS Remained
FROM cte
GROUP BY dt;
Cheers!