是否可以在不使用游标的情况下为集合中的每个记录运行一个函数?

时间:2019-07-15 18:46:22

标签: sql sql-server sql-server-2012

我有两个标量值函数,它们以用户ID和角色ID作为参数。第一个GetCountries返回以逗号分隔的国家/地区列表。第二个GetBusinesses返回以逗号分隔的业务列表。我将每个结果传递给一个名为StringSplit的表值函数,该函数返回一个单列表,其中每个值对应一行。然后,我交叉加入各个国家和企业,以获取所有组合。

问题是我不仅拥有一个角色ID,而且拥有一组角色ID。我可以使用光标,一次又一次地重复此过程,以获取国家和企业的完整列表,但是光标很慢,我想知道是否还有更高效的方法。

如果只有一个角色ID,这就是它的工作方式。

SELECT [Data] AS Countries INTO #TempCountries FROM StringSplit(GetCountries(@UserID, @RoleID), ',')
SELECT [Data] AS Businesses INTO #TempBus FROM StringSplit(GetBusinesses(@UserID, @RoleID), ',')

SELECT Countries, Businesses INTO #CountryBusCombo FROM #TempCountries CROSS JOIN #TempBus

DROP TABLE #TempCountries
DROP TABLE #TempBus

但实际上,我有以下角色ID的列表:

SELECT RoleID FROM UserRoles WHERE UserID = @UserID

因此,我必须重复执行上述操作,直到在#CountryBusCombo中具有每个不同角色ID的所有国家和企业组合为止。

有没有游标的方法吗?

-编辑 是的,代码库很糟糕。我正在使用已经存在的内容来尝试保持一致性,但是如果现有基础结构非常糟糕并且不会引入回归错误,我可以自己动手做。

这里是GetCountries

SELECT @CountryCodes = ISNULL((SELECT Country FROM HierarchyMap WHERE UserID = @UserID AND RoleID = @RoleID),'')

这里是GetBusinesses

SELECT @Businesses = ISNULL((SELECT Business FROM HierarchyMap WHERE UserID = @UserID AND RoleID = @RoleID ),'')

还有StringSplit

ALTER FUNCTION StringSplit
    (
      @RowData NVARCHAR(MAX) ,
      @SplitOn NVARCHAR(5)
    )
RETURNS @ReturnValue TABLE ( Data NVARCHAR(MAX) )
AS 
    BEGIN
        DECLARE @Counter INT
        SET @Counter = 1 
        WHILE ( CHARINDEX(@SplitOn, @RowData) > 0 ) 
            BEGIN  
                INSERT  INTO @ReturnValue
                        ( data
                        )
                        SELECT  Data = LTRIM(RTRIM(SUBSTRING(@RowData, 1,
                                                             CHARINDEX(@SplitOn,
                                                              @RowData) - 1)))
                SET @RowData = SUBSTRING(@RowData,
                                         CHARINDEX(@SplitOn, @RowData) + 1,
                                         LEN(@RowData)) 
                SET @Counter = @Counter + 1  
            END 
        INSERT  INTO @ReturnValue
                ( data )
                SELECT  Data = LTRIM(RTRIM(@RowData))  
        RETURN  
    END

-编辑2 因此,起点是HierarchyMap表,其中有如下所示的记录:

UserID|RoleID|Countries|Businesses
Bob|role1|AU,GB|Bus1,Bus2,Bus3
Bob|role2|BE|Bus4

预期结果:

Country|Business
AU|Bus1
AU|Bus2
AU|Bus3
GB|Bus1
GB|Bus2
GB|Bus3
BE|Bus4

2 个答案:

答案 0 :(得分:2)

我在这里完全不知所措,但是您的表格似乎已经以分隔格式存储了国家和地区;因此当您已经存储定界数据时,在功能GetCountriesGetBusiness中看不到重点。因此,我正在猜测表和列的名称,但是您应该能够通过以下方式做到这一点:

SELECT C.Item AS Country,
       B.Item AS Business
FROM dbo.Users U --What ever table has your users in
     JOIN dbo.UsersInRoles UR ON U.UserID = UR.UserID --What ever table has your many to many relationship for roles
     JOIN dbo.Roles R ON UR.RoleID = R.RoleID --What ever table has all your roles
     JOIN dbo.HierarchyMap HM ON U.UserID = HM.UserID
                             AND R.RoleID = HM.RoleID
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(HM.Countries,',') C
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(HM.Businesses,',') B;

DelimitedSplit8k_LEAD

我不使用拆分器的原因是因为它是多行表值函数,并且使用WHILE。恐怕它会非常糟糕。 Jeff,Eirikur和SSC社区的其他成员在上述功能上所做的工作将在一周的任何一天完成。

尽管理想情况下,您需要修复该设计,这确实很糟糕。我很乐意为您提供帮助,但是我确实需要一些适当的消耗性数据来实现这一目标。

编辑:注释中的OP表示存在诸如'AU,CA,,BE'之类的值,但是,这些值已从其样本数据中排除。如果您确实有这样的行,请添加一个WHERE

WHERE C.item <> ''
  AND B.item <> '';

DB<>Fiddle

答案 1 :(得分:1)

您只想要cross apply吗?

SELECT c.value as country, b.value as business
FROM UserRoles ur cross apply
     StringSplit(GetCountries(@UserID, ur.RoleID), ',') c CROSS APPLY 
     StringSplit(GetBusinesses(@UserID, ur.RoleID), ',') b;
WHERE ur.UserID = @UserID