1. SQL / Говнокод #14506

    −166

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    SELECT ...
            FROM users_account
        ....
        WHERE   
            (T.userid=@userid or (@userid=null and @account!=null)) 
            and (T.account=@account or (@userid!=null and @account=null))

    Есть табличка с юзерами и счетами на которых у юзеров есть игровая валюта.
    Есть индекс по обоим этим полям (userid, account).
    Каждый такой запрос сканирует весь индекс,а не делает по ней поиск, чем и грузит субд.
    Но попытки оптимизировать запрос не увечались успехом,
    т.к. я так и не понял что хотел сказать автор в своём условии where.

    Запостил: bliznezz, 05 Февраля 2014

    Комментарии (40) RSS

    • Собачки — это ведь переменные, не так ли? Вынести их нафиг из WHERE во внешнее условие (CASE или IF).

      Рассмотрим варианты:
      1. @userid=null and @account!=null
      Тогда (@userid=!null and @account=null) автоматически не выполнено и во WHERE остаётся только T.account=@account.
      2. @userid=!null and @account=null
      Тогда (@userid=null and @account=!null) автоматически не выполнено и во WHERE остаётся только T.userid=@userid.
      3. Оба условия не выполняются (@userid и @account обе не нули или обе нули).
      Тогда во WHERE аж два условия: (T.userid=@userid) and (T.account=@account).

      И почему здесь нельзя использовать индекс?
      Ответить
      • > И почему здесь нельзя использовать индекс?
        Дык он по обоим полям, и поэтому его можно поюзать только в случаях 2 и 3 ;)
        Ответить
        • Кстати, а какие индексы лучше создавать, если выбирать приходится иногда по первой колонке, иногда по второй, а иногда по совокупности?
          Ответить
          • Как сказал ниже DBdev - ситуации бывают разные. Проще всего, имхо, поиграться с explain analyze и посмотреть, дают ли индексы профит на твоих юзкейсах.

            Если это небольшой справочник или у этих полей офигенная селективность - то вполне хватает отдельного индекса на каждую такую колонку.

            P.S. Я ни разу не DBA, так что ждем более авторитетных ответов ;)
            Ответить
            • P.P.S. Если будешь играться с explain'ом - юзай реальные запросы на реальных (или близких к реальным) данных.

              Ибо другое количество строк в таблицах, распределение значений в колонках или примерное количество строчек в выхлопе могут перевернуть план с ног на голову ;)

              Например, тот же постгрес тупо забивает на индексы, если запрос возвращает бОльшую часть таблицы.
              Ответить
              • > Например, тот же постгрес тупо забивает на индексы, если запрос возвращает бОльшую часть таблицы.
                Как и все вменяемые СУБД.
                Ответить
              • В смысле забивает и как он определяет сколько вернет запрос?
                Ответить
                • > В смысле забивает
                  Ну не юзает их, т.к. если строк в выхлопе будет дохуя, то прямой скан таблички будет намного быстрее, чем куча рандомных чтений индекса и записей, на которые он ссылается.

                  > как он определяет сколько вернет запрос
                  На глаз :) СУБД собирает статистику по таблицам - сколько записей, какой разброс значений в полях и т.п. На основе этой статистики планировщик прикидывает сколько записей получится в ответе (можешь посмотреть в explain, там видно эту оценку). Тут же только порядок важен, на точное значение всем насрать.
                  Ответить
            • > вполне хватает отдельного индекса на каждую такую колонку.
              99,9% случаев решает этот метод.

              > P.S. Я ни разу не DBA, так что ждем более авторитетных ответов ;)
              Вполне авторитетный ответ.
              Ответить
    • //Мой прадед говорил
      (@userid!=null and @account=null) // Имею желание купить дом, но не имею возможности
      (@userid=null and @account!=null)) // Имею возможность купить козу, но не имею желания

      //Так выпьем же за то, чтобы
      T.userid=@userid //наши желания всегда совпадали
      T.account=@account // с нашими возможностями
      Ответить
    • А юзверей, небось, овер 9к?
      Надо нужных в отдельную таблицу выкинуть(и добавлять туда по мере регистрации новых), ибо каждый раз перебирать все записи - некомильфо.
      архитектуропроблемы
      Ответить
    • > @account=null
      Это в какой же СУБД работает такая проверка на налл? Небось опять майкрософт решил выпендриться и пойти против системы? :)
      Ответить
      • В МС нет такой херни, если только явно её не задать в настройках БД.

        99%, что просто запрос писал знаток SQL.

        ЗЫЖ != это не МС, в МС <>
        Ответить
        • Хм, мне казалось, что только в M$ переменные с собачками. Где-то еще такие есть?
          Ответить
          • Хех, проверил только что...
            В МС и != и <> работают. Это просто я постоянно <> юзаю. Так что может и МС.
            Ответить
          • В мускуле, не?
            Ответить
            • Вот что нашел по мускулю: A variable that begins with the @ sign at the beginning is session variable. It is available and accessible until the session ends. В остальном же, если верить примерам, там обычный declare.

              Так что походу все-таки это m$.
              Ответить
              • Если m$, то в коде автоматически проявятся грабли сравнения с null, ибо мелкомягкие сами говорят:

                To determine whether an expression is NULL, use IS NULL or IS NOT NULL instead of comparison operators (such as = or !=). Comparison operators return UNKNOWN when either or both arguments are NULL.
                Ответить
                • Да это не только мелкомягкие так говорят. Эта троичная логика с true/false/unknown юзается во всех субд, с которыми я встречался...

                  Я почему и спросил выше "Это в какой же СУБД работает такая проверка на налл?".

                  Поэтому предположение такое:
                  1) Это M$SQL, т.к. переменные с собачками
                  2) Автор кода тупо не знал, как правильно проверять на null
                  Ответить
    • Какого типа аккаунт? Если это внешний ключ, то смысл сей таблички ускользает от меня стремительным домкратом.
      Ответить
      • Самая обычная ассоциативная табличка для отношения многие-ко-многим:
        - у юзера может быть несколько счетов;
        - один счет может принадлежать нескольким юзерам.
        Ответить
        • Было бы удобно, если бы так было на ГК, потому что:
          1. Не нужно публиковать в Багминоте пароли от общих учёток.
          2. Не нужно перелогиниваться, чтобы задействовать мультов.
          Ответить
        • Вот многия счета меня и смутили. По обычной логике вещей, счёт может принадлежать только кому-то одному.
          Ответить
          • Ну может быть они поощряют мультоводство, и можно создать один счет и несколько юзерских учеток, чтобы играть в 2-3 окна.
            Ответить
            • Оправдать плохой архитектурой гораздо проще.
              Ответить
    • Переписал бы вот так:
      SELECT ...
      FROM users_account
      ....
      WHERE
      (T.userid=@userid or @userid IS NULL)
      and (T.account=@account or @account IS NULL)

      Тайный смысл таковой - если параметр НЕ передался в хранимку (по-дефолту NULL), то этот фильтр игнорируется. Танцы с неравенством не нужны.
      Если СУБД - МС, то можно форсировать использование индексов хинтами. Например:
      SELECT ...
      FROM users_account WITH (INDEX (myMegaIndex))
      ....

      А СУБД НЕ использует идексы из-за того, что у Вас в запросе переменные. Если подставить литералы, то индексы будут использоваться.
      Ответить
      • Его тайный еще смысл предполагает, что оба параметра не должны быть нулевыми. Они могут быть нулевыми, но тогда найдутся только те, у которых T.userid IS NULL и T.account IS NULL. А переписал бы он как вы предложили, то при обоих нулевых параметрах получил бы всю таблицу.
        Так что "Разрешите пригласить Вас на танец, Сударь".
        Ответить
        • > найдутся только те, у которых T.userid IS NULL и T.account IS NULL
          Лолшто. Не найдет он их. Даже если из налл написать вместо корявых сравнений.

          > А переписал бы он как вы предложили, то при обоих нулевых параметрах получил бы всю таблицу.
          Согласен. Можно пофиксить ифом и выбросом исключения. Ибо нехуй передавать два налла (поди туда не знаю куда, принеси то не знаю что).
          Ответить
        • А если будет 10 параметров? Будете городить: Param1 IS NULL AND Param2 IS NOT NULL AND ... Parampampam10 IS NOT NULL ?
          Да, в моём примере, если все параметры не переданы, то вернётся вся таблица.
          В таких случаях просто в начале делается проверка на то, что они все пусты и делается ретурн.

          > Так что "Разрешите пригласить Вас на танец, Сударь".
          Разрешаю. Приглашайте.
          Ответить
          • > Да, в моём примере, если все параметры не переданы, то вернётся вся таблица.
            Что во многих случаях вполне логично. Например, если эти параметры юзаются как фильтры какого-то справочника...
            Ответить
            • Если не нужно сделать наоборот, конечно. Например, если таблица ОЧЕНЬ большая и показывать её всю бесполезно.
              Ответить
              • Скажите, а LIMIT и пагинация не могут спасти гиганта мысли?

                Даже с фильтрами больше 40-50 записей во-первых не влезут в экран, а во-вторых юзер скорее всего даже не будет их смотреть, а тупо добавит еще фильтров, ибо лень ;)

                P.S. А если юзеру приходится переключаться на вторую страничку - значит или это какой-то нештатный случай, ручная сверка, или же автор софта отъявленный садист, не умеющий в юзабилити.
                Ответить
                • За пагинацию и лимит +1. Если таблица не справочная и она после фильтров едет к юзеру, то это по-умолчанию должно быть заимплеменчено.

                  Но ситуации бывают разные, и если фильтры применяются к справочной таблице, то может и все данные надо затянуть, на аппликейшн левел, например.
                  Ответить
                  • > За пагинацию и лимит +1.
                    Еще в студенческих и джуниорских работах поделках доставляют комбобоксы для выбора из справочников (каюсь, сам такой фигней страдал).

                    > Но ситуации бывают разные
                    Ключевая фраза ;)
                    Ответить
                    • Смотря какие комбобоксы, смотря где. До сих пор на аутору можно выбирать модели-марки из комбобокса, а там их с полсотни точно.

                      Никто не спорит, лимиты нужны. С другой стороны, всё опять же определяется задачей UI.
                      Ответить
                      • > а там их с полсотни точно
                        Вот именно такие я и имел в виду ;) А там где 5-10 - да, комбобокс вполне подходит.
                        Ответить
                        • Это уже вопрос к UX дизайнерам, но какое ещё вменяемое решение? Облако тегов в вебе есть, для десктопа не годится явно.
                          Единственное, что приходит навскидку в голову - интеллектуальная система подбора, что-то типа "ваз 2101 2014-го года в полной комплектации".
                          Ответить
                          • Ну edit с автодополнением хотя бы... Комбобокс с более чем 10 элементами это же издевательство над юзером...

                            А если там дохрена вариантов, и никак не догадаться, что именно нужно писать - просто кнопка по которой показывается справочник с гридом и нормальными фильтрами.
                            Ответить

    Добавить комментарий