ФорумРазработкаБазы данных → Задачка.

Задачка.

  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 15 августа 2009 г. 13:29

    Есть таблица `hotels`. Структура следующая:

    id INT AUTO INCREMENT PRIMARY KEY,
    name TINYTEXT CHARACTER SET utf8,
    region TINYTEXT CHARACTER SET utf8,
    type TINYTEXT CHARACTER SET utf8


    Мне нужно вставлять в нее строки, причем так, чтобы комбинация name|region|type была уникальная. Как лучше это сделать?
    У меня есть идея с использованием UNIQUE KEY comb (name(100), region(50), type(20)) и тогда писать в INSERT'e игнор на повторяющиеся строки.
    Только я не уверен, что все это будет работать.
  • md5

    Сообщения: 11960 Репутация: N Группа: в ухо

    Spritz 15 августа 2009 г. 13:31, спустя 1 минуту 31 секунду

    а почему не уверен?
    не уверен — не играй!
    все умрут, а я изумруд
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 15 августа 2009 г. 13:33, спустя 2 минуты 8 секунд

    Столбец name может содержать более 100 символов, значит в индекс войдет не все
    ну и другие подводные камни этого варианта решения
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 15 августа 2009 г. 19:08, спустя 5 часов 35 минут 7 секунд

    Ок. С индексом получилось. Как теперь его заюзать в селект запросе.
    Критерий такой WHERE ROW(name,region,type) IN ( (a,b,c), (d,e,f), (g,h,i) ).
    Экслпеин говорит, юзать его не буду, даже если явно прописать USE KEY в SELECT
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 15 августа 2009 г. 19:12, спустя 4 минуты 14 секунд

    пошли всю структуру страницы + запрос который хочешь выполнять
    Сапожник без сапог
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 15 августа 2009 г. 23:36, спустя 4 часа 23 минуты 59 секунд

    Страница - но принт. Скрипт будет в консоли крутиться. Вобщем не важно, вот код

    $hotelinfo;  # список, в котором в формате НАЗВАНИЕ ОТЕЛЯ|РЕГИОН|ТИП хранятся данные. строки могут повторяться, а в бд такого не надо.
    $unq = array_unique($hotelinfo);  #Рассматриваем неповторяющиеся записи.
    /*
    *Тут можно пойти 2мя путями. Сделать запрос вида SELECT name, region, type FROM `hotels` WHERE ROW(name, region, type) IN…
    * Или задать индекс по столбцам name, region, type и объявить его уникальным. Тогда повторов в таблице не будет.
    */
    //Коннект к БД
    $db = DB::getDB();
    $select = 'SELECT name, region, type FROM `hotels` WHERE ROW(name, region, type) IN (';
    foreach ($unq as $k => &$v) {
      $m = explode('|',$v);
      $select .= $db->prepare('($0,$1,$2), ', array($m[0],$m[1],$m[2]));
    }
    $select = substr($select, 0, sizeof($select) - 2) . ')';
    $res = $db->query($select, null, 'assoc');
    //Теперь в $res лежат отели, которые уже есть в базе. Их вставлять не надо…

    //Далее идет инсерт в таблицу исключая отели, которые уже есть. Тут не очень интересный код.

    //Затем вставленные отели снова выбираются из таблицы, чтобы узнать их ID, а потом этот ID записывается в другую таблицу.


    Структура таблицы:
    id INT AUTO INCREMENT PRIMARY KEY,
    name TINYTEXT CHARACTER SET utf8,
    region TINYTEXT CHARACTER SET utf8,
    type TINYTEXT CHARACTER SET utf8


    Пробовал так:

    id INT AUTO INCREMENT PRIMARY KEY,
    name VARCHAR(100) CHARACTER SET utf8,
    region VARCHAR(100) CHARACTER SET utf8,
    type VARCHAR(20) CHARACTER SET utf8,
    UNIQUE KEY nrt (name(100), region(100), type(20)))


    Тогда селект, который я написал выше вообще больше не нужен, но инсерт начинает тормозить.
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 15 августа 2009 г. 23:38, спустя 2 минуты 10 секунд

    но инсерт начинает тормозить.

    таблички myisam видимо юзаешь. + сколько записей в таблицах?
    Сапожник без сапог
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 15 августа 2009 г. 23:49, спустя 10 минут 20 секунд

    вообще толстеют очень быстро =)
    пока под 4,5к в одной и около 250к в связанной с ней =)
    Спустя 59 сек.
    4,5k - hotels
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 15 августа 2009 г. 23:54, спустя 5 минут

    попробуй на иннодб смени таблицы, может отпадут тормоза)
    Сапожник без сапог
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 16 августа 2009 г. 0:15, спустя 20 минут 50 секунд

    Это можно… Еще можно вместо инсерта из файлика загрузку делать…
    Индекс очень сильно тормозит на вставке, но самое стремное, он потом в селекте не используется.
    Ладно, пробовать буду завтра… посмотрим че выйдет…
  • ghost

    Сообщения: 883 Репутация: N Группа: Джедаи

    Spritz 21 августа 2009 г. 1:03, спустя 5 дней 48 минут

    можно тригер на инсерт поставить и обойтись без урезанных индексов.
    походу непонимаю с какой радости тайп в уникальности учитывается - в регионе могут быть несколько отелей с одинаковым именем но разными типами?
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 21 августа 2009 г. 12:19, спустя 11 часов 15 минут 18 секунд

    Угу, звездность и все такое (5+ HV-1 и т.п.). А названия отелей одинаковые, т.к. это политика управления брендом.
    Да и как триггер поможет в проверке уникальности? Можно конечно что-то там придумать, но я пока остановился на варианте SELECT -> поиск совпадений->INSERT->SELECT для вставки в другую таблицу и т.п.
  • AndryG

    Сообщения: 237 Репутация: N Группа: Адекваты

    Spritz 21 августа 2009 г. 15:58, спустя 3 часа 39 минут 15 секунд

    Не знаю, как mySQL (поддерживает ли он "insert into () select")
    Может такой вариант сойдет?
    insert into hotels (name,region,type_)
    select :name, :region, :type
    from rdb$database – Это таблица с одной строкой. Для mySQL, кажись, можно просто убрать предложение FROM
    where not exists(select * from hotels where name = :name and region = :region and type_ = :type)


    Если использовать mysqli, то предварительная подготовка запроса в самом начале работы скрипта должна Вам помочь с быстродействием.
    Спустя 94 сек.
    type_ c подчеркивание в конце, ибо "type" у меня - служебное слово
  • Batler

    Сообщения: 142 Репутация: N Группа: Кто попало

    Spritz 21 августа 2009 г. 16:50, спустя 52 минуты 15 секунд

    Prepared statement выполняются медленее в MySQL. Пруфлинк давал в другой ветке (см обсуждение класса для работы с БД).
    И я не понял немного логики этого запроса. Вставить в таблицу hotels значения, которые вернет оператор SELECT (а он вернет параметры которые мы проверяем, при условии, что вложенный запрос ничего не вернет). Если я правильно понял запрос, то тут есть проблемы:
    1) У нас массив отелей, значит запросов будет множество (пусть даже заранее подготовленных, но тем не менее их будет множество).
    2) Подзапрос в запросе. Это тоже не добавит к скорости.
    3) Я тоже не уверен, что MySQL держит селект в инсерте. В моей литературе сказано, что не держит, но там версия древняя.

    UPD: http://www.mysql.ru/docs/man/INSERT_SELECT.html Есть такая возможность, но там сказано, что нельзя использовать
    ту же таблицу, куда производится вставка.
  • AndryG

    Сообщения: 237 Репутация: N Группа: Адекваты

    Spritz 21 августа 2009 г. 21:03, спустя 4 часа 12 минут 29 секунд

    Prepared statement
    Из той ветки я понял, что подготовленный запрос выполняется медленнее, если он единственный.
    А если сравнить выполнение 1000 раз запроса обычного и 1000 раз запроса предварительно подготовленного (1 подготовка и 1000 выполнений)

    Если и на многократном исполнении подготовленный запрос проигрывает, то я вообще в шоке от этой СУБД :)
      (Но это уже вариации ПО - не будем спорить - делайте, как быстрее)

    Подзапрос в запросе.
     Подзапрос там "легкий" (exists) - СУБД не будет считать сикоко там записей нашлось, ни, тем паче, выдавать их клиенту.
     Да и наш подзапрос ложится в Ваш индекс - вообще мелочи

    insert into select Поддерживает? Замечательно! "нельзя использовать ту же таблицу, куда производится вставка." – а мы так и не делаем.

    Посмотрите на свой код.
     1. Формирование списка IN в foreach - это страшно. Вот где точно тормозить должно. Если IN и используете - стремитесь к его минимальному наполнению.
     2. Сперва Вы вытаскиваете (про страшность метода сказано в п.1)  на клиента список существующих отелей.
          Для этого заставляете СУБД просмотреть ВЕСЬ ваш список (в IN его запихнув)
     3. Потом в "неинтересном коде" Вы фильтруете "вставлять/не всталять" - опять просматриваете ВЕСЬ список.
     4. Потом Вы еще раз выбираете вставленные отели … я так понимаю, что это такой же страшный IN  ПОЧТИ ВЕСЬ список - часть отсеялась в п.3
     
    Частенько встречается слово ВЕСЬ. И данные в/из СУБД гоняются многократно.

    Пункт 4 убирается просто. ID записи нужно вставлять вместе с данными … другими словами его надоть узнать ДО вставки. Для этого используются последовательности (SEQUENCE) или их имитация (в гугле полно по этому поводу http://sqlinfo.ru/forum/viewtopic.php?id=813).
    В данном случае можно по другом:
     Вставили одну запись;
     LAST_INSERT_ID() – узнали ID и куда надо его и ставим; (используя мой запрос .. нужно контролировать, изменилось ли значение, ибо там вставка происходит не всегда)

     

Пожалуйста, авторизуйтесь, чтобы написать комментарий!