Правильная ссылка на эту страницу
http://az-design.ru/Support/DataBase/DBTree2/3220.shtml

Второй вариант заполнения таблицы Goods

Архангельский Андрей

       Другой вариант построения таблицы Goods отличается в первую очередь ее заполнением и, как следствие, незначительные изменения в описании таблицы и триггерах.
       Одной из проблем, которая мешала правильно сформировать деревья, была неопределенность корневых записей для каждого дерева, а также то, что неопределенная запись рассматривалась как самый верхний корень всех деревьев. Эту проблему можно решить если — во-первых, заполнить корневые записи при создании таблицы, и во-вторых, ограничить изменение пользователем этих записей в дальнейшем.
       Так как первоначальное заполнение корней деревьев приравнивается к построению таблицы, то оно записано в скрипте (FB_SQL\dat\DBCreate.doc):

/* Корневые записи деревьев */
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
 values(0,0,0,0,0,1,0,0,0,0,0,
'Внутрифирменные коды продавца',
'Внутрифирменные коды продавца',
'Коды внутренние','Коды внутренние','Коды внутренние',
'Коды внутренние','Коды внутренние');
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
 values(0,0,0,0,0,2,0,0,0,0,0,
'Товарная Номенклатура ВЭД СНГ',
'Товарная Номенклатура ВЭД СНГ',
'Коды ТН ВЭД СНГ','Коды ТН ВЭД СНГ','Коды ТН ВЭД СНГ',
'Коды ТН ВЭД СНГ','Коды ТН ВЭД СНГ');
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
 values(0,0,0,0,0,3,0,0,0,0,0,
'Штрих-код EAN-13 (European Article Numbering) или UPC',
'Штрих-код EAN-13 (European Article Numbering)',
'Коды EAN-13','Коды EAN-13','Коды EAN-13','Коды EAN-13','Коды EAN-13');
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
 values(0,0,0,0,0,4,0,0,0,0,0,
'Код по каталогу фирмы производителя',
'Код по каталогу фирмы производителя',
'Коды по каталогу','Коды по каталогу','Коды по каталогу',
'Коды по каталогу','Коды по каталогу');
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
 values(0,0,0,0,0,5,0,0,0,0,0,
'Аналоги товаров',
'Аналоги товаров',
'Аналоги товаров','Аналоги товаров','Аналоги товаров',
'Аналоги товаров','Аналоги товаров');
Commit;
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,
         1,-1,-1,-1,-1,-1,
  '<INTCD> Код временно не определен',
  '<INTCD> Код временно не определен',
  '<INTCD>','<INTCD>','<INTCD>','<INTCD>','<INTCD>'
    from GoodsNew Gd where GdNames='Внутрифирменные коды продавца'
                     and GdIntCode='Коды внутренние';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,1,
         2147483647, 2147483647, 2147483647, 2147483647, 2147483647,
  '<INTCD> Код не существует',
  '<INTCD> Код не существует',
  '.','.','.','.','.'
    from GoodsNew Gd where GdNames='Внутрифирменные коды продавца'
                     and GdIntCode='Коды внутренние';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,2,
         -1,-1,-1,-1, -1,
'<TNVED> Код временно не определен',
'<TNVED> Код временно не определен',
'<TNVED>','<TNVED>','<TNVED>','<TNVED>','<TNVED>'
    from GoodsNew Gd where GdNames='Товарная Номенклатура ВЭД СНГ'
                       and GdTNVED='Коды ТН ВЭД СНГ';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,2,
  2147483647, 2147483647, 2147483647, 2147483647, 2147483647,
  '<TNVED> Код не существует',
  '<TNVED> Код не существует',
  '.','.','.','.','.'
    from GoodsNew Gd where GdNames='Товарная Номенклатура ВЭД СНГ'
                       and GdTNVED='Коды ТН ВЭД СНГ';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,3,
         -1,-1,-1,-1, -1,
  '<EAN13> Код временно не определен',
  '<EAN13> Код временно не определен',
  '<EAN13>','<EAN13>','<EAN13>','<EAN13>','<EAN13>'
 from GoodsNew Gd where GdNames='Штрих-код EAN-13 (European Article Numbering)'
                  and GdEANCode='Коды EAN-13';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,3,
  2147483647, 2147483647, 2147483647, 2147483647, 2147483647,
  '<EAN13> Код не существует',
  '<EAN13> Код не существует',
  '.','.','.','.','.'
from GoodsNew Gd where GdNames='Штрих-код EAN-13 (European Article Numbering)'
                 and GdEANCode='Коды EAN-13';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,4,
         -1,-1,-1,-1, -1,
'<FIRMS> Код временно не определен',
'<FIRMS> Код временно не определен',
'<FIRMS>','<FIRMS>','<FIRMS>','<FIRMS>','<FIRMS>'
    from GoodsNew Gd where GdNames='Код по каталогу фирмы производителя'
                     and GdFirmCod='Коды по каталогу';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,4,
         2147483647, 2147483647, 2147483647, 2147483647, 2147483647,
  '<FIRMS> Код не существует',
  '<FIRMS> Код не существует',
  '.','.','.','.','.'
    from GoodsNew Gd where GdNames='Код по каталогу фирмы производителя'
                     and GdFirmCod='Коды по каталогу';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,5,
         -1,-1,-1,-1, -1,
'<Alias> Код временно не определен',
'<Alias> Код временно не определен',
'<Alias>','<Alias>','<Alias>','<Alias>','<Alias>'
    from GoodsNew Gd where GdNames='Код по каталогу фирмы производителя'
                     and GdFirmCod='Коды по каталогу';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM,GdPrnAls,GdTreeNo,
                  GdOrder,GdOrdTNK,GdOrdBAR,GdOrdFRM,GdOrdAls,GoodNamR,GdNames,
                  GdTNVED,GdEANCode,GdUPCCode,GdFirmCod,GdIntCode)
  Select Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,Gd.GoodsNo,5,
         2147483647, 2147483647, 2147483647, 2147483647, 2147483647,
  '<Alias> Код не существует',
  '<Alias> Код не существует',
  '.','.','.','.','.'
    from GoodsNew Gd where GdNames='Код по каталогу фирмы производителя'
                     and GdFirmCod='Коды по каталогу';
commit;

       При этом для каждого дерева создается по два дочерних элемента — <Код временно не определен> и <Код не существует>. Последний создан для тех элементов дерева, для которых классификация соответствующего дерева не существует или бессмыслена. Например, для автомобилей нет EAN-кода, хотя для автозапчастей они есть. Одновременно устанавливается порядок сортировки для этих элементов. Для элемента <Код временно не определен> порядок устанавливается в -1, что заставляет отображать его на самом верху дерева. Для элемента <Код не существует> порядок устанавливается в 2147483647, т.е. максимального положительного числа, что заставляет отображать его в самом низу дерева.
       Для того чтобы этот предопределенный порядок пользователь не мог случайно поменять в БД добавляются два исключения:

Create Exception NoRowEdit   'Эту запись редактировать нельзя';
Create Exception NoRowDelete 'Эту запись удалять нельзя';

       Для того чтобы пользователь не мог поменять ключевые поля, на которых строятся уникальные индексы, соответствующим образом меняется триггер на обновление таблицы:

SET TERM !! ;

CREATE TRIGGER GOODSNew_Update FOR GOODSNew ACTIVE BEFORE UPDATE POSITION 0 AS DECLARE VARIABLE Gnr Integer; BEGIN Gnr = GEN_ID(GoodsNew_GEN,0); If ((old.GDNames<>new.GDNames) or (old.GDIntCode<>new.GDIntCode) or (old.GDFirmCod<>new.GDFirmCod))then begin If ((new.GoodsNo<16) and (Gnr>15)) then Exception NoRowEdit; End . . . . . . . . . .

       А также триггер на удаление из таблицы:

