Подведем итоги. Нарастающие.

07.12.09

Разработка - Математика и алгоритмы

Как одним пакетом запросов получить таблицу просроченных долгов контрагента ?
Подробное иллюстрированное построение пакета запросов.


Скачать файлы

Наименование Файл Версия Размер
-
.dt 26,43Kb
409
.dt 26,43Kb 409 Скачать бесплатно

                                                              Шепоту посвящается.

Возможно  читателю покажется интересным приложение в конце статьи :
Что такое нарастающие итоги ? И зачем они нужны ?

Три подхода или кто прав ?

1. Eugeneer предлагает следующий алгоритм решения задачи получения просроченных долгов контрагентов . Организовать цикл обхода выборки контрагентов ,в каждой итерации которого выполнять "мелкий" запрос к базе и с помощью кодинга получить необходимые данные отчета. Eugeneer имеет весомый аргумент - отчет  //infostart.ru/public/60670/  ,практическое тестирование которого показало неплохие результаты при размере базы УТ - 13 Гб и количестве контрагентов - более 4 тыс.

2. Ish_2 в дискуссиях с Anig99 и Eugeneer настаивал на том , что лучший вариант с точки зрения быстродействия - это сделать один запрос к базе и за один проход выборки кодингом получить все необходимые для отчета данные . Ish_2 считает весомым аргументом  собственные рассуждения и  умозрительные построения.

3. Anig99  предлагает уместить алгоритм получения всех данных в один пакет запросов. Anig99 имеет весомый аргумент- отчет //infostart.ru/public/58966/ , который показал хорошее быстродействие при объеме базы 120(!) Гб

В настоящий момент , автор статьи считает , что прав в споре - Anig99. Опуская аргументацию почему Eugeneer и Ish_2 неправы (пожалуй , потянет на отдельную статью о вредности "семерочного" похода при работе с таблицами БД),скажу лишь , что выбранный Anig99  подход , не предполагающий кодинга вовсе  - самый простой и технологичный , при котором мы "отвязываемся" от "слабости" клиента и передаем всю обработку данных  на сервер баз данных. Итак, правильный подход к решению задачи выбран. Осталась мелочь - написать эффективный запрос , обеспечивающий наилучшее быстродействие. Мелочь эту мы и рассмотрим в статье.
     Рассматриваемый ниже вариант построения текста пакета запросов для решения  поставленной задачи представляется автору самым оптимальным  для как небольших, так и очень больших объемов данных (свыше 120-150 ГБ). В демонстрационных целях , для объяснения сути подхода к решению задача несколько упрощена . Несколько упрощен , соответственно , и  предлагаемый к рассмотрению текст пакета запросов . Теперь можно приступить к делу.

Постановка задачи.

Даны две таблицы: 
Таблица «Долги»

 

Контрагент

Долг

ДатаОтсрочки

Компания

  200

     01.11.09

 



Таблица «Обороты»

Контрагент

Период

Рег-р

Сумма

Компания

10.09.09

Накл. 1

        40

Компания

20.09.09

Накл. 2

        60

Компания

31.10.09

Накл. 3

        80

Компания

25.11.09

Накл. 4

      100

 

 

 

 

 

 

Требуется получить таблицу «Просроченные долги»

Контрагент

Период

Рег-р

Сумма

Долг

Просроченный долг

Компания

20.09.09

Накл. 2

        60

       20

                              20

Компания

31.10.09

Накл. 3

        80

        80

                              80

Компания

25.11.09

Накл. 4

      100

      100

                                0

 

 

 

 

 

 

 В выходную таблицу вошли документы на общую сумму равную долгу контрагента (=200).
Алгоритм получения выходной таблицы сводится к нахождению в таблице «Обороты» строки с регистратором «Накл 2»,.
Действительно , сумма долга 200 будет складываться как
Строка «Накл 4» - 100
Строка «Накл 3» - 80
Строка «Накл 2» - 20 , где 20 – это часть значения «Суммы» текущей строки
                                       таблицы «Обороты».
Итак , мы ищем строку в таблице «Обороты», которая является последней в последовательности строк таблицы «Обороты» , «набирающих» в обратном порядке следования строк необходимую сумму долга.

 

 

Frown Отступление для "семерочников".
Не спешите восклицать: " Делов -то ! Пройтись по таблице "Обороты" циклом,  да и дело с концом !" Представьте себе , что во встроенном языке 1с нет оператора цикла .  Возможно, тогда представленный алгоритм не покажется совсем уж нелепым.

 

Описание  алгоритма решения .

I. Получим  суммарные обороты с нарастающими итогами по месяцам из таблицы «Обороты». . Долг контрагента(=200) лежит в интервале 180-280, значит искомый период - сентябрь . Остаток долга для последующего поиска составит 200 – 180 = 20.

В вехней строке таблицы показаны нарастающие итоги оборотов.

0 100 180                                280

       Ноябрь

        Октябрь

         Сентябрь    

         100

           80

               100        


II.  Получим последовательность документов с нарастающими итогами в сентябре. Остаток долга 20 лежит в интервале 0-60 , значит искомая строка  таблицы «Обороты» найдена.
 

0                                                 60                                   100

    Накл.   1  от 20.09.09

       Накл. 1 от 10.09.09

                 60

                 40


III. Скопируем все строки из таблицы «Обороты» с периодом большим или равным 20.09.09 в новую таблицу. Добавим новые колонки «Долг» и «Просроченный долг». Заполним добавленные колонки очевидным образом ,учитывая , что «Долг»  для строки с «Накл. 2» составляет 20, и значение «Даты Отсрочки» - 01.11.09 .

Контрагент

Период

Рег-р

Сумма

Долг

Просроченный долг

Компания

20.09.09

Накл.  2

        60

        20

                              20

Компания

31.10.09

Накл.  3

        80

        80

                              80

Компания

25.11.09

Накл.  4

      100

      100

                                0

 

 

 

 

 

Frown Отступление для "семерочников".
Обратите внимание на то, что с точки зрения здравого ("семерочного") смысла мы сделали немыслимое : вместо очевидного перебора строк в таблице "Обороты"  просуммировали по месяцам всю таблицу. Поехали в Москву через Владивосток ? - Смотри приложение в конце статьи.

 

