Сравнение двух таблиц значений.




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

10 Comments

  1. awk

    Не буду комментировать, дабы не обижать.

    Reply
  2. brodya
    Для каждого строкаТЗ1 из ТЗ1 цикл

    Дальше читать не стал.

    ТЗ1 = Новый ТаблицаЗначений;
    ТЗ1.Колонки.Добавить(«Первая»);
    а = ТЗ1.Добавить();
    а.Первая = 1;
    
    ТЗ2 = Новый ТаблицаЗначений;
    ТЗ2.Колонки.Добавить(«Первая»);
    а = ТЗ2.Добавить();
    а.Первая = 1;
    а = ТЗ2.Добавить();
    а.Первая = 2;
    

    Показать

    Такие таблицы получаются «ПолностьюИдентичны».

    Reply
  3. awk

    Как-то так проще, функциональней и надежней.

    
    Функция Сравнить(ТаблицаОбразец, Таблица, СоответствиеКолонок=Неопределено, МассивИсключаемыхКолонок=Неопределено, Точность=Неопределено, ПравилаОкругления=Неопределено, ИгнорироватьИдентичныеВРезультате=Истина)
    // Проверка входящих параметров
    
    Если ПравилаОкругления=Неопределено Тогда
    ПравилаОкругления = РежимОкругления.Окр15как20;
    КонецЕсли;
    
    Массив = Новый Массив();
    Результат = Новый Структура(«Имя, КоличествоВОбразце, Количество, Критерий»,»КоличествоСтрок»,ТаблицаОбразец.Количество(),Таблица.Количество(),Таблица.Количество()=ТаблицаОбразец.Количество());
    Если Не ИгнорироватьИдентичныеВРезультате ИЛИ Не Результат.Критерий Тогда
    Массив.Добавить(Результат);
    КонецЕсли;
    
    Количество = Мин(ТаблицаОбразец.Количество(), Таблица.Количество()) — 1;
    ТипЧисло =  Тип(«Число»);
    ТипСтрока =  Тип(«Строка»);
    
    Если МассивИсключаемыхКолонок<>Неопределено и ТипЗнч(МассивИсключаемыхКолонок) = ТипСтрока Тогда
    МассивИсключаемыхКолонок = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(МассивИсключаемыхКолонок,,Истина);
    КонецЕсли;
    
    Если СоответствиеКолонок=Неопределено Тогда
    СоответствиеКолонок = Новый Соответствие;
    Для каждого Колонка Из ТаблицаОбразец.Колонки Цикл
    Если Таблица.Колонки.Найти(Колонка.Имя) = Неопределено
    ИЛИ (МассивИсключаемыхКолонок<>Неопределено И
    МассивИсключаемыхКолонок.Найти(Колонка.Имя) <> Неопределено) Тогда
    Продолжить;
    КонецЕсли;
    СоответствиеКолонок.Вставить(Колонка.Имя, Колонка.Имя);
    КонецЦикла;
    КонецЕсли;
    
    // Это можно вынести в функцию без параметров по умолчанию
    
    Для ит = 0 По Количество Цикл
    СтрокаОбразец = ТаблицаОбразец[ит];
    Строка = Таблица[ит];
    Для каждого КиЗ Из СоответствиеКолонок Цикл
    ТипОбразец = ТипЗнч(СтрокаОбразец[КиЗ.Ключ]);
    Тип = ТипЗнч(Строка[КиЗ.Значение]);
    Результат = Новый Структура(«Имя, Образец, Поле, Критерий, НомерСтроки»);
    Результат.НомерСтроки = ит;
    Результат.Имя = «Сравнение строки №»+ит;
    Результат.Образец = КиЗ.Ключ;
    Результат.Поле = КиЗ.Значение;
    Если Точность<>Неопределено И Тип = ТипОбразец И ТипОбразец = ТипЧисло Тогда
    Результат.Критерий = Окр(СтрокаОбразец[КиЗ.Ключ], Точность, ПравилаОкругления) = Окр(Строка[КиЗ.Значение], Точность, ПравилаОкругления);
    Иначе
    Результат.Критерий = СтрокаОбразец[КиЗ.Ключ] = Строка[КиЗ.Значение];
    КонецЕсли;
    Если Не ИгнорироватьИдентичныеВРезультате ИЛИ Не Результат.Критерий Тогда
    Массив.Добавить(Результат);
    КонецЕсли;
    КонецЦикла;
    КонецЦикла;
    Возврат Массив;
    КонецФункции
    
    
    

    Показать

    Reply
  4. Yashazz

    Есть такая вещь, как соединение таблиц в запросе. Если речь о сравнении колонок с типами, обрабатываемыми запросом (т.е. безо всяких там массивов и хранилищ значений), то можно и проще сделать, через запрос/СКД. А если ещё функции СКД подтянуть…

    Reply
  5. ildarovich

    Не хочется обсуждать очень уязвимый для критики вариант из статьи, а вот вариант (3) вполне себе «зрелый». Выскажусь не в плане критики этого варианта (это хороший и практичный вариант), а в плане того, что можно сделать по-другому.

    1) Симметричность. Было бы здорово сравнивать таблицы, не выбирая таблицу-образец. То есть, чтобы при неравном числе строк выводились бы элементы разницы. В данном же случае, когда в проверяемой таблице строк больше, чем в образце, это не делается.

    2) Маппинг колонок можно сделать проще — без соответствия. Для этого достаточно скопировать исходные таблицы, задав копируемые колонки. То есть два параметра: имена колонок через запятую из первой таблицы в первом параметре и имена колонок через запятую из второй таблицы во втором параметре. Тогда сравнивать можно будет колонки копий с одинаковым номером. И не потребуется раскладывать строки сторонней функцией.

    3) Чувствительность сравнения. Тут либо описывать правила сравнения для всех простых типов: для строкового учет регистра и длины (или даже расстояния расстояние левенштейна), для дат — период точности (до секунд, до дней), для чисел — число знаков, либо уж ничего не задавать, а всегда делать точное сравнение. Потому что иначе идет перекос в сторону сравнения числовых таблиц значений и так и нужно будет назвать функцию.

    4) Тип результата. Кажется, лучше помещать дельту не в массив структур, а в таблицу значений «Разница» с колонками: номер строки, номер колонки, значение в первой таблице, значение во второй таблице. Так у нас будет меньше разных сущностей. И тогда легче будет манипулировать с результатом в дальнейшем — группировать строки, сортировать, отбирать и так далее.

    Кстати, для точного сравнения без маппинга можно использовать

    ЗначениеВСтрокуВнутр(Таблица1) = ЗначениеВСтрокуВнутр(Таблица2)
    Reply
  6. awk

    (5) ildarovich, Не могу не согласиться. Особенно если учесть, что функция написана целиком с 12-44 по 13-19 (не забываем прибавить время на чтение чужого, …(вместо точек сами вставляйте) кода). :))) если в статье декларировать, что это шаблон (пусть будет идеальным), то скорость поиска/вспоминания шаблона, как мне кажется будет минут 10-20, что всего в два-три раза быстрее написания.

    Когда писал специально применил похожий на статью подход (по индексно). Хотя для сравнения у пользователей есть прекрасный инструмент сравнение значений(от 1С) или KDIFF. Для программистов же важнее либо А=Б или А!=Б. Если А!=Б, то какие строки в каких столбцах.

    Если уж делать различия типа стока добавлена/удалена/изменена, то не по принципу добавлены все строки которых больше чем в образце, а первая измененная строка делает измененными все строки после нее.

    Reply
  7. awk

    (4) Yashazz, Ну таки да, но для этого, иногда, надо вызов сервера делать, что не всегда приемлемо.

    Reply
  8. Lokiy

    Вся критика принимается, но когда мне надо было функцию для сравнения таблиц побыстрому, я не нагуглил, поэтому выложил чтобы кто-то кому тоже надо будет быстро — нагуглит. Ну нету на stackoverflow 1Са, а зря :).

    Reply
  9. AKV77

    Еще один вариант сравнения, который применяется в типовой УПП :

    Функция СравнитьТаблицыЗначений(ТаблицаЗначений1, ТаблицаЗначений2)
    
    Если ТипЗнч(ТаблицаЗначений1) <> Тип(«ТаблицаЗначений») ИЛИ ТипЗнч(ТаблицаЗначений2) <> Тип(«ТаблицаЗначений») Тогда
    Возврат Ложь;
    КонецЕсли;
    
    Если ТаблицаЗначений1.Количество() <> ТаблицаЗначений2.Количество() Тогда
    Возврат Ложь;
    КонецЕсли;
    
    Если ТаблицаЗначений1.Колонки.Количество() <> ТаблицаЗначений2.Колонки.Количество() Тогда
    Возврат Ложь;
    КонецЕсли;
    
    Для каждого Колонка Из ТаблицаЗначений1.Колонки Цикл
    
    Если ТаблицаЗначений2.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
    Возврат Ложь;
    КонецЕсли;
    
    Для каждого СтрокаТаблицы Из ТаблицаЗначений1 Цикл
    
    Попытка
    
    Если СтрокаТаблицы[Колонка.Имя] <> ТаблицаЗначений2[ТаблицаЗначений1.Индекс(СтрокаТаблицы)][Колонка.Имя] Тогда
    
    Возврат Ложь;
    
    КонецЕсли;
    
    Исключение
    
    Возврат Ложь;
    
    КонецПопытки;
    
    КонецЦикла;
    
    КонецЦикла;
    
    Возврат Истина;
    
    КонецФункции // СравнитьТаблицыЗначений()
    

    Показать

    Reply
  10. pakill
    Reply

Leave a Comment

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