SET TERM !! ;
CREATE TRIGGER GOODSNew_Delete FOR GOODSNew
ACTIVE BEFORE DELETE POSITION 0
AS BEGIN
If (old.GoodsNo<16) then Exception NoRowDelete;
. . . . . . . . . . .

       Для того, чтобы не требовалось указывать неопределенные родительские кода, немного меняется описание таблицы — для родительских кодов устанавливаются значения по умолчанию:

Create table GOODSNew
      (GOODSNO    AZInt32 NOT NULL PRIMARY KEY,
       GDPARENT   AZInt32 default 6,   GDCount    AZInt32 default 0,
                                       GDOrder    AZInt32 default 0,
       GDPrnTNK   AZInt32 default 8,   GDCntTNK   AZInt32 default 0,
                                       GDOrdTNK   AZInt32 default 0,
       GDPRNBAR   AZInt32 default 10,  GDCntBAR   AZInt32 default 0,
                                       GDOrdBAR   AZInt32 default 0,
       GDPRNFRM   AZInt32 default 12,  GDCntFRM   AZInt32 default 0,
                                       GDOrdFRM   AZInt32 default 0,
       GDPRNAls   AZInt32 default 14,  GDCntAls   AZInt32 default 0,
                                       GDOrdAls   AZInt32 default 0,
       GDTreeNo   AZInt32 default 1,
       GDNames    AZTitle not null,    -- сокращенное наименование товара
       GOODNAMR   AZLEGEND,   -- наименование товара на русском языке
       GOODNAME   AZLEGEND,   -- наименование товара на английском языке
       GDTradMrk  AZNames,    -- торговая марка
       GDTNVED    AZNOTXT,    -- Код товара по ТН ВЭД
       GDEANCODE  AZNOTXT,    -- Код товара по EAN-13
       GDUPCCODE  AZNOTXT,    -- Код товара по UPC
       GDFIRMCOD  AZNOTXT not null, -- Каталожный код товара
       GDINTCODE  AZNOTXT not null, -- Внутрифирменный код товара
       GDVINCODE  AZNames,    -- VIN-код или его подобие товара / или ТУ;ГОСТ
       GDDESCRPT  AZNotes);   -- описание – текст
Commit;

       т.е. если в запросе не указан один из родительских кодов, то он устанавливается значение элемента <Код временно не определен> соответствующего дерева.

Заполнение таблицы Goods из SQL-скрипта (второй вариант)

       Проблема одновременного заполнения пяти родительских кодов остается, но в данном случае используется вспомогательная таблица GoodsNewTemp:

Create table GoodsNewTemp (
       GoodsNo     AZInt32 default 0 references GoodsNew on update cascade 
                                                         on delete cascade,
       GDParent    AZInt32 default 0 references GoodsNew on update cascade
                                                         on delete cascade,
       GDPrnTNK    AZInt32 default 0 references GoodsNew on update cascade
                                                         on delete cascade,
       GDPrnBAR    AZInt32 default 0 references GoodsNew on update cascade
                                                         on delete cascade,
       GDPrnFRM    AZInt32 default 0 references GoodsNew on update cascade
                                                         on delete cascade,
       GDPrnAls    AZInt32 default 0 references GoodsNew on update cascade
                                                         on delete cascade);
Commit;

       И хранимая процедура, которая устанавливает родительский код для каждого дерева в отдельности:

SET TERM !! ;
Create procedure GoodsSetParent
as
DECLARE VARIABLE GID  Integer;
DECLARE VARIABLE Prnt Integer;
begin
   for select GoodsNo,GdParent from GoodsNewTemp where GdParent<>0 into :GID,:Prnt
     do begin
        Update GoodsNew Set GdParent=:Prnt Where GoodsNo=:GID;
        end
   for select GoodsNo,GdPrnTNK from GoodsNewTemp where GdPrnTNK<>0 into :GID,:Prnt
     do begin
        Update GoodsNew Set GdPrnTNK=:Prnt Where GoodsNo=:GID;
        end
   for select GoodsNo,GdPrnBAR from GoodsNewTemp where GdPrnBAR<>0 into :GID,:Prnt
     do begin
        Update GoodsNew Set GdPrnBAR=:Prnt Where GoodsNo=:GID;
        end
   for select GoodsNo,GdPrnFrm from GoodsNewTemp where GdPrnFrm<>0 into :GID,:Prnt
     do begin
        Update GoodsNew Set GdPrnFrm=:Prnt Where GoodsNo=:GID;
        end
   for select GoodsNo,GdPrnAls from GoodsNewTemp where GdPrnAls<>0 into :GID,:Prnt
     do begin
        Update GoodsNew Set GdPrnAls=:Prnt Where GoodsNo=:GID;
        end
end!!
SET TERM ; !!

       Теперь можно приступать к заполнению таблицы.
       Разделение записей на обычные и групповые (структурные) остается в силе и заполняются они по разному. Кроме того введенное поле GdTreeNo (номер дерева) позволяет по новому построить и отобразить дерево.
       Записи, пределяющие структуру дерева, заполняются напрямую с явной установкой определенных родительских кодов. Например, скрипт {FB_SQL\dat\GoodsNewBooks.txt}:

Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFrm,GdPrnAls,GdOrdFrm,
                     GdTreeNo,GdNames,GoodNamR,GdFirmCod,GdIntCode,GdEANCode)
Select 7,9,11,GoodsNo,15,00058275,4,
  'Издательский дом "Вильямс"',
  'Издательский дом "Вильямс"',
  'WilliamsPublishing','WilliamsPublishing','9798_8275'
  from GoodsNew
 where GdFirmCod='Издательства'
   and GdNames='Издательства, Издательские дома и средства массовой информации';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFrm,GdPrnAls,GdOrdFrm,
                     GdTreeNo,GdNames,GoodNamR,GdFirmCod,GdIntCode,GdEANCode)
Select 7,9,11,GoodsNo,15,00058275,4,
  'Издательский дом "Вильямс" Группа 8275',
  'Издательский дом "Вильямс" Группа 8275',
  'WilliamsPublishing','WilliamsPublishing','9798_8275'
  from GoodsNew
 where GdFirmCod='WilliamsPublishing'
   and GdNames='Издательский дом "Вильямс"';
Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFrm,GdPrnAls,GdOrdFrm,
                     GdTreeNo,GdNames,GoodNamR,GdFirmCod,GdIntCode,GdEANCode)
Select 7,9,11,GoodsNo,15,00058459,4,
  'Издательский дом "Вильямс" Группа 8459',
  'Издательский дом "Вильямс" Группа 8459',
  'WilliamsPublishing','WilliamsPublishing','9798_8459'
  from GoodsNew
 where GdFirmCod='WilliamsPublishing'
   and GdNames='Издательский дом "Вильямс"';
Commit;

       Первая особенность — поля "Parent" устанавливаются в положение <Код не существует> для всех деревьев, кроме текущего, т.е. для которого строится дерево. Родительский код для текущего дерева получается из запроса.
       Второе — из полей GdIntCode, GdTNVED, GdEANCode, GdFirmCod — заполняются поле, соответствующее текущему дереву, и GdIntCode, GdFirmCod, как входящие в уникальные индексы.
       Номер дерева устанавливается в соответствии первоначальным порядком заполнения.
       Однако, некоторые записи, которые оформляют структуру в одном дереве, могут являтся началом такой же структуры в другом дереве. В этом случае ничего не мешает изменить родительские коды на другие с помощью вспомогательной таблицы GoodsNewTemp:

