Процедуры группировки объекта "ТаблицаЗначений"




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2025-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='\

37 Comments

  1. artbear

    Приведи свой код группировки по Индексированной таблице.

    ИМХО наверняка ты что-то неверно написал 🙁

    Основная беда ИТЗ — сильный расход памяти при группировке.

    Reply
  2. waol

    там все предельно просто

    ЗначениеИзФайла(ИмяФайла,таб);

    табИТ =СоздатьОбъект(«ИндексированнаяТаблица»);

    табИТ.Загрузить(таб);

    табИТ.Группировать(«Контрагент:Контрагент; Док:Док»,»Сумма»,1);

    Reply
  3. waol

    помимо этого я написал процедуру, аналогичную СоздатьГруппировкиВТаблице именно для индексированной таблицы. Но там совсем медленно все протекает.

    Reply
  4. JohnyDeath

    а ты попробуй:

    Код
    табИТ.Группировать("Контрагент:*Контрагент; Док:Док","Сумма",1);

    Показать полностью

    приятно удивишься!

    Reply
  5. JohnyDeath

    Желательно везде ставить "*", т.е. делай так:

    Код
    табИТ.Группировать("Контрагент:*Контрагент; Док:*Док","Сумма",1);

    Показать полностью

    и ещё. вот это не совсем понял:

    Судя по всему, из за того, что в Индексированной Таблице не удаляются колонки при свертке.

    если ты нехочешь, чтоб оставались "дети", то и ставь третий параметр в методе "группировать" = 0

    Reply
  6. waol

    (5). Да, звездочка действительно рулит )). Спасибо за просвещение

    если писать

    табИТ.Группировать(«Контрагент:*Контрагент; Док:*Док»,»Сумма»,1);

    то скорость метода Группировать возрастает, на глаз, этак в 3-4 раза.

    а третий параметр, как я понял из описания, касается только последнего уровня:

    «чРасшифровкаПоследнегоУровня — число, если 1, то в каждой строке последнего уровня группировки будет таблица с расшифровкой, содержащая строки исходной таблицы в нетронутом виде.».

    собственно, так он и работает

    Reply
  7. JohnyDeath

    Ну расскажи теперь про замеры.

    И кстати, надеюсь, ты ставишь старт счетчика до «табИТ.Группировать(«Контрагент:*Контрагент; Док:*Док»,»Сумма»,1);», и не до «ЗначениеИзФайла(ИмяФайла,таб);» ? 😉

    Reply
  8. waol

    (7) я похож на дауна ?? ))

    фактически после применения пресловутой звездочки актуальность этих измерений потеряна. Метод Группировать() явно выигрышный вариант. Правда, попробую еще на больших таблицах, и сделаю окончательный вывод

    Кстати, Евгений, в документации про нее не сказано,

    ты не мог бы просвятить, где бы можно было получить более подробное описание класса (я пользовался описанием, скачанным с 1cpp.ru)?

    Reply
  9. Lapitskiy

    Актуальность не потеряна. Для тех случаев, когда невозможно использовать ВК.

    Reply
  10. JohnyDeath

    (8) в документации написано:

    Синтаксис: Группировать(стрГруппировки, стрКолонкиСумм, [чРасшифровкаПоследнегоУровня = 0])

    Параметры:

    стрГруппировки — тип: Строка. Строка, описывающая требуемую структуру группировки. Задаётся в виде <ИмяИндекса1>: <ИндексноеВыражение1> [; <ИмяИндекса2>: <ИндексноеВыражение2> … ]. ИндексноеВыражение — строка в том же формате, что и для метода ДобавитьИндекс()

    теперь смотрим на метод «ДобавитьИндекс»:

    Синтаксис: ДобавитьИндекс(стрИдентификатор, стрВыражение, [чТолькоУникальныеЗначения = 0])

    Параметры:

    стрИдентификатор — тип: Строка. Идентификатор создаваемого индекса;

    стрВыражение — тип: Строка. Индексное выражение. Индексное выражение состоит из списка идентификаторов колонок, разделённого запятыми. Если перед именем колонки стоит символ ‘-‘, то сортировка осуществляется в обратном порядке. Если перед именем колонки стоит символ ‘*’, то сортировка осуществляется по внутреннему представлению объекта.

    т.е. получается так: если ты не ставишь звёздочку, то идёт ещё одно обращение к БД, за счет чего и начинаются не нужные нам тормоза 😉

    Reply
  11. JohnyDeath

    (9) Lapitskiy, а когда невозможно использовать ВК?

    Reply
  12. waol

    JohnyDeath — твоя правда, моя невнимательность

    Reply
  13. waol

    Lapitskiy если невозможно использовать ВК, то актуальность этих измерений вообще нулевая.

    На больших таблицах (порядка 100000) проверил — 1с на этой процедуре уходит в глубокую задумчивость, из которой вывести удалось через диспетчер задач. Ну может быть, машина у меня слабая. 1с++ довольно резво расправлялась с таблицей свыше 200000 строк, создавая в ней упомянутые две группировки + последняя

    Reply
  14. JohnyDeath

    (13) я считаю, что ТЗ размером в 200000 строк — это слишком. Ведь ты же берёшь их откуда-то? Наверное из БД запросом. Может на этапе запроса выкидывать(группировать,сворачивать) нужные строки? Вряд ли кому-то понадобится отчет, у которого будет столько строк.

    Reply
  15. waol

    я пока только начинаю экспериментировать с прямыми запросами, причем работаю в ДБФ (через СоздатьОбъект(«OLEDBData»)). И пока не смог добиться, чтобы в запросе работала группировка ((.

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

    Reply
  16. artbear

    Прошу автора поправить свое описание про «Выигрыш моей процедуры» 🙂

    Ты же убедился, что ИТЗ группирует шустрее ? 🙂

    Reply
  17. waol

    ок, конечно!

    Reply
  18. waol

    artbear

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

    Reply
  19. JohnyDeath

    (18) это как? поясни.

    Reply
  20. waol

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

    Reply
  21. JohnyDeath

    (20) Обычно интересует сортировка по показателям (сумма, приход, расход и т.д.). Тогда немного непонятна твоя фраза «может дать проигрыш уже при выводе результатов».. Кому дать проигрыш? По сравнению с кем/чем?

    Reply
  22. ADirks

    (21) Вообще-то waol рассуждает вполне здраво. Для вывода сгруппированой ИТ нужно будет по новой создавать индексы, но уже не по внутреннему представлению, а по строковому. К примеру

    ИТ.Группировать(«Товар: *Товар», «Сумма»);

    ИТ.УдалитьИндекс(«Товар»);

    ИТ.ДобавитьИндекс(«Товар», «Товар»);

    вот на последней то строчке мы и проваливаемся. И такую операцию надо будет проделывать со всеми потомками.

    Reply
  23. artbear

    Даже без использования * ИТЗ выигрывает на средних и больших таблицах, на маленьких, естественно, будет выигрыш обычных видов группировок.

    (22) Лех, а ты не думал как-то оптимизировать именно этап создания таблиц-потомков, может быть, написать свой распределитель памяти или еще что?

    Reply
  24. artbear

    (waol) А с другой стороны, опытные пацаны делают так:

    1) как выше написано, группируют уже в запросе

    2) если не получается п.1, значит, они уже в запросе получают доп. поля типа КонтрагентНаименование/КонтрагентКод и т.д. для исключения лишних обращений к базе данных, и далее группировка делается уже не по Контрагент без *, а по КонтрагентНаименование/КонтрагентКод

    Reply
  25. waol

    (24) что касается п.1 — давай предположим, что он не получается

    а что касается п.2 — то группировать по наименованию — не очень корректно, поскольку наименования не всегда уникальны, т.получается группировка Код и еще группировка Наименование, опять таки при условии, что уникален код, т.е. лишняя группировка. В общем — нюансы, и для моего опыта не все так однозначно.

    Reply
  26. artbear

    (25) Ну так тебе никто не мешает использовать еще одно ОГРОМНОЕ преимущество ИТЗ — группировку по нескольким полям, например, Группировать(«Группировка1:КонтрагентНаименование,КонтрагентКод;Группировка2 и т.д.») !

    Ты пойми, я призываю тебя к тому, чтобы исключить/минимизировать лишние обращения к базе данных!

    Reply
  27. artbear

    ИТЗ умеет не только индексировать и группировать по нескольким полям таблицы.

    Такую фишку через штатный код 1С с более-менее подходящим быстродействием универсальным способом так просто не сделаешь 🙂

    А тут халява 🙂

    Reply
  28. JohnyDeath

    (22) Ну это ты написал для сортировки по «наименованию». Тут — да, будет польза, а я писал про сортировку по показателям.

    (25. п.2)

    А ты делай индекс, состоящий из двух колонок: Наименование и Код (предполагая, что эта комбинация уникальна).

    Reply
  29. JohnyDeath

    млин, по п.2 Артур уже опередил…

    Reply
  30. artbear

    (waol) Нужно расшифровывать про минимизацию обращений к базе через запросы? Или нет?

    Reply
  31. waol

    (30) не получилось у меня использовать метод группировки в запросах с использованием

    OLEDBData ((… я уже писал, кажется.

    Если дашь полезный совет — не откажусь ))

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

    Reply
  32. ADirks

    (23) Про возможности сокращения накладных расходов конечно думал, хотя не слишком упорно 🙂

    Правильнее было бы вообще не создавать вложенных таблиц, а создавать некую деревянную структуру для обхода, и отдельно висящие итоговые строки, и каким-то образом организовать обход всего этого добра.

    Reply
  33. artbear

    (31) 1. Весь смысл в том, чтобы ты все необходимые поля доставал в запросе — например, для Контрагентов нужны сам элемент, его наименование, код, в запросе соответственно нужно получить ИД, Наименование, Код.

    Для ИТЗ неважно, каким образом получен запрос — прямой запрос 1С++ или обычный запрос 1С.

    Главное, чтобы ИТЗ брал из таблицы готовые данные, а не пытался обратиться к базе данных.


    Т.е. вместо довольно медленного ИТЗ.Группировать(«Контрагент:Контрагент», «Сумма») нужно юзать что-то вроде ИТЗ.Группировать(«Контрагент:КонтрагентНаименование,КонтрагентКод», «Сумма»)

    .

    2. Максимально оптимально, чтобы и группировка была в запросе, но раз у тебя это пока не получается, можешь делать группировку в ИТЗ, скорости у ИТЗ не столь плохие при соблюдении условия из п.1

    Reply
  34. waol

    artbear в принципе все логично, исходя из предыдущего диалога ))

    довольно полезные советы, благодарствую

    ADirks это уже будет по-видимому другой объект

    за ИндексированнаяТаблица отдельное спасибо, это действительно прорыв!

    сравнимо с появлением обычной ТаблицаЗначений в 77

    Reply
  35. waol

    в процедурах СоздатьГруппировкиВТаблице, СоздатьГруппировкиВТаблицеСВложеннымиПериодами исправлена ошибка. Возникала в тех случаях, когда группировка возникала в последней строке таблицы

    Reply
  36. hlud

    а вобще можно и так :

    ИТЗ.Группировать(«Контрагент:КонтрагентНаименование,*Контрагент»,

    «Сумма»)

    🙂

    Reply
  37. waol

    (36) верно ) за промежуток между 35 и 36 сообщением я не поверишь, это уже понял ) и то что штука наимощнешая в общем тоже

    Reply

Leave a Comment

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