Страница 1 из 1

Галактика аварийно вылетает на создании временной таблицы

Добавлено: 27 авг 2024, 13:21
Bodybomber
Доброго времени суток.

Код: Выделить всё

      If sqlCreateTmpTable(  'Table tblTempMC1 (cMC : Comp, cMOL : Comp, cParty : Comp, cPodr : Comp, cOrg : Comp, ' +
                          ' sOrgName : String, sDocNumber : String, dDocDate : Date, wDocType : Word, sDocTypeName : String, ' +
                          ' sNomNumber : String, sMCName : String, sCOCode : String, sCOName : String, ' +
                          ' dblRestBeg : Double, dblRestEnd : Double, dblKolFact : Double, ' +
                          ' dblPrice : Double, dblTotal : Double, dblSumByMC : Double, sDogNumber  : String);',
                           ctmNormal
                       ) = tsOk Then
        sqlDropTmpTable ( 'tblTempMC1' );
Эмпирическим путем выяснил, что таблица перестает создаваться после определенного количества полей. Галактика аварийно падает!

posting.php?mode=post&f=2#
Прошу подтвердить или опровергнуть эту тезу. Есть ли ограничение на количество полей при создании временной таблицы на сервере (СУБД MS SQL Server 2008 R2) с помощью инструкции прямого SQL?

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 27 авг 2024, 14:45
Irina_
Здравствуйте.
А если воспользоваться sqlAddStr, а потом sqlCreateTmpTable? Думаю, что проблема не в кол-ве полей в таблице, а в длине строки ее описания.

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 27 авг 2024, 16:12
KVS
Bodybomber писал(а): 27 авг 2024, 13:21 Эмпирическим путем выяснил, что таблица перестает создаваться после определенного количества полей. Галактика аварийно падает!
Для создания временных таблиц с большим количеством полей, рекомендую использовать функцию

Код: Выделить всё

function sqlCreateTmpTableAs(tableName: string; tableNum: integer; mode: word): integer;
Это намного практичнее с точки зрения форматирования текста, плюс очень удобно при последующем использовании функции

Код: Выделить всё

function ReinitTableAsTmp(tableNum: integer; tableName: string): word;

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 27 авг 2024, 16:49
KVS
Bodybomber писал(а): 27 авг 2024, 13:21 Прошу подтвердить или опровергнуть эту тезу. Есть ли ограничение на количество полей при создании временной таблицы на сервере (СУБД MS SQL Server 2008 R2) с помощью инструкции прямого SQL?
В данном случае проблема в том, что функция sqlCreateTmpTable принимает или тип String или longInt, где longInt может быть результатом выполнения функций sqlAddStr.

В Вашем случае, в функцию попал тип wideString, который функцией не поддерживается, в итоге на стороне бибилиотеки с данной функцией возникло необработанное исключение, а необработанные исключения в бибилиотеках всегда приводят к падению атлантиса (обычно с ошибкой разделения памяти)

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 27 авг 2024, 17:23
Bodybomber
KVS писал(а): 27 авг 2024, 16:12
Bodybomber писал(а): 27 авг 2024, 13:21 Эмпирическим путем выяснил, что таблица перестает создаваться после определенного количества полей. Галактика аварийно падает!
Для создания временных таблиц с большим количеством полей, рекомендую использовать функцию

Код: Выделить всё

function sqlCreateTmpTableAs(tableName: string; tableNum: integer; mode: word): integer;
Это намного практичнее с точки зрения форматирования текста, плюс очень удобно при последующем использовании функции

Код: Выделить всё