Insert into GoodsNewTemp(GoodsNo,GdPrnBAR)
Select gd.GoodsNo,gp.GoodsNo from GoodsNew as gd, GoodsNew as gp
 where gd.GdFirmCod='WilliamsPublishing'
   and gd.GdNames='Издательский дом "Вильямс" Группа 8275'
   and gp.GdEANCode='9798'
   and gp.GdNames='Россия, СНГ - книги и другие издания';
Insert into GoodsNewTemp(GoodsNo,GdPrnBAR)
Select gd.GoodsNo,gp.GoodsNo from GoodsNew as gd, GoodsNew as gp
 where gd.GdFirmCod='WilliamsPublishing'
   and gd.GdNames='Издательский дом "Вильямс" Группа 8459'
   and gp.GdEANCode='9798'
   and gp.GdNames='Россия, СНГ - книги и другие издания';

       Первое поле GoodsNo, является идентификатором записи и для его поиска используются поля входящие в один или другой уникальный индекс.
       Второе поле, в данном случае GdPrnBAR, является родительским к текущей записи в соответствующем дереве.
       После выполнения хранимой процедуры GoodsSetParent таблица GoodsNew будет обновлена и для всех записей перечисленных в таблице GoodsNewTemp будут установлены правильные родительские кода. Во избежании ошибок выполнение очистки таблицы GoodsNewTemp лучше выполнять в начале скрипта, а выполнение процедуры GoodsSetParent в конце того же скрипта.
       Заполнение обычных записей можно выполнять с того дерева, которое в каждом конкретном случае будет удобнее. Например, если скрипт строится на основании каталога фирмы, то основным будет дерево No.4, так как показано ниже:

Insert into GoodsNew(GdPrnFrm,GdOrdFrm, GdNames, GoodNamR,
                     GdFirmCod, GdIntCode, GdTNVED, GdEANCode,GdDescrpt)
Select GoodsNo,82750250,
  'Ларман Крэг "Применение UML и шаблонов проектирования" 2-е изд. [Вильямс]',
  'Ларман Крэг "Применение UML и шаблонов проектирования" 2-е изд. [Вильямс]',
  '5-8459-0250-9','УДК:681.3.07','4901 00 000 0','9798_8275_0250',
  'Книга поможет освоить основные принципы и самые современные приемы 
   объектно-ориентированного анализа и проектирования (ООА/П). В ней вы 
   найдете новые сведения о шаблонах проектирования, прецедентах, 
   архитектурном анализе и многих других вопросах, которые рассматриваются 
   в рамках одного из самых популярных итеративных процессов проектирования UP. 
   На протяжении всей книги рассматривается один реальный пример, 
   модифицированный по сравнению с первым изданием книги. 
   Для построения моделей используется унифицированный язык моделирования UML, 
   ставший фактическим стандартом объектно-ориентированного анализа и 
   проектирования. Данная книга будет хорошим путеводителем для всех, кто 
   интересуется вопросами ООА/П, как для новичков, так и для специалистов.'
  from GoodsNew
 where GdFirmCod='WilliamsPublishing'
   and GdNames='Издательский дом "Вильямс" Группа 8459';

       В качестве родительского кода указывается только один — по текущему дереву. Остальные автоматически установятся в положение <код временно не определен>. В полях GdFirmCod, GdIntCode, GdTNVED, GdEANCode сразу записываются все необходимые коды. После чего в таблицу GoodsNewTemp выбираются родительские коды, как показано ниже:

Insert into GoodsNewTemp(GoodsNo,GdParent)
Select gd.GoodsNo,gp.GoodsNo from GoodsNew as gd, GoodsNew as gp
 where gd.GdFirmCod='5-8459-0250-9'
   and gd.GdNames='Ларман Крэг "Применение UML и шаблонов проектирования" 2-е изд. [Вильямс]'
   and gp.GdIntCode='УДК:681.3.07' 
   and gp.GdNames='<Аспекты запоминающих устройств (ЗУ) и ввода - вывода>. Исключено E&C 18 [1996]';
Insert into GoodsNewTemp(GoodsNo,GdPrnTNK)
Select gd.GoodsNo,gp.GoodsNo from GoodsNew as gd, GoodsNew as gp
 where gd.GdFirmCod='5-8459-0250-9'
   and gd.GdNames='Ларман Крэг "Применение UML и шаблонов проектирования" 2-е изд. [Вильямс]'
   and gp.GdNames='Печатные книги, брошюры, листовки и аналогичные печатные материалы, сброшюрованные или в виде отдельных листов:'
   and gp.GdTNVED='4901';
Insert into GoodsNewTemp(GoodsNo,GdPrnBAR)
Select gd.GoodsNo,gp.GoodsNo from GoodsNew as gd, GoodsNew as gp
 where gd.GdFirmCod='5-8459-0250-9'
   and gd.GdNames='Ларман Крэг "Применение UML и шаблонов проектирования" 2-е изд. [Вильямс]'
   and gp.GdNames='Издательский дом "Вильямс" Группа 8459'
   and gp.GdEANCode='9798_8459';

       И, наконец, в конце скрипта стоит выполнение хранимой процедуры:

Execute procedure GoodsSetParent;
commit;

 

Отображение дерева таблицы Goods (второй вариант)

       Отображение дерева начинается с события onFormCreate, в котором ComboBox (cbxTreeSel) заполняется названиями деревьем из таблицы Goods. Т.е. то что обычно используется для построения корня дерева, здесь используется для построения списка выбора — какое дерево нужно отображать.

procedure TForm1.FormCreate(Sender: TObject); {Example06}
begin
   cbxTreeSel.Items.Clear;
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select GdNames from GoodsNew where GdParent=0');
   qrExeProc.Open;  qrExeProc.First;
   While not qrExeProc.Eof do begin
      cbxTreeSel.Items.Add(qrExeProc.FieldValues['GdNames']);
      qrExeProc.Next;
   end;
   cbxTreeSel.ItemIndex:=0;
   GoodsDatasetOpen();
end;

       После построения списка ItemIndex устанавливается в 0, что приводит форму в начальное состояние:


Рис.2-5 Example06 - начальное состояние

       Обработка события onSelect чрезвычайно проста:

procedure TForm1.cbxTreeSelSelect(Sender: TObject);
begin
   GoodsDatasetOpen(); // построить дерево
   tvGoods.SetFocus;   // передать фокус
end;

       Запускается процедура построения дерева в соответствии с выбранным значением ItemIndex:

procedure TForm1.GoodsDatasetOpen();
Var
   RowChild   : Integer;
   sFld  : String;
   ChildNode,NewNode : TTreeNode;