В демонстрационном примере мы рассматривали лишь два вида интервала для поиска Месяц-документ. В реальных же задачах, когда очень большая таблица "Обороты" имеет строки  с высокой плотностью по времени (розничные продажи по дисконтным картам , как у пользователя Anig99) количество видов интервалов для поиска должно быть увеличено, например Год - квартал - месяц - декада - день - документ.

 

 

 

Текст пакета запросов

 

Рассмотрим текст пакета запросов из отчета "ПросроченныйДолг", содержащегося в демонстрационной конфигурации "ПросроченныйДолг.dt". Таблицы "Долги"и "Обороты" реализованы как справочники . Сделано допущение,  что поле "Период" в справочнике "Обороты" содержит только уникальные значения. 

 


1. Найдем обороты по месяцам

  ВЫБРАТЬ
          Обороты.Контрагент,
         НАЧАЛОПЕРИОДА(Обороты.Период, МЕСЯЦ) КАК НачПериода,
         КОНЕЦПЕРИОДА(Обороты.Период, МЕСЯЦ) КАК КонПериода,
         СУММА(Обороты.Сумма) КАК Сумма
ПОМЕСТИТЬ ОборотыПоМесяцам
ИЗ
         Справочник.Обороты КАК Обороты
СГРУППИРОВАТЬ ПО
         Обороты.Контрагент,
        НАЧАЛОПЕРИОДА(Обороты.Период, МЕСЯЦ),
        КОНЕЦПЕРИОДА(Обороты.Период, МЕСЯЦ)
;

  Временная таблица "ОборотыПоМесяцам"

Контрагент

НачПериода

КонПериода

Сумма

Компания

01.09.09

30.09.09

      100

Компания

01.10.09

31.10.09 

        80

Компания

01.11.09

30.11.09

      100

 

 

 

 


2.
Получим нарастающие итоги для "ОборотыПоМесяцам".

ВЫБРАТЬ
        ОборотыПоМесяцам.Контрагент,
        ОборотыПоМесяцам.НачПериода,
        ОборотыПоМесяцам.КонПериода,
        ОборотыПоМесяцам.Сумма,
        СУММА(ОборотыПоМесяцамКопия.Сумма) КАК СуммаПосле,
        СУММА(ОборотыПоМесяцамКопия.Сумма) - ОборотыПоМесяцам.Сумма КАК СуммаДо
ПОМЕСТИТЬ ОборотыПоМесяцамНарастающие
ИЗ
        ОборотыПоМесяцам КАК ОборотыПоМесяцам
  ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОборотыПоМесяцам КАК ОборотыПоМесяцамКопия
  ПО    ОборотыПоМесяцам.Контрагент   =  ОборотыПоМесяцамКопия.Контрагент
   И      ОборотыПоМесяцам.НачПериода <= ОборотыПоМесяцамКопия.НачПериода
СГРУППИРОВАТЬ ПО
        ОборотыПоМесяцам.НачПериода,
        ОборотыПоМесяцам.КонПериода,
        ОборотыПоМесяцам.Контрагент,
        ОборотыПоМесяцам.Сумма
;

 Временная таблица "ОборотыПоМесяцамнНарастающие"

Контрагент

НачПериода

КонПериода

Сумма

 СуммаДо   СуммаПосле  

Компания

01.09.09

30.09.09

      100

        180              280

Компания

01.10.09

31.10.09 

        80

        100              180

Компания

01.11.09

30.11.09

      100

           0              100

 

 

 

 



3.
 Найдем строку, интервал которой от "СуммаДо " до "СуммаПосле" содержит в себе значение долга (=200)
 , используя внутренне соединение таблиц "Долги" и "ОборотыПоМесяцамНарастающие".

ВЫБРАТЬ
        Долги.Контрагент,
        Долги.Долг,
        Долги.ДатаОтсрочки,
        ОборотыПоМесНарастающие.НачПериода,
        ОборотыПоМесНарастающие.КонПериода,
        Долги.Долг - ОборотыПоМесНарастающие.СуммаДо КАК ОстатокДолга
ПОМЕСТИТЬ ДолгиПоВыбраннымМесяцам
ИЗ
        Справочник.Долги КАК Долги
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОборотыПоМесяцамНарастающие КАК ОборотыПоМесНарастающие
        ПО Долги.Контрагент = ОборотыПоМесНарастающие.Контрагент
        И Долги.Долг > ОборотыПоМесНарастающие.СуммаДо
        И Долги.Долг <= ОборотыПоМесНарастающие.СуммаПосле
;

 Временная таблица "ДолгиПовыбраннымМесяцам"

Контрагент

НачПериода

КонПериода

  Долг

 ДатаОтсрочки   ОстатокДолга 

Компания

01.09.09

30.09.09

     200

     01.11.09              20

 

 

 

4.  Выберем из таблицы "Обороты" документы только за указанный период    

ВЫБРАТЬ
        Обороты.Контрагент,
        Обороты.Документ,
        Обороты.Период,
        ДолгиПоВыбМесяцам.Долг,
        ДолгиПоВыбМесяцам.ОстатокДолга,
        Обороты.Сумма,
        ДолгиПоВыбМесяцам.ДатаОтсрочки
ПОМЕСТИТЬ ДвиженияПоВыбраннымМесяцам
ИЗ
        Справочник.Обороты КАК Обороты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ДолгиПоВыбраннымМесяцам КАК ДолгиПоВыбМесяцам
       ПО Обороты.Контрагент = ДолгиПоВыбМесяцам.Контрагент
       И Обороты.Период >= ДолгиПоВыбМесяцам.НачПериода
       И Обороты.Период <= ДолгиПоВыбМесяцам.КонПериода

;

 Временная таблица "ДвиженияПоВыбраннымМесяцам"

Контрагент

Период

Рег-р

Сумма

Долг

ОстатокДолга

ДатаОтсрочки

Компания

10.09.09

Накл  1

        40

   200

         20

01.11.09

Компания

20.10.09

Накл. 2

        60

   200

         20      

01.11.09

 

 



5.
Получим нарастающие итоги по документам   

