Как преобразовать объединение значений пары столбцов в линейную таблицу? - PullRequest
2 голосов
/ 21 сентября 2011

Проблема

У нас есть таблица с дублирующимися номерами клиентов:

A varchar(16) NOT NULL,
B varchar(16) NOT NULL

Эти столбцы начинались как Старые и Новые (Удалить и Сохранить), нопереданный туда, где ни один не является предпочтительным.Столбцы в действительности представляют собой просто «A» и «B» - два числа для одного и того же клиента в любом порядке.

Кроме того, таблица может иметь произвольное количество пар для одного и того же клиента.Вы можете увидеть строки типа

a,b
b,c

, означающие, что a, b, c предназначены для одного и того же клиента.Вы также можете увидеть строки типа

a,b
b,a
c,a

, означающие, что a, b, c являются одним и тем же клиентом.

Это , а не чистое ациклическое представление, такое как "old" и«новые» значения.Список идентификаторов клиентов для клиента представлен в этой таблице фрагментами из одной или нескольких строк, где единственное соединение состоит в том, что значение для столбца A или B в одной строке может отображаться в столбце A или B в некоторой другой строке.,Моя миссия состоит в том, чтобы связать их все вместе в список для каждого клиента.

Я хочу преобразовать этот беспорядок в что-то вроде

MasterKey int NOT NULL,
CustNum varchar(16) NOT NULL UNIQUE,
PRIMARY KEY( MasterKey, CustNum )

Один или несколько номеров для клиента разделяютMasterKey в этой таблице.Как говорит ограничение UNIQUE, данный CustNum не может появляться более одного раза.

Так, например, строки, подобные этой из исходного

1a,1b
1b,1c
2a,2b
2b,2c
2d,2a
...

, должны заканчиваться как строки, подобные этой, вновая таблица

1 1a
1 1b
1 1c
2 2a
2 2b
2 2c
2 2d
...

Редактировать: Приведенные выше значения просто для того, чтобы сделать шаблон более четким.Фактические значения номера клиента являются произвольными varchar с.

Мои попытки решения

Это похоже на работу для рекурсии и, следовательно, CTE.Но потенциально циклическая природа исходных данных затрудняет мне получение якорного случая.Я пытался предварительной очисткой его в более ациклической форме, но я до сих пор не могу показаться, чтобы получить это право.

1041 * Я также упорно пытаюсь сделать это как на основе множество операций SQLвместо того, чтобы прибегать к курсору и циклу.Но, возможно, это не возможно.

Я потратил 8 часов на размышления об этом и пробовал разные подходы, но это ускользает.Любые идеи или предложения о правильном подходе, или даже пример кода?

Ответы [ 6 ]

1 голос
/ 21 сентября 2011

Я думаю, вам придется сделать цикл. Здесь я смотрю по 1 строке за раз, чтобы убедиться, что я получаю все цепочечные значения, принадлежащие одному masterkey.

