Расчёт медианы числовых рядов запросом




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

14 Comments

  1. NeviD

    Основная сложность присвоить порядковый номер в запросе. Интересное решение через свое поле Ключ, но этот вариант применим, если есть такие поля, по которым можно его посчитать. Если просто передавать в запрос только список чисел, то так сделать уже не получится.

    Ради интереса написал запрос, который выполняет нумерацию заданных значений. В нем есть заранее установленное ограничение на максимальное количество одинаковых чисел (в примере 256).

    ВЫБРАТЬ
    0 КАК Ч
    ПОМЕСТИТЬ Ц
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
    1
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    Ц.Ч + Ц2.Ч * 2 + Ц4.Ч * 4 + Ц8.Ч * 8 + Ц16.Ч * 16 + Ц32.Ч * 32 + Ц64.Ч * 64 + Ц128.Ч * 128 КАК Ч
    ПОМЕСТИТЬ ЧЧ
    ИЗ
    Ц КАК Ц,
    Ц КАК Ц2,
    Ц КАК Ц4,
    Ц КАК Ц8,
    Ц КАК Ц16,
    Ц КАК Ц32,
    Ц КАК Ц64,
    Ц КАК Ц128
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    Числа.Число КАК Число
    ПОМЕСТИТЬ Числа
    ИЗ
    &Числа КАК Числа
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    Числа.Число,
    КОЛИЧЕСТВО(*) КАК КоличествоЧисел
    ПОМЕСТИТЬ ЧислаСКолвом
    ИЗ
    Числа КАК Числа
    
    СГРУППИРОВАТЬ ПО
    Числа.Число
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    ЧислаСКолвом.Число,
    ЧислаСКолвом.КоличествоЧисел,
    СУММА(ЕСТЬNULL(ЧислаСКолвом1.КоличествоЧисел, 1) — 1) КАК Доп
    ПОМЕСТИТЬ ЧислаСКолвомДоп
    ИЗ
    ЧислаСКолвом КАК ЧислаСКолвом
    ЛЕВОЕ СОЕДИНЕНИЕ ЧислаСКолвом КАК ЧислаСКолвом1
    ПО ЧислаСКолвом.Число > ЧислаСКолвом1.Число
    
    СГРУППИРОВАТЬ ПО
    ЧислаСКолвом.Число,
    ЧислаСКолвом.КоличествоЧисел
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    ЧислаСКолвомДоп.Число,
    ЧислаСКолвомДоп.Доп,
    ЧЧ.Ч КАК НомерЧисла
    ПОМЕСТИТЬ ЧислаСНомером
    ИЗ
    ЧЧ КАК ЧЧ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЧислаСКолвомДоп КАК ЧислаСКолвомДоп
    ПО ЧЧ.Ч < ЧислаСКолвомДоп.КоличествоЧисел
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    ЧислаСНомером.Число,
    КОЛИЧЕСТВО(*) + ЧислаСНомером.Доп КАК ПорядковыйНомер
    ИЗ
    ЧислаСНомером КАК ЧислаСНомером
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЧислаСНомером КАК ЧислаСНомером1
    ПО ЧислаСНомером.Число >= ЧислаСНомером1.Число
    И (ВЫБОР
    КОГДА ЧислаСНомером.Число = ЧислаСНомером1.Число
    ТОГДА ЧислаСНомером.НомерЧисла >= ЧислаСНомером1.НомерЧисла
    ИНАЧЕ ЧислаСНомером1.НомерЧисла = 0
    КОНЕЦ)
    
    СГРУППИРОВАТЬ ПО
    ЧислаСНомером.Число,
    ЧислаСНомером.НомерЧисла,
    ЧислаСНомером.Доп
    

    Показать

    Reply
  2. SpaceOfMyHead

    (1) NeviD, Согласен. Если мы ищем медиану числового ряда, написанного на бумаге или отрисованного на мониторе, то каждый элемент уникален хотя бы своим физическим расположением на носителе (бумага, монитор), даже если содержит одинаковые значения. Поэтому не прокатит

    просто передавать в запрос список чисел.

    Сильно важно это понимать.

    Вариант с ключом применим в принципе всегда. Просто иногда придётся содержать служебные таблицы (регистр сведений, например) с полями: «Объект», «Ключ». Тип объекта составной, тип ключа — число. ok.

    Reply
  3. bulpi

    Вместо ЦеныНоменклатуры.Регистратор.Дата лучше использовать ЦеныНоменклатуры.Период, быстрее будет.

    Reply
  4. SpaceOfMyHead

    (3) bulpi, верно, упустил! Благодарю, поправил

    Reply
  5. serg_infostart

    Да, интересный ход! Плюсую.

    Reply
  6. ildarovich

    Идея составления ключа интересная, работающая. Тот же прием (сложение числа из периода со значением) используется, например, при решении задачи 4 в статье Минимализмы.

    Но в этой задаче, кажется, легче было разделить записи при равенстве цены дополнительной проверкой, то есть записать условие в виде

    ПО (втЦеныНоменклатуры.Цена > втЦеныНоменклатуры1.Цена ИЛИ втЦеныНоменклатуры.Цена = втЦеныНоменклатуры1.Цена И втЦеныНоменклатуры.Период >= втЦеныНоменклатуры1.Период)
    И втЦеныНоменклатуры.Номенклатура = втЦеныНоменклатуры1.Номенклатура

    Вообще, если цен много, то запрос получится (из-за использования знака неравества) трудоемким. Простого решения этой проблемы в общем случае нет, но вот если бы номенклатура была единственной, то медиану цены можно было бы найти таким приемом: записать таблицу N/2 пустых записей ПЕРЕД таблицей цен, а затем выбрать ПОСЛЕДНЮЮ 1 ИЗ ПЕРВЫХ N записей.

    Для иллюстрации приведу текст запроса, с которым можно поэкспериментировать в консоли. В зависимости от переданного в запрос номера он выбирает соответствующую запись из НЕПРОНУМЕРОВАННОЙ таблицы Дано:

    ВЫБРАТЬ
    0 КАК Х
    ПОМЕСТИТЬ Бит
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    1
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    NULL КАК Х
    ПОМЕСТИТЬ Сдвиг
    ИЗ
    Бит КАК Б0,
    Бит КАК Б1,
    Бит КАК Б2,
    Бит КАК Б3,
    Бит КАК Б4,
    Бит КАК Б5,
    Бит КАК Б6,
    Бит КАК Б7
    ГДЕ
    Б0.Х + 2 * (Б1.Х + 2 * (Б2.Х + 2 * (Б3.Х + 2 * (Б4.Х + 2 * (Б5.Х + 2 * (Б6.Х + 2 * Б7.Х)))))) < 256 — &Номер
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    1 КАК Х
    ПОМЕСТИТЬ Дано
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    2
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    3
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    4
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    5
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    6
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    7
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    8
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    9
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    10
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ ПЕРВЫЕ 2
    ВЗ.Х
    ИЗ
    (ВЫБРАТЬ ПЕРВЫЕ 256
    ВЗ.Х
    ИЗ
    (ВЫБРАТЬ
    Сдвиг.Х
    ИЗ
    Сдвиг КАК Сдвиг
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
    Дано.Х
    ИЗ
    Дано КАК Дано) КАК ВЗ
    
    УПОРЯДОЧИТЬ ПО
    Х) КАК ВЗ
    
    УПОРЯДОЧИТЬ ПО
    Х УБЫВ

    Показать

    Reply
  7. mmch

    В свое время рассчитал медиану средствами СКД…, может кому этот путь покажется проще..

    ВЫБОР
    КОГДА ВычислитьВыражение(«Количество(Медиана)»,,, «Первая», «Последняя») % 2 = 0 ТОГДА
    ВычислитьВыражение(«Среднее(Медиана)»,,, «Первая(«+Формат((ВычислитьВыражение(«Количество(Медиана)»,,, «Первая», «Последняя»)-1) / 2, «ЧДЦ=0; ЧГ=0″)+»)», «Первая(«+Формат((ВычислитьВыражение(«Количество(Медиана)»,,, «Первая»,  «Последняя»)+1) / 2, «ЧДЦ=0; ЧГ=0″)+»)»)
    ИНАЧЕ
    ВычислитьВыражение(«Среднее(Медиана)»,,, «Первая(«+Формат((ВычислитьВыражение(«Количество(Медиана)»,,, «Первая», «Последняя»)+1) / 2, «ЧДЦ=0; ЧГ=0″)+»)», «Первая(«+Формат((ВычислитьВыражение(«Количество(Медиана)»,,, «Первая»,    «Последняя»)+1) / 2, «ЧДЦ=0; ЧГ=0″)+»)»)
    КОНЕЦ
    Reply
  8. mmch

    но тут есть ограничение… обязательная сортировка по параметру для которого считается медиана

    Reply
  9. mmch
  10. Ovrfox

    (6) Проще Выбрать максимум из n/2 первых, отсортированных по возрастанию, ничего не добавляя в исходные данные

    Но Вот Вопрос — как выбрать первые n/2 записей, если это значение не известно?

    В чистом SQL для этого можно было бы воспользоваться процедурой sp_executesql

    Reply
  11. ildarovich

    (10) Ovrfox, понятно, что первые n/2 решают эту задачу. Динамическое построение запроса в 1С тоже поможет.

    Я предложил добавить искусственную таблицу перед основной, чтобы запрос не переформировывать, чтобы можно было твердо написать ПЕРВЫЕ 256, например.

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

    Задача с медианой не очень жизненная. Другой задачи на этот прием пока в голову не приходит. Может, задача и найдется со временем. А прием уже вот, готовый.

    Если бы задача с медианой действительно была актуальной, ее можно было бы быстро решить (в запросе) последовательным делением пополам, но это — из пушки по воробьям.

    Reply
  12. Ovrfox

    (11) Почитал (9) и прозрел.

    Reply
  13. legionne

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

    Reply
  14. relines

Leave a Comment

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