ВЫБРАТЬ
       ДвиженияПоВыбМесяцам.Контрагент,
       ДвиженияПоВыбМесяцам.Документ,
       ДвиженияПоВыбМесяцам.Период,
       ДвиженияПоВыбМесяцам.Сумма,
       СУММА(ДвиженияПоВыбМесяцамКопия.Сумма) КАК СуммаПосле,
       СУММА(ДвиженияПоВыбМесяцамКопия.Сумма) - ДвиженияПоВыбМесяцам.Сумма КАК СуммаДо,
       ДвиженияПоВыбМесяцам.ОстатокДолга,
       ДвиженияПоВыбМесяцам.ДатаОтсрочки
ПОМЕСТИТЬ ДвиженияПредварительные
ИЗ
       ДвиженияПоВыбраннымМесяцам КАК ДвиженияПоВыбМесяцам
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ДвиженияПоВыбраннымМесяцам КАК ДвиженияПоВыбМесяцамКопия
       ПО ДвиженияПоВыбМесяцам.Контрагент = ДвиженияПоВыбМесяцамКопия.Контрагент
       И ДвиженияПоВыбМесяцам.Период <= ДвиженияПоВыбМесяцамКопия.Период
СГРУППИРОВАТЬ ПО
       ДвиженияПоВыбМесяцам.Контрагент,
       ДвиженияПоВыбМесяцам.Документ,
       ДвиженияПоВыбМесяцам.Период,
       ДвиженияПоВыбМесяцам.Сумма,
       ДвиженияПоВыбМесяцам.ОстатокДолга,
       ДвиженияПоВыбМесяцам.ДатаОтсрочки

;

   Временная таблица "ДвиженияПредварительные "

Контрагент

Период

....

ОстатокДолга

СуммаДо

СуммаПосле

Компания

10.09.09

....

        20

        60 

      100

Компания

20.10.09

....

        20

        0

       60

 

 




6.
По "ОстаткуДолга" =20 определим единственную строку и выведем ее в таблицу "ДвиженияОкончательные" 

ВЫБРАТЬ
       ДвиженияПредв.Контрагент,
       ДвиженияПредв.Документ,
       ДвиженияПредв.Период,
       ДвиженияПредв.ОстатокДолга - ДвиженияПредв.СуммаДо КАК СуммаДолга,
       ДвиженияПредв.Сумма,
       ДвиженияПредв.ДатаОтсрочки
ПОМЕСТИТЬ ДвиженияОкончательные
ИЗ
       ДвиженияПредварительные КАК ДвиженияПредв
ГДЕ
       ДвиженияПредв.ОстатокДолга > ДвиженияПредв.СуммаДо
       И ДвиженияПредв.ОстатокДолга <= ДвиженияПредв.СуммаПосле
;

  Временная таблица "ДвиженияОкончательные"

Контрагент

Период

....

ОстатокДолга

СуммаДо

СуммаПосле

Компания

20.10.09

....

        20

        0

       60

 

 

 

 

7. Используя таблицу "ДвиженияОкончательные" и внутреннее соединение с исходной таблицей "Обороты" получим выходную таблицу запроса.

ВЫБРАТЬ
       Обороты.Контрагент,
       Обороты.Период,
       Обороты.Документ,
       Обороты.Сумма,
       ВЫБОР
       КОГДА Обороты.Документ = ДвиженияОконч.Документ
                  ТОГДА ДвиженияОконч.СуммаДолга
                   ИНАЧЕ Обороты.Сумма
        КОНЕЦ КАК СуммаДолга,
        ///
        ВЫБОР
        КОГДА Обороты.Период < ДвиженияОконч.ДатаОтсрочки
                    ТОГДА ВЫБОР
                                 КОГДА Обороты.Документ = ДвиженияОконч.Документ
                                              ТОГДА ДвиженияОконч.СуммаДолга
                                              ИНАЧЕ Обороты.Сумма
                                 КОНЕЦ
                    ИНАЧЕ 0
        КОНЕЦ КАК СуммаПросроченногоДолга
ИЗ
        Справочник.Обороты КАК Обороты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ДвиженияОкончательные КАК ДвиженияОконч
        ПО Обороты.Контрагент = ДвиженияОконч.Контрагент
        И Обороты.Период >= ДвиженияОконч.Период

Выходная таблица "ПросроченныеДолги"

Контрагент

Период

Рег-р

Сумма

Долг

Просроченный долг

Компания

20.09.09

Накл.  2

        60

        20

                              20

Компания

31.10.09

Накл.  3

        80

        80

                              80

Компания

25.11.09

Накл.  4

      100

      100

                                0

 

 

 

 


 В прикрепленном к статье  файле "ПросроченныйДолг.dt"(26Кб)  находится небольшая демонстрационная конфигурация,содержащая  :

  • справочники "Контрагенты","Долги","Обороты".
  • отчет "ПросроченныйДолг".

 После запуска конфигурации см. Рисунок ниже :

 

Приложение .
Что такое нарастающие итоги ? И зачем они нужны ?

Дана таблица с именем  «Таблица» наших месячных оборотов :

        Месяц

         Оборот

Сентябрь

            100

Октябрь

              80

Ноябрь

            100

 

Вопрос : В каком месяце мы достигли суммарного оборота 200 ,начиная с сентября  ?
               
Решение.

Представим , что  у нас в языке 1с нет оператора цикла и «в лоб» перебрать строки таблицы мы не можем. Но у нас есть язык запросов.
Вначале сделаем «ненужный» вспомогательный запрос , проясняющий суть соединения по неравенству . Таблица "Таблица" соединяется "сама с собой".


Выбрать Т1.Месяц , Т1.Оборот, Т2.месяц,Т2.Оборот
ИЗ Таблица как Т1
ВнутреннееСоединение Таблица как Т2
по Т1.Месяц >=Т2.Месяц

 

Т1Месяц

  Т1Оборот

  Т2Месяц

   Т2Оборот

Сентябрь

      100

  Сентябрь

        100

Октябрь

        80

  Сентябрь

        100

Октябрь

        80

  Октябрь

          80

Ноябрь

       100

   Сентябрь

        100

Ноябрь

       100

   Октябрь

          80

Ноябрь

       100

   Ноябрь

        100


Уберем из полей выборки лишние поля и сгруппируем по Т1Месяц с суммой поля Т2Оборот

 

Выбрать Т1.Месяц , Т1.Оборот, Сумма(Т2.Оборот) как СуммаПосле // так вот назвал
ИЗ Таблица как Т1
ВнутреннееСоединение Таблица как Т2
по Т1.Месяц >=Т2.Месяц
Сгруппировать ПО Т1.Месяц , Т1.Оборот

 

