Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Microsoft SQL Server Новый топик    Ответить
 Асинхрон с ADO  [new]
Подпол
Member

Откуда:
Сообщений: 10
Привет всем. Ну вконец достало меня это ADO... Как не крути, но как-то нужно реализовать асинхронный запрос.
Причем если запрос "атомарный", т.е. SELECT (с TADOQuery) или UPDATE/DELETE/INSERT (с TADOCommand) - нет проблем.
Если же запрос вот такой...

DECLARE @IDStart int = :IDStart
DECLARE @IDEnd int = :IDEnd
DECLARE @Counts int = 0

DECLARE @TabEdit TABLE(ID int)
INSERT INTO @TabEdit
SELECT ID
FROM dbo.TableData (nolock)
WHERE ID BETWEEN @IDStart AND @IDEnd
SET @Counts = @@ROWCOUNT
SELECT @Counts

/* здесь написана ересь .. чисто для понимания процесса
UPDATE dbo.TableData
SET Field1 = t1.Field2, Field2 = t2.Field5
FROM TableData t (nolock)
INNER JONI @TabEdit t_ ON t_.ID = t.ID
INNER JOIN TableDist1 t1 ON t1.ID = t.ID
INNER JOIN TableDist2 t2 ON t2.ID = t.ID_1
*/

т.е. задается диапазон айди, имеющиеся айди в этом диапазоне загружаются во временную таблицу, а потом идет апдейт целевой таблицы с джойном на эту таблицу и джойнами к нужным таблицам справочников.

Ни хрена не получается. Настраиваю опции TADOConnection на асинхрон, у TADOQuery выставляю ExecuteOptions := [eoAsyncExecute,eoAsyncFetch]. Для TADOCommand тоже настраиваю асинхрон...

С TADOQuery постоянно торчит в цикле
with quMain do begin
    ...
    Open;
    while State <> dsBrowse do
     Application.ProcessMessages;
    Counts := Fields[0].AsInteger;
    Close;
    ...
end;

поскольку все время State = dsOpening

C TADOCommand все получается, но ... никаким образом не могу добыть вот это SELECT @Counts в рекордсете... Уже задумываюсь о другом провайдере к MS SQL Server... Ппц какое-то...

Подскажите, как реализовать асинхрон. Иначе никак нельзя. Таблицы есть огромные, целостность данных никакая. Периодически нужно полностью или частично обновлять данные. Все бы хорошо, но есть джоб на серваке, и при выполнении некоторых его шагов нельзя вести это обновление. Отслеживание джоба - не проблема. Нужно немедленно прервать обновление при старте "запретного" джоба (их как правило три из 15-ти). Без асинхрона никуда. TADOCommand устраивает, но кое-какое обновление нужно отслеживать по кол-ву обновленных строк... Никак не достану из него вот этот SELECT @Counts.

Delphi 7, MS SQL Server 2008
29 июн 16, 17:15    [19351141]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
aleks2
Guest
Фигня фсе это. Но если очень хоцца - можно через таблицу сообщаться с процедурой.
Только муторно это.
Читать таблицу надо with(nolock).


+
ALTER PROCEDURE [dbo].[accPriceRemover_Remove_v3]
  @UseTransaction as bit = 0 -- 1 = выполнять удаление в транзакции