begin
  inherited;
  If qrTVGoods.Database.Connected then
     begin
        tvGoods.Items.BeginUpdate;
        tvGoods.Visible := False;
        tvGoods.Items.Clear;
        tvGoods.Visible := True;
        If cbxTreeSel.ItemIndex>0 then begin
        trTVGoods.Active := True;
        qrTVGoods.Close;   qrTVGoods.SQL.Clear;
        qrTVGoods.SQL.Add('Select * from GoodsNew');
        If cbxTreeSel.ItemIndex=1 then
                       qrTVGoods.SQL.Add('where GDParent=1 order by GdOrder');
        If cbxTreeSel.ItemIndex=2 then
                       qrTVGoods.SQL.Add('where GdPrnTNK=2 order by GDOrdTNK');
        If cbxTreeSel.ItemIndex=3 then
                       qrTVGoods.SQL.Add('where GdPrnBAR=3 order by GdOrdBAR');
        If cbxTreeSel.ItemIndex=4 then
                       qrTVGoods.SQL.Add('where GdPrnFRM=4 order by GdOrdFRM');
        If cbxTreeSel.ItemIndex=5 then
                       qrTVGoods.SQL.Add('where GdPrnAls=5 order by GdOrdAls');
        qrTVGoods.Open;     qrTVGoods.First;
        While not qrTVGoods.EOF do
          begin
             RowChild := 0;
             If cbxTreeSel.ItemIndex=1 then
                If not VarIsNull(qrTVGoods.FieldValues['GDINTCODE'])
                    then sFld := qrTVGoods.FieldValues['GDINTCODE'];
             If cbxTreeSel.ItemIndex=2 then
                If not VarIsNull(qrTVGoods.FieldValues['GDTNVED'])
                    then sFld := qrTVGoods.FieldValues['GDTNVED'];
             If cbxTreeSel.ItemIndex=3 then
                If not VarIsNull(qrTVGoods.FieldValues['GDEANCODE'])
                    then sFld := qrTVGoods.FieldValues['GDEANCODE'];
             If cbxTreeSel.ItemIndex=4 then
                If not VarIsNull(qrTVGoods.FieldValues['GDFIRMCOD'])
                    then sFld := qrTVGoods.FieldValues['GDFIRMCOD'];
             If cbxTreeSel.ItemIndex=5 then
                If not VarIsNull(qrTVGoods.FieldValues['GDINTCODE'])
                    then sFld := qrTVGoods.FieldValues['GDINTCODE'];
             sFld := sFld+' - '+qrTVGoods.FieldValues['GDNames'];
             NewNode := tvGoods.Items.Add(tvGoods.TopItem,sFld);
             NewNode.ImageIndex := qrTVGoods.FieldValues['GoodsNo'];
             NewNode.OverlayIndex := qrTVGoods.FieldValues['GdTreeNo'];
             If cbxTreeSel.ItemIndex=1 then
                                 RowChild := qrTVGoods.FieldValues['GDCount'];
             If cbxTreeSel.ItemIndex=2 then
                                 RowChild := qrTVGoods.FieldValues['GDCntTNK'];
             If cbxTreeSel.ItemIndex=3 then
                                 RowChild := qrTVGoods.FieldValues['GDCntBAR'];
             If cbxTreeSel.ItemIndex=4 then
                                 RowChild := qrTVGoods.FieldValues['GDCntFRM'];
             If cbxTreeSel.ItemIndex=5 then
                                 RowChild := qrTVGoods.FieldValues['GDCntAls'];
             If RowChild>0 then 
               ChildNode := tvGoods.Items.AddChild(NewNode,IntToStr(RowChild));
             qrTVGoods.Next;
          end; // While not qrTVGoods.EOF do
          end;
          tvGoods.Items.EndUpdate;
          tvGoods.Update;
          qrTVGoods.Close;      trTVGoods.Active := False;
    end; // qrTVGoods.Database.Connected
end;

       В принципе эта процедура мало чем отличается от такой же процедуры для одного дерева. В зависимости от значения cbxTreeSel.ItemIndex выбирается условия для основного запроса, поле кода для текста строки узла дерева, а также поле количества детей у данного элемента. Новшеством является хранение в Node.OverleyIndex номера дерева, который берется из записи (это не обязательно соответствует номеру дерева, которое отображается).
       Естестенно, что использование свойств узла ImageIndex и OverlayIndex возможно только в том случае если для узлов не используются картинки и свойство TreeView.Image пустое.
       Точно таким же способом строится дерево при раскрытии узла. Для этого используется процедура, запускаемая событием onExpanding. Но новый узел добавляется не в корень, а в текущий узел:

procedure TForm1.tvGoodsExpanding(Sender: TObject; Node: TTreeNode; 
                             var AllowExpansion: Boolean); {Example06}
Var
   RowChild : Integer;
   sFld : String;
   ChildNode,NewNode : TTreeNode;
begin
  inherited;
     If Node.HasChildren then begin
        Node.DeleteChildren;
        trTVGoods.Active := True;
        qrTVGoods.Close;   qrTVGoods.SQL.Clear;
        qrTVGoods.SQL.Add('Select * from GoodsNew');
        sFld := IntToStr(Node.ImageIndex);
        If cbxTreeSel.ItemIndex=1 then
                qrTVGoods.SQL.Add('where GDParent='+sFld+' order by GdOrder');
        If cbxTreeSel.ItemIndex=2 then
                qrTVGoods.SQL.Add('where GdPrnTNK='+sFld+' order by GDOrdTNK');
        If cbxTreeSel.ItemIndex=3 then
                qrTVGoods.SQL.Add('where GdPrnBAR='+sFld+' order by GdOrdBAR');
        If cbxTreeSel.ItemIndex=4 then
                qrTVGoods.SQL.Add('where GdPrnFRM='+sFld+' order by GdOrdFRM');
        If cbxTreeSel.ItemIndex=5 then
                qrTVGoods.SQL.Add('where GdPrnAls='+sFld+' order by GdOrdals');
        qrTVGoods.Open;     qrTVGoods.First;
        While not qrTVGoods.EOF do
          begin   RowChild := 0;
             If cbxTreeSel.ItemIndex=1 then
                If not VarIsNull(qrTVGoods.FieldValues['GDINTCODE'])
                    then sFld := qrTVGoods.FieldValues['GDINTCODE'];
             If cbxTreeSel.ItemIndex=2 then
                If not VarIsNull(qrTVGoods.FieldValues['GDTNVED'])
                    then sFld := qrTVGoods.FieldValues['GDTNVED'];
             If cbxTreeSel.ItemIndex=3 then
                If not VarIsNull(qrTVGoods.FieldValues['GDEANCODE'])
                    then sFld := qrTVGoods.FieldValues['GDEANCODE'];
             If cbxTreeSel.ItemIndex=4 then
                If not VarIsNull(qrTVGoods.FieldValues['GDFIRMCOD'])
                    then sFld := qrTVGoods.FieldValues['GDFIRMCOD'];
             If cbxTreeSel.ItemIndex=5 then
                If not VarIsNull(qrTVGoods.FieldValues['GDINTCODE'])
                    then sFld := qrTVGoods.FieldValues['GDINTCODE'];
             sFld := sFld+' '+qrTVGoods.FieldValues['GDNames'];
             NewNode := tvGoods.Items.AddChild(Node,sFld);
             NewNode.ImageIndex := qrTVGoods.FieldValues['GoodsNo'];
             NewNode.OverlayIndex := qrTVGoods.FieldValues['GdTreeNo'];
             If cbxTreeSel.ItemIndex=1 then 
                           RowChild := qrTVGoods.FieldValues['GDCount'];
             If cbxTreeSel.ItemIndex=2 then 
                           RowChild := qrTVGoods.FieldValues['GDCntTNK'];
             If cbxTreeSel.ItemIndex=3 then
                           RowChild := qrTVGoods.FieldValues['GDCntBAR'];
             If cbxTreeSel.ItemIndex=4 then 
                           RowChild := qrTVGoods.FieldValues['GDCntFRM'];
             If cbxTreeSel.ItemIndex=5 then 
                           RowChild := qrTVGoods.FieldValues['GDCntAls'];
             If RowChild>0 then
           ChildNode := tvGoods.Items.AddChild(NewNode,IntToStr(RowChild));
             qrTVGoods.Next;
          end; // While not qrTVGoods.EOF do
           tvGoods.Items.EndUpdate;
           trTVGoods.Active := False;
     end;// Node.HasChildren
     tvGoods.Update;
end;

       И для того, чтобы дерево красиво выглядело, его можно раскрасить.

procedure TForm1.tvGoodsCustomDrawItem(Sender: TCustomTreeView;  Node: TTreeNode; 
                    State: TCustomDrawState; var DefaultDraw: Boolean); {Example06}
var
  NodeRect: TRect;