Т1Месяц

  Т1Оборот

   СуммаПосле

Сентябрь

       100

         100

Октябрь

         80

         180

Ноябрь

       100

         280


Но этого мало , в каждой строке нужно иметь интервал СуммаДо и СуммаПосле.
Поэтому перепишем запрос :


Выбрать Т1.Месяц , Т1.Оборот, Сумма(Т2.Оборот) как СуммаПосле ,
Сумма(Т2.Оборот) – Т1.Оборот как СуммаДо
ИЗ Таблица как Т1
ВнутреннееСоединение Таблица как Т2
по Т1.Месяц >=Т2.Месяц
Сгруппировать ПО Т1.Месяц , Т1.Оборот



Т1Месяц

  Т1Оборот

   СуммаДо

   СуммаПосле

Сентябрь

       100

               0

         100

Октябрь

         80

           100

         180

Ноябрь

       100

           180

         280


Теперь нужно определить в какой строке  выполняется двойное неравенство
СуммаДо <= 200 <= CуммаПосле. Или , другими словами, в каком месяце мы достигли оборота со значением 200.

Для этого поместим результат первого запроса во временную таблицу Итоги и приведем весь  пакетный запрос решения :


Выбрать Т1.Месяц , Т1.Оборот, Сумма(Т2.Оборот) как СуммаПосле ,
Сумма(Т2.Оборот) – Т1.Оборот как СуммаДо
ПОМЕСТИТЬ  Итоги
ИЗ Таблица как Т1
ВнутреннееСоединение Таблица как Т2
по Т1.Месяц >=Т2.Месяц
Сгруппировать ПО Т1.Месяц , Т1.Оборот
;
Выбрать Итоги.Т1Месяц,Итоги.Т1Оборот,Итоги.СуммаДо , Итоги.СуммаПосле  
ИЗ Итоги как Итоги
ГДЕ  200 >Итоги.СуммаДо и 200 <=Итогм.СуммаПосле

 

Выходная таблица решения :

Т1Месяц

  Т1Оборот

   СуммаДо

   СуммаПосле

Ноябрь

       100

           180

         280

 

Вывод : нарастающие итоги (значения колонок СуммаДо и СуммаПосле, задающих определенный интервал) нужны в контексте темы статьи для поиска некоторого определенного значения (Оборот = 200).


Примечание : Представим, что исходная таблица "Таблица" имеет миллионы записей . Тогда какого размера достигнет таблица в результате соединения "сама с собой" ( первый запрос  в приложении) ?
Поэтому нарастающие итоги применять нужно для таблиц ограниченного
размера. В этом весь смысл приведенного в статье алгоритма :
нарастающие итоги применяются сначала для поиска в небольшой таблице месячных итогов , затем "вырезается" интервал в таблице "Обороты" и в этом небольшом месячном интервале снова используются нарастающие итоги для поиска нужного значения оборотов.

 

 

См. также

SALE! 20%

Infostart Toolkit: Инструменты разработчика 1С 8.3 на управляемых формах

Инструментарий разработчика Роли и права Запросы СКД Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Конфигурации 1cv8 Платные (руб)

Набор инструментов программиста и специалиста 1С для всех конфигураций на управляемых формах. В состав входят инструменты: Консоль запросов, Консоль СКД, Консоль кода, Редактор объекта, Анализ прав доступа, Метаданные, Поиск ссылок, Сравнение объектов, Все функции, Подписки на события и др. Редактор запросов и кода с раскраской и контекстной подсказкой. Доработанный конструктор запросов тонкого клиента. Продукт хорошо оптимизирован и обладает самым широким функционалом среди всех инструментов, представленных на рынке.

13000 10400 руб.

02.09.2020    122170    670    389    

714

Начните уже использовать хранилище запросов

HighLoad оптимизация Запросы

Очень немногие из тех, кто занимается поддержкой MS SQL, работают с хранилищем запросов. А ведь хранилище запросов – это очень удобный, мощный и, главное, бесплатный инструмент, позволяющий быстро найти и локализовать проблему производительности и потребления ресурсов запросами. В статье расскажем о том, как использовать хранилище запросов в MS SQL и какие плюсы и минусы у него есть.

11.10.2023    16186    skovpin_sa    14    

98

MS SQL Server: изучаем планы запросов

Запросы HighLoad оптимизация Запросы Бесплатно (free)

Многие знают, что для ускорения работы запроса нужно «изучить план». При этом сам план обычно обескураживает: куча разноцветных иконок и стрелочек; ничего не понятно, но очень интересно! Аналитик производительности Александр Денисов на конференции Infostart Event 2021 Moscow Premiere рассказал, как выполняется план запроса и что нужно сделать, чтобы с его помощью находить проблемы производительности.

20.06.2023    16006    Филин    37    

113

Все консоли запросов для 1С

Запросы Инструментарий разработчика Бесплатно (free)

Список всех популярных обработок.

17.03.2023    35544    kuzyara    84    

179

Идентификатор объекта в запросе. Вы этого хотели?

Запросы Механизмы платформы 1С Платформа 1С v8.3 Запросы Бесплатно (free)

В платформе 8.3.22 появилась возможность получать идентификатор в запросе. Лично я ждал этого давно, но по итогу ждал большего. Что не так?

12.01.2023    39664    dsdred    26    

96

Практическая шпаргалка по новым возможностям языка запросов 1С

Механизмы платформы 1С Запросы Платформа 1С v8.3 Запросы Конфигурации 1cv8 Бесплатно (free)

В предлагаемой статье решил привести примеры применения новых возможностей языка запросов 1С, начиная с версии платформы 8.3.20.

21.11.2022    23425    quazare    36    

