Well, the issue is now resolved:
While it would seem logical to use filtered indexes (NOT NULL), to reduce database size and as so many sources on the web say, increase performance, the reality it seems is something else entirely.
In layman's terms, SQL Server query planner resolves even your basic inner joins without making any assumptions as to the content of the columns. Even though NULL values do not form a join, they must be included in the column index in order for query planner to use it, unless otherwise specified with predicates such as WHERE joinCol_ID IS NOT NULL. Basically, SQL Server does not use filtered indexes for joins at all, unless the queries themselves are modified to account for the filter value. Instead, it will create new statistics on these columns and / or use a clustered index scan or other indexes including the column, whichever it deems most effective. Using filtered indexes on foreign keys is therefore an absolutely horrid idea.
We still have no idea how months worth of testing this in multiple other environments never produced the same results outside of this one, single DB, but this is the way it's supposed to work. Apparently something that as far as we know is not related to cache, statistics or configurations, caused the production DB to behave differently and correctly detect and use the filtered indexes, while all of the testing environments simply used the old indexes (seeing as the indexes were dropped and recreated with the same name, this seems a valid theory even if there is no real proof).
So the lesson of the story: The web is filled with examples of how underused filtered indexes are, how awesome they can be. But this serious downside never popped up except as a nagging thought in the back of my head saying "if these are so great, then why aren't NULL values filtered out of indexes by default, since they only take up space and only serve a purpose in special circumstances"? Well, now I know why. :)
How about an indexed view?
CREATE VIEW dbo.LoginsByDate
WITH SCHEMABINDING
AS
SELECT
UserID,
LoginDate = CONVERT(DATE, LoginDate),
LoginCount = COUNT_BIG(*)
FROM dbo.LoginTable
GROUP BY UserID, CONVERT(DATE, LoginDate);
GO
CREATE UNIQUE CLUSTERED INDEX IX_LoginsByDate
ON dbo.LoginsByDate(UserID, LoginDate);
GO
What this does is pre-aggregate the login data for you (you pay the cost at write time), and flattens it into one row per UserID per LoginDate. Depending on how many times a user logs in in a given day, and how often you run these reports, this can be substantially more efficient than calculating those at query time (and much less hassle than running your own nightly report that aggregates the data and stores it somewhere).
Now, to get the total login events by day:
SELECT LoginDate, SUM(LoginCount)
FROM dbo.LoginsByDate
GROUP BY LoginDate;
To get the distinct logins by day:
SELECT LoginDate, COUNT(UserID)
FROM dbo.LoginsByDate
GROUP BY LoginDate;
Of course if you're looking for individual user activity, you can do that too, and you can easily aggregate the data to get whatever "total unique" really means to you. If UserID = 1 logs in today 4 times and tomorrow twice, is that 1 total unique (unique per entire time frame) or 2 total uniques (unique per day)?
If the former:
SELECT COUNT(DISTINCT UserID)
FROM dbo.LoginsByDate
WHERE LoginDate >= 'yyyymmdd'
AND LoginDate < 'yyyymmdd';
If the latter:
SELECT SUM(c) FROM
(
SELECT LoginDate, c = COUNT(UserID)
FROM dbo.LoginsByDate
WHERE LoginDate >= 'yyyymmdd'
AND LoginDate < 'yyyymmdd'
GROUP BY LoginDate
) AS x;
Best Answer
If anyone's curious, I ended up doing this. It gives me something, but I'm not really convinced that the data is good enough to be better than no data.