Расчет хэш-функции в запросе




Принцип обмена данными из 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='\

38 Comments

  1. Diversus

    (0) Смотришь на ваши статьи по запросам и думаешь: «…Так, чем сегодня удивит ildarovich».

    Мне нравятся Ваши статьи и у Вас подход совершенно не 1С-овский к решению задач. Обычно 1С-ники сначала сделают, а потом думают как оптимизировать.

    Вы же идете совершенно по другому пути: теория, вперемешку с тестами и результатом всего этого является красивое и лаконичное решение да еще и запросом.

    Язык запросов 1С сильно ограничен, но глядя на Ваши статьи начинаешь задумываться, а так ли это? 🙂

    Reply
  2. Alien_job

    Я правильно понял — хитрости с хешем нужны потому, что нет конкатенации строк в запросе?

    Reply
  3. Поручик

    Как всегда, поставил зачёт, почитал, и даже подумал.

    Reply
  4. minimajack

    нафига козе боян? Что делать с коллизиями?

    Reply
  5. Alien_job

    Очень любопытное использование запроса. Но разве сложение хешей дает новый хеш?

    (20 + 40*31) * 3 = (30 + 60 * 31)*2. Получим 6 вхождений «хеша»

    Да и число 31 кажется недостаточно большим — символы с кодами большими 31 вносят дополнительную опасность смешивания результатов.

    (62 + 10*31) = (31 + 11*31)

    Касательно кодов номенклатуры — если использовать префиксы, то «кл0000021» == «Мк0000021».

    Reply
  6. minimajack

    (5) Alien_job,

    >число 31 кажется недостаточно большим

    существует объяснение почему именно 31 от Joshua Bloch:

    The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) — i. Modern VMs do this sort of optimization automatically.

    Effective Java

    Reply
  7. ildarovich

    (2) Alien_job, конкатенация могла бы помочь, но до определенного предела. В запросе нельзя сравнивать (группировать) строки длиннее 1024 символа. Поэтому можно будет классифицировать наборы ограниченного количества свойств. А использование хэш-функции таких ограничений не ставит. Кроме того, это всего лишь один из примеров применения данного приема, наверняка найдутся и другие задачи.

    Reply
  8. ildarovich

    (4) minimajack, тут приведено несколько совершенно реальных практических задач, которые быстро и просто решаются данным методом. В данных задачах коллизии не столь страшны. Кроме того, они крайне маловероятны. Ну, крайне редко случайно смешаются два разных набора товаров, получат общий суммарный рейтинг и поднимутся в топ повыше. Или в один класс раз в сто лет смешаются номенклатуры с разными наборами свойств. — Что тут страшного? А для ответственных применений можно разрядность повысить, чтобы сделать вероятность коллизий еще меньше.

    Reply
  9. ildarovich

    (5) Alien_job, формул вычисления хэш-функций очень много. В статье ссылки приведены. Я взял известную, проверенную. Спорить о том, какая лучше, какая хуже бессмысленно, не имея конкретной статистики строк. Если коллизий окажется много, основание степени можно и поменять.

    Арифметическое сложение хэшей дает новый хэш, но худшего качества. Поскольку распределение из равномерного становится нормальным, где в средней части вероятность коллизий выше. Например, по сравнению с поразрядным XOR. В данной задаче это снижение качества не кажется существенным.

    Вообще говоря, первоначально было реализовано вычисление хэша в виде CRC путем моделирования работы регистра сдвига с обратными связями. Однако быстродействие того метода не удалось довести до приемлемых значений, поскольку нужно учитывать отдельные биты кодов символов. Поэтому взята более простая формула. И если она используется в Java, почему бы не использовать ее в запросе.

    Reply
  10. minimajack

    (8) на практике это означает буквально лишение премии.

    пример явно не самый удачный…да и использование хэширования в 1С8 для реальных нужд в больших объемах не могу представить; тут либо производительность, либо качество подведут.

    Reply
  11. kolya_tlt

    (1) Diversus, Такой же подход рекомендуют и на курсах Microsoft в баумантке, поэтому он не только 1совский

    Reply
  12. Synoecium

    (8)

    Ну, крайне редко случайно смешаются два разных набора товаров, получат общий суммарный рейтинг и поднимутся в топ повыше. Или в один класс раз в сто лет смешаются номенклатуры с разными наборами свойств. — Что тут страшного?

    А смысл в алгоритме, который может иногда давать неправильные результаты? Данная задача нормально решается кодом, например, собираем сумму кодов номенклатуры в длинную строку, а потом по этой строке группируем документы, чтобы определить какой набор встречается чаще.

    Конечно это не отменяет полезность хеширования и тема раскрыта интересная.

    Reply
  13. ildarovich

    (12) Synoecium, давайте сначала оценим вероятность неверного выбора Топ-5 наборов при использовании приведенного запроса.

    Пусть есть M действительно разных наборов. Вероятность того, что два из них будет иметь одинаковый хэш, равный сумме хэш-функций кодов номенклатуры, можно оценить числом от (M-1)M/2#k8SjZc9Dxk32/K до (M-1)M/2#k8SjZc9Dxk32, где К — максимальное число товаров в наборе. То есть, если разных наборов 1000, то вероятность будет меньше, чем 999000/4294967296 = 0,000232598.

    Далее нужно учесть то, что хэш должен совпасть у наборов, совместная встречаемость которых попадает в Топ-5. Если 5-е место чаще 10-го в два раза (предположим), то, значит, для попадания суммой в Топ-5 по отдельности наборы должны попадать в Топ-10, что дает еще 10/1000*10/1000=1/10000 шансов. Получается, что неверным выбор будет в 2,32 из 100 000 000 (ста миллиона) случаев.

    Даже если вы хотите перезаложиться на этот маловероятный расклад (как делают начинающие игроки в преферанс, сильно раздражая более опытных игроков), это не будет иметь практического смысла, так как в жизни более вероятным будет несоответствие выписанных товаров реально отпущенным (пересортица) и ваш суперточный расчет перечеркнется невнимательностью кладовщиков. Возможно, есть люди, которые, выходя из дома, надевают каску, учитывая отличную от нуля вероятность попадания в них метеорита (зная площадь земли и количество долетающих до нее метеоритов, эту вероятность тоже можно посчитать). Уподобляясь этим суперперестраховщикам, для данной задачи можно увеличить разрядность хэш-функции до 64-х или посчитать еще один хэш по другому основанию степенного ряда. Тогда вероятность совпадения хэшей наборов уменьшиться еще примерно в 4 миллиарда раз.

    Кажется, этого по любому должно быть достаточно, чтобы доверять сравнению наборов по таким хэшам.

    Reply
  14. Synoecium

    (13) аргументы ваши убедительны, но (говорю только за себя) хочется быть уверенным в своем алгоритме и не держать в голове, что теоретически, он может сбойнуть, хоть и очень редко. Достаточно одного раза, чтобы остался осадок.

    Reply
  15. minimajack

    (14) Synoecium, (13)

    до 15 000 000 (пятнадцать миллионов) значений хеш-функция абсолютна безопасна( с учетом того что коды используются только числовые)

    0000…1

    0000…2

    ……..

    15000000

    коллизий не было

    ИМХО использовать для хэширования можно. Использовать в наборах я бы не стал

    зы на java(i3)10 мил-нов хэшей(10 символов) расчитывается за 4.5 сек

    Reply
  16. fishca

    Хочется только сказать: «ПОБОЛЬШЕ БЫ ТАКИХ АВТОРОВ НА ИНФОСТАРТЕ»

    Reply
  17. ildarovich

    Добавлен Пример 3, показывающий, как можно выбирать из таблиц базы данных необходимое число записей СЛУЧАЙНЫМ образом. Ранее планировал сделать отдельную публикацию на эту тему, но запрос до такой степени прост, что отдельной публикации, кажется, не заслуживает.

    Reply
  18. AlexO
    Например, присоединив к коду элементу справочника строку текущей даты

    Это, опять же, костыль. Нужно добавить Дату.

    И, по сути, это Дата дает «случайный порядок», который вы просто пытаетесь «поймать».

    Reply
  19. ildarovich

    Нужно сказать, что это никакой не костыль, а общий принцип построения генераторов случайных чисел. Все они используют какой-либо «источник энтропии». Многие — текущие время. Можно посмотреть таблицу по ссылке: Генератор псевдослучайных чисел. Из таблицы видно, что, например, в Windows в качестве источника энтропии используется: текущее время, размер жёсткого диска, размер свободной памяти, номер процесса и NETBIOS-имя компьютера. Затем на основе этих данных строится хэш-функция MD5, что и дает в итоге случайное число.

    В нашем случае о криптографии речь не идет и все чуть попроще: источник энтропии — только текущее время, а хэш-функция — «31».

    Reply
  20. AlexO

    (19)

    а хэш-функция — «31».

    Это потому что ответ на «Главный вопрос жизни, Вселенной и всего вообще» уже дан? )))

    Reply
  21. Dach

    Сергей, при попытке выполнить в консоли запросов запрос из Примера 3 возникла следующая ошибка:

    Microsoft SQL Server Native Client 11.0: The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.

    HRESULT=80040E57, SQLSrvr: SQLSTATE=22003, state=1, Severity=10, native=535, line=1

    Версия СУБД MS SQL 2012… слишком большая точность для даты?

    Reply
  22. ildarovich

    (22) Dach, а параметр &ТекущаяДата не забыли проинициализировать? Он должен иметь тип «дата» и включать время до секунд.

    Reply
  23. Dach

    (23) конечно, я же в консоли запросов выполнял запрос.

    Reply
  24. ildarovich

    (24) Dach, очень странно. Вычисляется всего лишь число секунд с 1.01.0001. Это не слишком большое число. Попробуйте тогда взять

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(2015, 1, 1), &ТекущаяДата, СЕКУНДА)

    Суть не в начальной дате. Главное, чтобы результат выражения при каждом следующем выполнении запроса давал разное число. Но а по-поводу диапазона значений аргументов в этой функции в вашей версии сервера — попробую изучить этот вопрос.

    Reply
  25. Dach

    (25) заработало, но очень странно. Все время возвращает одну и ту же выборку, причем состав выборки меняется только если задать другую дату….

    Reply
  26. ildarovich

    (26) Dach, так и было задумано. Энтропия должна быть внесена в запрос извне. В самом запросе нет надежного способа получения непредсказуемых значений. А так получается: перед каждым вызовом определяем текущее время и получаем сколько нужно «случайно-выбранных» элементов справочника. Случайность выбора определяется тем, что момент запуска запроса не предопределен.

    Reply
  27. Dach

    И путем нехитрых экспериментов, методом деления пополам была найдена максимальная дата, начиная с которой идет переполнение длины возвращаемого числа:

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1947, 1, 1) : вот так не работает

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1948, 1, 1), &ТекущаяДата, СЕКУНДА) : вот так работает

    Reply
  28. ildarovich

    (28) Dach, спасибо, на мой взгляд, так быть не должно. Видимо, это ошибка у 1С. А какая версия платформы?

    Reply
  29. Dach

    (27) точно, прошу прощения, невнимательно прочитал описание примера)))

    Хм… то есть если знать источник энтропии — то можно формировать какие-надо выборки… Все думают, что случайно, а оно на самом деле…

    Знаешь энтропию — владеешь миром! ))))

    Reply
  30. Dach

    (29) платформа 8.3.5.1383

    Reply
  31. ildarovich

    (31) Dach, просьба: попробуйте, пожалуйста, вариант вот в таком виде:

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0), &ТекущаяДата, СЕКУНДА) 
    Reply
  32. Dach

    Теперь понятен прикладной смысл задачи вообще и ход Ваших рассуждений. То есть наверное была задача получать случайную выборку чего-нибудь. Как обеспечить случайность? Надо иметь источник энтропии и чтобы исходные данные имели уникальный ключ. Тогда надо в статью добавить фразу, что это работает только для исходных данных, где есть такой ключ — в примере это код справочника «Номенклатура». Для регистра сведений это будет уже набор измерений ну и т.д.

    Reply
  33. Dach

    (32) РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0), &ТекущаяДата, СЕКУНДА) : не работает, та же ошибка

    Reply
  34. ildarovich

    (34) Dach, в общем, понял в чем дело. Разность дат не может быть больше, чем помещается в 32 бита, то есть 2#k8SjZc9Dxk32 = 4294967296, даже меньше 2#k8SjZc9Dxk31 = 2147483648. Тогда получится 18.03.1947 от сегодняшней даты.

    Поправил запрос. Будет еще лет шестьдесят работать.

    Reply
  35. kasper076

    Подтверждаю (22)

    Microsoft SQL Server Native Client 10.0: Функция datediff вызвала переполнение. Слишком большое количество частей даты, разделяющих два экземпляра даты-времени. Попробуйте использовать функцию datediff с частью даты меньшей точности.

    HRESULT=80040E57, SQLSrvr: SQLSTATE=22003, state=1, Severity=10, native=535, line=1

    1С:Предприятие 8.3 (8.3.5.1517) SQL 2008R2

    Reply
  36. ildarovich

    (36) kasper076, поправил теперь в обоих запросах. Чтобы запрос работал в SQL-базе. Нужно было в функции

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1), &ТекущаяДата, СЕКУНДА)

    исправить первый аргумент так

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(2015, 1, 1), &ТекущаяДата, СЕКУНДА)

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

    На самом деле именно разность дат вовсе и не обязательно использовать. В другом варианте того же запроса вместо всего выражения

    РАЗНОСТЬДАТ(ДАТАВРЕМЯ(2015, 1, 1), &ТекущаяДата, СЕКУНДА) * 129082719

    можно ввести в запрос ОДНО случайное число, полученное объектом ГСЧ.

    Reply
  37. yarainboy

    Спасибо

    Reply
  38. ineshyk

    (10) Ну одно из применений хеширования в 1С — это выборочная регистрация объектов на узлах планов обменов.

    Например, у меня есть поле контрагент и номенклатура — при изменении которых есть смысл ставить на регистрацию объект. Сейчас в БСП это работает формированием текста запроса, а насколько удобней такие вещи делать через хеширование? И по производительности, и по качеству это намного лучше того, что имеем сейчас.

    Reply

Leave a Comment

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