122
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
99. alexqc 150 10.12.09 13:02 Сейчас в теме
А тем не менее, "пройтись циклом" действительно самый оптимальный вариант. Его трудоемкость O(n), в то время как трудоемкость варианта объединения двух таблиц - от O(n * log n) если хорошо сработает оптимизатор до O(n^2) в плохих случаях. Так что, не стоит тыкать пальцем и покатываться со смеху с предлагающих это "семерочников". И говорить про слабость клиента тоже не стоит - раз клиент получил набор данных, значит, что-то уже с ним будет делать (да хоть, например, выводить) и обязательно хоть разок, но пройдет по набору. Вычисление итогов в этом случае - небольшой довесок к выполнению.
100. Ish_2 1104 10.12.09 13:55 Сейчас в теме
(99) Вначале статьи приведены 3 варианта решения задачи.
Под пунктом 2 приведено первоначальное мнение автора этой статьи.
Полностью совпадающее с Вашим. Вообще говоря, количество операций (трудоемкость) при этом варианте значительно меньше.
И если в кого-то автор тыкает пальцем и покатывается со смеху , то прежде всего в себя.
Первый вариант (запросы в цикле) был рассмотрен выше в коментариях.
Надеюсь Вы согласны, что запрос в цикле - это зло.
Второй вариант (одним запросом получить выборку для всех контрагентов и из нее получить выходную таблицу) имеет одно ограничение.
Мы не знаем периода этого запроса ( какова самая рання дата неоплаченного документа ), т.е. вынуждены выгружать на клиента все обороты всех контргаентов за весь период. До определенного размера базы этот ограничение не имеет смысла. Но ведь где -то этот предел существует ! И время передачи только одной выходной таблицы запроса может стать неприемлемо большим.
Для конфигурации в крупной розничной сети , торгующей по дисконтным картам 3-4 года, это ограничение может быть актуальным. Грузить на клиента огромную выборку проблематично.
Говорить о слабости клиента стоит !
При использовании подхода "все в одном запросе" на клиента поступает очень небольшая таблица для визуального отображения и не более того.
Это и есть важнейшее преимущество третьего подхода - его универсальность. Технологичность же подхода определяет отсутствие кодинга : запрос составляется в конструкторе.
Поэтому такой подход и был назван оптимальным : на малых базах(до 30 Гб)много не проиграет , на больших выиграет.
103. alexqc 150 10.12.09 19:02 Сейчас в теме
(100) Да, под п.2 мнение совпадающее с моим, однако далее Вы приходите к тому что лучше скрестить 2 таблицы, не говоря почему.

Сейчас вы аргументируя это тем, что "мы не знаем периода запроса".

НО! При подходе "скрестить две таблицы" мы его тоже не знаем!

Дальнейшее рассуждение "При использовании подхода "все в одном запросе" на клиента поступает очень небольшая таблица для визуального отображения и не более того" - значит что? Что по нарастающему итогу делается фильтрация! Т.е. как у вас в статье в приложении. Однако, кроме как там, о фильтре по н.итогу более нигде не сказано - а это всего лишь одно из применений. Может конечно в исходной постановке речь шла именно об том - тут в этих статьях и ссылках друг на друга черт ногу сломит, может я что-то и пропустил - только этого не видно.

Так вот, если задача стоит лишь в определении одного показателя - ее можно решить и другими методами - например, искать остатки двоичным поиском, сужая период.

Ну и наконец, никто не запрещает таки цикл на сервере делать (эта возможность не является стандартом и зависит от реализации, но и MSSQL, и Postgre ее предоставляют). А коли 8ка этого не умеет - проблемы 8ки :). 7.7 с 1С++ это позволяет.
105. anig99 2843 10.12.09 19:34 Сейчас в теме
(103) См. мою статью там как раз другой метод оптимизации. Чем-то схожий с двоичным.
106. Ish_2 1104 10.12.09 20:16 Сейчас в теме
(103)
Да, под п.2 мнение совпадающее с моим, однако далее Вы приходите к тому что лучше скрестить 2 таблицы, не говоря почему.
Сейчас вы аргументируя это тем, что "мы не знаем периода запроса".
НО! При подходе "скрестить две таблицы" мы его тоже не знаем!


Предельно конкретно.
У вас 100 контргаентов . Общее количество документов всех контргаентов за весь период 2-4 тыс. Вы выгрузили одним запросом все эти документы в выборку. И за один проход в цикле выборки получили выходную таблицу . Вы выиграете у метода "Все в одном запросе" ? Отвечаю : Да ! Много выиграете ? Отвечаю : несущественно (для пользователя).

И мы с Вами говорим : Ура! "семерочные" подходы рулят !

У Вас 100 000 контрагентов 30 000 000 документов за весь период.
Если Вы скажете что этого мало - я Вам скажу 1 000 000 и 300 000 000.
Не торопитесь выгружать эти обороты в одну таблицу(выборку) и пройтись по ней циклом. Вы жестоко проиграете на одной переброске такой таблицы с сервера на клиента. Вы должны также сказать, что и клиентская машина должна быть очень не простой .
Т.е. появляется масса "НО". Чтобы от этих "НО" отвязаться не проще ли отказаться от перегонки таблиц "сервер-клиент" вовсе , а всю обработку передать на "сервер" , а на клиентскую машину выдавать только результат запроса (оч.маленькую таблицу для отображения в отчете) ?
Это лишь один аргумент.

Второй аргумент . Да , Количество операций в методе "все в одном запросе" больше. Но и выделенный сервер базы данных гораздо мощнее клиентской машины. Операции группирования и суммирования выполняются многократно быстрее чем при при "ручном кодинге цикла" на клиентской машине.

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

С одним я согласен . Полная аргументация в статье в пользу выбранного подхода опущена напрасно.

Из Ярославля в Москву быстрее доехать через Владивосток, потому что мы поедем на Сервере Баз Данных .
На дохлом велосипеде (клиентской машине) до Москвы добираться дольше.
107. hogik 443 11.12.09 00:01 Сейчас в теме
(106)
"Вы выгрузили одним запросом все эти документы в выборку. И за один проход в цикле выборки получили выходную таблицу ...И мы с Вами говорим : Ура! "семерочные" подходы рулят !"
- Маленькое замечание. В "1С 7.7" цикл выполняется не по выборке, а непосредственно по таблице базы данных. В DBF-ной версии - всегда так. В SQL-ной версии - не всегда. Т.е. термин "семерочные подходы" имеет разный смысл в разных версиях "1С 7.7".
109. alexqc 150 11.12.09 11:22 Сейчас в теме
(107) Речь исходно идет именно об SQL версии. К тому же ДБФ такие объемы и не снились.

