The query from @Magisch is pretty impressive so let me offer an alternative, both in the source tables used as in the approach.
Where @Magisch uses the Post table, that contains the generated HTML you also find in a page, I decided to look at the actual markdown. That is what you see when you edit a post. The markdown is found in the posthistory table for rows with posthistorytypes 2 and 5 (initial/edit). It turns out that a spoiler starts at a new line and then can't have any line breaks. So a spoiler end at the next linebreak (a char(13) and char(10) in the implementation of SE).
Instead of doing a stored procedure I used a recursive Common Table Expression with the same goal as the SP, find all the spoilers in the same post.
Bringing this all together lead to this query where I tried to cater for cases where the spoiler markdown is >!
and > !
. On Anime.se no other variations exist.
I had hoped to stay clear of the posts table with this query but to meet the requirement to indicate if the post is a question or answer I had no other option then to join with the Posts table in the final query to obtain the posttypeid (1=Q, 2=A) for the post.
with spoil(id, postid, length, start, eol) as
(
select id
, ph.postid
, len(text) length
, IIF(charindex('>!', text, 1) > charindex('> !', text, 1)
, charindex('>!', text, 1)
, charindex('> !', text, 1)
) start
, IIF(
charindex(char(13) + char(10)
, text
, IIF(charindex('>!', text, 1) > charindex('> !', text, 1)
, charindex('>!', text, 1)
, charindex('> !', text, 1)
)
) = 0
, len(text)
, charindex(char(13) + char(10)
, text
, IIF(charindex('>!', text, 1) > charindex('> !', text, 1)
, charindex('>!', text, 1)
, charindex('> !', text, 1)
)
)
) eol
from posthistory ph
where posthistorytypeid in (2,5)
and (text like N'%>![^[]%' -- skip images
or text like N'%> ![^[]%') -- allow space, skip images
and id = (select max(id)
from posthistory
where posthistorytypeid in (2,5)
and postid = ph.postid)
union all
select ph.id
, ph.postid
, len(text) length
, IIF(charindex('>!', text, ts.eol) > charindex('> !', text, ts.eol)
, charindex('>!', text, ts.eol)
, charindex('> !', text, ts.eol)) start
, charindex(char(13) + char(10)
, text
, IIF(charindex('>!', text, ts.eol) > charindex('> !', text, ts.eol)
, charindex('>!', text, ts.eol)
, charindex('> !', text, ts.eol))) eol
from posthistory ph
inner join spoil ts on ts.id = ph.id
and charindex('>!', ph.text, ts.eol) > 0
where charindex(char(13) + char(10)
, text
, IIF(charindex('>!', text, ts.eol) > charindex('> !', text, ts.eol)
, charindex('>!', text, ts.eol)
, charindex('> !', text, ts.eol))
) > 0
)
select postid as [Post Link]
, (case when max(posttypeid) = 1 then 'Question' else 'Answer' end) [Q/A]
, max(length) [body length]
, sum(eol-start) [spoiler length]
, count(*) [number of spoiler blocks]
, cast(sum(eol-start) as decimal) / max(length) * 100 [perc]
from spoil
inner join posts p on p.id = spoil.postid
group by postid, posttypeid
order by cast(sum(eol-start) as decimal) / max(length) * 100 desc
Check this example post why it is important to look at the last edit of the post.