2016-05-03 3 views
2

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

таблицы У меня есть следующая:

User 
-------------- 
UserId 


Sticker 
------------- 
Id 


UserStickers 
------------- 
UserId 
StickerId 
Count 

Пример данные:

User 
------------- 
'FFE16530-E42B-48F5-9CE2-A4D58E94C1D1' 
'F4EF0B59-81AB-41BF-8FB8-BE4E138D294B' 
'F1A2F44A-EFD3-4AA9-8210-D4977C68E4A5' 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18' 

Sticker 
------------- 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 

UserStickers 
------------- 
'FFE16530-E42B-48F5-9CE2-A4D58E94C1D1', 1, 2 
'FFE16530-E42B-48F5-9CE2-A4D58E94C1D1', 2, 1 
'FFE16530-E42B-48F5-9CE2-A4D58E94C1D1', 3, 3 
'F4EF0B59-81AB-41BF-8FB8-BE4E138D294B', 1, 3 
'F4EF0B59-81AB-41BF-8FB8-BE4E138D294B', 2, 1 
'F4EF0B59-81AB-41BF-8FB8-BE4E138D294B', 4, 3 
'F4EF0B59-81AB-41BF-8FB8-BE4E138D294B', 5, 2 
'F4EF0B59-81AB-41BF-8FB8-BE4E138D294B', 6, 1 
'F1A2F44A-EFD3-4AA9-8210-D4977C68E4A5', 1, 2 
'F1A2F44A-EFD3-4AA9-8210-D4977C68E4A5', 4, 3 
'F1A2F44A-EFD3-4AA9-8210-D4977C68E4A5', 8, 2 
'F1A2F44A-EFD3-4AA9-8210-D4977C68E4A5', 10, 3 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18', 1, 1 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18', 4, 5 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18', 7, 2 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18', 8, 2 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18', 9, 2 
'148CFBB4-94D5-4F85-A2BF-A155EC60DF18', 10, 2 

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

Я создал функцию StickerNeeds:

CREATE FUNCTION StickerNeeds (@UserId UNIQUEIDENTIFIER) 
RETURNS @StickerNeeds TABLE 
    (
    StickerId  INT 
    ) 
AS 
BEGIN 
    DECLARE @SearchUsersStickers TABLE 
    (
     StickerId int 
    ); 

    INSERT INTO @SearchUsersStickers 
    SELECT StickerId 
    FROM UserStickers 
    WHERE UserId = @UserId; 

    INSERT @StickerNeeds 
     SELECT 
      S.Id 
     FROM 
      Stickers S 
     LEFT JOIN 
      @SearchUsersStickers SUS 
     ON 
      S.Id = SUS.StickerId 
     WHERE 
      SUS.StickerId IS NULL  
    RETURN 
END 

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

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

DECLARE @UserId UNIQUEIDENTIFIER = 'FFE16530-E42B-48F5-9CE2-A4D58E94C1D1' 

DECLARE @UserMatches TABLE 
(
UserId UNIQUEIDENTIFIER, 
StickerTake int, 
StickerGive int 
); 

INSERT INTO @UserMatches 
SELECT TOP 20 
    UserId, 
    Count(*), 
    NULL 
FROM 
    UserStickers US 
INNER JOIN 
    StickerNeeds(@UserId) SUN 
ON 
    US.StickerId = SUN.StickerId 
WHERE 
    US.[Count] > 1 
GROUP BY 
    UserId 
ORDER BY 
    Count(*) DESC 

-- Find Stickers to Give AND UPDATE @UserMatches 
SELECT 
    UM.UserId, 
    COUNT(*) As StickerCount 
FROM 
    (SELECT 
      US.StickerId AS StickerId 
     FROM 
      dbo.UserStickers US 
     WHERE 
      US.UserId = @UserId 
      AND US.[Count] > 1 
     ) STG -- StickerToGive 
LEFT JOIN 
    UserStickers US 
ON 
    US.StickerId = STG.StickerId 
LEFT JOIN 
    @UserMatches UM 
