在不使用游标

时间:2016-05-03 10:43:59

标签: sql-server matching

我有一个项目,我存储用户拥有的贴纸,我希望将用户与他们可以交易的其他用户进行匹配。

我的表格是:

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

我现在正在尝试编写我的匹配代码,我可以让前20位用户给原始用户贴纸。然而,如果没有光标运行缓慢,计算一个他们需要的贴纸的数量是很困难的。

这是我目前所拥有的,但是我生成贴纸的最终声明不会返回任何内容。

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将包含用户匹配,原始用户可以使用的贴纸数量以及原始用户可以提供的贴纸数量。我不能在不使用光标的情况下计算给出。

2 个答案:

答案 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

只需粘贴原始用户ID代替origin_User

即可

答案 1 :(得分: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