AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 20.03.2012, 12:34   #1  
malex is offline
malex
Участник
 
164 / 19 (1) ++
Регистрация: 10.08.2004
Адрес: Тверь, Москва
Post Оптимизация запросов: CustInvoiceTrans + markupTrans
На одном из клиентов (версия системы 4.0, SQL 2005) стала жутко тормозить форма обработки фактуры по накладной клиента.

Причина - FactureEditLinesEngineCust_RU.dsExecuteMarkup

Там в один курсор пытаются вернуть как markupTrans относящиеся к шапке, так и к строкам. Но поскольку обычные подзапросы в аксапте не работают, связка по шапке добавлена внутрь связки по строкам
X++:
    select markupCursor
        where   markupCursor.CustVendPosted_RU                         &&
              ! markupCursor.ItemPosted_RU
    exists join tTrans
        where (
               markupCursor.TransTableId  == tTrans.TableId            &&
               markupCursor.TransRecId    == tTrans.RecId              &&
               tTrans.SalesId             == tJour.SalesId             &&
               tTrans.InvoiceId           == tJour.InvoiceId           &&
               tTrans.InvoiceDate         == tJour.InvoiceDate         &&
               tTrans.NumberSequenceGroup == tJour.NumberSequenceGroup
              )
              ||
              (
               markupCursor.TransTableId  == tJour.TableId             &&
               markupCursor.TransRecId    == tJour.RecId
              );
В SQL приходит следующее:
SELECT *
FROM MARKUPTRANS A
WHERE (A.DATAAREAID='exp' AND A.CUSTVENDPOSTED_RU<>0 AND A.ITEMPOSTED_RU=0) AND
EXISTS (SELECT 'x' FROM CUSTINVOICETRANS B
WHERE (B.DATAAREAID='exp' AND
((A.TRANSTABLEID=64 AND A.TRANSRECID=B.RECID AND B.SALESID='РКЗП-00032212' AND B.INVOICEID='14294-11' AND
B.INVOICEDATE='2012-03-13 00:00:00.000' AND B.NUMBERSEQUENCEGROUP='ТД') OR
(A.TRANSTABLEID=62 AND A.TRANSRECID=5637435947) )))

Если сделать по нормальному, то все работает быстро. Но как это можно написать в Аксапте одним запросом непонятно.

SELECT * FROM MARKUPTRANS A
WHERE (A.DATAAREAID='exp' AND A.CUSTVENDPOSTED_RU=0 AND A.ITEMPOSTED_RU=0) AND
((A.TRANSTABLEID=62 AND A.TRANSRECID=5637435947) OR
EXISTS (SELECT 'x' FROM CUSTINVOICETRANS B
WHERE (B.DATAAREAID='exp' AND
((A.TRANSTABLEID=64 AND A.TRANSRECID=B.RECID AND B.SALESID='РКЗП-00032212' AND B.INVOICEID='14294-11' AND
B.INVOICEDATE='2012-03-13 00:00:00.000' AND B.NUMBERSEQUENCEGROUP='ТД') ))))

PS: Индексы перестраивались, статистика обновлялась
PSS: План запроса во вложении (переименован в txt для загрузки)
Вложения
Тип файла: txt planMarkupTrans.txt (58.3 Кб, 445 просмотров)

Последний раз редактировалось malex; 20.03.2012 в 12:39.
Старый 20.03.2012, 12:54   #2  
ivas is offline
ivas
Участник
Аватар для ivas
 
252 / 68 (3) ++++
Регистрация: 22.12.2005
может на 2 запроса разбить? 1ый по CustInvoiceTrans 2ой по CustInvoiceJour.
__________________
aLL woRk aNd nO JoY MAKes jAck a dULL Boy
Старый 20.03.2012, 12:58   #3  
malex is offline
malex
Участник
 
164 / 19 (1) ++
Регистрация: 10.08.2004
Адрес: Тверь, Москва
Цитата:
Сообщение от ivas Посмотреть сообщение
может на 2 запроса разбить? 1ый по CustInvoiceTrans 2ой по CustInvoiceJour.
Разбить можно, но как потом склеить результаты чтобы передать в ds формы?
X++:
    MarkupTrans      markupCursor = markupDS.cursor();
/* запрос */
    markupDS.findRecord(markupCursor);
Старый 20.03.2012, 13:07   #4  
S.Kuskov is offline
S.Kuskov
Участник
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
 
3,440 / 1775 (66) ++++++++
Регистрация: 28.04.2007
Адрес: Калуга
А если добавить директиву firstonly
X++:
select markupCursor
        where   markupCursor.CustVendPosted_RU                         &&
              ! markupCursor.ItemPosted_RU
    exists join firstonly tTrans
        where (...)