ON 
    US.UserId = UM.UserId 
WHERE 
    US.StickerId IS NULL 
GROUP BY 
    UM.UserId 


SELECT * FROM @UserMatches 

В идеале @UserMatches будет содержать данный пользователь, счетчик стикера пользователя происхождения может принять и количество наклеек пользователь происхождения может дать. Я не могу вычислить значение без использования курсора.

+0

http://stackoverflow.com/help/how-to-ask – Chris

+0

Привет @ Chris-MayhemSoftware, что бы вы хотели, чтобы я расшириться на? –

+0

Можете добавить некоторые тестовые (фиктивные) данные на ваш вопрос? –

ответ

1

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

select top 20 UserId, 
    (select count(*) from UserStickers u3 where u3.UserId = u1.UserId and u3.count > 1 and u3.StickerId not in (select StickerId from UserStickers u4 where u4.UserId = <origin_User> and u4.count > 1)) as CountOriginCanTake, 
    (select count(*) from UserStickers u5 where u5.UserId = <origin_User> and u5.count > 1 and u5.StickerId not in (select StickerId from UserStickers u6 where u6.UserId = u1.UserId and u6.count > 1)) as CountOriginCanGive 
from UserStickers u1 
where u1.StickerId not in (select StickerId from UserStickers u2 where u2.UserId = <origin_User> and u2.count > 1) 
and u1.count > 1 
group by UserId 
order by 2 desc 

Просто вставьте идентификатор пользователя происхождения вместо origin_User

+0

Я получаю сообщение об ошибке: Идентификатор из нескольких частей "u2.userId" не может быть связан. –

+0

Извините, у меня была опечатка - u2.UserId должен был быть u4.UserId. Также я думаю, что вам нужно «count> 1» в нескольких местах. Я обновил ответ. – Chris

+0

Ok Awesome, теперь это выполняется, но через некоторое время возвращает эту ошибку. Я предполагаю, что это потому, что UserId является GUID? Ошибка: Ошибка конверсии при преобразовании значения nvarchar '003082F0-35B8-434B-9BBE-AF0388205DE9' в тип данных int. –

0

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

;WITH UserNeeds 
AS 
(
    SELECT UserId, StickerId 
    FROM User U 
    CROSS APPLY Sticker S 
    EXCEPT 
    SELECT UserId, StickerId 
    FROM UserStickers 
), 
UserGives 
As 
(
    SELECT UserId, StickerId, Num - 1 As Num 
    FROM UserStickers 
    WHERE Num > 1 
), 
PossibleMatches 
As 
(
    SELECT UG.UserId GivingUser, UG.StickerId, UN.UserId ReceivingUser, UG.Num 
    From UserNeeds UN 
    INNER JOIN UserGives UG 
     ON UN.StickerId = UG.StickerId 
), 
BestMatches 
As 
(
    SELECT GivingUser, ReceivingUser, Count(*) as Matches, ROW_NUMBER() OVER (PARTITION BY GivingUser ORDER BY COUNT(*) DESC) AS RN 
    FROM PossibleMatches 
    GROUP BY GivingUser, ReceivingUser 
) 
SELECT PM.* 
FROM PossibleMatches PM 
INNER JOIN BestMatches BM 
    ON PM.GivingUSer = BM.GivingUser AND PM.ReceivingUser = BM.ReceivingUser 
WHERE RN = 1 
ORDER BY PM.GivingUser, PM.ReceivingUser 
+0

В первой строке я получаю сообщение об ошибке: все запросы, объединенные с помощью оператора UNION, INTERSECT или EXCEPT, должны иметь равное количество выражений в своих целевых списках. –

+0

@ Tom.Bowen89 Я обновил запрос, повторите попытку. –

+0

В реальной базе данных у нас много данных. С моим курсором я получил результаты примерно через 1 минуту. Однако с вашим запросом он дошел до 12 минут, прежде чем закончил выполнение. Если это поможет, возвращение всего 20 лучших - это все, что нужно. –