TSQL: сопоставление нескольких значений в одном столбце с отдельным внешним ключом - PullRequest
0 голосов
/ 21 марта 2012

У меня есть таблица, в которой перечислены пользователи, которые находятся в одном разговоре. Например:

id | conversation | user
1  |  1           |  Bob
2  |  1           |  Jane
3  |  2           |  Tim
4  |  2           |  Lily
5  |  1           |  Rick
6  |  3           |  Lily
7  |  1           |  Tim

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

Например. Тим хочет начать новый разговор с Лили. Были ли эти двое в разговоре раньше, когда они были ЕДИНСТВЕННЫМИ пользователями в беседе? Запрос sql определит, что они были в беседе 2 как эксклюзивные участники.

EDIT: Вот моя попытка достичь желаемого результата при возврате разговор_идентификатора, в котором участники беседуют исключительно. Интересно, что эта попытка возвращает желаемый результат :

SELECT in2.[conversation] AS cid, COUNT(in2.[conversation]) AS [matchedParticipants], (SELECT COUNT(in1.[conversation]) FROM [cInboxMembers] in1 WHERE in1.[conversation] = in2.[conversation] GROUP BY in1.[conversation]) AS totalParticipants  FROM [cInboxMembers] in2 WHERE [username] IN ('Tim','Lily') GROUP BY in2.[conversation] HAVING COUNT(in2.[conversation]) = '2' AND (SELECT COUNT(in1.[conversation]) FROM [cInboxMembers] in1 WHERE in1.[conversation] = in2.[conversation] GROUP BY in1.[conversation]) = '2'

В этой попытке я перечислил двух пользователей, которых я хотел бы найти, а затем перечислил количество участников, которые должны участвовать в эксклюзивном разговоре (это «2»), и ограничил результаты, где [matchedParticipants] = 2 и общее количество участников = 2.

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

Ответы [ 3 ]

2 голосов
/ 21 марта 2012

Хорошо, я вроде как обдумал эту идею;Я верю, что это сработает, но вы должны перепроверить.По сути, он добавляет номер строки к каждому диалогу пользователями и использует табличное значение для хранения входящих идентификаторов пользователей.Затем он выбирает любые разговоры, в которых номер строки равен количеству пользователей и равен количеству людей в беседе.Двойной подзапрос, вероятно, неидеален, и, возможно, SQL-мастер сможет его оптимизировать.

CREATE TABLE #ConversationPeople
(
     ID int NOT NULL IDENTITY(1,1) PRIMARY KEY
    ,[conversation] int NOT NULL
    ,[user] int NOT NULL
)
INSERT INTO #ConversationPeople
([conversation], [user])
VALUES
     (1,1)
    ,(1,2)
    ,(2,3)
    ,(2,4)
    ,(2,1)
    ,(1,5)
    ,(3,4)
    ,(1,3)
GO

CREATE TYPE dbo.UserList AS TABLE
    ([user] int)
GO
DECLARE @users dbo.UserList
INSERT INTO @users VALUES (3),(4)