while (1=1)
begin

    -- get the next key that is not inserted yet as MasterKey or key
    select top 1 @masterKey = a
    from myTable 
    where not exists (select 1
        from #temp
        where #temp.MasterKey = myTable.a
        or #temp.Key = myTable.a)

    if(@masterKey is null) -- out of a's so now work the b's
        select top 1 @masterKey = b
        from myTable 
        where not exists (select 1
            from #temp
            where #temp.MasterKey = myTable.b
            or #temp.Key = myTable.b)

    if(@masterKey is null) -- totally done.
        break

    insert into #temp
    (masterKey, key)
    values(@masterKey, @masterKey)


    while (1=1) -- now insert anything new with this masterKey in a
    begin
        insert into #temp
        select top 1 @masterKey, myTable.b
        from myTable
        where myTable.a = @masterKey
        not exists (select 1
        from #temp
        where #temp.MasterKey = myTable.b
        or #temp.Key = myTable.b))

        if @@rowcount < 1
            break
    end 


    while (1=1) -- now insert anything with this masterKey in b
    begin
        insert into #temp
        select top 1 @masterKey, myTable.a
        from myTable
        where myTable.b = @masterKey
        not exists (select 1
        from #temp
        where #temp.MasterKey = myTable.a
        or #temp.Key = myTable.a))

        if @@rowcount < 1
            break

    end 

end

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

1 голос
/ 21 сентября 2011

заданные входные данные:

a,b
b,c
d,e
e,f
g,d

Я бы добавил две новые таблицы, одну со значениями pk, а другую со значениями pk и дубликаты в соотношении один-ко-многим с pks, напримерэто:

pk
a
b
c
d
e
f
g


pk dup
a   b   
b   a
b   c
c   b   
d   e
e   d
e   f
f   e   
g   d
d   g

строки в таблице pk / dup заполняются вашим входным файлом с pks и дубликатами, вставляющими как в последовательности (pk, dup), так и в последовательности (dup, pk).

это дает вам ваш первый набор отношений между ключами и дубликатами, но вам нужно будет повторить этот набор снова, чтобы получить косвенные отношения, например, 'c является дубликатом'

, который вы можетеполучить эти отношения путем самостоятельного присоединения к таблице pk / dup в pkdup1.dup = pkdup2.pk.это объединяет строку (a, b) со строками (b, a) и (b, c), позволяя идентифицировать отношение (a, c).это также поднимет (d, f) (f, d) (g, e).вам нужно будет повторить итерацию, чтобы подобрать (g, f)

HTH

0 голосов
/ 22 сентября 2011

Я собираюсь сделать что-то, чего раньше не делал, и опубликовать ответ на свой вопрос.Я должен поблагодарить Бет и Дж. Брукса за то, что они двигали меня в правильном направлении.Я действительно хотел решить эту проблему на основе множеств, декларативным способом.И, возможно, это все еще возможно, используя CTE и рекурсию.Но как только я сдался и сказал, что это нормально для того, чтобы это было императивно и итеративно, это было намного проще сделать.

В любом случае, учитывая эту таблицу целей из моего вопроса:

CREATE TABLE UniqueCustomers
(
    uid     int NOT NULL,
    gpid    varchar(16) NOT NULL UNIQUE, -- Important: UNIQUE to disallow duplicates
    PRIMARY KEY( uid, gpid ) -- Important: Disallow duplicates
)

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

CREATE PROCEDURE ReportDuplicateCustomerIDs
(
    @id1 varchar(16),
    @id2 varchar(16)
)
AS
BEGIN
    IF @id1 <> @id2
    BEGIN
        -- Retrieve the uid (if any) for each of the ids
        DECLARE @uid1 int
        SELECT @uid1 = NULL
        SELECT @uid1 = uid FROM UniqueCustomers WHERE gpid = @id1

        DECLARE @uid2 int
        SELECT @uid2 = NULL
        SELECT @uid2 = uid FROM UniqueCustomers WHERE gpid = @id2

        -- If we've seen NEITHER of the id's yet
        IF @uid1 IS NULL AND @uid2 IS NULL
        BEGIN
            -- Add both of them using a brand-new uid
            DECLARE @uidNew int
            SELECT @uidNew = Max(uid) + 1 FROM UniqueCustomers
            IF @uidNew IS NULL
                SET @uidNew = 0
            INSERT INTO UniqueCustomers VALUES( @uidNew, @id1 )
            INSERT INTO UniqueCustomers VALUES( @uidNew, @id2 )
        END
        ELSE
        BEGIN
            -- If we've seen BOTH id's already
            IF @uid1 IS NOT NULL AND @uid2 IS NOT NULL
            BEGIN
                -- If this pair bridges two existing chains.
                IF @uid1 <> @uid2
                BEGIN
                    -- Update everything using uid2 to use uid1 instead.
                    -- Consolidates the two dupe chains into one.
                    UPDATE UniqueCustomers SET uid = @uid1 WHERE uid = @uid2
                END
                -- ELSE nothing to do
            END
            ELSE
                -- If we've seen only id1, then insert id2 using
                -- the same uid that id1 is already using
                IF @uid1 IS NOT NULL
                    INSERT INTO UniqueCustomers VALUES( @uid1, @id2 )
                -- If we've seen only id2, then insert id1 using
                -- the same uid that id2 is already using
                ELSE -- @uid2 IS NOT NULL
                    INSERT INTO UniqueCustomers VALUES( @uid2, @id1 )
        END
    END
END
GO
0 голосов
/ 21 сентября 2011

На основании некоторых примеров данных в комментарии, я думаю, что это должно сработать?

CREATE TABLE #sample
(A NVARCHAR(50)
,B NVARCHAR(50))

INSERT INTO #sample VALUES('FOO12','12DEF')
INSERT INTO #sample VALUES('12GHJ','12ABC')
INSERT INTO #sample VALUES('GURGLE721','GURGLZ721')
INSERT INTO #sample VALUES('word21','book721')
INSERT INTO #sample VALUES('orange21','apple21')

;WITH CTE as
(
SELECT A
,PATINDEX('%[A-Za-z]%',A) as text_start
,PATINDEX('%[0-9]%',A) as num_start
FROM #sample
UNION ALL
SELECT B
,PATINDEX('%[A-Za-z]%',B) as text_start
,PATINDEX('%[0-9]%',B) as num_start
FROM #sample
)
,cte2 AS
(
SELECT
*
,CASE WHEN text_start > num_start --Letters after numbers
    THEN SUBSTRING(A,text_start - num_start + 1,99999)
    WHEN text_start = 1 --Letters at start of string
    THEN SUBSTRING(A,1,num_start - 1)
    END AS letters
,CASE WHEN num_start > text_start --Numbers after letters
    THEN SUBSTRING(A,num_start - text_start + 1,99999)
    WHEN num_start = 1 --Numbers at start of string
    THEN SUBSTRING(A,1,text_start- 1)
    END AS numbers
FROM cte
)
SELECT DISTINCT
DENSE_RANK() OVER (ORDER BY numbers ASC) as group_num
,numbers + letters as cust_details
FROM cte2
ORDER BY numbers + letters asc
0 голосов
/ 21 сентября 2011

Какой шаблон для поиска ключа?Если это просто первое число в строке, то это вытащит его:

select substring('FOO12',patindex('%[0-9]%','FOO12'),100)

Если оно начинается с числа, то это вытащит его:

select substring('12FOO',1,patindex('%[A-Z]%','12FOO')-1)

Оба возвращают12.

0 голосов
/ 21 сентября 2011

Похоже, работа для союза для меня.Приведенный ниже код предполагает, что вы не можете иметь 1a, 2b в одной записи.

создать таблицу #temp (a varchar (10), b varchar (10))

insert into #temp
values ('1a', '1b')
,('1b', '1c')
,('2a', '2b')
,('2b', '2c')
,('2d', '2a')

select * from #temp

select a, b, left (a, 1) as id into #temp2 from #temp

select id, a from #temp2 
union 
select id, b from #temp2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...