【问题标题】:Why (and how) to split column using master..spt_values?为什么(以及如何)使用 master..spt_values 拆分列?
【发布时间】:2011-05-15 12:14:56
【问题描述】:

对问题"Split one column into multiple rows" 的答案提出质疑,我在此将其改写为 [1]。

Type = 'P' 的(含义)是什么?为什么要使用未记录的 master..spt_values 来拆分列?它有什么好处?


[1]

CREATE TABLE dbo.Table1 
(
    Col1        CHAR(1),
    Col2        CHAR(1),
    Col3        CHAR(1),
    Col4        VARCHAR(50)
)
GO

INSERT INTO dbo.Table1 VALUES ('A','B','C','1,2,3')
GO
INSERT INTO dbo.Table1 VALUES ('D','E','F','6,7,8,9')
GO


SELECT
    T.col1, RIGHT(LEFT(T.col4,Number-1),
    CHARINDEX(',',REVERSE(LEFT(','+T.col4,Number-1))))
FROM
    master..spt_values,
    table1 T
WHERE
    Type = 'P' AND Number BETWEEN 1 AND LEN(T.col4)+1 AND
    (SUBSTRING(T.col4,Number,1) = ','
    -- OR SUBSTRING(T.col4,Number,1)  = '') --this does not work correctly anyway

相关问题:

