10
\$\begingroup\$

Zombies are questions with no upvoted answers. I came up with this SEDE query to find answers with zero votes on questions that have no accepted answer and no answers with greater than zero score. In other words, answers on questions that show up on the Unanswered tab, until the answer receives an upvote. (Latest version is here, FYI, not subject for review.)

DECLARE @username AS NVARCHAR(60) = ##DisplayName:string? ##;
DECLARE @userId AS INT = ##UserId:int?-1##;

SELECT TOP 100
  u.Id AS [User Link],
  a.Id AS [Post Link],
  a.CreationDate
FROM Posts a
  JOIN Users u ON a.OwnerUserId = u.Id
  JOIN (
    SELECT q.Id, MAX(a.Score) MaxAnswerScore
    FROM Posts q
    JOIN Posts a ON a.ParentId = q.Id
    WHERE q.AcceptedAnswerId IS NULL
    GROUP BY q.Id
  ) q
  ON a.ParentId = q.Id
WHERE 
  (@username = '' OR u.DisplayName = @username)
  AND (@userId = -1 OR u.Id = @userId)
  AND a.Score = 0
  AND q.MaxAnswerScore = 0
ORDER BY a.CreationDate DESC

Is there a better way to write this? Can it be improved?

\$\endgroup\$
  • \$\begingroup\$ Ironic how this is on the best way to become a Zombie, too ;) \$\endgroup\$ – Vogel612 Jan 22 '15 at 17:51
5
\$\begingroup\$

This query looks very nice and clean. I can't see any obvious or glaring issues here, I do have some ideas for improvement though:

Naming

a, u, q and p are not all that "expressive" aliases. There is no need to shorten these aliases to be incomprehensible for people outside the SE-System, that don't understand how Posts a is different from Posts q

Select X

How about also making the number of results variable?

SELECT TOP ##topX:int?100##

allows the user to decide how many results they want.

CTE's

Last time I checked, Data Explorer supported CTE's. Make use of them:

WITH Zombies AS (
     SELECT q.Id ZombieId, MAX(a.Score) MaxAnswerScore
     -- ...
)

SELECT ##topX:int?100## FROM Zombies
\$\endgroup\$
3
\$\begingroup\$

ORDER BY a.CreationDate DESC

I think it would make more sense to sort by ASC so older Zombies show up first. I also think that SELECT TOP 100 is more an obstacle than a help. Since the data only refreshes once a week, if we all wanted to reduce the amount of Zombies we could only do the first 100, then have to wait for refresh. I did notice on SEDE you used SELECT TOP ##Limit:int?100## which is a better approach, but not part of the code in your question.

It looks like your JOIN with a subquery and other sub-JOIN is overly complicating the logic of the query.

I moved some things around to eliminate the unnecessary subquery JOIN and moved the MAX(a.Score) aggregate up into your primary SELECT (notice the HAVING clause at the very end) so this way it simplifies the execution plan and maintainability.

SEDE Query

DECLARE @username AS NVARCHAR(60) = ##DisplayName:string? ##;
DECLARE @userId AS INT = ##UserId:int?-1##;

SELECT TOP ##Limit:int?100##
  u.Id AS [User Link],
  a.Id AS [Post Link],
  a.CreationDate,
  MAX(a.Score) AS MaxAnswerScore
FROM Posts a
  JOIN Users u 
    ON a.OwnerUserId = u.Id
  JOIN Posts q
    ON a.ParentId = q.Id
WHERE 
  (@username = '' OR u.DisplayName = @username)
  AND (@userId = -1 OR u.Id = @userId)
  AND a.Score = 0
  AND q.AcceptedAnswerId IS NULL
GROUP BY
  u.Id,
  a.Id,
  a.CreationDate
HAVING 
  MAX(a.Score) = 0
ORDER BY a.CreationDate ASC
\$\endgroup\$
  • \$\begingroup\$ That doesn't seem right. I would expect a GROUP BY q.Id (or, equivalently, GROUP BY a.ParentId) somewhere. As written, your query considers any zero-scored answer to be a zombie-killer. \$\endgroup\$ – 200_success Jan 22 '15 at 20:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.