И, кстати, "в ДБФ цикл выполняется по таблице" тоже неверно - это разве что в простейшем случае, типа ВыбратьЭлементы()/получитьЭлемент(). Для н/итогов (да и просто итогов :) ) всяко некая промежуточная выборка должна быть построена.
111. Ish_2 1104 11.12.09 12:28 Сейчас в теме
(107) Конечно. Термин "семерочный" применен для удобства.
На самом деле речь идет о применении оператора "цикл" или о шаблоне мышления программистов СУБД гораздо более старом чем "семерка" .
Суть этого шаблона в том , что с таблицами нужно работать , перебирая записи явно в цикле (т.е. сканируя таблицу). Я же считаю , что это очень дурной тон тогда , когда можно обойтись агрегатными командами или запросами.
Например, в FoxPro решая такую задачу , можно обойтись без циклов и запросов , а только используя агрегатную командe Replace .

Отступление. Привычным и понятным для 1с-ника , например при расчете зарплаты, является подход , реализованный в тип.конф-иях :
позиционируемся на первую запись таблицы - Сотрудник и для него начинаем применять те или иные расчеты , позиционируемя на след . Сотрудника и так далее. На мой взгляд , боле правильным и выгодным подходом для СУБД является следующий : Берем таблицу "Сотрудники" и последовательно к ней применяем Первый расчет (для всей таблицы) , потом Второй расчет и т.д.
Другими словами алгоритм представляет собой последовательность получения временных таблиц. Правда для 1с-ника построение такой БД будет выглядеть диковато, но для специалиста по базам данных - нормально.
В СУБД , проще говоря, нужно стремиться оперировать не записями, а таблицами.
112. alexqc 150 11.12.09 13:47 Сейчас в теме
(111)
Проблема в том, что работа со временными таблицами появилась если не ошибаюсь начиная с 8.1. Соответственно "берем ... и применяем первый расчет, второй" и т.д. было недоступно в типовом механизме.

Далее, в SQL (по крайней мере MS) нет возможности указать возвращаемый набор результатов. Т.е. нельзя выполнить в одном пакете формирование нескольких врем. таблиц (или табличных переменных), и что-то с них получить: вернется не последний select пакета, а все результаты (в т.ч. промежуточных insert'ов и update'ов). А механизм 1Ски отдаст только первую выборку из набора. Вот и приходится делать несколько пакетов. Но это ладно.