не поможет?

Цитата:
Сообщение от malex Посмотреть сообщение
Разбить можно, но как потом склеить результаты чтобы передать в ds формы?
X++:
    MarkupTrans      markupCursor = markupDS.cursor();
/* запрос */
    markupDS.findRecord(markupCursor);
Интересно. А findRecord умеет работать с запросом который возвращает больше одной строки? Я как-то раньше не задумывался об этом.
Старый 20.03.2012, 13:13   #5  
Alexanderis.ua is offline
Alexanderis.ua
Участник
 
53 / 40 (2) +++
Регистрация: 25.12.2008
Адрес: Киев, Украина
Умеет.
Фактически в датасорс тут передается все, что получено запросом, с учетом команды next.

Я эту проблему в свое время победил. Топорно немного, но, насколько я знаю, до сих пор работает.

Если другого решения не будет, постараюсь сегодня свое описать
__________________
If it ain't broke, take it apart and find out why (с)
За это сообщение автора поблагодарили: S.Kuskov (5).
Старый 20.03.2012, 13:13   #6  
fed is offline
fed
Moderator
Аватар для fed
Ex AND Project
Соотечественники
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
2,909 / 5730 (197) ++++++++++
Регистрация: 13.03.2002
Адрес: Hüfingen,DE
Я бы еще попробовал на сервере выключить параллелизм. Там план запроса распараллелен, чего для таких простеньких запросов ни в коем случае нельзя делать. Welcome -- Ax Database Configuration Checklist part 1 Искать по ключевому слову MAXDOP.
IMHO, проблема не в запросе как таковом, а в том что SQL Server кривой план запроса генерит. Я бы начал лечение с MAXDOP==1, а потом попробовал накатить свежие Service Pack и CU на сиквел...
Старый 20.03.2012, 16:41   #7  
Alexanderis.ua is offline
Alexanderis.ua
Участник
 
53 / 40 (2) +++
Регистрация: 25.12.2008
Адрес: Киев, Украина
Ну раз все молчат, вверну свой быдлокод.
Собственно в решении ничего неожиданного нет.
Заменил использование markupDS.findRecord на банальное накладываение range по заранее собранным RecId (индекс в таблице такой есть, так что это не проблема). А сбор RecId уже спокойно можно разбить на 2 запроса.

Ниже уже конкретная реализация, если кому интересно.

Метод dsExecuteMarkup по всему семейству сделал типа boolean, чтобы в форме FactureEditLines_RU у датасорса метод executeQuery имел вид
X++:
public void executeQuery()
{
    // SOI -->
    /*
    factureEngine.dsExecuteMarkup();
//    super(); // PS 1791
    */
    if (factureEngine.dsExecuteMarkup())
    {
        super();
    }
    // SOI <--
}
Далее, в классе FactureEditLinesEngineCustVend_RU в метод dsExecuteMarkup добавляем
X++:
mrkDS.clearRanges(); // в потомках мы накладываем range, логично его сначала почистить
return false;
И, наконец, в наследниках (тут пример FactureEditLinesEngineCust_RU, в Vend - аналогично)
X++:
public boolean dsExecuteMarkup()
{
    MarkupTrans             markupCursor;
    CustInvoiceJour       tJour = custInvoiceJourDS.cursor();
    CustInvoiceTrans     tTrans;
    // SOI -->
    QueryBuildDataSource    qbds;
    QueryBuildRange         qbr;
    Set                     set = new Set(Types::Int64);
    SetEnumerator           sE;
    counter                 counter;
    str                     range;
    // SOI <--
    ;

    super();

    // SOI -->
    qbds = markupDs.query().dataSourceTable(tablenum(MarkupTrans));

    // собственно собираем все, что связано и со строками и с заголовком
    while select recid
            from markupCursor
           where markupCursor.CustVendPosted_RU
              && !markupCursor.ItemPosted_RU
     exists join tTrans
           where tTrans.SalesId             == tJour.SalesId
              && tTrans.InvoiceId           == tJour.InvoiceId
              && tTrans.InvoiceDate         == tJour.InvoiceDate
              && tTrans.NumberSequenceGroup == tJour.NumberSequenceGroup
              && tTrans.TableId             == markupCursor.TransTableId
              && tTrans.RecId               == markupCursor.TransRecId
    {
        set.add(markupCursor.RecId);
    }

    while select recid
            from markupCursor
           where markupCursor.CustVendPosted_RU
              && !markupCursor.ItemPosted_RU
              && markupCursor.TransTableId  == tJour.TableId
              && markupCursor.TransRecId    == tJour.RecId
    {
        set.add(markupCursor.RecId);
    }

    // и лепим это все в range 
    sE = set.getEnumerator();
    while (sE.moveNext())
    {
        counter++;
        if (counter > 15) // не по-научному, знаю, но зато работает :)
        {
            qbr = qbds.addRange(fieldnum(MarkupTrans, RecId));
            qbr.value(range);
            range   = strmin();
            counter = 0;
        }
        range = queryRangeConcat(range, sE.current());
    }

    if (!set.elements())
    {
        range = sysQuery::value(0); // ничего не нашли - ничего и не покажем
    }
    if (range)
    {
        qbr = qbds.addRange(fieldnum(MarkupTrans, RecId));
        qbr.value(range);
    }

    return true;
    // SOI <--
}
Повторюсь - решение топорное и где-то даже некрасивое, но решать нужно было быстро. Да и со своей задачей оно справляется. Потому больше ничего и не делалось в этом направлении.
Если у кого есть какие другие варианты или критика/дополнения - буду рад увидеть.
__________________
If it ain't broke, take it apart and find out why (с)
Старый 20.03.2012, 21:15   #8  
Владимир Максимов is offline
Владимир Максимов
Участник
КОРУС Консалтинг
 
