在SQL Server中解析管道分隔值

时间:2016-07-13 02:16:44

标签: sql-server substring

我的一个表中有一个 User_Role 列,其中包含管道分隔值,如

guest|user_Admin|user_guest
user_Admin|guest
guest|user_guest|user_Admin
user_guest|user_Admin
user_Admin

我必须从上面的列中获取角色,其中包含 Admin 。 我不想要整个有Admin的行。

例如,如果列数据是 guest | user_guest | user_Admin ,则查询应仅返回 user_Admin 而不是整个值。

我怎样才能实现它?

2 个答案:

答案 0 :(得分:1)

首先,我要告诉你你应该做什么......规范你的桌子。

如果相关表是用户角色表,则将user_role列拆分为单个值。如果它是包含其他列的较大表的一部分,那么您应该使用单独的表来存储用户角色信息。我还建议为每个值分配不同的级别,这样你就不必解析字符串了。

例如,有一个user_roles表,如

CREATE TABLE userRoles (userID INT, userRole INT);

...然后有一个角色表,如:

CREATE TABLE Roles (userRole INT IDENTITY(1, 1), roleDescription VARCHAR(255), roleLevel INT);
INSERT Roles (roleDescription, roleLevel) VALUES ('guest', 1), ('user_guest', 2), ('user_Admin', 3);

...这样,您可以通过连接和查找级别3而不是解析字符串来查找管理员角色。如果你的表格很小,没什么大不了的,但是你的表格越大,解析字符串就越昂贵。通过适当的索引,这将是检索所需数据的最快方法,同时在必要时可以轻松更改数据(需要更改角色名称?没问题......在Roles表中编辑roleDescription)。 / p>

但如果不这样做,可以考虑采用一些解决方案......

declare @1 table (ID INT IDENTITY(1, 1), User_Role varchar(255));
insert @1 (User_Role) values ('guest|hello_Kitty_Admin|user_guest'),('user_Admin_zzz|guest'),('guest|user_guest|greatest_admin_of_all_time'),('user_guest|user_Admin2'),('user_Admin'),('regular_user');
with t as (
    select id, user_role, left(User_Role, charindex('admin', User_Role) + isnull(nullif(charindex('|', substring(User_Role, charindex('admin', User_Role), len(User_Role))), 0) - 2, len(User_Role))) zzz
    from @1)
select id, isnull(right(zzz, isnull(nullif(charindex('|', reverse(zzz)), 0) - 1, len(zzz))), user_role) adminrole
from t;

...以上所做的就是解析你的user_role列,找到admin所在的位置并获取该值的行括号之间的所有内容(如果一个不存在,它只是完整地返回user_role)< / p>

declare @2 table(ID INT IDENTITY(1, 1), User_Role varchar(255));
insert @2 (User_Role) values ('guest|hello_Kitty_Admin|user_guest'),('user_Admin_zzz|guest'),('guest|user_guest|greatest_admin_of_all_time'),('user_guest|user_Admin2'),('user_Admin'),('regular_user');
with cte as (
    select id, user_role, substring(user_role, 1, isnull(nullif(charindex('|', user_role), 0) - 1, len(user_role))) roles, nullif(charindex('|', user_role), 0) cindex, 1 L
    from @2
    union all
    select id, user_role, substring(user_role, cindex+1, isnull(nullif(charindex('|', user_role, cindex+1), 0) - 1 - cindex, len(user_role))), nullif(charindex('|', user_role, cindex+1), 0), L + 1
    from cte
    where cindex is not null)
select id, max(isnull(x.y, user_role)) adminrole
from cte t
outer apply (
    select min(roles) over (partition by len(roles))
    from cte
    where id = t.id
    and roles like '%admin%') x(y)
group by id, x.y
order by id;

...这与使用不同方法的另一个做同样的事情。

或者您可以使用类似于其他答案的光标,稍作修改。

但实际上,正常化!

答案 1 :(得分:-1)

这应该有效。它使用游标来处理每个角色。

---更新---

更新了光标处理以涵盖许多边缘情况。

DECLARE @roles varchar(1000), @ADMININDEX int, @STARTINDEX int, @ENDINDEX int, @TEMP varchar(1000)

DECLARE MY_CURSOR CURSOR 
  LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR 
SELECT roles
FROM test

OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO @roles
WHILE @@FETCH_STATUS = 0
BEGIN 
    --Do something with Id here
    SET @ADMININDEX = CHARINDEX('Admin',@roles)
    IF (@ADMININDEX > 0)
    BEGIN
        SET @ADMININDEX += 5
        SET @TEMP = CHARINDEX('|', SUBSTRING(@roles, @ADMININDEX, LEN(@roles)))
        IF @TEMP = 0
            SET @ENDINDEX = LEN(@roles) + 1
        ELSE
            SET @ENDINDEX = @TEMP - 1 + @ADMININDEX
        SET @roles = SUBSTRING(@roles, 0, @ENDINDEX)
        SET @STARTINDEX = LEN(@roles) - CHARINDEX('|', REVERSE(@roles)) + 2
        IF (@STARTINDEX>@ADMININDEX)
            PRINT @roles
        ELSE
            PRINT SUBSTRING(@roles, @STARTINDEX, @ENDINDEX)
    END
    FETCH NEXT FROM MY_CURSOR INTO @roles
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR