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




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2026-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 не будет опубликован. Обязательные поля помечены *