Получить "ранг" значка на SO для моего пользователя - запрос медленный - возможно ли ускорение? - PullRequest
2 голосов
/ 20 июня 2019

Мне было любопытно, сколько людей получили python gold badge до меня - я могу получить эту информацию

python    2019-01-02 09:09:15   Gold    454

с этим (медленно работает) запрос :

(Мне не удалось выполнить однократную / перекрестную регистрацию с моим основным пользователем в проводнике данных, следовательно, анонимный вход)

-- insert your user id here:
declare @uid int = 7505395

-- get all badges of all users
select Name, Date, [Gold/Silver/Else], [Row#] from ( 
  SELECT Name, 
         Date, 
         userId,
         case when class = 1 then 'Gold'
              when class = 2 then 'Silver'
              when class = 3 then 'Bronze'
              else convert(varchar(10), class)
              end as 'Gold/Silver/Else',
              ROW_NUMBER() OVER(PARTITION BY name, class ORDER BY date ASC) AS Row# 
  FROM badges
  WHERE 1 = 1
    -- you can restrict this further, f.e. for looking only by gold badges
    -- and Class = 1  -- gold == 1, silver == 2, bronze == 3
    -- -- or for certain named badges
    -- and name like 'python%' 
) as tmp
where userID = @uid 
ORDER by name asc, Date asc

(Запрос как есть дает мне все мои значки с тем, сколько их досталось до меня и должен перебрать все возможные значки)

Вопрос:

Я пытался CTE (только ошибки, не работали), и мои навыки sql ржавые - как ускорить этот запрос?

Ответы [ 2 ]

2 голосов
/ 20 июня 2019

Проблема в том, что в таблице, похоже, нет индексов, которые были бы полезны для этого.Мы получаем планы выполнения, такие как:

bad plan

- Сканирование индекса неоптимально.Нам нужен индексный поиск.

Тем не менее, вы можете сократить время почти вдвое:

  1. Предварительный выбор значков пользователя.
  2. Использование коррелированного подзапроса для ранга.
  3. Использование Id в качестве прокси для Date.(Id s уникальны, увеличиваются и часто быстрее сортируются.)

Также обратите внимание:

  1. Использование параметра magic ##UserId:INT##.
  2. В столбце Class имеется только 3 значения.
  3. Вы можете сократить время запроса еще на несколько секунд, пропустив предложение ORDER BY.

В любом случае, этот запрос работает лучше:

WITH zUsersBadges AS (
    SELECT  b.Id
            , b.UserId
            , b.Name
            , b.Date
            , b.Class
            , [Badge Class] = (
                CASE    WHEN b.Class = 1 THEN 'Gold'
                        WHEN b.Class = 2 THEN 'Silver'
                        WHEN b.Class = 3 THEN 'Bronze'
                END
            )
            , [Is tag badge] = IIF (b.TagBased = 1, 'Yes', 'No')
    FROM    Badges b
    WHERE   b.UserId = ##UserId:INT##
)
SELECT      ub.Name                 AS [Badge Name]
            , ub.[Badge Class]
            , ub.[Is tag badge]
            , ub.Date               AS [Date Earned]
            , [In Top N of earners] = (
                SELECT  COUNT (ob.ID)
                FROM    Badges ob
                WHERE   (ob.Name = ub.Name  AND  ob.Class = ub.Class  AND  ob.Id <= Ub.Id)  -- Faster but may give slightly higher rank
                --WHERE   (ob.Name = ub.Name  AND  ob.Class = ub.Class  AND  ob.Date <= Ub.Date)  -- Slower, but gives exact rank.
            )
FROM        zUsersBadges ub
ORDER BY    ub.Name, ub.Date

Обновление: Этот запрос работает еще лучшепотому что он объединяет многократно заработанные значки:

WITH zUsersBadges AS (
    SELECT      b.UserId
                , b.Name
                , minId = MIN (b.Id)
                , [First Earned] = MIN (b.Date)
                , [Earned N times] = COUNT (b.Date)
                , b.Class
                , [Badge Class] = (
                    CASE    WHEN b.Class = 1 THEN 'Gold'
                            WHEN b.Class = 2 THEN 'Silver'
                            WHEN b.Class = 3 THEN 'Bronze'
                    END
                )
                , [Is tag badge] = IIF (b.TagBased = 1, 'Yes', 'No')
    FROM        Badges b
    WHERE       b.UserId = ##UserId:INT##
    GROUP BY    b.UserId, b.Class, b.Name, b.TagBased
)
SELECT      ub.Name                 AS [Badge Name]
            , ub.[Badge Class]
            , ub.[Is tag badge]
            , ub.[First Earned]
            , ub.[Earned N times]
            , [In Top N of earners] = (
                SELECT  COUNT (ob.ID)
                FROM    Badges ob
                WHERE   (ob.Class = ub.Class  AND  ob.Id <= Ub.minId  AND  ob.Name = ub.Name)  -- Faster but may give slightly higher rank
                --WHERE   (ob.Class = ub.Class  AND  ob.Date <= Ub.[First Earned]  AND  ob.Name = ub.Name)  -- Faster but may give slightly higher rank
            )
FROM        zUsersBadges ub
ORDER BY    ub.Name, ub.[First Earned]
1 голос
/ 20 июня 2019

Вы можете использовать агрегацию с фильтрацией:

select count(*)
from badges b
where b.name = 'python' and b.class = 2 and
      b.date < (select b2.date
                from badges b2
                where b2.name = 'python' and b2.class = 2 and
                      b2.userID = @uid 
               );
...