逗号分隔数据库字段中的值

6 sql t-sql database stored-procedures

我有一张产品表.该表中的每一行对应一个产品,并由唯一的Id标识.现在每个产品可以有多个与该产品相关的"代码".例如:

Id     |    Code
----------------------
0001   |   IN,ON,ME,OH
0002   |   ON,VI,AC,ZO
0003   |   QA,PS,OO,ME

我要做的是创建一个存储过程,以便我可以传入像"ON,ME"这样的代码,让它返回包含"ON"或"ME"代码的每个产品.由于代码以逗号分隔,我不知道如何拆分它们并搜索它们.这只能使用TSQL吗?

编辑:这是一个关键任务表.我无权改变它.

Jas*_*yne 11

您应该将代码存储在单独的表中,因为您有多对多的关系.如果你将它们分开,那么你将很容易检查.

可以使用您现在拥有的系统类型,但需要对列进行文本搜索,每行需要多次搜索,这会在数据增长时出现巨大的性能问题.

如果您尝试关闭当前路径:您必须拆分输入字符串,因为没有什么能保证每条记录上的代码与输入参数的顺序相同(或连续).然后你必须做一个

Code LIKE '%IN%'
AND Code Like '%QA%'
Run Code Online (Sandbox Code Playgroud)

查询每个要检查的代码的附加语句.非常低效.

下面的UDF想法也是一个好主意.但是,根据您的数据大小以及查询和更新的频率,您可能也会遇到问题.

是否可以创建一个规范化的附加表,该表在计划的基础上(或基于触发器)进行同步以供您查询?


Ira*_*omo 8

首先,让我们使原始表变为这样:


Id   | Value
-----+------
0001 | IN
0001 | ME
0001 | OH
0001 | ON
0002 | AC
0002 | ON
0002 | VI
0002 | ZO
0003 | ME
0003 | OO
0003 | PS
0003 | QA
Run Code Online (Sandbox Code Playgroud)

它通过将逗号分隔值解析为行来完成.然后使用强大的CROSS APPLY关键字与原始表连接以检索它的Id.下一步就是查询此CTE.


create function FnSplitToTable
(
    @param nvarchar(4000)
)
returns table as
return
    with
    Num(Pos) as -- list of positions, numbered from 1 to 4000, largest nvarchar
    (
        select cast(1 as int)
        union all 
        select cast(Pos + 1 as int) from Num where Pos < 4000
    )
    select substring(@Param, Pos, 
        charindex(',', @Param + ',', Pos) - Pos) as Value
        from Num where Pos <= convert(int, len(@Param)) 
        and substring(',' + @Param, Pos, 1) = ','
go


create proc ProcGetProductId
(
    @Codes nvarchar(4000)
)
as
with
Src
(
    Id,
    Code
)
as
(
    select '0001', 'IN,ON,ME,OH'
    union all
    select '0002', 'ON,VI,AC,ZO'
    union all
    select '0003', 'QA,PS,OO,ME'
),
Parse as
(
    select 
        s.Id, 
        f.Value
    from 
        Src as s
    cross apply
        FnSplitToTable(s.Code) as f 
)
select distinct 
    p.Id
from 
    Parse as p
join
    FnSplitToTable(@Codes) as f
on
    p.Value = f.Value
option (maxrecursion 4000)
go

exec ProcGetProductId 'IN,ME' -- returns 0001 & 0003
Run Code Online (Sandbox Code Playgroud)

  • 在我发布的所有帖子中,您是第一个发表评论的人.谢谢!:) (2认同)
  • 你以后会很高兴你做到了这一点 (2认同)

ang*_*son 7

其他人似乎非常渴望告诉你,你不应该这样做,虽然我没有看到任何明确的解释为什么不.

除了违反规范化规则之外,原因是您将对所有行进行表扫描,因为您不能在该列中的单个"值"上有索引.

简单地说,数据库引擎无法保留哪些行包含代码"AC"的某种快速列表,除非您将其分解为单独的表,或者将其单独放入列中.

现在,如果你在你的SELECT语句的其他标准,将行数限制到一些管理的数量,那么也许这将是好的,但除此之外,我想,如果可以的话,尽量避免这种情况的解决方案,并做别人有已经告诉过你,把它分成一个单独的表.

现在,如果您坚持使用此设计,则可以使用以下类型的查询进行搜索:

...
WHERE ',' + Code + ',' LIKE '%,AC,%'
Run Code Online (Sandbox Code Playgroud)

这将:

  • 匹配'ON,VI,AC,ZO'
  • 不匹配'ON,VI,TAC,ZO'

我不知道在你的情况下最后一个是否是可行的选项,如果你只有2个字母的代码,那么你可以使用这个:

...
WHERE Code LIKE '%AC%'
Run Code Online (Sandbox Code Playgroud)

但同样,除非您使用其他标准限制行数,否则这将执行得非常糟糕.


Cha*_*ana 5

尽管所有以前的海报是关于你的数据库模式的规范化正确,你可以做你想做什么使用的字符串中需要一个分隔字符串,并返回一个表,其中每行值"表值UDF" ...您可以像使用存储过程中的任何其他表一样使用此表,加入它等...这将解决您的直接问题...

这是这样一个UDF的链接: FN_Split UDF

虽然本文讨论了如何使用它将数据值的分隔列表传递给存储过程,但您可以使用相同的UDF对存储在现有表的列中的分隔字符串进行操作....


Meh*_*ari 4

您存储数据的方式违反了规范化规则。每个字段中只应存储一个原子值。您应该将每个项目存储在一行中。