Использование Union вместо OR




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

35 Comments

  1. DJDUH
    OR происходит в одной операции, так что, когда селективность для каждого столбца объединяется и она превышает определенный процент, то сканирование считается более эффективным.

    То я так и не понял «OR» — плохо или нет?

    Reply
  2. Cерый

    Благодарю за статью, тем не менее, читабельность упала вдвое (вам ехать или шашечки?)

    Reply
  3. w.r.

    (1) вообще, по своему опыту могу сказать, что почти без разницы. С OR — один запрос, UNION — 2 запроса с объединением выборок. Поэтому в целом быстрее будет именно OR. Нужны специфические условия, чтобы с UNION юбыл план с index seek, а с OR — index scan и при том выборка была достаточно большой, чтобы UNION был значительно быстрее. Но, в любом случае, нужно смотреть планы конкретных запросов и их анализировать

    Reply
  4. acanta

    В любом случае, если 1сник в запросе напишет

    ИЛИ, то платформа 1с не превратит этот запрос в UNION.

    Reply
  5. w.r.

    (2) в данном случае да. Но когда человек пишет 10 условий OR, тогда читабельность выше наверно у UNION. В таких случаях лучше использовать IN. Так как IN работает эквивалентно OR, при этом воспринимается намного удобнее

    Reply
  6. user-z99999

    (1)

    То я так и не понял «OR» — плохо или нет?

    «OR» — плохо для индексов.

    Поэтому лучше писать через UNION запросы.

    Reply
  7. Cерый

    До сих пор полагал, что СУБД для каждой записи вычислит WHERE, причем за один проход?

    Судя по оригиналу, речь о Microsoft SQL Server …

    Reply
  8. acanta

    А то, что любое СКД основано на условиях в отборах компоновки в списке, в иерархии или не в списке, не в иерархии, это тоже плохо для индексов или там используется какой-то другой механизм ?

    Reply
  9. Diversus

    (3)

    С OR — один запрос, UNION — 2 запроса с объединением выборок. Поэтому в целом быстрее будет именно OR.

    В статье написано, что с OR один скан 121000 строк, а с UNION «Одна ветвь затрагивает 358 строк, а другая — 346 строк».

    Так что надо использовать UNION, так будет быстрее в случае автора. Да и 1С дает точно такую же рекомендацию в системе стандартов и методик разработки (см скриншот).

    Источник: Глава «Использование логического ИЛИ в условиях»

    Reply
  10. herfis

    Спасибо за перевод (если свой). Хорошее объяснение, почему конкретно UNION как правило эффективней.

    Так-то это давно известный способ оптимизации тяжелых запросов.

    Reply
  11. herfis

    (1) OR — хорошо. Но UNION зачастую лучше, если нужна оптимизация.

    Reply
  12. w.r.

    (9) так получилось, потому что в плане в первом случае index scan, во второму index seek

    Reply
  13. Diversus

    (12) Я про то, что в (3) вы пишите:

    вообще, по своему опыту могу сказать, что почти без разницы. С OR — один запрос, UNION — 2 запроса с объединением выборок. Поэтому в целом быстрее будет именно OR.

    А это не верное утверждение. OR в целом будет не быстрее.

    Reply
  14. w.r.

    (11) не всегда. Например реальный случай, если выборка одного из условий очень большая. Тогда план запроса одной из веток при Union все-равно будет построен с использованием index scan, то есть будет просканирован весь индекс. А это нивелирует вторую ветку с поиском по индексу (index seek). Можете проверить, если хотите

    Reply
  15. w.r.

    (13) не быстрее будет только в одном случае — если при OR план будет использовать index scan, а при Union — index seek во всех ветках. Если хотя бы в одной ветке Union будет использоваться index scan, то OR будет даже немного быстрее. Кстати, если записей в целом выбирается немного план выполнения OR тоже использует поиск по индексу. Тем более clustered index scan сам по себе довольно быстрый

    Reply
  16. Diversus

    (15)

    не быстрее будет только в одном случае — если при OR план будет использовать index scan, а при Union — index seek во всех ветках. Если хотя бы в одной ветке Union будет использоваться index scan, то OR будет даже немного быстрее. Кстати, если записей в целом выбирается немного план выполнения OR тоже использует поиск по индексу. Тем более clustered index scan сам по себе довольно быстрый

    А как так может получиться, что при OR будет index scan, а при Union — index seek? Если учитывать, что условия по одним и тем же полям?

    Мне кажется, то о чем вы говорите быть даже теоретически не может.

    На сайте 1С классный пример на эту тему:

    Через ИЛИ

    ВЫБРАТЬ Товар.Наименование ИЗ Справочник.Товары КАК Товар ГДЕ Артикул = «001» ИЛИ Артикул = «002»

    Через ОБЪЕДИНИТЬ ВСЕ

    ВЫБРАТЬ Товар.Наименование ИЗ Справочник.Товары КАК Товар ГДЕ Артикул = «001»
    |ОБЪЕДИНИТЬ ВСЕ
    |ВЫБРАТЬ Товар.Наименование ИЗ Справочник.Товары КАК Товар ГДЕ Артикул = «002»

    Обратите внимание, речь идет в данном случае о том, что есть ОДИНАКОВОЕ поле для отбора.

    Reply
  17. herfis

    (14)

    не всегда.

    Дык «зачастую» <> «всегда». «Зачастую» = «часто»

    А еще чаще UNION рулит, когда избавляешься от OR в условии соединения.

    Reply
  18. herfis

    (15)

    Тем более clustered index scan сам по себе довольно быстрый

    Смотря с чем сравнивать. Если есть покрывающий обычный индекс, или объем извлекаемых данных не из индекса невелик, то по обычному индексу будет быстрее чем по кластерному.

    Reply
  19. skv_79

    Как показывает практика использование OR лучше отказываться в пользу UNION. Даже если на первый взгляд разницы нет, если количество выборки будет увеличиваться, а со временем обычно так и происходит, то UNION начнет в скорости превосходить.

    Reply
  20. herfis

    (19) Такое… Я бы назвал это преждевременной оптимизацией. Читабельность и простоту кода уже ухудшили, а пригодится ли эта оптимизация — неизвестно. Ессно, когда известно заранее — тогда стоит сразу заложиться. Тут без вопросов.

    Reply
  21. w.r.

    (19) проверял на таблице в несколько миллионов строк OR vs Union. OR немного быстрее, так как в плане, из-за большого количества записей по отборам, и там и там использовалось полное сканирование индекса (index scan)

    Reply
  22. w.r.

    (16) вы невнимательно читали статью. Селективность при OR объединяется, и когда она достигает большого значения, сервер вместо поиска по индексу (index seek) выбирает полное сканирование индекса (index scan). При Union селекивность не объединяется, так как по сути это несколько выборок, объединённых в общий набор записей

    Reply
  23. Diversus

    (22)

    вы невнимательно читали статью. Селективность при OR объединяется, и когда она достигает большого значения, сервер вместо поиска по индексу (index seek) выбирает полное сканирование индекса (index scan). При Union селекивность не объединяется, так как по сути это несколько выборок, объединённых в общий набор записей

    Вы написали, что OR в целом будет быстрее чем UNION в подобных запросах. Я ответил, что нет и привел ссылку на сайт 1С и на систему стандартов и методик.

    Так какой ответ верный по вашему мнению?

    Reply
  24. w.r.

    (23) проблема в том, что и при Union может быть большая селективность или в OR очень маленькая. И так чаще всего и происходит. Проверял это на реальных данных — анализировал планы запроса

    Reply
  25. Diversus

    (24)

    Проверял это на реальных данных — анализировал планы запроса

    Вы так и не ответили на вопрос.

    Рекомендации 1С использовать всегда UNION в подобных запросах ошибочна или нет?

    Reply
  26. w.r.

    (17) не согласен. По моим наблюдениям все наоборот. В условиях соединений — может быть, нужно смотреть опять же план запроса

    Reply
  27. w.r.

    (25) вы опять невнимательно читали свою же ссылку. Там написано, что ИЛИ не рекомендуется использовать в условиях соединения запросов, то есть при СОЕДИНЕНИЕ…ПО (JOIN…ON), а не в ГДЕ (WHERE)

    Reply
  28. Diversus

    (27)

    вы опять невнимательно читали свою же ссылку. Там написано, что ИЛИ не рекомендуется использовать в условиях соединения запросов, то есть при СОЕДИНЕНИЕ…ПО (JOIN…ON), а не в ГДЕ (WHERE)

    В смысле не внимательно? Это вы не внимательны.

    Цитата из статьи 1С:

    Не следует использовать ИЛИ в секции ГДЕ запроса.

    И статья про это же самое, даже примеры совпадающие на сайте 1С и в переводе.

    Reply
  29. w.r.

    (28) извиняюсь сейчас не с компьютера и статья открылась не полностью ваша. По поводу этой выдержки из вашей ссылки

    Не следует использовать ИЛИ в секции ГДЕ запроса. Это может привести к тому, что СУБД не сможет использовать индексы таблиц и будет выполнять сканирование, что увеличит время работы запроса и вероянтность возникновения блокировок. Вместо этого следует разбить один запрос на несколько и объединить результаты.

    Во первых, переведённая статья совсем не о том. Посмотрите в планы запроса на скриншотах. Индексы там везде используются, только идёт или поиск по индексу или сканирование индекса. Во вторых, рекомендации возможно устарели. И так было лет 20 назад чтобы индексы не использовались при условии OR в запросах SQL Server

    Reply
  30. Yashazz

    Методика давно известна, холивар давно наскучил, а вот за труды по переводу, если сами переводили, плюсую.

    Reply
  31. w.r.

    (30) тут и не может быть в принципе холивара. Холивары разводят как раз любители разных методик. Каждый отдельный запрос должен анализироваться, исходя из конкретной ситуации, и выдаваться рекомендации

    Reply
  32. triviumfan

    (25) Не всегда, а лишь рекомендация. В большинстве случаев OR работает быстрее.

    Reply
  33. w.r.

    (25) поэтому и говорю, что каждый конкретный случай нужно рассматривать и анализировать план запроса

    Reply
  34. Ndochp

    (32) Аще-то или работает медленнее, но оно понятнее человекам. Так как большинство запросов для людей, то нужно писать ИЛИ, а в нагруженных местах ставить Объединить.

    Reply
  35. triviumfan

    (34) Спасибо за глупый комментарий «человеков-архитекторов».

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *