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




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

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 не будет опубликован. Обязательные поля помечены *