Как я могу найти группы записей, которые соответствуют другим группам записей (реляционное деление?)

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

Я думаю, что может сработать поворот владельцев с помощью динамического sql, а затем использование функций ранжирования, но я не хочу использовать этот подход; У меня нет верхнего предела количества имен, которые можно связать с данной учетной записью, поэтому я хочу избежать динамического SQL.

Мои данные (также они находятся по адресу http://www.sqlfiddle.com/#!3/1d36e< /а>)

 CREATE TABLE allacctRels
 (account INT NOT NULL,
 module CHAR(3) NOT NULL,
 custCode CHAR(20) NOT NULL)


 INSERT INTO allacctrels
 (account, module, custCode)
 VALUES
 (1, 'DDA', 'Wilkie, Walker'),
 (1, 'DDA', 'Houzemeal, Juvy'),
 (2, 'CDS', 'Chase, Billy'),
 (2, 'CDS', 'Norman, Storm'),
 (3, 'CDS', 'Chase, Billy'),
 (3, 'CDS', 'Norman, Storm'),
 (7, 'CDS', 'Perkins, Tony'),
 (15, 'SVG', 'Wilkie, Walker'), --typo in name before mwigdahl's response
 (16, 'SVG', 'Wilkie, Walker'), -- corrected typo here too
 (606, 'DDA', 'Norman, Storm'),
 (606, 'DDA', 'Chase, Billy'),-- corrected 2nd typo found 
 (4, 'LNS', 'Wilkie, Walker'),
 (4, 'LNS', 'Houzemeal, Juvy'),
 (44, 'DDA', 'Perkins, Tony'),
 (222, 'DDA', 'Wilkie, Walker'),
 (222, 'DDA', 'Houzemeal, Juvy'),
 (17, 'SVG', 'Wilkie, Walker'), -- added these three rows in edit, SVG 17 doesn't match any dda 
 (17, 'SVG', 'Welch, Raquel'),
 (17, 'SVG', 'Houzemeal, Juvy')

Я хочу выяснить для каждого MODULE-ACCOUNT, какая самая низкая учетная запись DDA имеет точно таких же владельцев, связанных с ней.

В примере данных мне нужны эти результаты, третий столбец — это самая низкая учетная запись DDA, имеющая тех же владельцев. В результатах должно быть столько же строк, сколько и в комбинациях модуль/учетная запись — по одной строке на каждую строку в «SELECT DISTINCT module, account FROM allAcctRels»)

1, DDA, 1
2, CDS, 606
3, CDS, 606
15, SVG, NULL
16, SVG, NULL
606, DDA, 606
4, LNS, 1
7, CDS, 44
44, DDA, 44
222, DDA, 1
17, SVG, NULL -- added to original post.

SVG 15 и 16 не соответствуют ни одной учетной записи DDA, поэтому не имеет значения, совпадают ли они друг с другом, они получают NULL для учетной записи, с которой нужно объединиться. РЕДАКТИРОВАТЬ: SVG 17 ничего не соответствует, несмотря на то, что существует счет DDA, все держатели которого включены в SVG 17, комбинация держателей в SVG 17 не происходит ни для одного счета DDA. Каждая учетная запись DDA будет соответствовать самой себе, если только не существует учетная запись DDA с теми же владельцами и меньшим DDA (как в случае DDA 222).

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

Мне кажется, что это проблема "реляционного деления", при этом относительное деление, вероятно, "подпитывается" КРЕСТНЫМ ПРИМЕНЕНИЕМ. Я попытался написать функцию, которая брала бы таблицу владельцев учетных записей, связанных с определенной учетной записью, и находила учетную запись с наименьшим значением DDA в соответствии с линиями, показанными ниже, идея заключалась в том, чтобы увидеть, одинаково ли все количество людей в данной учетной записи. как количество людей, когда эта учетная запись присоединена к данной учетной записи dda, но я не могу понять, как «подавать» таблицы номеров учетных записей в функцию.

-- this is what I tried but I'm not sure it the logic would work
-- and I can't figure out how to pass the account holders for each
-- account in.  This is a bit changed from the function I wrote, some
    -- extraneous fields removed and cryptic column names changed.  So it 
    -- probably won't run as is.

    -- to support a parameter type to a tape
-- CREATE type VisionCustomer as Table
-- (customer varchar(30))

CREATE FUNCTION  consolidatable 
(@custList dbo.VisionCustomer READONLY)
RETURNS char(10)
AS  
BEGIN
DECLARE @retval Varchar(10)
DECLARE @howmany int
select @howmany=Count(*) FROM @custlist;

SELECT @retval = min (acct) FROM allAcctRels
    JOIN @custlist
        On VendorCustNo = Customer
            WHERE acctType = 'DDA'
            GROUP BY acct
            HAVING (count(*) = @howmany)
            and
            COUNT(*) = (select Count(*) FROM allAcctRels X
    WHERE X.acctType = 'DDA'
    AND X.account = AllAcctRels.account) ;
RETURN @retval
END;

person Levin Magruder    schedule 10.04.2012    source источник
comment
Обратите внимание, что Чейз, Билли в строке 606 DDA не соответствует набору результатов, который вы хотите вернуть; Я думаю, ты хочешь, чтобы это был Чейз, Билли, верно?   -  person mwigdahl    schedule 11.04.2012
comment
Да, это правильно, извините за это, и спасибо, я сейчас отредактирую   -  person Levin Magruder    schedule 11.04.2012


Ответы (2)


