1. PHP / Говнокод #13731

    +148

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    $try = $db->getRow(
    	"SELECT * FROM user_{$name}, item_{$name} ".
    	"WHERE user_{$name}.usr_id=? AND user_{$name}.{$type}_id=? AND user_{$name}.{$type}_id=item_{$name}.{$type}_id",
    	array($user->usr_id, $id)
    );

    последствия неправильно спроектированной БД

    Запостил: xara, 03 Сентября 2013

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

    • ХУЙНЯ, ХУЙНЯ, ХУЙНЯ!
      ХУЙНЯ, ХУЙНЯ, ХУЙНЯ!
      Ответить
    • А я бы бил по рукам просто за
      "SELECT * FROM ..."
      Ответить
      • Пачиму?
        Ответить
        • > Пачиму?
          Реально не догадываетесь?
          1. В 99% случаях используются данные не из всех колонок. Так нафига гонять лишние данные по сети?
          2. Таблица в базе, если проект живой, со временем изменяется, а значит могут появиться новые колонки, которые в существующих запросах нафиг не упёрлись. Если приложение проверяет постоянно на соответствие метаданных, то может слететь по "metadata is out of synchronization".
          3. Больше всего убивает, когда хотят посчитать кол-во строк, и для этого затягивают всё на уровень приложения с "SELECT * FROM..." и там считают :-\

          А вообще, как показывает практика, если человек пишет "SELECT * FROM..." на уровне приложения - он слабо представляет роль СУБД в общей архитектуре.
          Ответить
          • А SELECT * FROM ... из джвух заджойненных таблиц еще и поломаться может ненароком, если в них попадутся одноименные поля (даже если они никому нафиг не сдались в данном запросе).
            Ответить
            • при повторяющихся именах колонок (к примеру поле id в обеих таблицах), ломается не SELECT *, а условия вида WHERE id=?, группировки GROUP BY id и прочие. И то в них достаточно указать через точку имя таблицы, из которой id должен использоватся.

              Вот пример, когда в двух заджойненных таблицах повторяющееся поле id, но никакого батхерта не происходит (за счёт использования u.id):
              SELECT *
              FROM users u
              JOIN achievements ach ON ach.uid = u.id
              WHERE u.id = ?
              Ответить
              • А все, вспомнил когда появляется этот column reference "id" is ambiguous. Оно не при звездочке вылезает, а наоборот, когда указал в select'е имя поля, которое есть в обеих таблицах, но не указал, из какой таблицы его взять.
                select id from users u join achivements a on a.uid = u.id
                select u.id from users u join achivements a on a.uid = u.id
                Затупил я с этой поломкой, сорри ;)
                Ответить
          • У этого правила есть одно исключения, на мой взгляд. Выбирая данные из таблицы-справочника по первичному ключу, лучше использовать все-таки SELECT *, для того, чтобы эти запросы кешировались базой (или даже самим приложением).

            ИМХО лучше в 5 разных местах использовать:
            SELECT * FROM ref_items WHERE id=?

            чем запросы вида:
            SELECT name, price FROM ref_items WHERE id=?
            SELECT icon, name FROM ref_items WHERE id=?
            SELECT damage_bonus FROM ref_items WHERE id=?

            К тому-же такой подход хорошо сочитается с кешированием-в-модели.
            $item = Item::get($itemId); // выймет данные из SELECT * FROM ref_items WHERE id=?
            $item = Item::get($itemId); // вернёт тот-же обьект из собственного кеша
            Ответить
            • Не-не-не, Дэвид Блейн.
              Тогда уж на уровне приложения в DAO вычитываем справочник запросом:
              SELECT name, price, icon, name, damage_bonus FROM ref_items WHERE id=?
              А через классы врапперы, описывающие доменную модель, обращаемся к конкретным полям.

              ЗЫЖ Да и кеширование на СУБД - это ещё та серая магия. Надеяться на кеш архитектурно - моветон.
              Ответить
              • хм, да, в ДАО действительно можно колонки перечислить, но по сути это будет SELECT *, т.к. из-за незнания, какие колонки будут использоваться, ДАО должно выгружать всю запись.

                т.е. в этом случае разница между
                SELECT name, price, icon, name, damage_bonus
                и SELECT * только в человеческом факторе, программно будет выполнятся все-равно одна и та-же логика.
                Ответить
                • Явное перечисление полей способствует раннему обнаружению ошибок и предотвращает неявную загрузку полей, значения которых не используются. Я за явное перечисление полей.
                  Ответить
                  • А ещё является нормальной практикой в справочниках держать поля со служебной информацией, что-то вроде: дата вставки записи, автор изменения, дата последнего изменения и т.п. Такая информация на уровне приложения часто не нужна.
                    Ответить
                    • а ну если так - да, SELECT * нельзя использовать.
                      Ответить
                  • в том-то и дело, что в DAO нужно все-равно все поля загружать, все поля используются. Вот и выходит, что перечисление полей в DAO даёт только плюшки по человеческому фактору ("способствует раннему обнаружению ошибок", "меньше обьяснять джуниорам" и пр.).

                    Как по мне в DAO цена перечисления полей себя не оправдывает. Лично я бы для них сделал исключениее в правиле "никогда не пиши SELECT *"
                    Ответить
    • даже без изменения БД ту-же логику можно было записать:
      $try1 = $db->getRow("SELECT * FROM user_{$name} WHERE usr_id=? AND {$type}_id=?", array($user->usr_id, $id));
      $try2 = $db->getRow('SELECT * FROM item_{$name} WHERE {$type}_id=?', $id);
      $try = array_merge($try1, $try2);
      Ответить
    • для самых пытливых - вся эта канитель достаёт предмет из инвентаря пользователя + прикрепляет все его справочные параметры. В адекватном варианте было бы что-либо вроде:
      $userItem = $db->getRow("
      SELECT *
      FROM user_storage us
      JOIN ref_items ri ON ri.id = us.item_id
      WHERE us.uid=? AND us.item_id=?
      ", array($user->id, $itemId));
      Ответить
      • Эээ... каждая бутылочка с маной это уникальный предмет, имеющий свою запись в ref_items? :)
        Или юзер не может иметь более двух однотипных предметов? :)

        Одно из двух...
        Ответить
        • > Или юзер не может иметь более двух однотипных предметов? :)
          P.S. Вот тут я погорячился. Может.
          Ответить
        • Предметов (на самом деле ресурсов) в игре около 40, и практически всегда игрок имеет Хшт определённого ресурса. Поэтому в user_storage хранится не только id базового предмета, но и кол-во.

          За счёт этого на один тип ресурса у одного игрока может быть только одна запись в БД, т.е. для таблицы user_storage PRIMARY KEY состоит из (user_id, item_id).
          Ответить

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