Создание динамического SQL для большого количества столбцов - PullRequest
0 голосов
/ 02 мая 2018

Я пытаюсь выяснить различия между двумя таблицами (IAM и IAM_audit) для даты последнего изменения для множества полей Audit_fields (около 20). Я просто показываю два поля aud_fields здесь. Если я использую курсоры, это медленно. Вот пример двух столбцов. Пожалуйста, помогите мне, как создать один динамический запрос вместо того, чтобы давать результаты вместе? Большое спасибо!

Создание таблицы:

create table #Iam
(
      Accnum int,
      invnumber int,
      name varchar(10),
      Ac_status varchar(10)
)

insert into #Iam (Accnum, invnumber, name, Ac_status)
values (120, 131, 'abc', 'A'), (121, 132, 'def', 'C')

create table #Iam_audit
(
     accnum int,
     invnumber int,
     audit_field varchar(10),
     field_after varchar(10),
     modified_date datetime
)

insert into #Iam_audit (accnum, invnumber, audit_field, field_after, modified_date)
values (120, 131, 'name', 'abd', '2014-08-09'),
       (121, 132, 'ac_status', 'A', '2015-07-09'),
       (120, 131, 'name', 'def', '2014-09-15'),
       (121, 132, 'ac_status', 'A', '2015-09-14')

Отдельные запросы:

SELECT 
    a.invnumber, a.Accnum,
    i.audit_field, i.field_after, name, i.maxdate AS Modified_date
FROM 
    #Iam a
JOIN 
    (SELECT
         a.invnumber, a.Accnum, a.field_after, audit_field, maxdate
     FROM
         #Iam_audit a WITH(nolock)
     INNER JOIN
         (SELECT 
              Accnum, invnumber, MAX(Modified_Date) AS maxdate
          FROM
              #Iam_audit a2 WITH(nolock)
          WHERE
              a2.Audit_field = 'name'
          GROUP BY
              Accnum, invnumber) AS aa ON aa.Accnum = a.Accnum 
                                       AND aa.invnumber = a.invnumber 
                                       AND aa.maxdate = a.modified_Date
    WHERE
        a.Audit_Field = 'name') i ON i.audit_field = 'name'  
                                  AND i.Accnum = a.Accnum 
                                  AND i.invnumber = a.invnumber  
                                  AND a.name <> i.field_after

SELECT a.invnumber,a.Accnum,i.audit_field,i.field_after,ac_status,i.maxdate as Modified_date
FROM #Iam a
JOIN (Select  a.invnumber,a.Accnum,  a.field_after,audit_field,maxdate
from #Iam_audit a(nolock)
inner join (Select Accnum,invnumber, max(Modified_Date) as maxdate
        from #Iam_audit a2(nolock)
        where a2.Audit_field='ac_status'
        group by Accnum,invnumber
        ) as aa  on aa.Accnum = a.Accnum and aa.invnumber=a.invnumber and aa.maxdate=a.modified_Date
        where a.Audit_Field='ac_status') i
ON i.audit_field='ac_status'  and i.Accnum=a.Accnum and i.invnumber=a.invnumber  AND a.name<>i.field_after

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Должен признать, что я действительно получил то, что вам нужно, какое-то сравнение на основе столбцов ...

Следующее использует возможности XML для работы с неизвестными именами столбцов на основе строк:

редактировать: упрощенный

SELECT  ia.accnum
       ,ia.invnumber
       ,ROW_NUMBER() OVER(PARTITION BY ia.accnum,ia.invnumber ORDER BY ia.modified_date DESC) SortIndex
       ,ia.modified_date
       ,ia.audit_field
       ,ia.field_after
       ,(
            SELECT * 
            FROM #Iam AS i
            WHERE i.accnum=ia.accnum AND i.invnumber=ia.invnumber  
            FOR XML PATH('row'),TYPE
        ).query(N'/row/*[lower-case(local-name())=lower-case(sql:column("ia.audit_field"))]')
         .value('(//text())[1]','nvarchar(max)') AS CurrentValue
FROM #Iam_audit AS ia;

Некоторое объяснение

Вы выбираете строки вашей таблицы аудита. Коррелированный подзапрос передается в XML. Вкратце: вы получите соответствующую строку #Iam в виде XML (со всеми столбцами, сколько их может быть) ...