begin
  with tvGoods.Canvas do
  begin
    If cdsHot in State then tvGoods.Canvas.Font.Style := [fsUnderline];
    If Node.OverlayIndex=0 then
       begin tvGoods.Canvas.Font.Color := clBlack;
             tvGoods.Canvas.Font.Style := []; end;
    If Node.OverlayIndex=1 then
       begin tvGoods.Canvas.Font.Color := clNavy;
             tvGoods.Canvas.Font.Style := [fsBold]; end;
    If Node.OverlayIndex=2 then
       begin tvGoods.Canvas.Font.Color := clGreen;
             tvGoods.Canvas.Font.Style := [fsBold]; end;
    If Node.OverlayIndex=3 then
       begin tvGoods.Canvas.Font.Color := clBlue;
             tvGoods.Canvas.Font.Style := [fsBold]; end;
    If Node.OverlayIndex=4 then
       begin tvGoods.Canvas.Font.Color := clMaroon;
             tvGoods.Canvas.Font.Style := [fsBold]; end;
    If Node.OverlayIndex=5 then
       begin tvGoods.Canvas.Font.Color := clRed;
             tvGoods.Canvas.Font.Style := [fsBold]; end;
    if cdsSelected in State then begin
      Brush.Color := clInfoBk;
      If Node.OverlayIndex=0 then tvGoods.Canvas.Font.Style := [fsBold];
    end else Begin
      Brush.Color := clWindow;
      If Node.OverlayIndex=0 then tvGoods.Canvas.Font.Style := [];
    end;
    NodeRect := Node.DisplayRect(True);
    tvGoods.Canvas.FillRect(NodeRect);
  end;
end;

       Что в результате получилось показано ниже:


Рис.2-6 Выборка по первому дереву

       В качестве "интересного товара" возьмем книгу издательства "Вильямс" "Использование Visual Basic 6". Первое дерево строится по внутрифирменным кодам продавца. Так как книжные магазины в подавляющем большинстве работают по УДК (Универсальной Десятичной Классификации) то и в данном случае будет использоваться эта классификация. Каждое издательство на каждой книге печатает код УДК для конкретной книги. Правда не всегда правильно, что и видно на примере (все данные в БД взяты из реального каталога издательства "Вильямс"). Как было указано в процедуре tvGoodsCustomDrawItem записи формирующие структуру первого дерева отображаются цветом clNavy и стилем Bold. Записи о товаре отображаются черным цветом и обычным стилем без выделений. Однако, выбранная запись о товаре отображается жирным шрифтом.


Рис.2-7 Выборка по второму дереву

Записи, которые формируют второе дерево отображаются зеленым цветом. Правила для отображения записи о товаре остаются теми же. В БД для примеров "Товарная Номенклатура" с целью экономии урезана до 3 уровня. Но данная группа (Группа 49) представлена полностью. Таково представление таможеников о книгах.


Рис.2-8 Выборка по третьему дереву

       При отображении третьего дерева расцветка позволяет определить какое дерево является основным для записей формирующих структуру. В данном случае часть структуры EAN-кода, которая относится к издательству соответствует структуре кода ISBN, который издательства используют для кодирования. Таким образом основное дерево по EAN-коду окрашено синим цветом, часть дерева, которое относится к издательству окрашено бордовым. Для книг из EAN кода убрана контрольная цифра и изменено разбиение на группы для удобства чтения.


Рис.2-9 Выборка по четвертому дереву

       И, наконец, та же книга в кодировке издательства "Вильямс". Здесь записи формирующие структуру четвертого дерева окрашены в бордовый цвет.

 

Отображение записи на форме

       Для отображения всех полей записи на форме используется прием, описанный ранее. По событию onChange строится запрос для получения группы записей, которые являются дочерними к текущей:

procedure TForm1.tvGoodsChange(Sender: TObject; Node: TTreeNode); {Example06}
begin
  trNodeGoods.Active := False;  trNodeGoods.Active := True;
  qrNodeGoods.Close;   qrNodeGoods.SQL.Clear;
  qrNodeGoods.SQL.Add('Select * from GoodsNew');
  If cbxTreeSel.ItemIndex=1 then 
             qrNodeGoods.SQL.Add(' where GdParent='+IntToStr(Node.ImageIndex));
  If cbxTreeSel.ItemIndex=2 then
             qrNodeGoods.SQL.Add(' where GdPrnTNK='+IntToStr(Node.ImageIndex));
  If cbxTreeSel.ItemIndex=3 then
             qrNodeGoods.SQL.Add(' where GdPrnBAR='+IntToStr(Node.ImageIndex));
  If cbxTreeSel.ItemIndex=4 then
             qrNodeGoods.SQL.Add(' where GdPrnFrm='+IntToStr(Node.ImageIndex));
  If cbxTreeSel.ItemIndex=5 then
             qrNodeGoods.SQL.Add(' where GdPrnAls='+IntToStr(Node.ImageIndex));

       Для основного запроса определяется поле, которое является родительским в текущем дереве

// Для обеспечения редактирования
  updNodeGoods.RefreshSQL.Clear;
  updNodeGoods.RefreshSQL.Add('Select * from GoodsNew');
  updNodeGoods.RefreshSQL.Add('where GoodsNo=:OLD_GoodsNo');
// Запрос на удаление
  updNodeGoods.DeleteSQL.Clear;
  updNodeGoods.DeleteSQL.Add('Delete from GoodsNew');
  updNodeGoods.DeleteSQL.Add(' where GoodsNo = :OLD_GoodsNo');

       Запросы на обновление и удаление обычные в таких случаях

// Запрос на вставку
  updNodeGoods.InsertSQL.Clear;
  If cbxTreeSel.ItemIndex=1 then
                 updNodeGoods.InsertSQL.Add('Insert into GoodsNew(GdParent,');
  If cbxTreeSel.ItemIndex=2 then
                 updNodeGoods.InsertSQL.Add('Insert into GoodsNew(GdPrnTNK,');
  If cbxTreeSel.ItemIndex=3 then
                 updNodeGoods.InsertSQL.Add('Insert into GoodsNew(GdPrnBAR,');
  If cbxTreeSel.ItemIndex=4 then
                 updNodeGoods.InsertSQL.Add('Insert into GoodsNew(GdPrnFrm,');
  If cbxTreeSel.ItemIndex=5 then
                 updNodeGoods.InsertSQL.Add('Insert into GoodsNew(GdPrnAls,');
  updNodeGoods.InsertSQL.Add('GdNames,GoodNamR,GoodNamE,GdTradMrk,');
  updNodeGoods.InsertSQL.Add(' GdTNVED,GdEANCode,GdFirmCod,GdIntCode,');
  updNodeGoods.InsertSQL.Add('GdVINCode,GdDescrpt)');
  updNodeGoods.InsertSQL.Add(' values('+IntToStr(Node.ImageIndex)+',');
  updNodeGoods.InsertSQL.Add(':GdNames,:GoodNamR,:GoodNamE,:GdTradMrk,');
  updNodeGoods.InsertSQL.Add(':GdTNVED,:GdEANCode,:GdFirmCod,:GdIntCode,');
  updNodeGoods.InsertSQL.Add(':GdVINCode,:GdDescrpt)');

       Запрос на вставку также определяет родительское поле для текущего дерева. Для остальных родительских полей вставляются коды по умолчанию, которые указывают на гнездо <код временно не определен>.