AS
BEGIN
	SET NOCOUNT ON;
    set xact_abort on;

    if @UseTransaction is null set @UseTransaction = 0;

    declare @msgid int, @msg nvarchar(1024);

    if object_id('tempdb..##PriceRemover') is null begin
      create table ##PriceRemover(
        id int not null identity,
        spid int not null,
        msg nvarchar(1024) not null,
        primary key nonclustered (id),
        unique clustered(spid, id)
      );
    end;

    raiserror('Запуск...', 0, 1) with nowait;
    delete ##PriceRemover where spid = @@spid
    insert ##PriceRemover(spid, msg) values( @@spid, N'Запуск...' );
    set @msgid = SCOPE_IDENTITY();

    if object_id('tempdb..#PriceRemover') is null begin

      update ##PriceRemover set msg = N'Нет данных нет данных для удаления.' where id =  @msgid ;
      raiserror(N'accPriceRemover_Remove: нет данных для удаления', 16, 1);
      return -1;

           create table #PriceRemover(
             IdType tinyint not null,
             Id int not null,
             selected bit not null default(0),
             primary key (IdType, Id)
           );

    end;

    if not exists(select * from #PriceRemover where IdType = 0) begin
      update ##PriceRemover set msg = N'Нет данных выбора по партнеру.' where id =  @msgid ;
      raiserror(N'accPriceRemover_Remove: Нет данных выбора по партнеру.', 16, 1);
      return -1;
    end;

    if not exists(select * from #PriceRemover where IdType = 1) begin
      update ##PriceRemover set msg = N'Нет данных выбора по пакету.' where id =  @msgid ;
      raiserror(N'accPriceRemover_Remove: Нет данных выбора по пакету.', 16, 1);
      return -1;
   end;

    if not exists(select * from #PriceRemover where IdType = 2) begin
      update ##PriceRemover set msg = N'Нет данных выбора по объекту.' where id =  @msgid ;
      raiserror(N'accPriceRemover_Remove: Нет данных выбора по объекту.', 16, 1);
      return -1;
    end;

    if object_id('dbo.tbl_Costs_PriceRemover_Saved') is null begin
      select top(0) * 
            , getdate() as updatetime
            , newid() as batchid
            , ORIGINAL_LOGIN() as username
        into dbo.tbl_Costs_PriceRemover_Saved 
        from dbo.tbl_Costs with(nolock);
    end;

    if object_id('dbo.tbl_Costs_PriceRemover_Saved') is null begin

      update ##PriceRemover set msg = N'нет таблицы для сохранения данных.' where id =  @msgid ;
      raiserror('accPriceRemover_Remove: нет таблицы для сохранения данных.', 16, 1);
      return -1;

    end;

    declare @csids table(CS_ID int primary key);
    declare @total int;

    with 
       pr as ( select * from #PriceRemover where IdType = 0)
     , pk as ( select * from #PriceRemover where IdType = 1)
     , ob0 as ( select * from #PriceRemover where IdType = 2)
     , ob as ( select * from ob0 where selected = 1 or not exists( select * from ob0 where selected = 1 ) )
     , pn  as ( select * from #PriceRemover where IdType = 3)
     , rm  as ( select * from #PriceRemover where IdType = 4)
     , cn as (select c.CS_ID
                from dbo.tbl_Costs c with(nolock)
                     inner join pr on pr.Id = c.CS_PRKey
                     inner join pk on pk.Id = c.CS_PKKEY
                     inner join ob on ob.Id = c.CS_CODE
                where ( c.CS_SUBCODE2 in ( select Id from pn where selected = 1 ) or not exists( select * from pn ) )
                       and
                      ( c.CS_SUBCODE1 in ( select Id from rm where selected = 1 ) or not exists( select * from rm ) )
             )
    insert @csids select CS_ID from cn;

    set @total = @@rowcount;

    if @total = 0 begin
      update ##PriceRemover set msg = N'Нет цен для удаления.' where id =  @msgid ;
      raiserror('accPriceRemover_Remove: Нет цен для удаления.', 16, 1 );
      return -2;
    end;

    raiserror('Удаляем %i цен...', 0, 1, @total ) with nowait;
    set @msg = 'Удаляем ' + cast(@total as nvarchar(16)) + N' цен...';
    insert ##PriceRemover(spid, msg) values( @@spid, @msg );

    declare @rc int = 1
          , @cnt int = 0
          , @batchsize int = round( @total/10, -2 ) --50000 -- удаление за раз
          , @batchId uniqueidentifier = newid()  -- идентификатор пачки
          , @getdate datetime = getdate() -- дата удаления
    ;

    if @batchsize > 50000 set @batchsize = 50000;
    if @batchsize < 1000  set @batchsize = 1000;

    if @UseTransaction = 1 begin transaction;

      while (@rc > 0) begin

        delete top(@batchsize) c
           output deleted.CS_SVKEY
                  , deleted.CS_CODE
                  , deleted.CS_SUBCODE1
                  , deleted.CS_SUBCODE2
                  , deleted.CS_PRKEY
                  , deleted.CS_PKKEY
                  , deleted.CS_DATE
                  , deleted.CS_DATEEND
                  , deleted.CS_WEEK
                  , deleted.CS_COSTNETTO
                  , deleted.CS_COST
                  , deleted.CS_DISCOUNT
                  , deleted.CS_TYPE
                  , deleted.CS_CREATOR
                  , deleted.CS_RATE
                  , deleted.CS_UPDDATE
                  , deleted.CS_LONG
                  , deleted.CS_BYDAY
                  , deleted.CS_FIRSTDAYNETTO
                  , deleted.CS_FIRSTDAYBRUTTO
                  , deleted.CS_PROFIT
                  , deleted.CS_CINNUM
                  , deleted.CS_TypeCalc
                  , deleted.cs_DateSellBeg
                  , deleted.cs_DateSellEnd
                  , deleted.CS_ID
                  , deleted.CS_CHECKINDATEBEG
                  , deleted.CS_CHECKINDATEEND
                  , deleted.CS_LONGMIN
                  , deleted.CS_TypeDivision
                  , deleted.CS_UPDUSER
                  , deleted.CS_TRFId
                  , deleted.CS_COID
                  , @getdate
                  , @batchId 
                  , ORIGINAL_LOGIN()
           into dbo.tbl_Costs_PriceRemover_Saved
           from  dbo.tbl_Costs c with(tablock, xlock) inner join @csids ci on c.CS_ID = ci.CS_ID;

        set @rc =  @@rowcount;
        
        set @cnt = @cnt + @rc;

        if @rc > 0 begin

          raiserror('удалено  %i из %i', 0, 1, @cnt, @total ) with nowait
          set @msg = 'удалено ' + cast(@cnt as nvarchar(16)) + N' из ' + cast(@total as nvarchar(16));
          insert ##PriceRemover(spid, msg) values( @@spid, @msg );
        
          if @UseTransaction = 0 waitfor delay '00:00:01';

        end;

      end;

    if @UseTransaction = 1 commit transaction;

    raiserror( 'удалено.', 0, 1 ) with nowait;
    set @msg = 'удалено.';
    insert ##PriceRemover(spid, msg) values( @@spid, @msg );

return 0;
END
29 июн 16, 18:28    [19351489]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
Подпол
Member

Откуда:
Сообщений: 10
aleks2,
Я видимо плохо сформулировал проблему...
29 июн 16, 23:39    [19352528]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
invm
Member

Откуда: Москва
Сообщений: 9406
Подпол
Периодически нужно полностью или частично обновлять данные. Все бы хорошо, но есть джоб на серваке, и при выполнении некоторых его шагов нельзя вести это обновление. Отслеживание джоба - не проблема. Нужно немедленно прервать обновление при старте "запретного" джоба (их как правило три из 15-ти).
А что джоб не может подождать завершения обновлений?
Подпол
Без асинхрона никуда.
Можно решить на стороне сервера без всяких "асинхронов".

ЗЫ: Учтите, что прервать обновление "немедленно" невозможно.
29 июн 16, 23:54    [19352587]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
invm
Member

Откуда: Москва
Сообщений: 9406
А вообще, самое очевидное решение - шаги джобов, где требуется неизменность данных в таблице, должны выполняться в транзакции с уровнем изоляции snapshot.
30 июн 16, 00:12    [19352645]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
HandKot
Member

Откуда: Sergiev Posad
Сообщений: 2996
Подпол
aleks2,
Я видимо плохо сформулировал проблему...

Подпол
никаким образом не могу добыть вот это SELECT @Counts в рекордсете

Если я правильно понял, то используйте context_info для получения результата в процессе батча
внутри батча
DECLARE @Counts int = 100;
Declare @Context_Info As VarBinary(128);
SET CONTEXT_INFO @Context_Info


в отдельном запросе
 SELECT Cast(context_info as int)
FROM sys.dm_exec_sessions
WHERE session_id = номер асинхронного батча;


еще можно raiseerror прикрутить, но я таким не пользовался и подсказать не могу
30 июн 16, 07:43    [19352855]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
Подпол
Member

Откуда:
Сообщений: 10
Всем спасибо... Велик и могуч русский язык...
"Без асинхрона никуда" - это как аксиома...без доказательств.
Джоб - движок СУБД, неповоротливый, прожорливый как "Маус".
Обновления нужны не сказать что редко, но не обойтись...
TADOCommand работает на "ура", его Cancel вполне удовлетворительно (по времени) прерывает асинхронный запрос. Причем работает как в студии при закрытии окна выполняющегося запроса (не прерывании кнопкой, а закрытии). Причем выполняющийся запрос входит в блокировку с начавшимся в это время шагом джоба. Так они могут висеть долго (полчаса ждал). Но стоит сделать запросу Cancel, как шаг джоба мгновенно (относительно конечно) завершается.
Т.е. вся проблема - после завершения запроса посредством TADOCommand.Execute, каким-то образом добраться до датасета с данными селекта из запроса SELECT @Counts. Если это удастся - проблема решена. Можно, конечно, формировать некую временную таблицу, куда этот Counts складывать, но ... это же не красиво... TADOCommand.Execute возвращает же рекордсет, но... он пустой, типа TADOCommand.Fields.Count = 0... Или я не знаю как туда залезть, или что подкрутить... В хэлпах пишут, что TADOCommand не рекомендуется использовать для селектов, но у меня уверенно получается доставать данные простыми селектами.
30 июн 16, 09:27    [19353081]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
Noskov
Member

Откуда: Москва
Сообщений: 394
SET NOCOUNT ON в начале запроса или дёргать NextRecordset до тех пор, пока не доберётесь до непустого
30 июн 16, 10:15    [19353225]     Ответить | Цитировать Сообщить модератору
 Re: Асинхрон с ADO  [new]
Подпол
Member

Откуда:
Сообщений: 10
Noskov
SET NOCOUNT ON в начале запроса или дёргать NextRecordset до тех пор, пока не доберётесь до непустого
Премного благодарен... Час назад до меня дошло, что есть еще "пустые" рекорсеты... Решил проблему NOCOUNT ON.
30 июн 16, 15:35    [19355417]     Ответить | Цитировать Сообщить модератору
Все форумы / Microsoft SQL Server Ответить