В SQL почему-то отсутствует ряд возможностей, которые вроде-бы хорошо востребованы. Те же н/итоги ("кумулятивное суммирование"); работа с иерархией (оператор with, введенный в стандарт только то ли в SQL2003, толи 2005 и поддерживаемый далеко не всеми); генерируюшие функции (например, нужно получить последовательность дней с ... по... - приходится извращаться. В постри хоть есть generate_series, но это уже вне стандарта).
121. Ish_2 1104 11.12.09 22:42 Сейчас в теме
(112)
Далее, в SQL (по крайней мере MS) нет возможности указать возвращаемый набор результатов. Т.е. нельзя выполнить в одном пакете формирование нескольких врем. таблиц (или табличных переменных), и что-то с них получить: вернется не последний select пакета, а все результаты (в т.ч. промежуточных insert'ов и update'ов). А механизм 1Ски отдаст только первую выборку из набора. Вот и приходится делать несколько пакетов. Но это ладно.
Странный абзац. В 1с Пакет запросов состоящий из нескольких запросов исполнен может быть двумя способами :
1. Запрос.Выполнить() - вернет результат последнего запроса в пакете.
2. Запрос.ВыполнитьПакет() - вернет массив содержащий результаты всех запросов , входящих в пакет.
122. Ish_2 1104 11.12.09 22:56 Сейчас в теме
(112) Для меня в первую голову непонятно почему в 8.х для временных таблиц невозможны команды создания и модификации таблиц (create table, update). Целостность основной базы при реализации такой возможности не нарушается . И как бы тогда выглядела задача нарастающих итогов и просроченных долгов ?
В чем тут дело сказать трудно. Ведь и сами временные таблицы - тоже совершенно очевидная вещь - появились только через 4(!) года в 8.1, а до этого в 8.0 писались километровые вложенные запросы.
119. hogik 443 11.12.09 16:12 Сейчас в теме
(111)
"Я же считаю , что это очень дурной тон тогда , когда можно обойтись агрегатными командами или запросами."
Именно об этом я и сказал в той юмористической теме. Т.е цель - писать красивые "запросы" (алгоритмы)... ;-) И "суть этого шаблона в том ", что за красотой не видна сама задача и пути её решения. Повторю, что "проблемы", обозначенной в Вашей статье, вообще не существует при наличии подходящих инструментов. Очень жаль, что "1С 8.х" таких инструментов не предоставляет... :-(
P.S. Не ставлю минус на данную публикацию, т.к. минус надо ставить не на Вашу публикации, а на "1С 8.х".
108. alexqc 150 11.12.09 11:05 Сейчас в теме
(106) Акцентирую Ваше внимание еще раз: в начальной постановке пропущено (точнее, указано вне основной части и как то вскользь) критическое условие: мы не просто строим н/итоги, мы ФИЛЬТРУЕМ по н/итогу. В таком случае - да, от того где выполняется построение итогов зависит объем пришедшей на клиента выборки, и клиент начинает иметь значение. Но, ИМХО, это все-таки очень частный случай - обычно н/итоги нужны "как есть". Точно также как и просто суммы - случаи, когда производится фильтрация по колонкам sum(), сравнительно немного (и чаще всего это having sum()<>0) - обычно нечто из серии "выбрать крупных клиентов", что уж точно не в оперативной работе применяется.

Теперь по поводу объемов.

Во-первых, не могу представить реальной ситуации, где действительно нужно обработать такую выборку. Конечный потребитель (человек) просто не воспримет "миллион контрагентов". Т.е. наверняка "масштаб" выборки можно будет укрупнить, что-то свернув. А если надо не по всем, а по одному/нескольким - то же самое, у вас 300 млн доков на 1 млн контрагентов - всего 300 доков на контрагента, не так уж и много.

Во вторых, при таких объемах для подобных задач рулят уже другие системы (а-ля OLAP) и другие подходы (промежуточные хранилища данных, заранее свернутая аналитика и итоги по периодам), которые выходят за рамки 1С.

Ну и в третьих, такие объемы, да на 1С - это опять же исключение. Штучные экземпляры. В массе для обычной фирмы-пользователя 1С база 20 гиг - это уже "ого-го". А там где больша - часто практикуется "закрытие периода".
110. alexqc 150 11.12.09 11:35 Сейчас в теме
+ к (108) да, и забыл еще раз упомянуть - циклы ведь можно делать и на сервере! Правда, не знаю можно ли это делать через 8ку, подозреваю что нет.
123. Ish_2 1104 11.12.09 23:19 Сейчас в теме
(110) В языке запросов 1с "цикла" точно нет.
115. Ish_2 1104 11.12.09 15:19 Сейчас в теме
(108)
"Акцентирую Ваше внимание еще раз: в начальной постановке пропущено (точнее, указано вне основной части и как то вскользь) критическое условие: мы не просто строим н/итоги, мы ФИЛЬТРУЕМ по н/итогу. В таком случае - да, от того где выполняется построение итогов зависит объем пришедшей на клиента выборки, и клиент начинает иметь значение."

Честно говоря, не очень понял.
В приложении написано , что нарастающие итоги для каждой записи (СуммаДо,СуммаПосле) строятся для того, чтобы определить запись для которой СуммаДо < ИскомыйПараметр <= СуммаПосле. Судя по всему Вы под этим понимаете фильтрацию по нарастающим итогам.

НО. При 2 варианте в начале статьи (ish_2) никакой фильтрации нет вовсе.
Делается запрос по всем движениям всех контргаентов и используется выборка Запрос.Выполнить().Выбрать(). И затем сканируя двухуроневую (контргаент,движения) выборку в цикле будем накапливать итог (сумма всех пред.движений) для каждого контргаента и сранивать этот итог с текущим долгом каждого контрагента.
116. anig99 2843 11.12.09 15:26 Сейчас в теме
(108) по поводу объемов. Действительно свыше 100 контрагентов уже сложно воспринимать - для этого нужны агрегированные показатели. Например, просроченная дебиторка(не буду углублятся, но для этого тоже нужны нарастающие итоги) по менеджерам. Выходная таблица небольшая, но анализируемый объем данных огромный.
117. Ish_2 1104 11.12.09 15:36 Сейчас в теме
(108) В целом согласен.
Но интересен эксперимент , который всё расставит по местам : с какого размера таблицы вариант 3(Anig99) превзойдет вариант 2 (Ish_2) по быстродействию ?

1. Пока установлен только факт Вариант 1( Eugeneer) значительно проигрывает по быстродействию Варианту 3(Anig99) на базе 120Гб.
Информация о этом факте представлена Anig99.

2. По информации того же Anig99, времена выполнения Варианта2(ish_2) и Варианта 3(Anig99) на этой же базе 120Гб относятся как 5:1
118. anig99 2843 11.12.09 15:41 Сейчас в теме
(117) я уже в личке говорил, теперь так озвучу, так как не успел к желаемому сроку... Пишу сейчас в свободное время обработку для тестирования времени получения нарастающих итогов разных подходов для разных объемов данных.
120. Ish_2 1104 11.12.09 16:34 Сейчас в теме
(118),(119)
Очень хорошо , что ты пишешь тест сравнивающий разные подходы !
Текущей статье не хватает замеров производительности , о которой писал еще Игорь в (21) . Без такого фактического сравнения можно еще долго спорить о "красивости" и "некрасивости" алгоритмов.

С Вами , Владимир , я опять не согласен. И ничего такого в минусе от Вас на текущую тему не вижу.
101. anig99 2843 10.12.09 14:05 Сейчас в теме
(99) Самый оптимальный для чего????
Вот пример...
Для бомжа жить под теплоцентралью и бутылки собирать - оптимальный вариант... Тепло, относительно сухо, почти каждый вечер можно собрать на бутылку и закусь... Зашибись. Самый оптимальный вариант - для бомжа...
А вот попробуйте таким образом собрать на Ламборджини??? Мало того, что бутылки собирать будете пол жизни, так вам ещё нужно найти кто бомжику без паспорта Ламборджини продаст... так ещё и ездить как-то надо научится, на бензин опять-таки.
С другой стороны, нафуа бомжу Ламборджини??? Ведь большинство бомжуют не из-за отсутствия способностей, а из-за нежелания работать и/или удовлетворения текущим положением вещей.
А положение вещей, конечно, может быть разным... Может климатические условия очень хорошие - бананы там всякие, кокосы... или бомжик совсем маленький и Ламборджини ему игрушечной хватает....
Тут сразу столько оптимумов появляется... Ух...

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

А мы здесь обсуждаем как БЫСТРО заработать на Ламборджини, а не как легко прожить.
102. Ish_2 1104 10.12.09 14:17 Сейчас в теме
104. alexqc 150 10.12.09 19:22 Сейчас в теме
(101) Аналогии хороши для иллюстрации, для доказательства - зло.
Ламборджини - это скорее к САПу.
Встречный вопрос - у Вас очевидно, она уже есть?

А про оптимальность я вполне аргументировал: цикл - линейная трудоемкость, пересечение таблиц - в пределе квадратичная. Говоря Вашими словами - мы покупаем Ломбарджини за ее обычную цену, но предварительно еще оплатив детали, труд сборшиков, механиков и продавца.
113. alexqc 150 11.12.09 13:52 Сейчас в теме
А вот как можно (с извратом) сделать на MSSQL

--наши исходные данные
create table #t(i int,s int)
insert into #t values (1,100)
insert into #t values (2,200)
insert into #t values (4,400)
insert into #t values (3,300)
insert into #t values (5,500)
select * from #t

-- наш результат
create table #tt(i int primary key,s int,ss int)
insert into #tt(i,s) select i,s from #t


/* А вот тут прикол - обработка таблицы будет вестись в порядке ее
первичного ключа. Вот такой неявный цикл.
Это не специфицировано в стандарте, это особенность реализации от MS.*/

declare @d int
set @d=0
update #tt set @d=@d+s, ss=@d

--окончательный результат
select * from #tt
114. Ish_2 1104 11.12.09 14:03 Сейчас в теме
(113) Странно. На почту какие-то приходят соощения , какие-то нет. Отвечу чуть позже.
124. Ish_2 1104 11.12.09 23:28 Сейчас в теме
/* А вот тут прикол - обработка таблицы будет вестись в порядке ее
первичного ключа. Вот такой неявный цикл.
Это не специфицировано в стандарте, это особенность реализации от MS.*/
declare @d int
set @d=0
update #tt set @d=@d+s, ss=@d
--окончательный результат
select * from #tt


(113) Виноват , не понял в чем прикол ? Если мы создали tt с первичным ключом , то ,разумеется, модификация таблицы tt по команде
update #tt set @d=@d+s, ss=@d
будет вестись в порядке ее первичного ключа. Тут есть что-то прикольное ?
125. alexqc 150 14.12.09 11:30 Сейчас в теме
(124) В SQL не специфицирован порядок обработки данных. Вообще классически таблицы рассматриваются как "НЕУПОРЯДОЧЕННЫЙ" набор. ПК (если есть) должен однозначно идентифицировать запись, но не является обязательным указанием порядка обработки. Обработка данных в порядке ПК - это расширение стандарта, сделанное в отдельных реализациях, но не факт что аналогичная конструкция будет работать в другом SQL-сервере. И, наконец, такая конструкция противоречит "декларативному" характеру языка.

(122) Серьезно??? В 8.1 нельзя менять врем. таблицы??? А как они тогда вообще делаются - только через select into? А индексы на поля ставятся?
(Может, это просто разработчики вообще ничего кроме select не реализовали - а неизменение врем. таблиц - только следствие)
Я, признаться, думал что 8.1 позволяет делать все что можно с SQL.

(122) (123) Наверно нам надо четко указывать что имеем ввиду - 77 или 8. В 77 1С++ позволяет послать запрос в любом виде, но получает только 1-й набор пакета (таковы ограничения механизма семерки). Покольку я не работаю с 8кой, то не знаю ее возможностей и ограничений.
В любом случае, если в 8ке столько ограничений - толку от пакета никакого (точнее, пакет ничем не лучше последовательного выполнения неск. запросов).

Но речь шла не только о том, что нельзя получить результаты пакета кроме первого, а о том что вообще нельзя управлять выдачей результата. Т.е. если у меня в пакете есть "промежуточные" select,update,insert - то в результат попадет это все, хотя мне нужен только самый последний select. А нахрена лишние данные гонять? А уж тем более - зачем мне результат допустим insert'а?.



126. Ish_2 1104 14.12.09 11:56 Сейчас в теме
(125)
Серьезно??? В 8.1 нельзя менять врем. таблицы??? А как они тогда вообще делаются - только через select into? А индексы на поля ставятся?
(Может, это просто разработчики вообще ничего кроме select не реализовали - а неизменение врем. таблиц - только следствие)

Ага . Именно так.
К тому же сама возможность работы в 8.1 с временными таблицами ( в 8.0 этого не было) подавалось фирмой 1с - как большое достижение.
128. y-str 60 25.08.11 10:04 Сейчас в теме
Добрый день!

Создал новую публикацию (http://infostart.ru/public/88999/) с возможным альтернативным алгоритмом.
Милости прошу комментировать :)

---
С уважением,
Юрий Строжевский
129. ZLENKO 398 06.09.12 15:20 Сейчас в теме
Насколько я понимаю, предложенный тут вариант годится только для "размазывания" остатка долга на последние документы. На текущий момент мне интересен вариант решения похожей задачи, но чтобы было видно какой документ каким документом "закрылся". Это нужно для отчета по платежной дисциплине - чтобы было видно насколько своевременно происходили оплаты. Пока что решил задачу так: 1) Запрос к базе с получением дебетовых и кредитовых оборотов с детализацией до регистратора; 2) Цикл по "сопоставлению" дебета и кредита; 3) Запись результата сопоставления в регистр сведений; 4) Построение различных отчетов (в т.ч. по просроченной дебиторке) по регистру сведений.
Запрос получающий данные не совсем оптимальный для данной задачи, но взял его готовый из своего отчета по просроченной дебиторке - получает данные секунд 15. Цикл разнесения отрабатывает очень быстро - порядка 3 секунд. Получаем набор записей регистра сведений порядка 130 тыс строк - пишется долго - около 20 секунд (наверное сервер все таки не очень быстрый у клиента).
130. ZLENKO 398 06.09.12 15:25 Сейчас в теме
(129) Приведенные замеры времени для вызова на клиенте. На самом деле все это вынес в регламентное задание - время выполнения на стороне сервера не засекал (по идее должно быть быстрее т.к. не надо данные туда-сюда гонять). Но все же интересно можно ли решить не циклом а запросом ?
P.S.: Данные по оплаченности документов реализации также потом используются для отчета по оплаченной реализации с детализацией до номенклатуры.
131. Ish_2 1104 08.09.12 12:23 Сейчас в теме
(129) С точки зрения программиста задача просроченного долга может иметь несколько вариантов решения.
Варианты эти могут быть сложные или простые , плохие или хорошие..
Но с точки зрения Заказчика ,как мне недавно объяснили, единственным правильным подходом
является следующий : организовать учет по расчетным документам, т.е использовать 3 субконто на счете 62 в БП2.0. Все проблемы тогда решаются сами собой.
132. ZLENKO 398 09.09.12 21:42 Сейчас в теме
(131) Самым правильным вариантом несомненно является вариант учета по расчетным документам. Однако заказчик далеко не всегда хочет как правильно. Когда я был моложе, я старался убедить заказчика как именно правильно. Однако с возрастом появился вопрос "зачем". Сейчас я стараюсь дать заказчику именно то что он хочет и совершенно неважно (ни для него ни для меня) правильно это или нет. В итоге все остаются довольны (и заказчик и я) :-)
133. Ish_2 1104 09.09.12 23:44 Сейчас в теме
(132) Я еще молод,поэтому не упускаю случая , чтобы сказать Заказчику как правильно.
134. user1287561 26.02.20 13:34 Сейчас в теме
Здравствуйте, подскажите пожалуйста, а каким образом реализовать, если в оборотах имеются отрицательные значения, к примеру суммы от Возвратов товара от покупателя?
135. Ish_2 1104 26.02.20 14:54 Сейчас в теме
(134) Я извиняюсь, давно это было (2009г). Попробуйте самостоятельно.
Оставьте свое сообщение