Bodybomber писал(а): 27 авг 2024, 17:23
Отлично. Непременно воспользуюсь. 
 
 
Обязательно попробуйте функцию 
function ReinitTableAsTmp(tableNum: integer; tableName: string): word;
Она позволяет работать с временной таблице на стороне SQL так, как если бы она была описана в словаре, т.е. с ней будут работать гетфёрсты, лупы и прочие виповые штуки без необходимости использования прямого SQL, при этом запросы из реляционного графа будут формироваться оптимизированно, что в некоторых случаях использования невероятно увеличивает производительность.
Ниже пример использования этой функции из моей библиотеки примеров.
Там же использование стандартных виповских группировок в операторе _loop и использование таблиц в памяти (открытие словарных таблиц, как таблиц в памяти)
Код: Выделить всё
Interface MemoryTablesAndGroup 'Таблицы в памяти и группировка данных' ('', hcNoContext, sci1Esc);
  Show at(,,60,5);
  table struct MySuperPick = Pick;
  Create view
  As select *
  From
    oborot (readOnly) //во избежание непреднамеренной порчи данных...
  , pick
  , MySuperPick
  //При сортировке открытой в ТП физической таблы не по индексу (см SchetSubschKau1), обязательно ограничения
  //реализуем через баунды, иначе при восстановлении режима работы, выхватим ошибку.
  bounds B1 =
    0000000000000000h           == oborot.CPLANSSCH and  //План счетов
    date(01,01,Year(cur_date)) <<= oborot.datob and
    date(31,12,Year(cur_date)) >>= oborot.datob
  //Дефайн, чтобы не ломалась структура кода, на работу программы он не влияет
  #define def_order order SchetSubschKau1 external by = oborot.SCHETO, oborot.SUBOSSCH, oborot.KAUOS[1]
  #def_order
  ;
  file ResultFile;
  Screen scMemoryTablesAndGroup '' ('', hcNoContext, sci1Esc);
    Show at(,,,);
    noTableNavigation ;
  Fields
  Buttons
    cmOborot     , [singleLine];
    cmPick       , [singleLine];
    cmMySuperPick, [singleLine];
  <<
 <.Поехали.> Выгрузить OBOROT в ТП и вывести отчет в разрезе Счёт/Субсчёт/Кау1
 <.Поехали.> Переключить таблицу PICK в режим работы в BD
 <.Поехали.> Открыть несловарную временную таблицу во VIEW
  >>
  End;
  function FSUMM(value : double) : string;
  {
    result := DoubleToStr(value, '[|-]366666666666666666666.\2p88')
  }
  procedure MyLogStrToFile(value : wideString) ;
  {
    ResultFile.WriteLn(value);
  }
  #declare WriteToFile(Indent, grName, Itog)
    MyLogStrToFile(
      #Indent+' '
    + 'Сумма = "' + FSUMM(#grName.grSum(Oborot.SUMOB))+ '" '
    + 'Минимум = "' + FSUMM(#grName.grMin(Oborot.SUMOB))+ '" '
    + 'Среднее = "' + FSUMM(#grName.grAvg(Oborot.SUMOB))+ '" '
    + 'Максимум = "' + FSUMM(#grName.grMax(Oborot.SUMOB))+ '" '
    + 'Количество = "' + FSUMM(#grName.grCount(Oborot.SUMOB))+ '" '
    + 'ПерваяСумма = "' + FSUMM(#grName.grFirst(Oborot.SUMOB))+ '" '
    + 'ПоследняяСумма = "' + FSUMM(#grName.grLast(Oborot.SUMOB)) + '"/>'
    );
  #end
  HandleEvent // Interface
    cmOborot :
    {
      ResultFile.OpenFile('Oborot.txt', stCreate)
      //Открываем таблицу oborot, как таблицу в памяти
      if ReinitTable(tnOborot, fmMemory) <> tsOk then exit;
      //Накладываем ограничения
      PushBounds(tbB1);
      //Закачиваем во временную таблицу данные С УЧЁТОМ ОГРАНИЧЕНИЙ!
      //mfFilters - посмотрит на ограничений во view и загрузит только данные,
      //удовлетворяющие им. Если нужны все данные - mfNormal.
      //mfFilters + mfClear закачает данные и очистит таблицу в памяти перед закачкой.
      //На стороне СУБД отработает запрос а-ля:
      //SELECT T0.* FROM OBOROT T0 WHERE T0."SCHETO"=:P1 AND T0."DATOB">=:P2 AND T0."DATOB"<=:P3 ORDER BY T0."SCHETO",T0."DATOB"
      //Важный момент. В ограничения 100% попадают ТОЛЬКО ПОДЦЕПКИ! Узловые фильтры не всегда попадают, однако результирующий набор данных
      //будет такой, как будто попали.
      if mtRetrieve(tnOborot, mfFilters + mfClear) <> tsOk then exit;
      //Ограничения нам тут больше не нужны, т.к. в ТП есть только те данные,
      //которые этим ограничениям соответствуют. Ну или можно другие наложить, если нужно...
      ResetBounds(tbB1);
      //Чтобы группировки работали предсказуемо, нужно предварительно сортировать данные в разрезах группировок
      if mtSetTableOrder(tnOborot, tiSchetSubschKau1) <> tsOk then exit;
      StartNewVisual(vtIndicatorVisual, vfTimer + vfBreak + vfConfirm, 'Формирование отчёта', recordsInTable(tnOborot) );
      //Тут цикл идет по таблице в памяти, а не по таблице в БД
      _loop Oborot{
        MyLogStrToFile('<Счёт Код ="' + Oborot.SCHETO + '">')
        var tmpSubSch : string;
        //Группируем данные по счёту
        groupBy grScheto : Oborot.SCHETO {
          MyLogStrToFile(chr(9)+'<Субсчёт Код = "' + Oborot.SUBOSSCH + '">')
          //Группируем данные по субсчёту
          groupBy grSubOsSch : Oborot.SUBOSSCH {
            //Функции группировки для сложных типов(строка - это массив чаров, дата - вордов и т.д.) недоступны
            //Поэтому для использования таких типов нужно их запоминать в переменные.
            tmpSubSch := Oborot.SUBOSSCH;
            MyLogStrToFile(chr(9)+chr(9)+'<КАУ Код = "' + string(Oborot.KAUOS[1],0,0) + '">')
            //Группируем данные по КАУ1
            groupBy grKau1 : Oborot.KAUOS[1] {
              //Обновление глобальной визуализации следует размещать
              //в нижнем уровне группировки
              if not NextVisual then break;
              SetVisualHeader(
                'Обработка'
              + ''#13'Счет: ' + Oborot.SCHETO + '.' + Oborot.SUBOSSCH
              + ''#13'КАУ1: ' + string(Oborot.KAUOS[1],0,0)
              );
              MyLogStrToFile(
                chr(9)+chr(9)+chr(9)+'<ТекущаяСумма nRec = "' + string(Oborot.nRec,0,0) + '" '
              + 'Сумма = "' + FSUMM(Oborot.SUMOB) + '" '
              + 'НарастающийИтог = "' + FSUMM(grKau1.grSum(Oborot.SUMOB))+ '"/>'
              )
            }
            #WriteToFile(chr(9)+chr(9)+chr(9)+'<ИтогоСумма',grKau1, string(grKau1.grFirst(Oborot.KAUOS[1]),0,0))
            MyLogStrToFile(chr(9)+chr(9)+'</КАУ>')
          }
          #WriteToFile(chr(9)+chr(9)+'<Итого',grSubOsSch, tmpSubSch)
          MyLogStrToFile(chr(9)+'</Субсчёт>')
        }
        #WriteToFile(chr(9)+'<Итого', grSubOsSch, Oborot.SCHETO)
        MyLogStrToFile('</Счёт>')
      }
      StopVisual( '', 0 );
      //ProcessText('Oborot.txt', vfDefault + vfNewTitle, 'Отчёт по оборотам');
      ResultFile.Close;
      //Удаляем созданный индекс в памяти
      mtDropIndex(tnOborot, tiSchetSubschKau1);
      //Открываем таблицу в обычном режиме, т.е. в режиме работы в БД.
      if ReinitTable(tnOborot, fmNormal) <> tsOk then exit;
    }
    cmPick :
    {
      delete all from pick;
      insert Pick set cRec := 0064000000000003h;
      insert Pick set cRec := 0064000000000004h;
      insert Pick set cRec := 0064000000000005h;
      insert Pick set cRec := 0064000000000006h;
      insert Pick set cRec := 0064000000000007h;
      insert Pick set cRec := 0064000000000008h;
      insert Pick set cRec := 0064000000000009h;
      insert Pick set cRec := 006400000000000Ah;
      insert Pick set cRec := 006400000000000Bh;
      insert Pick set cRec := 006400000000000Ch;
      //По умолчанию, таблицы с флагом "Пользовательская таблица", "Временная таблица"
      //открываются, как таблицы в памяти (DataBase.TempTableInMem, DataBase.UserTableInMem).
      //Поэтому тут принудительно сбрасываем все изменения в БД.
      //Можно вызвать сразу после, например, выбора с пометкой записей.
      //При этом, изменения из верхнего delete all тоже залетают, т.е. в базе у нас будет 10 записей.
      mtFlush(tnPick, mfBulkCopy+mfCreateNREC);
      //И эти записи теперь доступны на стороне БД
      var q : iQuery = queryManager.CreateQuery('select name from katmc where exists(select 1 from pick where crec = katmc.nrec)');
      var Results : IResultSet = q.getResultSet;
      if Results = nullRef or Results.Count = 0 then exit;
      do {
        logStrToFile('katmc_from_pick.txt', Results.row.ValAt(1))
      } while Results.getPrev = tsOk;
    }
    cmMySuperPick :
    {
      _try {
        //Создаём временную таблицу на сервере СУБД.
        if sqlCreateTmpTableAs('MySuperDBMSPick', tnMySuperPick, ctmNormal ) <> tsOk then _raise ExVip;
        //Связываем временную таблицу в СУБД с таблицей в памяти
        if ReinitTableAsTmp(tnMySuperPick, 'MySuperDBMSPick') <> tsOk then _raise ExVip;
        //А вот тут работаем уже с нашей временной таблицей в СУБД, как будто она словарная
        //Такую штуку можно использовать для фильтрации через жёсткие подцепки.
        delete all MySuperPick;
        insert MySuperPick set cRec := 0064000000000003h;
        insert MySuperPick set cRec := 0064000000000004h;
        insert MySuperPick set cRec := 0064000000000005h;
        insert MySuperPick set cRec := 0064000000000006h;
        insert MySuperPick set cRec := 0064000000000007h;
        insert MySuperPick set cRec := 0064000000000008h;
        insert MySuperPick set cRec := 0064000000000009h;
        insert MySuperPick set cRec := 006400000000000Ah;
        insert MySuperPick set cRec := 006400000000000Bh;
        insert MySuperPick set cRec := 006400000000000Ch;
        //Вставили VIP'ом, а читаем напрямую с сервера для наглядности
        var q : iQuery = queryManager.CreateQuery('select cRec from MySuperDBMSPick');
        var Results : IResultSet = q.getResultSet;
        if Results = nullRef or Results.Count = 0 then _raise ExVip;
        do {
          logStrToFile('MySuperDBMSPick.txt', string(Results.row.ValAt(1),0,0))
        } while Results.getPrev = tsOk;
      }
      //Удаляем временную таблицу на сервере СУБД
      _finally sqlDropTmpTable('MySuperDBMSPick');
    }
  End;
End.
Присмотритесь к работе со словарными таблицами, как с таблицами в памяти.
Если у пользователей достаточное количество ОЗУ(от 4 ГБ), то для отчётов следует ВСЕГДА открывать таблицы, как ТП, и работать с ними на клиенте.
Если этого не делать, то на стороне СУБД будет наблюдаться RBAR(Row By Agonizing Row), что убивает производительность сервера, особенно если есть клиенты, у которых нестабильное соединение с сетью (какие-нибудь АТПР, удаленные клиенты с мобильным интернетом а-ля базы отдыха и т.п.). Посмотрите статистику своего сервера, я 100% уверен, что самое большое ожидание - это Network I/O.
В общем, следует придерживаться принципа - отдать как можно более полный набор данных на клиента и обрабатывать его уже там, минимально обращаясь к СУБД с уточняющими запросами.