function ReinitTableAsTmp(tableNum: integer; tableName: string): word;
Отлично. Непременно воспользуюсь. :cool:

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 28 авг 2024, 09:16
KVS
Bodybomber писал(а): 27 авг 2024, 17:23 Отлично. Непременно воспользуюсь. :cool:
Обязательно попробуйте функцию
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.
В общем, следует придерживаться принципа - отдать как можно более полный набор данных на клиента и обрабатывать его уже там, минимально обращаясь к СУБД с уточняющими запросами.

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 29 авг 2024, 11:22
Den
то для отчётов следует ВСЕГДА открывать таблицы, как ТП, и работать с ними на клиенте.
KVS, логично, для отчетов сразу выбирать нужные данные( разными реализациями DSQL) по возможным заданным критериям и работать с ними уже. Допустим , тот же Oborot , 100 млн записей на уровне БД. И все это перетаскивать во временную структуру на сервере (ReinitTableAsTmp) ?

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 29 авг 2024, 12:09
KVS
Den писал(а): 29 авг 2024, 11:22
то для отчётов следует ВСЕГДА открывать таблицы, как ТП, и работать с ними на клиенте.
KVS, логично, для отчетов сразу выбирать нужные данные( разными реализациями DSQL) по возможным заданным критериям и работать с ними уже. Допустим , тот же Oborot , 100 млн записей на уровне БД. И все это перетаскивать во временную структуру на сервере (ReinitTableAsTmp) ?
Не встречал в своей практике случаев(для Галактики), когда нужно обработать 100кк записей - это ж за какой период, и кто будет потребителем такого отчёта?

Мой посыл был в сторону каких-то регламетных отчётиков, которые бухи формируют по нескольку раз на дню, если же мы оперируем объемами в десятки миллионов записей, то для этого существуют уже другие технологии, которые реализованы в сторонних продуктах сильно лучше чем в Галке (OLTP + OLAP, например Nifi+PowerBI)

Рекомендация использовать ТП для отчётов всегда, как я еще указал при наличии достаточного объема ОЗУ на клиенте, вызвана тем, что я поголовно видел самописные проекты, где отчёты собирались через _loop с чтением с базы, а не через прямой sql или ТП.

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 29 авг 2024, 12:23
Den
Kvs, ок, понятно

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 30 авг 2024, 12:08
spark
Den писал(а): 29 авг 2024, 11:22
то для отчётов следует ВСЕГДА открывать таблицы, как ТП, и работать с ними на клиенте.
KVS, логично, для отчетов сразу выбирать нужные данные( разными реализациями DSQL) по возможным заданным критериям и работать с ними уже. Допустим , тот же Oborot , 100 млн записей на уровне БД. И все это перетаскивать во временную структуру на сервере (ReinitTableAsTmp) ?
Обратите внимание на то, что в примере не вся таблица oborot выкачивается в оперативку, а только та ее часть, которая попадает под ограничения в Create view. Так что в каком-то смысле это как раз один из подходов к DSQL.

Re: Галактика аварийно вылетает на создании временной таблицы

Добавлено: 30 авг 2024, 12:35
KVS
spark писал(а): 30 авг 2024, 12:08 Обратите внимание на то, что в примере не вся таблица oborot выкачивается в оперативку, а только та ее часть, которая попадает под ограничения в Create view. Так что в каком-то смысле это как раз один из подходов к DSQL.
Еще забыл написать, что подход с открытием словарных таблиц в ТП очень удобен, когда нужно сделать сложную групповую обработку данных и эту обработку сложно или невозможно сделать за запрос, тогда открываем таблицу в ТП, делаем сложные вычисления, апдейтим таблицу и делаем флюш.
В таком случае все изменения полетят на сервер в пакетах, что ускорит и оптимизирует процесс обновления данных.

Также есть интересный способ использования ТП: например, у нас есть фейс1 и фейс2. Нам нужно передать из фейса1 в фейс2 таблицу с N записями. В фейс1 открываем таблицу в ТП и наполняем её. В фейс2 эту же таблицу открываем в ТП, в итоге у нас в фейс2 будут данные, которые мы получили в фейс1 т.е. мы типа передали таблицу из одного фейса в другой.
Это можно сделать и с помощью табличных объектов, но тогда появится фейс3, а это не всегда целесообразно.