<?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='\
Мелкие замечания.
1. На мой взгляд , лучше говорить «второй запрос в пакете» , а не » второй подзапрос».
2. Как-то надо обозначить опасность применения в запросе соединения по неравенству.
При розничной торговле с гибкой политикой цен пользователь может быть сильно разочарован таким отчетом. «В среднем » более надежно отсечь период до «Даты начала» и вначале использовать соединение с виртуальной таблицей СрезПоследних и получить цены на «дату начала» для каждой номенклатуры. А затем уже делать соединение по неравенству с регистром , используя условие для записей регистра «Где Период >= &ДатаНачала»
1. Спасибо, поправил
2. Ну все таки отчет приведенный в статье не имеет более общий характер. Соглашусь с Вашим мнением, но все же это уже относиться к реальным задачам. Статья — просто пример 😉
Метод, примененный в СКД очень неэффективен, т.е. будет работать значительно медленнее, чем пакете/запросе.
Спасибо, очень интересные решения. Кстати, в одной из задач на экзамене по Бух есть именно такая задача по построению отчета.
Находил иные решения на Мисте, но против данных решений — они просто монстры.
Красиво и просто
Реализация всевдо среза первых и последних записей на каждый день периода для расчета среднесписочной численности:http://infostart.ru/public/77462/
(3) в СКД можно добиться некоторых плюсов, если некоторые данные не нужны. У них можно установить флаги ограничений, и СКД не будет их получать. А так согласен в подавляющем большинстве случаев запрос эффективнее.
Вот тутhttp://infostart.ru/public/21181/ моя статья по той же теме.
Запросы это хорошо , обожаю запросы 🙂 , вот вам еще один вариант, может кто сможет еще короче написать:
Показать
Кстати во втором запросе есть ошибки и недочеты;
1) в первом запросе данные группировать НЕ надо! т.к. данные из виртуальной таблицы уже сгруппированы
2) во втором запросе не учитывается типцен, поэтому он выдаст направильный результат, если есть несколько типов цен
3) в третьем запросе условие на типцен должно быть в соединении а не в условии, поэтому третий запрос вернет только продажи той номенклатуры, для который установлена цена &ТипЦен
(10) Проверить не могу. Навскидку :
Выглядит симпатично. Могу предположить , что работает быстрее ,чем пример в статье.
И думаю, вот почему : твой запрос не требует создания дополнительных временных индексов , а использует уже существующие индексы регистра. Насколько я понимаю, все выражения в условии соединения составлены так , что не требуют «дополнительных» усилий от оптимизатора запроса. Ну , а сам то ты сравнивал по быстродействию свой запрос и запрос из статьи ?
(9) ух.. глазастый какой.
Смотри (10).
(11) сравнивал, но на небольшой базе, использовал модифицированный запрос из (0), убрал АВТОУПОРЯДОЧИВАНИЕ и исправил ошибки, в итоге мой запрос совсем чуть-чуть быстрее, а если добавить АВТОУПОРЯДОЧИВАНИЕ, то мой запрос заметно проигрывает, прямо даже на глаз видно, не знаю с чем это связано. Буду на работе попробую, если будет время, на большой базе. Есть у нас клиент, у которого очень много типов цен, интересно на такой базе проверить. Я думаю, что в такой базе, мой запрос может показать лучшие результаты, из-за того что в запросе из (0) второй запрос по идее должен выполнятся долго, т.к. это та же проблема что и с нарастающими итогами.
(12) Ага. Интересно проверить и сравнить оптимальный, исправленный запрос из (0) и твой. Причем в твоем запросе , та же проблема что и с нарастающими итогами.
Если таблицу регистра цен увеличить в 2 раза то длительность твоего запроса , думаю, увеличится как минимум в 3 раза.
И вот еще что . Как и в (0) нужно , на мой взгляд, отсечь период до «Даты начала» сделав вначале соединение со срезом последних регистра цен.
(12) +13 Ведь твой вложенный запрос в (8) выполнится столько раз — сколько записей в регистре ЦеныНоменклатуры (если продажи анализируются для всей номенклатуры и без ограничения периода). Еще раз повторяю — выглядит симпатично , но нужно проверять.
С другой стороны, если бы поле выборки языка запроса 1с могло быть запросом (как во всех диалектах SQL), то :
Показать
В этом случае вложенный запрос выполнится столько раз сколько записей в основном запросе.
А вообще, пост (14) об относительности совершенства любого алгоритма.
С точки зрения специалиста 1с — приведенный в (0) алгоритм можно считать образцом.
Cпециалист же знакомый с SQL воскликнет :
«Какое извращение ! Из за ограничений языка запросов 1с ребятам приходится кувыркаться и изобретать велосипед с квадратными колесами (0)»
(12) насчет среза согласен, но в этом случае я бы делал как в статье, а то мой запрос из красивого и короткого превратится длинный и некрасивый
Народ, ну вам же не надо объяснять, что «длинный» и «некрасивый» запрос 1С 8 — это НЕ всегда неоптимально и малоэффективно… 😀
Тоже долго думал как собрать среднесписочное количество сотрудников, добавил справочник «ПериодыУчета» с 3-мя полями: «ДатаНачала», «ДатаОкончания», «ВидПериода»(День,Месяц,Год). В результате получить по дням можно все что угодно, при этом запрос остается читабельным и компактным. Ну а для регистров накопления конечно лучше использовать параметр виртуальной таблицы «День».
(12) Так, что , Игорь , поговорили … и всё ?
В (8) ты привел свой альтернативный вариант запроса. Давай уж выясним до конца :
Быстрее ли он работает , чем вариант приведенный в статье (0) ?
На больших объемах данных. Что скажешь ?
(19) может быть и не быстрее, зато красиво. С группировками способ все знают, а мой наверное меньше. Я не против проверить, но бызы нет под рукой, могу только на работе. Вообще идея просто сгенерировать базу, в которой будет много номенклатуры(? только сколько)и например каждый день записана цена и каждая номенклатура продавалась каждый день 1 раз. А вообще я ща в гости иду, так что сегодня некогда.
(20) Много не пей. Послезавтра ..проверять запрос.
(22) Знал, что ты ответишь !
Задал ты задачку.
Так что же лучше «в среднем» для обычной работы в УТ ?
Очень похоже , что при отработке твоего запроса при первом запуске sql-сервер создает и грузит в память ВЕСЬ индекс регистра по полю Период. Именно работа по этому индексу съедает 90% времени. При последующих запусках этого не происходит поэтому и время всего 10 сек.
НО.
Если после первого запуска произошли серьезные изменения в регистре цен (пользователи начали работу), то старый индекс в кэше не может быть использован и он должен быть пересоздан. И тогда при следующем запуске твоего отчета — снова тормоза ?
Свою первую статью помнишь ? С тех пор прогнозирую с опаской.
Так что = это всего лишь предположения.
Если они верны , то тогда я бы отдал предпочтение запросу приведенному в статье ( со всеми необходимыми исправлениями и добавлениями)
(0) Про команду «Индексировать».
На мой взгляд явно создавать индекс при помощи этой команды нужно только при многократном обращении к таблице . При однократном обращении этого делать не нужно — оптимизатор запроса сам создаст нужный индекс в нужном ему виде.
Казалось бы это замечание малосущественно. Но.
Мало того , что явное создание индекса командой «Индексировать» при однократном обащении не дает выигрыша —
оно еще и даёт очень существенный проигрыш.
На этот эффект мы натолкнулись с Alex-is при тестировании Примера 3 в его «Заметочках…».
(23) а как узнать точно что скл делал всё это время, план выполнения я смотрел и он не меняется
(24) а насчет индексов: с индексами где-то раза в два медленнее выполнялся запрос
(25) Прав я или нет — можно узнать если на горячем сервере после первого запуска твоего отчета в регистр Цены занести 1000 новых записей. И после этого запустить твой отчет .
Если время будет 400 сек — то я прав. Если 10 сек — то я не прав.
(26) да оказался прав но на 50% )), занес 10000 записей в один день и время запроса составило 175 сек, а потом опять 10. Потом я переписал на временные таблицы свой запрос и добавил в запрос из топика срез последних, в итоге на холодном сервере время выполнения обоих запросов 45-50 сек, на горячем 35-40. Не пойму только почему раньше были более стабильные результаты. Много тестов не делал, той мой запрос выиграет, то из топика, вообщем буду считать что одинаково. В последних запросах теперь мне не нравится, что в регистре Цены номенклатуры куча индексов (в моей базе только по ЦеныНоменклатуры данные — 236 Мб, а индексы 730 Мб), которые почти никак не используются
(27) Сбил с толку. Совсем.
Смутил этими 50 %(175 сек) , малопонятны пока и (45-50),(35-40).
(28)
что непонятного , один раз запущу запрос выполнился за 45 сек, другой раз за 50 а третий может быть за 47. т.к. повторов делал мало написал через тире минимальное и максимальное занчение.
(29) Да, нет.
Малопонятен сам такой эффект . Почему так получилось … такие диапазоны значений.
Подскажите пожалуйста. Сделал по вашему примеру отчет в наборе данных. После добавления цены в ресурсы и выбранные поля, в отчете в строках цены пусто. Если убираю галочку «рассчитывать по» номенклатуре напротив цены, тогда цена в строках появляется.
(8) Вот еще короче
Показать
Здесь вообще один запрос. Идея в том, чтобы вместо поиска периода, на котором начинается актуальная цена, искать саму цену. Для этого подобрана функция, которая «отправляет» цену прошлого периода на свой «эшелон», высота которого зависит от древности цены. Ширина «эшелона» задается параметром «Много» — это величина, гарантированно перекрывающая диапазон изменения цены (1000000, 10000000 и т.п.). После нахождения ближайшей (нижайшей) «летящей» цены, высота соответствующего «эшелона» вычитается и цена «опускается на землю».
Можно подобрать функцию для дат, булевых и строковых значений.
Кроме краткости записи, других достоинств у запроса нет. Минусы — некоторый проигрыш по времени «классическому» запросу из-за большего объема вычислений в группировках, необходимость думать над значением «Много», работа только с простыми типами.
Аналогичная задача на запросе.
Тут остатки на каждый день * цены номенклатуры на дату остатком,
там суммы документов в у.е. * курсы валют на день документа.
«Курсы валют на разные даты в одном запросе. Делаем свой нестандартный срез последних.»
http://infostart.ru/public/140933/
ну ваще ниньзя
PS: тоже люблю максимально работать запросами
всё правильно, только не забыть:
1) в первом запросе обязательно сгруппировать данные,
в несгруппированных все цифры потом задвоятся, затроятся.
2) в третьем запросе главное не сгруппировывать или обязательно сгруппировывать по периоду
(я сам не понял почему)
3) все группировки из первого запроса должны быть и во втором, чтоб цифры не задвоились.
(а может и нет)
что не нравится:
группировать по числовым колонкам нежелательно,
это тоже самое что написать
ТЗ.Свернуть(«Товар,СуммаРуб»,»СуммаШт») — СуммаРуб должна быть в правой части а не в левой.
Спасибо автору, пригодилось для разработки. Почему-то с соединением средствами СКД не взлетело, а чисто на запросе получилось.
Пришлось воспользоваться советом из поста (8), иначе при некоторых условиях выдавались не совсем верные цифры
(32)
Делал отчет по перемещениям товаров на основе материалов статьи. Так как в моей базе данных немного, то отчёт работал нормально. Но клиент прислал замечания.
исходная ситуация № 1
1) товар используется в документе условно 23 10 2013
2) цена на начало месяца 1796
смотрим отчет — всё ок
исходная ситуация № 2
1) товар используется в документе условно 23 10 2013
2) цена на начало месяца 1796
3) установка цен 20 10 2013 меняет цены продажи на 2000
смотрим отчет — всё ок
исходная ситуация № 3
1) товар используется в документе условно 23 10 2013
2) цена на начало месяца 1796
3) установка цен 25 10 2013!!!!!!!!!! меняет цены продажи на 2000 (то есть дата больше документа. Та же проблема если изменить дату документа установки и на ноябрь)
смотрим отчет: цена = 1796 + 2000 = 3796 (сумма цен на начало месяца и цены, установленной после документа отчета)
Времени на изучение ситуации и переделку запроса не было, поэтому взял совет из поста 8.
Для тех, кто не понимает (пока?) вложенные запросы и соединения есть еще вариант для СКД — Вычисляемые поля.
Делаем вычисляемое поле Цена и в Выражение ставим — Ценообразование.ПолучитьЦенуНоменклатуры(Субконто1, Субконто2.ТипЦенРозничнойТорговли, Период, Константы.ВалютаРегламентированногоУчета.Получить(), 1, 1)
Это частный пример для бухгалтерии 2.0.
И очень надеемся на кеширование. )
Столкнулся с проблемой в СКД.
Ни в какую не хочет показывать данные на дату. Показывает только срез на текущую дату.
Как только не вертел, не пойму в чем причина.
Такое ощущение, что из источника в приемник параметр не передается.
Решил проблему.
Похоже, в соединении наборов данных, первую связь нужно делать по периоду, вторую по другим полям.
Так заработало… колдунство какое-то.
Во втором наборе данных нужно было передавать параметры через расширение языка запросов.
Иначе отработает неправильно.
(3) не согласен. В данном конкретном случае пакетный запрос проигрывает по производительности СКД.
(43) Не может такого быть…
(44) почему? В данном случае создаются две временные таблицы, а это не самая производительная операция. СКД же обойдется без записи данных на диск.
(45) Мои умозаключения покажутся Вам такими же умозрительными, как и мне сейчас кажутся Ваши. Просто проведите эксперимент. Он, скорее всего, будет в мою пользу и Вам легче будет поверить объяснениям.
(46) по поводу производительности я уже экспериментировал, и не однократно. Может когда-то там, в далекие времена 8.1, СКД и была тормозной, сейчас это не так.
Тогда поподробнее расскажите как тестировали, может быть запрос не оптимально написали, на каких данных тестировали, файловый или серверный вариант, схему компоновки и запрос приведите. Действительно интересно. Просто я сравнивал как-то разные варианты «нарезки» последних. Выяснил, что классический метод из двух запросов очень эффективен — там практически нет потерь времени. Поэтому не представляю, где может быть выигрыш. А то, что Вы говорите в памяти или на диске может не быть решающим фактором.
(48) ладно, наваяю.
(48) ildarovich,
если в СКД отборы, то фильтр дает ускорение
но — это не совсем корректное сравнение — запрос без отбора и СКД с отбором
если в СКД есть отборы, то в текст запроса надо их тоже добавить, программно, например «из остатки(,»+отбор+»)»
и тогда сравнивать
_also,
В том виде, что есть сейчас, если опубликованный запрос запустить на УТ 10 и т.п., неподготовленный человек сильно удивится )
Во все три выборки надо вставить характеристику
(51) ЧИА, насколько помню, демо-пример вообще на демо УПП писал. Да и учет по характеристикам может быть включен или не включен.
Статья мне сильно помогла при разработке отчёта на СКД, но с более широкими возможностями. Сделал по второму варианту, но так как выводились не все товары с несуществующими ценами, то сделал связь по типу первого варианта. Чисто первый вариант работает очень долго по всей номенклатуре, поэтому от него отказался, хотя он и проще и быстрее в реализации.
(36) Поручик, что делать, если дата пустая ?
спасибо за запросы — пригодились!
Запрос должен быть совсем другим, не нужно группировать лишнюю информацию и по максимум сократить подзапрос втМаксПериод
Показать
(22) I_G_O_R,
Дополню.
Если подобная конструкция используется в Универсальном отчете (или вообще через ПостроительОтчетов), то может оказаться, что пользователь не задал «Период» как одно из измерений строк. При этом отчет будет ругаться на строчку
(не будет находить Поле «Период» таблицы «Продажи»).
Решением проблемы может стать способ
З.Ы. Проверено на себе
(57) WellMaster,
слишком злобная конструкция. Задачу-то она может и позволит решить, да только в большой базе может сильно ударить по производительности…
(58) puzakov, не спорю. Но хоть что-то.
А как вычислить стоимость Цена*Количество, если Количество в первом наборе данных, а Цена во втором?
(8) I_G_O_R,
Показать
Вышел на эту тему по поисковику. Предложенный запрос очень лаконичен, но сильно просаживает производительность. Причем на файловом варианте 8.3 на порядок медленнее чем на MS SQL2012.
А вот так будет стрелять везде:
Показать
(61) Вот примерно так я и сделал года два назад в паре своих отчётов.
(61) Ха! Тема еще жива))
Сам тоже по возможности пользуюсь именно вторым вариантом. Но что еще интересно, сами 1С такие запросы до сих пор не юзают, наверное не на всех СУБД такие запросы нормально работают, но я сейчас пишу только под ms sql, там отлично работают.
Спасибо большое, пригодилось!
Условие по ТипуЦен надо указывать в связи пацаны 😉
Оч. здорово! Спасибки за статью!
(10)(22) Спасибо, отлично работает!
отлично. как раз нужно
Спасибо!
Спасибо!
именно по вашей статье получилось
Добрый день!
Вопрос подобный.
Возможно ли средствами 1с вывести список материалов, которые не списаны более 30 ней?
Спасибо!
Подскажите как в данный пример прикрутить пересчет валютной цены из регистра ЦеныНоменклатуры по курсу?
(63)
потому что большинство пользуются конструкторами запросов. А условие
В конструкторе будет вызывать ошибку.
Большое спасибо! Избавили от мук творчества и сэкономили массу времени и нервов.
Ну, то что тип цен в левое соединение нужно прикручивать, надеюсь, кто хотел — догадался.
Автор ты молодец конечно.
Но твой пример будет работать только если у тебя в базе один-единственный вид цен.
А если видов цен несколько — то во втором запросе пакета надо наверное поставить условие по виду цен, иначе ты там много чего наловишь.
Вот столько времени прошло, а иногда подсматриваю, когда забываю, как оно работает. Респект автору за читабельность.