// Запрос на модификацию
  updNodeGoods.ModifySQL.Clear;
  updNodeGoods.ModifySQL.Add('Update GoodsNew Set');
  updNodeGoods.ModifySQL.Add(' GdNames = :GdNames,');
  updNodeGoods.ModifySQL.Add(' GoodNamR = :GoodNamR,');
  updNodeGoods.ModifySQL.Add(' GoodNamE = :GoodNamE,');
  updNodeGoods.ModifySQL.Add(' GdTradMrk = :GdTradMrk,');
  updNodeGoods.ModifySQL.Add(' GdTNVED = :GdTNVED,');
  updNodeGoods.ModifySQL.Add(' GdEANCode = :GdEANCode,');
  updNodeGoods.ModifySQL.Add(' GdFirmCod = :GdFirmCod,');
  updNodeGoods.ModifySQL.Add(' GdIntCode = :GdIntCode,');
  updNodeGoods.ModifySQL.Add(' GdVINCode = :GdVINCode,');
  updNodeGoods.ModifySQL.Add(' GdDescrpt = :GdDescrpt');
  updNodeGoods.ModifySQL.Add(' Where GoodsNo = :OLD_GoodsNo');

       Запрос на модификацию обычный для такого случая.

  qrNodeGoods.Open;
end;

       Пример формы и сетки для редактирования узла показан ниже:


Рис.2-10 Форма для редактирования товаров

Сохранение таблицы Goods в SQL-скрипте

       Сохранение таблицы Goods в SQL-скрипте в общем случае невозможно. Т.е. это невозможно в случае, когда некоторая часть таблицы (начиная с конкретного узла дерева) переносится в другую БД, в которой уже есть некоторый набор записей, пусть даже не совпадающих с новыми. И виноваты в этом генераторы.
       Возможно сохранить всю таблицу целиком весьма простым приемом:
       — Сохранить в скрипте все записи, игнорируя связи между ними (поля Parent);
       — Сохранить в скрипте операторы Update, устанавливающие связи между записями:

Update Goods set GdParent=123456, GdPrnTNK=452156, GdPrnBAR=35124, GdPrnFrm=5241, GdPrnAls=1425
 Where GoodsNo=15342;

       Так обычно делают программы или компоненты экспорта/импорта.
       Если бы идентификатор записи формировался как функция от ключевых полей, в данном случае GdIntCode,GdNames, то проблем бы не было. Но идентификатор формируется как значение генератора, соответствующее порядку вставки записей от создания БД. И новый порядок записей не будет соответствовать тому, который был в старой БД, следовательно такой способ не подходит.
       Основная задача в данном случае состоит в том, чтобы с помощью скрипта объединять различные разделы (в тематическом смысле) таблицы, созданные в разных БД. И для этого необходим какой-то алгоритм, который позволит это сделать независимо от того как формируются идентификаторы.
       Если разбить задачу на несколько частей и проанализировать каждую из них, то можно обнаружить что по отдельности каждая решается достаточно легко.
       Первое — так как в таблице есть явный признак того, что запись относится либо к структуре, либо к товару, то можно структурные записи сохранять отдельно как классификацию какого-нибудь раздела.
       Второе — как минимум одно дерево сохранять не требуется. Это "Товарная номенклатура ВЭД", оно не редактируется (по крайней мере пользователем) и существует при поставке БД.
       Третье — при сохранении записей о товарах для того чтобы не искать родителей по соседним деревьям, можно сохранить товары по ветке одного дерева, а затем (в другом скрипте) товары по ветке другого дерева. Это создаст некоторую избыточность и ошибки уникальности при заполнении БД, но на выполнении скрипта никак не скажется.
       Если ни один из вариантов не нравиться, то можно применить прием, который описан в примере {Example04} "Сохранение таблицы Persons в SQL-скрипте. Вариант 2".

Часть первая

       Сохранение структурных записей по выбранному дереву в SQL-скрипт.
       Этот вариант очень простой, похож на сохранение таблицы с одиночным деревом, с некоторыми дополнениями. В примере {Example06} он привязан к кнопке "Сохранить ветку в SQL".

procedure TForm1.btnSaveChildToSQLClick(Sender: TObject); {Example05}
Var
   SvCursor : TCursor;
   Br,GdCode,sFld : String;
   Str1,Str2,Str3,Str4,Str1A,Str2A : String;
   TrNo,NodID,RC : Integer;
   blCod : Boolean;
begin
  inherited;
SvCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
 if not DirectoryExists(ExtractFilePath(Application.ExeName)+'\DtSave') then
    if not CreateDir(ExtractFilePath(Application.ExeName)+'\DtSave') then
    raise Exception.Create('Cannot create '
                           +ExtractFilePath(Application.ExeName)+'\DtSave');

       В каталоге где лежит приложение создается каталог DtSave для сохранения данных. Для того чтобы не запрашивать у пользователя имя файла оно определяется как буква (A,B,C,D,E) соответствующая номеру дерева и ID записи, с которой начинается сохранение. В скрипт сразу вывводится оператор очищающий таблицу GoodsNewTemp, так как, несмотря на, то что сохраняется только одна ветка, у узлов могут быть связи с другими деревьями и их придеться обрабатывать.

{Сохранение таблиц для построения Goods }
   TrNo := cbxTreeSel.ItemIndex;
   Br := Char(Ord('A')+TrNo-1);
   NodID := tvGoods.Selected.ImageIndex;
   AssignFile(flReprt,ExtractFilePath(Application.ExeName)
                              +'\DtSave\Goods'+Br+IntToStr(NodID)+'.txt');
   ReWrite(flReprt);
   WriteLn(flReprt,'Delete from GoodsNewTemp;');
   WriteLn(flReprt,'Commit;');   WriteLn(flReprt,'');
   qrExeProc.Close;    qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Delete from GoodsNewLev');
   qrExeProc.ExecSQL;
   qrExeProc.Close;    qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Execute procedure GoodsNewChildLev('
                     +IntToStr(NodID)+','+IntToStr(TrNo)+',0)');
   qrExeProc.ExecSQL;

       Для получения списка всех детей выбранного узла вызывается процедура GoodsNewChildLev, которая заносит их в таблицу GoodsNewLev. В качестве входных данных процедуре передается ID узла и номер выбранного дерева.

   qrExeProc.Close;    qrExeProc.SQL.Clear;
Str1A := 'Insert into GoodsNew(GdParent,GdPrnTNK,GdPrnBAR,GdPrnFRM, GdPrnAls';
   case cbxTreeSel.ItemIndex of
      1 : begin