【问题讨论】:

    标签: sql sql-server tsql database-design


    【解决方案1】:

    在 TSQL 中拆分字符串的许多常见解决方案都需要一个数字列表;在这种情况下,有人正在使用 spt_values 表来提供它们。经检查,此查询返回 2048 个连续整数的列表:

    select number from master..spt_values where type = 'P'
    

    我假设原始查询编写器将 spt_values 用于整数,因为 a) 它“保证”可用,因此查询将始终有效,并且 b) 它避免了对获取整数的替代方法的冗长解释。

    主要的缺点是该表没有记录,因此使用它可能会造成混淆并且也有些风险(至少在原则上,升级或服务包可能会更改表数据或结构,甚至完全删除它)。

    有许多替代方法可以在不使用未记录的表格的情况下获取数字列表(我使用表值函数):

    SQL, Auxiliary table of numbers

    【讨论】:

    • 谢谢。我明白这一切。但是“P”是什么意思?
    【解决方案2】:

    目的

    为什么要使用无证 master..spt-values

    Sybase 以及它的混蛋 MS SQL 为产品提供了各种特性和功能,这些特性和功能在系统过程中实现(与作为服务启动的 sqlserver 之类的二进制文件相反)。这些系统过程过程是用 SQL 代码编写的,命名为sp_%.,除了一些秘密的内部结构外,它们与任何其他 SQL 代码具有相同的限制和需求。它们是 Sybase ASE 或 SQL Server 产品的一部分。因此,他们不是要求记录它;并且内部位不能被合理地标记为“未记录”。

    master..spt_values 在 SQL 表中包含所述系统过程所需的所有各种点点滴滴,以生成各种报告。 sp 表示系统程序; spt 表示系统过程的表;当然values 是内容。

    查找表

    Type = 'P'的(意义)是什么

    人们经常将spt_values 描述为“去规范化”,但这是不正确的术语。正确的术语是folded,或packed。它是 26 个左右的逻辑查找表,每一个都经过规范化处理,折叠成一个物理表,并带有一个 Type 列来区分逻辑表。

    现在在普通数据库中,这将是一个严重错误(只需查看“一个或多个查找表”的答案)。但在服务器目录中,它取代了 26 个物理表是可取的。

    • “L”代表LockType Lookup; “V”代表DeviceType Lookup(V是Device through the server的缩写);等。类型“P2”包含按位序数,用于扩展打包到 INT 中的位。

    • 需要一组已知范围内的连续数字,以 SQL 表的形式提供,以便执行许多系统过程必须执行的投影。类型“P”是 0 到 2047 之间的连续数字列表。

    • Projection 一词在这里被用作技术上精确的含义,自然逻辑含义,而不是关系代数含义,这是不自然的。

    因此,spt_values, 只有一个目的是包含 26 个折叠的,否则是单独的,参考表和一个投影表。

    扩展

    spt_values 的普通用法是作为普通的查找或引用或ENUM 表。一、Lookup值:

        SELECT *                    -- list Genders
            FROM Gender 
    

    它的使用方式与 Person 有一个需要扩展的 GenderCode 相同(非常扩展,这些怪异的日子):

        SELECT  P.*,                -- list Person
                G.Name              -- expand GenderCode to Name
            FROM Person P
            JOIN Gender G
                ON P.GenderCode = G.GenderCode
    

    例如。 sp_lock 生成活动锁报告,将锁类型显示为字符串 names。但是master..syslocks 包含锁类型为数字,它不包含那些名称;如果是这样,那将是一个严重的非规范化表!如果执行查询(Sybase ASE 代码,则必须转换):

        SELECT *                    -- list LockTypes
            FROM master..spt_values 
            WHERE type = "L"
    

    您会在 Lookup 表中注意到 66 个 LockType numbersnames。这允许sp_lock 执行简单的代码,例如上面的 Person::Gender:

        SELECT  spid,               -- list Active Locks
                DB_NAME(dbid),
                OBJECT_NAME(id, dbid),
                v.name,             -- expand lock name
                page,
                row
        FROM master..syslocks   L,
             master..spt_values LT
        WHERE L.type = LT.number    -- 
        AND   type = "L"            -- LockType Lookup table
        ORDER by 1, 2, 3, 4, 5, 6   -- such that perusal is easy
    

    投影

    Type = 'P' 的(意义)是什么?

    什么是投影,它是如何使用的?

    举例来说,您想要一个所有 66 个LockType 的列表,而不是上面查询产生的活动锁,显示活动锁(或Null)的数量。您不需要游标或WHILE 循环。我们可以投影 LockType 查找表,通过活动锁的计数:

        SELECT  LT.name,            -- list LockTypes
                [Count] = (         -- with count
            SELECT COUNT(*)
                FROM master..syslocks
                WHERE type = LT.number
                    )
            FROM master..spt_values LT
            WHERE type = "L"
    

    有几种方法,这只是一种。另一种方法是使用派生表而不是子查询。但是你仍然需要投影。

    这通常是 spt_values 用于扩展或投影。既然您知道它在那里,您也可以使用它。它是安全的(在master 数据库中)并且几乎被所有系统过程使用,这意味着系统过程没有它就无法运行。

    用于拆分列?

    啊,你不明白“将一个CSV列拆分为多行”的代码。

    • 暂时忘记spt_values,然后再次检查该代码。它只需要一个连续数字的列表,这样就可以逐字节地遍历 CSV 列中的值列表。代码仅针对逗号或字符串结尾的每个字节激活。

    • 从哪里获得一组以 SQL 表形式出现的连续数字,而不是从头开始创建一个并插入其中?为什么,master..spt_values 当然。如果你知道它就在那里。

    • (你可以通过阅读系统存储过程的代码来了解一下ASE或SQL Server的内部结构。)

    • 请注意,一列中的任何 CSV 字段都是严重的标准化错误,它会破坏 2NF(包含重复值)和 1NF(非原子)。请注意,它不是打包或折叠的,它是一个重复组,它是未标准化的。这种严重错误的许多负面后果之一是,必须使用复杂的代码来确定和提取非规范化 CSV 字段的内容,而不是使用简单的 SQL 将重复组导航为行。这里spt_values P 为该复杂代码提供了一个向量,使其更容易。

    这样做有什么好处?

    我想我已经回答过了。如果你没有它,每个需要数字列表的系统过程都必须创建一个临时表;并将行插入其中;在运行它的代码之前。当然,不必执行这些步骤,使系统过程更快。

    现在,当您需要执行投影时,例如。未来的日历日期或其他任何日期,您可以使用spt_values,而不必每次都创建自己的临时表(或创建自己的私有永久表并维护它)。

    【讨论】:

    • 这是一个更好的答案,它可能是我从使用过 Microsoft SQL 并希望与之关联的任何人那里读到的最好的答案之一。
    【解决方案3】:

    我知道这是一篇较旧的帖子,但我想我会添加一个更新。基于 Tally Table 和 cteTally table 的拆分器都有一个主要问题。他们使用连接的分隔符,当元素变宽并且字符串变长时,这会降低速度。

    我已经解决了这个问题并写了一篇关于它的文章,可以在以下 URL 中找到。 http://www.sqlservercentral.com/articles/Tally+Table/72993/

    新方法让 VARCHAR(8000) 的所有 While 循环、递归 CTE 和 XML 方法不再适用。

    我还要告诉你,一个名叫“Peter”的家伙甚至改进了该代码(在本文的讨论中)。这篇文章仍然很有趣,我将在接下来的一两天内使用 Peter 的增强功能更新附件。在我的主要改进和 Peter 所做的 tweek 之间,我不相信您会找到更快的 T-SQL-Only 解决方案来拆分 VARCHAR(8000)。我还解决了这种类型的 VARCHAR(MAX) 拆分器的问题,并且正在为此写一篇文章。

    【讨论】:

      【解决方案4】:

      现在一切正常

      SELECT T.col1, RIGHT(LEFT(T.col4,Number-1),CHARINDEX(',',REVERSE(LEFT(','+T.col4,Number-1))))
      FROM
          master..spt_values,
          table123 T
      WHERE
          Type = 'P' AND Number BETWEEN 1 AND LEN(T.col4)+1 AND
          (SUBSTRING(T.col4,Number,1) = ','
        OR SUBSTRING(T.col4,Number,1)  = '')
      

      【讨论】:

        【解决方案5】:

        现在在 SQL Server 2016 中,我们有了新功能 String_Split,我们可以使用它来拆分列。

        例如,这里是脚本:

        DECLARE @String NVARCHAR(1000) = 'abc,def,ghi,jkl,mno,pqr,stu,vw,xyz';
        SELECT * FROM STRING_SPLIT(@String,',');
        

        执行上述脚本后,会返回如下结果。

        嗯,就是这样。这是 Kathi 的resource,她比较了性能。

        【讨论】:

        • 这个问题具体是关于使用master..spt_values 来分割字符串。我确信在 SQL Server 中存在更多关于字符串拆分的通用问题,我认为这应该属于这个答案。 (然后,如果您想引起人们对新功能的关注,您可以在对此问题的评论中发布指向它的链接。)
        猜你喜欢
        • 1970-01-01
        • 2014-03-27
        • 2011-05-15
        • 1970-01-01
        • 1970-01-01
        • 2019-05-16
        • 2010-10-17
        • 2020-01-05
        • 2012-03-06
        相关资源
        最近更新 更多