SELECT CP_Data.*, CP.ROW
FROM (
SELECT CP.ID, ROW_NUMBER() OVER(PARTITION BY CP.[conversation] ORDER BY CP.[user]) AS [ROW]
FROM
 @users U
JOIN #ConversationPeople CP
ON CP.[user] = U.[user]) CP
JOIN #ConversationPeople CP_Data
ON CP.ID = CP_Data.ID
WHERE CP.ROW = (SELECT COUNT(*) FROM @users)
AND CP.ROW = (SELECT COUNT(*) FROM #ConversationPeople WHERE [conversation] = CP_Data.conversation)

DROP TYPE dbo.UserList
DROP TABLE #ConversationPeople
GO
1 голос
/ 21 марта 2012

Это не очень эффективно, но при условии, что у вас есть таблица, подобная показанной:

id | conversation | user 
1  |  1           |  Bob 
2  |  1           |  Jane 
3  |  2           |  Tim 
4  |  2           |  Lily 
5  |  1           |  Rick 
6  |  3           |  Lily 
7  |  1           |  Tim 

вы можете получить количество участников на разговор во временной таблице

SELECT 
    T1.Conversation,
    COUNT(*) NumberOfUsers
INTO
    #TEMP
FROM
    YourTable T1
        INNER JOIN YourTable T2
            ON T1.Conversation = T2.Conversation
            AND T1.id <> T2.id
AND T1.username = 'Tim'
GROUP BY T1.Conversation

тогдаотфильтруйте это снова, используя количество участников = 1 и имя = Лили

SELECT
    *
FROM
    YourTable T
    INNER JOIN #TEMP T2
        ON T.Conversation = T2.Conversation
        AND NumberOfUsers = 1
        AND T.UserName = 'lily'
--      AND T.UserName = 'jane'

, если вы получите одну строку, он имел с ней «личную» беседу, если вы получите 0 строк, которых он не сделал.При этом вы можете отфильтровать число, которое вы хотите, например NumberOfUsers> 50, если вы хотите очень публичный разговор ...

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

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

1 голос
/ 21 марта 2012

Для целей этого запроса было бы удобно, если бы клиент мог запросить что-то похожее на это:

conversation   AllUsers
1              Bob Jane Rick Tim
2              Lily Tim

(В разговоре 3 только один пользователь, который, как я полагаю, недействителен и его можно игнорировать.)

Как это сделать, учитывая структуру таблицы из вопроса? По сути, я хочу PIVOT для произвольного числа столбцов, а затем объединить их значения, чтобы создать разделенный пробелами список всех участников беседы в алфавитном порядке. К сожалению, PIVOT требует, чтобы вы перечислили каждое из значений, которые вы превращаете в столбцы. Рекурсивный CTE на помощь:

CREATE TABLE Conversations (
    id INT NOT NULL,
    conversation INT NOT NULL,
    [user] NVARCHAR(50) NOT NULL
)

INSERT INTO Conversations (id, conversation, [user])
SELECT 1, 1, 'Bob'
UNION SELECT 2, 1, 'Jane'
UNION SELECT 3, 2, 'Tim'
UNION SELECT 4, 2, 'Lily'
UNION SELECT 5, 1, 'Rick'
UNION SELECT 6, 3, 'Lily'
UNION SELECT 7, 1, 'Tim'

;WITH ConversationNext AS (
    SELECT C1.conversation, C1.[user], MIN(C2.[user]) AS NextUser
    FROM Conversations C1
    JOIN Conversations C2
    ON C2.conversation = C1.conversation
    AND C2.[user] > C1.[user]
    GROUP BY C1.conversation, C1.[user]
),
ConversationRoot AS (
    SELECT conversation, MIN([user]) AS [user], MIN(NextUser) AS NextUser,
        CAST(MIN([user]) + ' ' + MIN(NextUser) AS NVARCHAR(500)) AS AllUsers,
        2 AS NumberOfParticipants
    FROM ConversationNext
    GROUP BY conversation
),
ConversationRecursive AS (
    SELECT *
    FROM ConversationRoot
    UNION ALL
    SELECT ConversationRecursive.conversation, ConversationRecursive.[user], ConversationNext.NextUser,
        CAST(ConversationRecursive.AllUsers + ' ' + ConversationNext.NextUser AS NVARCHAR(500)),
        ConversationRecursive.NumberOfParticipants + 1
    FROM ConversationRecursive
    JOIN ConversationNext
    ON ConversationNext.conversation = ConversationRecursive.conversation
    AND ConversationNext.[user] = ConversationRecursive.NextUser
),
Final AS (
    SELECT Conversation, MAX(NumberOfParticipants) as N
    FROM ConversationRecursive
    GROUP BY Conversation
)
SELECT ConversationRecursive.conversation, ConversationRecursive.AllUsers
FROM Final
JOIN ConversationRecursive
ON ConversationRecursive.conversation = Final.conversation
AND ConversationRecursive.NumberOfParticipants = Final.N

DROP TABLE Conversations

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

Если пробелы являются допустимыми символами в [user], то используйте другое недопустимое имя пользователя для разделения значений.

Если [пользователь] на самом деле является INT, вы можете CAST каждый элемент в CHAR (11) при создании AllUsers.

CAST к NVARCHAR (500) является произвольным. Без этого вы получите ошибку, что типы данных на привязке и рекурсивной части не совпадают. Вы должны рассчитать значение, превышающее 500, исходя из длины [пользователя] и максимального количества пользователей, которые могут участвовать в одном разговоре.

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