qrExeProc.SQL.Add('Select gp.GDNames as gp_GDNames,' +'gp.GDIntCode as gp_GDCode,'); Str2A := ' Select GoodsNo,9,11,13,15'; GdCode := 'GdIntCode'; end; 2 : begin qrExeProc.SQL.Add('Select gp.GDNames as gp_GDNames,' +'gp.GDTNVED as gp_GDCode,'); Str2A := ' Select 7,GoodsNo,11,13,15'; GdCode := 'GdTNVED'; end; 3 : begin qrExeProc.SQL.Add('Select gp.GDNames as gp_GDNames,' +'gp.GDEANCode as gp_GDCode,'); Str2A := ' Select 7,9,GoodsNo,13,15'; GdCode := 'GdEANCode'; end; 4 : begin qrExeProc.SQL.Add('Select gp.GDNames as gp_GDNames,' +'gp.GDFirmCod as gp_GDCode,'); Str2A := ' Select 7,9,11,GoodsNo,15'; GdCode := 'GdFirmCod'; end; 5 : begin qrExeProc.SQL.Add('Select gp.GDNames as gp_GDNames,' +'gp.GDIntCode as gp_GDCode,'); Str2A := ' Select 7,9,11,13,GoodsNo'; GdCode := 'GdIntCode'; end; end; qrExeProc.SQL.Add('gd.*'); qrExeProc.SQL.Add('from GoodsNew as gd,GoodsNew as gp, GoodsNewLev gl'); qrExeProc.SQL.Add('where gd.GoodsNo=gl.GoodsNo and gp.GoodsNo=gl.GdParent'); qrExeProc.SQL.Add(' and gd.GdTreeNo<>0'); qrExeProc.SQL.Add('order by gl.GdsLevel'); qrExeProc.Open; qrExeProc.First;

       Прежде чем перебирать получившиеся записи, в зависимости от выбранного дерева строится запрос, который выдаст не только все поля выбранных детей, но и ключевые поля их родителей. При этом условие gd.GdTreeNo<>0 выводит только структурные записи. Сортировка получившегося набора выполнена по уровню узла в дереве, что позволяет из получившегося скрипта отрезать необходимое количестве уровней. Однако эту же процедуру можно встроить в диалог, спросив у пользователя на сколько уровней дерева необходимо сохранять структуру.
       Здесь также определяются заголовки строк (Str1A,Str2A) для вывода в скрипт. В дальнейшем сохранение данных в скрипте происходит обычным образом — перебирая все поля текущей записи и, если есть значение, то оно выводится в файл.

   While not qrExeProc.Eof do begin
      Str1 := Str1A;   Str2 := Str2A;
      If not VarIsNull(qrExeProc.FieldValues['GdOrder']) then begin
         RC := qrExeProc.FieldValues['GdOrder'];
         if RC>0 then begin Str1 := Str1 + ',GdOrder';
                            Str2 := Str2 + ',' + IntToStr(RC);   end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GdOrdTNK']) then begin
         RC := qrExeProc.FieldValues['GdOrdTNK'];
         if RC>0 then begin Str1 := Str1 + ',GdOrdTNK';
                            Str2 := Str2 + ',' + IntToStr(RC);   end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GdOrdBAR']) then begin
         RC := qrExeProc.FieldValues['GdOrdBAR'];
         if RC>0 then begin Str1 := Str1 + ',GdOrdNAR';
                            Str2 := Str2 + ',' + IntToStr(RC);   end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GdOrdFRM']) then begin
         RC := qrExeProc.FieldValues['GdOrdFRM'];
         if RC>0 then begin Str1 := Str1 + ',GdOrdFRM';
                            Str2 := Str2 + ',' + IntToStr(RC);   end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GdOrdAls']) then begin
         RC := qrExeProc.FieldValues['GdOrdAls'];
         if RC>0 then begin Str1 := Str1 + ',GdOrdAls';
                            Str2 := Str2 + ',' + IntToStr(RC);   end;
      end;
         Str1 := Str1 + ',GdTreeNo';  Str2 := Str2 + ',' + IntToSTr(TrNo);
      If not VarIsNull(qrExeProc.FieldValues['GoodNamR']) then begin
         sFld := qrExeProc.FieldValues['GoodNamR'];
         If sFld<>'' then begin
           Str1 := Str1 + ',GoodNamR';
           Str2 := Str2 + ','+
+'''' + sFld + '''';
         end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GoodNamE']) then begin
         sFld := qrExeProc.FieldValues['GoodNamE'];
         If sFld<>'' then begin
            Str1 := Str1 + ',GoodNamE';
            Str2 := Str2 + ','+
+'''' + sFld + '''';
         end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GdDescrpt']) then begin
         sFld := qrExeProc.FieldValues['GdDescrpt'];
         If sFld<>'' then begin
            Str1 := Str1 + ',GdDescrpt';
            Str2 := Str2 + ','+
+'''' + sFld + '''';
         end;
      end;
      If not VarIsNull(qrExeProc.FieldValues['GdNames']) then begin
         sFld := qrExeProc.FieldValues['GdNames'];
         If sFld<>'' then begin
            Str1 := Str1 + ',GdNames';
            Str2 := Str2 + ','+
+'''' + sFld + '''';
         end;
      end;
      blCod := True;
      If not VarIsNull(qrExeProc.FieldValues['GdIntCode']) then begin
         sFld := Trim(qrExeProc.FieldValues['GdIntCode']);
         If sFld<>'' then begin
            Str1 := Str1 + ',GdIntCode';
            if blCod then begin Str2 := Str2 + ','+