Против этого XML вы можете использовать предикат XQuery, чтобы выбрать столбец, соответствующий строке в поле 'audit_field` (именно здесь происходит магия).

Запрос возвращает отсортированный набор вашей таблицы аудита вместе с подходящим значением основной таблицы

+--------+-----------+-----+-------------------------+-------------+-------------+--------------+
| accnum | invnumber | inx | modified_date           | audit_field | field_after | CurrentValue |
+--------+-----------+-----+-------------------------+-------------+-------------+--------------+
| 120    | 131       | 1   | 2014-09-15 00:00:00.000 | name        | def         | abc          |
+--------+-----------+-----+-------------------------+-------------+-------------+--------------+
| 120    | 131       | 2   | 2014-08-09 00:00:00.000 | name        | abd         | abc          |
+--------+-----------+-----+-------------------------+-------------+-------------+--------------+
| 121    | 132       | 1   | 2015-09-14 00:00:00.000 | ac_status   | A           | C            |
+--------+-----------+-----+-------------------------+-------------+-------------+--------------+
| 121    | 132       | 2   | 2015-07-09 00:00:00.000 | ac_status   | A           | C            |
+--------+-----------+-----+-------------------------+-------------+-------------+--------------+

Это не будет быстро, но будет быстрее, чем CURSOR ...

UPDATE

Должно быть быстрее предварительно создать XML и присоединить их к запросу. Особенно, если в accnum/invnumber есть много строк:

CREATE TABLE #XMLLookup(accnum INT, invnumber INT, TheXml XML, CONSTRAINT pk PRIMARY KEY(accnum,invnumber));
INSERT INTO #XMLLookup
SELECT accnum
      ,invnumber
      ,(SELECT *
        FROM #Iam AS ia2
        WHERE ia.accnum=ia2.accnum
          AND ia.invnumber=ia2.invnumber
        FOR XML PATH('row'))
FROM #Iam AS ia;


SELECT  ia.accnum
       ,ia.invnumber
       ,ROW_NUMBER() OVER(PARTITION BY ia.accnum,ia.invnumber ORDER BY ia.modified_date DESC) AS inx
       ,ia.modified_date
       ,ia.audit_field
       ,ia.field_after
       ,xl.TheXml
         .query(N'/row/*[lower-case(local-name())=lower-case(sql:column("ia.audit_field"))]')
         .value('(//text())[1]','nvarchar(max)') AS CurrentValue
FROM #Iam_audit AS ia
INNER JOIN #XMLLookup AS xl ON ia.accnum=xl.accnum
                           AND ia.invnumber=xl.invnumber
0 голосов
/ 02 мая 2018

Краткое описание решения:

  1. Создать курсор для имен столбцов таблицы
  2. Вставьте имя каждого столбца в предопределенный оператор
  3. Выполнить инструкцию

Предопределенный оператор использует cross apply для выборки самой последней записи аудита.

Полное решение:

declare @colName nvarchar(50);
declare @stmt nvarchar(500);

DECLARE colNameCur CURSOR FOR
   SELECT COLUMN_NAME
   FROM INFORMATION_SCHEMA.COLUMNS
   WHERE TABLE_NAME = 'Iam' AND TABLE_SCHEMA='dbo';

OPEN colNameCur;

FETCH NEXT FROM colNameCur   
INTO @colName;

WHILE @@FETCH_STATUS = 0  
BEGIN

    SET @stmt = 
    'select a.InvNumber, a.AccNum, ''' + @colName + ''' as audit_field, last_audit.field_after, a.Name, last_audit.modified_date
    from Iam a
    cross apply (  select top 1 aa.field_after, aa.modified_date
                   from Iam_audit aa
                   where aa.AccNum = a.AccNum
                     and aa.InvNumber = a.InvNumber
                     and aa.audit_field = ''' + @colName + '''
                     and aa.field_after <> a.' + @colName + '
                   order by aa.modified_date desc ) last_audit';

  exec (@stmt);

   FETCH NEXT FROM colNameCur   
   INTO @colName;
END

CLOSE colNameCur;
DEALLOCATE colNameCur;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...