#sql #sql_server #оптимизация #datastackexchangecom
Дано:
Posts — сообщения двух типов: вопросы и ответы, связаны через ParentId, тип указан
в PostTypeId (1 — вопрос, 2 — ответ).
Вопрос закрыт, если ClosedDate is not null.
Вопрос отвечен, если AcceptedAnswerId is not null или есть ответ со Score > 0.
Вопросы содержат счётчики AnswerCount, ViewCount, CommentCount — количество ответов,
просмотров, комментариев.
Tags — текстовые метки, связанные многие-ко-многим с вопросами через PostTags.
Требуется:
Собрать статистику по вопросам, сгруппированную по меткам: сколько всего вопросов,
сколько закрыто, сколько имеет хоть какой-то ответ, сколько отвечено. А также собрать
статистику: сколько в среднем ответов, просмотров, комментариев. Статистика должна
быть за некоторый временной период (например, за последние 8 недель, кроме последних
3 дней).
Решение:
declare @IncludeRecentWeeks int = 8
declare @ExcludeRecentDays int = 3
declare @LastDate datetime =
(select max(CreationDate) from Posts)
declare @StartDate datetime =
dateadd(week, -@IncludeRecentWeeks, @LastDate)
declare @EndDate datetime =
dateadd(day, -@ExcludeRecentDays, @LastDate)
;with
Questions as (
select
q.*,
pt.TagId
from
Posts as q
inner join
PostTags as pt on pt.PostId = q.Id
where
q.PostTypeId = 1 -- question
and q.CreationDate > @StartDate
and q.CreationDate < @EndDate
),
TagStats as (
select
t.TagName as [Tag],
(
select count(q.Id) from Questions as q where q.TagId = t.Id
) as [QuestionCount],
(
select count(q.Id) from Questions as q where q.TagId = t.Id
and q.ClosedDate is not null
) as [ClosedQuestionCount],
(
select count(q.Id) from Questions as q where q.TagId = t.Id
and q.AnswerCount > 0
) as [AnsweredQuestionCount],
(
select count(*)
from (
select q.Id
from Questions as q
inner join Posts as a on a.ParentId = q.Id
where
q.TagId = t.Id
and (a.Score > 0 or q.AcceptedAnswerId = a.Id)
group by q.Id
) as _
) as [UpvotedAnsweredQuestionCount],
(
select sum(AnswerCount) from Questions as q where q.TagId = t.Id
) as [TotalAnswerCount],
(
select sum(ViewCount) from Questions as q where q.TagId = t.Id
) as [TotalViewCount],
(
select sum(CommentCount) from Questions as q where q.TagId = t.Id
) as [TotalCommentCount]
from
Tags as t
)
select top 50
Tag,
QuestionCount as [Qs],
format(1.0 * ClosedQuestionCount / QuestionCount, 'p') as [Qs Closed %],
format(1.0 * AnsweredQuestionCount / (QuestionCount - ClosedQuestionCount), 'p')
as [Qs with As %],
format(1.0 * UpvotedAnsweredQuestionCount / (QuestionCount - ClosedQuestionCount),
'p') as [Qs with + As %],
format(1.0 * TotalAnswerCount / QuestionCount, 'f') as [Avg As],
format(1.0 * TotalViewCount / QuestionCount, 'f') as [Avg views],
format(1.0 * TotalCommentCount / QuestionCount, 'f') as [Avg Cs]
from
TagStats
order by
QuestionCount desc
Запрос на Data.StackExchange.com, где можно посмотреть результаты выполнения.
Вопрос:
В получившемся вопросе присутствует большое количество подзапросов, что вряд ли положительно
сказывается на производительности. Можно ли оптимизировать запрос и избавиться от такого
большого числа подзапросов?
Предупреждение:
Этот запрос — продукт технологий программирования SODD (Stack Overflow Driven Development)
и CPDD (Copy-Paste Driven Development). SQL я не умею, поэтому любые замечания по качеству
кода приветствуются.
Ответы
Ответ 1
На самом деле разница если и получится, то не очень большая. Проблему составляет выборка "вопросы помноженные на метки", которые нужны все для сортировки и они занимают около 100% времени выполнения. Остальные операции занимают сущие копейки. Тем не менее, данные можно получить одним запросом (ну почти), код просто будет короче и местами быстрее: declare @IncludeRecentWeeks int = 8 declare @ExcludeRecentDays int = 3 declare @LastDate datetime = (select max(CreationDate) from Posts) declare @StartDate datetime = dateadd(week, -@IncludeRecentWeeks, @LastDate) declare @EndDate datetime = dateadd(day, -@ExcludeRecentDays, @LastDate) select top 50 tagname Tag, cnt Qs, format(1.0*closed/cnt,'p') [Qs Closed %], format(1.0*answered/(cnt-closed),'p') [Qs with As %], format(1.0*accepted/(cnt-closed),'p') [Qs with + As %], format(avganswers, 'f') [Avg As], format(avgviews, 'f') [Avg views], format(avgcomments,'f') [Avg Cs] from ( select t.tagname, count(pt.postid) cnt, sum(iif(q.ClosedDate is not null,1,0)) closed, sum(iif(q.AnswerCount>0,1,0)) answered, sum(iif(q.AcceptedAnswerId is not null or a.upvotedanswers>0,1,0)) accepted, sum(q.AnswerCount) answers, avg(q.AnswerCount+0.0) avganswers, sum(q.ViewCount) views, avg(q.ViewCount+0.0) avgviews, sum(q.CommentCount) comments, avg(q.CommentCount+0.0) avgcomments from postTags pt join Tags t on pt.tagid = t.id join Posts q on pt.postId = q.id join (select qq.id postId, sum(isnull(aa.id,0)) upvotedanswers from Posts qq left join Posts aa on qq.id = aa.parentid and aa.score>0 where qq.CreationDate between @StartDate and @EndDate group by qq.id ) a on q.id = a.postid where q.CreationDate between @StartDate and @EndDate group by t.tagname ) aggtags order by aggtags.cnt desc http://data.stackexchange.com/ru/query/313286/tags-stats-mod ЗЫ Добавил также фильтр по дате для вопросов с заплюсованными ответами, но (лень план запроса разбирать подробно) индекса либо нет, либо он не используется - разницы не заметил.
Комментариев нет:
Отправить комментарий