+'''' + sFld + '''';
                                blCod := False; end
                     else Str2 := Str2 + ',''' + sFld + '''';
            end;
         end;
      If not VarIsNull(qrExeProc.FieldValues['GdTNVED']) then begin
         sFld := Trim(qrExeProc.FieldValues['GdTNVED']);
         If sFld<>'' then begin
            Str1 := Str1 + ',GdTNVED';
            if blCod then begin Str2 := Str2 + ','+
+'''' + sFld + '''';
                                blCod := False; end
                     else Str2 := Str2 + ',''' + sFld + '''';
            end;
         end;
      If not VarIsNull(qrExeProc.FieldValues['GdEANCode']) then begin
         sFld := Trim(qrExeProc.FieldValues['GdEANCode']);
         If sFld<>'' then begin
            Str1 := Str1 + ',GdEANCode';
            if blCod then begin Str2 := Str2 + ','+
+'''' + sFld + '''';
                                blCod := False; end
                     else Str2 := Str2 + ',''' + sFld + '''';
            end;
         end;
      If not VarIsNull(qrExeProc.FieldValues['GdUPCCode']) then begin
         sFld := Trim(qrExeProc.FieldValues['GdUPCCode']);
         If sFld<>'' then begin
            Str1 := Str1 + ',GdUPCCode';
            if blCod then begin Str2 := Str2 + ','+
+'''' + sFld + '''';
                               blCod := False; end
                     else Str2 := Str2 + ',''' + sFld + '''';
            end;
         end;
      If not VarIsNull(qrExeProc.FieldValues['GdFirmCod']) then begin
         sFld := Trim(qrExeProc.FieldValues['GdFirmCod']);
         If sFld<>'' then begin
            Str1 := Str1 + ',GdFirmCod';
            if blCod then begin Str2 := Str2 + ','+
+'''' + sFld + '''';
                               blCod := False; end
                     else Str2 := Str2 + ',''' + sFld + '''';
         end;
      end;
      Str1 := Str1 + ')';
      sFld := qrExeProc.FieldValues['gp_GdNames'];
      Str3 := ' from Goods where GdNames='''+sFld+'''';
      sFld := Trim(qrExeProc.FieldValues['gp_GdCode']);
      Str4 := '              and '+GdCode+'='''+sFld+''';';
      WriteLn(flReprt,Str1);  WriteLn(flReprt,Str2);
      WriteLn(flReprt,Str3);  WriteLn(flReprt,Str4);  WriteLn(flReprt,'');

       После вывода всей строки определяется входит ли узел в структуру другого дерева и, если входит, то в скрипт выводится запрос, формирующий эту связь. Так как при проектировании таблицы было установлено, что структурные записи во всех деревьях, кроме своего имеют узлы <код не существует> для соответствующего дерева, то и определить факт вхождения узла в другое дерево просто. Для этого проверяется условие — если соответствующий родительский код не равен <код не существует> и номер дерева не соответствует выбранному, то соответствующий родительский код входит в другую структуру.

// Если узел входит в структуру другого дерева, то сохранить и эту связь
      If (cbxTreeSel.ItemIndex<>1) and (qrExeProc.FieldValues['GdParent']<>7)
        then Begin
         RC := qrExeProc.FieldValues['GdParent'];
         qrExeProc2.Close;   qrExeproc2.SQL.Clear;
         qrExeProc2.SQL.Add('Select GdIntCode,GdNames from GoodsNew'
                           +' where GoodsNo='+IntToStr(RC));
         qrExeProc2.Open;
         WriteLn(flReprt,'Insert into GoodsNewTemp(GoodsNo,GdParent)');
         WriteLn(flReprt,'Select gd.GoodsNo,gp.GoodsNo from '
                        +'GoodsNew as gd, GoodsNew as gp');
         WriteLn(flReprt,'where gd.GdIntCode='''
                        +Trim(qrExeProc.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gd.GdNames='''
                        +qrExeProc.FieldValues['GdNames']+'''');
         WriteLn(flReprt,'  and gp.GdIntCode='''
                        +Trim(qrExeProc2.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gp.GdNames='''
                        +qrExeProc2.FieldValues['GdNames']+''';');
         WriteLn(flReprt,'');
      end;
      If (cbxTreeSel.ItemIndex<>2) and (qrExeProc.FieldValues['GdPrnTNK']<>9)
        then Begin
         RC := qrExeProc.FieldValues['GdPrnTNK'];
         qrExeProc2.Close;   qrExeproc2.SQL.Clear;
         qrExeProc2.SQL.Add('Select GdTNVED,GdNames from GoodsNew'
                           +' where GoodsNo='+IntToStr(RC));
         qrExeProc2.Open;
         WriteLn(flReprt,'Insert into GoodsNewTemp(GoodsNo,GdPrnTNK)');
         WriteLn(flReprt,'Select gd.GoodsNo,gp.GoodsNo from '
                        +'GoodsNew as gd, GoodsNew as gp');
         WriteLn(flReprt,'where gd.GdIntCode='''
                        +Trim(qrExeProc.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gd.GdNames='''
                        +qrExeProc.FieldValues['GdNames']+'''');
         WriteLn(flReprt,'  and gp.GdTNVED='''
                        +Trim(qrExeProc2.FieldValues['GdTNVED'])+'''');
         WriteLn(flReprt,'  and gp.GdNames='''
                        +qrExeProc2.FieldValues['GdNames']+''';');
         WriteLn(flReprt,'');
      end;
      If (cbxTreeSel.ItemIndex<>3) and (qrExeProc.FieldValues['GdPrnBAR']<>11)
        then Begin
         RC := qrExeProc.FieldValues['GdPrnBAR'];
         qrExeProc2.Close;   qrExeproc2.SQL.Clear;
         qrExeProc2.SQL.Add('Select GdEANCode,GdNames from GoodsNew'
                           +' where GoodsNo='+IntToStr(RC));
         qrExeProc2.Open;
         WriteLn(flReprt,'Insert into GoodsNewTemp(GoodsNo,GdPrnBAR)');
         WriteLn(flReprt,'Select gd.GoodsNo,gp.GoodsNo from'
                        +' GoodsNew as gd, GoodsNew as gp');
         WriteLn(flReprt,'where gd.GdIntCode='''
                        +Trim(qrExeProc.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gd.GdNames='''
                        +qrExeProc.FieldValues['GdNames']+'''');
         WriteLn(flReprt,'  and gp.GdEANCode='''
                        +Trim(qrExeProc2.FieldValues['GdEANCode'])+'''');
         WriteLn(flReprt,'  and gp.GdNames='''
                        +qrExeProc2.FieldValues['GdNames']+''';');
         WriteLn(flReprt,'');
      end;
      If (cbxTreeSel.ItemIndex<>4) and (qrExeProc.FieldValues['GdPrnFrm']<>13)
        then Begin
         RC := qrExeProc.FieldValues['GdPrnFrm'];
         qrExeProc2.Close;   qrExeproc2.SQL.Clear;
         qrExeProc2.SQL.Add('Select GdFirmCod,GdNames from GoodsNew'
                           +' where GoodsNo='+IntToStr(RC));
         qrExeProc2.Open;
         WriteLn(flReprt,'Insert into GoodsNewTemp(GoodsNo,GdPrnFRm)');
         WriteLn(flReprt,'Select gd.GoodsNo,gp.GoodsNo from'
                        +' GoodsNew as gd, GoodsNew as gp');
         WriteLn(flReprt,'where gd.GdIntCode='''
                        +Trim(qrExeProc.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gd.GdNames='''
                        +qrExeProc.FieldValues['GdNames']+'''');
         WriteLn(flReprt,'  and gp.GdFirmCod='''
                        +Trim(qrExeProc2.FieldValues['GdFirmCod'])+'''');
         WriteLn(flReprt,'  and gp.GdNames='''
                        +qrExeProc2.FieldValues['GdNames']+''';');
         WriteLn(flReprt,'');
      end;
      If (cbxTreeSel.ItemIndex<>5) and (qrExeProc.FieldValues['GdPrnAls']<>15)
        then Begin
         RC := qrExeProc.FieldValues['GdPrnAls'];
         qrExeProc2.Close;   qrExeproc2.SQL.Clear;
         qrExeProc2.SQL.Add('Select GdIntCode,GdNames from GoodsNew'
                           +' where GoodsNo='+IntToStr(RC));
         qrExeProc2.Open;
         WriteLn(flReprt,'Insert into GoodsNewTemp(GoodsNo,GdPrnAls)');
         WriteLn(flReprt,'Select gd.GoodsNo,gp.GoodsNo from'
                        +' GoodsNew as gd, GoodsNew as gp');
         WriteLn(flReprt,'where gd.GdIntCode='''
                        +Trim(qrExeProc.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gd.GdNames='''
                        +qrExeProc.FieldValues['GdNames']+'''');
         WriteLn(flReprt,'  and gp.GdIntCode='''
                        +Trim(qrExeProc2.FieldValues['GdIntCode'])+'''');
         WriteLn(flReprt,'  and gp.GdNames='''
                        +qrExeProc2.FieldValues['GdNames']+''';');
         WriteLn(flReprt,'');
      end;
      qrExeProc.Next;
   end; //   if not qrExeProc.Eof then

       После завершения всего цикла в скрипт выводится оператор выполнения процедуры GoodsSetParent, для установления связей перечисленных в таблице GoodsNewTemp. Хорошей практикой в данном случае начинать скрипт очисткой этой таблицы, а заканчивать выполнением процедуры. Это исключает ошибки, в случае применения пакетов скриптов, созданных в разное время и разных местах.

WriteLn(flReprt,'Execute procedure GoodsSetParent;');
WriteLn(flReprt,'Commit;');  WriteLn(flReprt,'');
CloseFile(flReprt);
Screen.Cursor := SvCursor;
ShowMessage('Ветка сохранена');
end;

Часть вторая

       Сохранение товаров по выбранному дереву в SQL-скрипт.
       Эта процедура, првязанная в примере {Example06} к кнопке "Сохранить товары в SQL" практически во всем повторяет процедуру для сохранения структуры. Отличия минимальны:
       — создаваемое имя файла начинается с букв Az, Bz, Cz, Dz, Ez, для того чтобы не было совпадения с именем файла структуры, сохраненного из той же точки.
       — после получения всех детей выбранного узла выбираются только записи, у которых поле GdTreeNo=0, что соответствует товарам, независимо от того образуют они между собой структуру или нет.
       — запрос который выводится в скрипт содержит только родительское поле по выбранному дереву. Остальные поля заполняются по умолчанию кодом <код временно не определен>.
       — соответственно последний раздел процедуры, которые определяет необходимость установления связей в других деревьях, сравнивает родительские поля не с константами 7, 9, 11, 13, 15, которые соответствуют коду <Код не существует>, а с константами 6, 8, 10, 12, 14, которые соответствуют коду <Код временно не определен>
       Таким образом, если из одной точки выбранного дерева нажать последовательно на кнопки "Сохранить ветку в SQL" и "Сохранить товар в SQL", то получится два SQL-скрипта, которые описывают структуру и товары ниже этой точки и их связи с другими деревьями. В случае, если в другом дереве отсутствуют необходимые структурные записи, то соответствующие поля будут заполнены кодами по умолчанию.
       Разделение скриптов на два вида — структурные и товарные — позволяет собирать БД по любой необходимой тематике из готовых скриптов.

© 01.08.2009, Архангельский А.Г.

<<Пред. Оглавление
Начало раздела
След.>>




Дата последнего изменения:
Thursday, 21-Aug-2014 09:10:44 MSK


Постоянный адрес статьи:
http://az-design.ru/Support/DataBase/DBTree2/3220.shtml