1,701 / 1195 (43) ++++++++
Регистрация: 13.01.2004
Записей в блоге: 3
Вы сделали распространенную ошибку, решив что на SQL-сервере выполняется напрямую SQL-запрос. На самом деле это не так. Там создается курсор, в который и "оборачивает" SQL-запрос. А вот у курсора и "чистого" запроса планы исполнения могут отличаться. Причем, как правило, отличия возникают именно при объединении по Exists Join.

Т.е. у Вас два варианта решения проблемы

1. Отказаться от Exists Join в пользу Inner Join. Поскольку у Вас ведущей является таблица строк накладных расходов, то связь получится по типу много-к-одному. Т.е. здесь даже не надо проверять на возможные дубли. Их и так не будет

2. Можно заняться оптимизацией. НО! Оптимизацией не того запроса, что Вы выцепили из профайлера, а обернутого в курсор. Т.е. Вам надо оптимизировать вот это

PHP код:
DECLARE cursor1 CURSOR FOR

SELECT *
FROM MARKUPTRANS A 
WHERE 
(A.DATAAREAID='exp' AND A.CUSTVENDPOSTED_RU<>AND A.ITEMPOSTED_RU=0) AND 
EXISTS (SELECT 'x' FROM CUSTINVOICETRANS B 
WHERE 
(B.DATAAREAID='exp' AND 
((
A.TRANSTABLEID=64 AND A.TRANSRECID=B.RECID AND B.SALESID='РКЗП-00032212' AND B.INVOICEID='14294-11' AND 
B.INVOICEDATE='2012-03-13 00:00:00.000' AND B.NUMBERSEQUENCEGROUP='ТД') OR 
(
A.TRANSTABLEID=62 AND A.TRANSRECID=5637435947) )))

OPEN cursor1
FETCH NEXT FROM cursor1
CLOSE cursor1
DEALLOCATE cursor1 
Если посмотреть его план выполнения, то Вы увидите, что он принципиально отличается от первоначального варианта
__________________
- Может, я как-то неправильно живу?!
- Отчего же? Правильно. Только зря...
За это сообщение автора поблагодарили: AlGol (1), Romb (1), malex (1), Alexanderis.ua (1).
Старый 21.03.2012, 01:11   #9  
Alexanderis.ua is offline
Alexanderis.ua
Участник
 
53 / 40 (2) +++
Регистрация: 25.12.2008
Адрес: Киев, Украина
м-да. а ларчик-то просто открывался...
всего-то нужно было мозг включить. из-за режима работы "ааа, мля, давай срочно меняй" привык делать все в лоб.
надо что-то решать
__________________
If it ain't broke, take it apart and find out why (с)
 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
оптимизация запроса статистики по клиенту wojzeh DAX: Программирование 2 26.04.2011 05:08
Просмотрщик запросов QueryBrowser DAX 3.0 SP4 Russland DAX: База знаний и проекты 30 11.03.2011 11:00
Оптимизация запросов к БД в коде Ace of Database DAX: База знаний и проекты 57 18.03.2010 09:38
Оптимизация запросов psv DAX: Администрирование 6 29.07.2004 23:17
Оптимизация запросов Mystery DAX: Программирование 3 25.02.2004 13:12

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 16:04.