Я считаю, что это то, что вы ищете ( http://www.sqlfiddle.com/#!3/f96c5/1 ):

;WITH AccountsWithOwners AS
(
  SELECT DISTINCT
    DA.module
    , DA.account
    , STUFF((SELECT 
                 ',' + AAR.custCode
               FROM allacctRels AAR 
               WHERE AAR.module = DA.module 
                 AND AAR.account = DA.account
               ORDER BY AAR.custCode
               FOR XML PATH(''))
              , 1, 1, '') AS Result
  FROM allacctRels DA
) 
, WithLowestDda AS
(
    SELECT
        AWO.module
        , AWO.account
        , MatchingAccounts.account AS DdaAccount
        , ROW_NUMBER() OVER(PARTITION BY AWO.module, AWO.account ORDER BY MatchingAccounts.account) AS Row
    FROM AccountsWithOwners AWO
    LEFT JOIN AccountsWithOwners MatchingAccounts
        ON MatchingAccounts.module = 'DDA'
        AND MatchingAccounts.Result = AWO.Result
)
SELECT
    account
    , module
    , DdaAccount
FROM WithLowestDda
WHERE Row = 1
person Jeremy Pridemore    schedule 10.04.2012
comment
Это выглядит как победитель, оно появилось как раз в тот момент, когда мне нужно было начать несколько резервных копий и отправиться домой, я посмотрю на него сегодня вечером, но я вижу, что он дает правильные результаты на моих тестовых данных. Я не знаком с FOR XML, поэтому я хочу выяснить, как он работает, прежде чем принять его. - person Levin Magruder; 11.04.2012
comment
@LevinMagruder Это в основном создание XML, который не имеет фактической разметки и используется для создания списка, разделенного запятыми. Дайте знать, если у вас появятся вопросы. - person Jeremy Pridemore; 11.04.2012

Это на самом деле оказывается довольно просто, если я правильно вас понял. Попробуй это:

SELECT a.account, a.module, MIN(b.account) 
FROM allacctRels a
    LEFT JOIN allacctRels b ON a.custCode = b.custCode AND b.module = 'DDA'
GROUP BY a.account, a.module

РЕДАКТИРОВАТЬ: вышеизложенное не работает после разъяснений, но это должно быть. Это действительно тип относительного деления. Возможно, это не самый эффективный план запросов в мире, но он работает.

SELECT a.account, a.module, MIN(b.account)
FROM allacctRels a
    LEFT JOIN allacctRels b ON b.module = 'DDA'
    AND
    -- first test is to confirm that the number of matching names for this combination equals the number of names for the DDA set...
    (
        SELECT COUNT(*) 
        FROM allacctRels b2 
            INNER JOIN allacctRels a2 ON b2.custCode = a2.custCode 
        WHERE a.account = a2.account AND b.account = b2.account
    ) = 
    (
        SELECT COUNT(*) 
        FROM allacctRels b2 
        WHERE b.account = b2.account
    )
    AND 
    -- second test is to confirm that the number of names for the DDA set equals the number of names for the base set...
    (
        SELECT COUNT(*) 
        FROM allacctRels b2 
        WHERE b.account = b2.account
    ) = 
    (
        SELECT COUNT(*) 
        FROM allacctRels a2 
        WHERE a.account = a2.account
    )
GROUP BY a.account, a.module
person mwigdahl    schedule 10.04.2012
comment
+1; это первое, что пришло мне в голову, когда я смотрел на это. - person lyrisey; 10.04.2012
comment
Но здесь вы сохраняете все строки, у которых есть совпадения, на стороне «b» (DDA), не так ли? Например, если я добавлю Уэлч Ракель в каждую учетную запись, не относящуюся к DDA, результаты для учетных записей, не относящихся к DDA, не изменятся, но не должно быть никаких совпадений, поскольку ни одна учетная запись, не относящаяся к DDA, не имеет Ракель Уэлч. У меня было две опечатки в Walker Wilkie в моем образце данных, из-за которых этот запрос давал те же результаты, что и в моем примере, и у меня не было примеров в моих данных учетных записей с дополнительным человеком, которые не должны совпадать. Я обновил скрипку sql и обновлю приведенные выше примеры. - person Levin Magruder; 10.04.2012
comment
Спасибо за разъяснения. Я смотрю на это снова. - person mwigdahl; 11.04.2012
comment
Обновленный ответ, должно быть то, что вы ищете сейчас. - person mwigdahl; 11.04.2012
comment
Мне пришлось ввести модуль в предложения where (sqlfiddle.com/#!3/8d7c0/ 4, чтобы избежать ошибки, показанной на sqlfiddle.com/#!3/cbb1b/1). Результаты выглядят хорошо, и я думаю, что понимаю, как это работает, но не сразу очевидно, как min(b.account) привязывается к учетной записи, которая идет с a.account, поскольку LEFT JOIN просто помещает все DDA accts с каждой записью слева. — Левин Магрудер 6 минут назад - person Levin Magruder; 11.04.2012
comment
Однако LEFT JOIN этим не занимается. LEFT JOIN существует только для того, чтобы вы могли получить запись даже для учетных записей, которые не соответствуют какой-либо подгруппе DDA. Если есть хотя бы одно совпадение, вам будут предоставлены только те подгруппы DDA, которые точно соответствуют набору custCode для данной учетной записи. MIN(b.account) просто сворачивает эти возвращенные подгруппы и дает вам самый низкий идентификатор. - person mwigdahl; 11.04.2012