Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sqlite/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 如何在SQL Server中拆分带分隔符的字符串而不创建函数?_Sql Server - Fatal编程技术网

Sql server 如何在SQL Server中拆分带分隔符的字符串而不创建函数?

Sql server 如何在SQL Server中拆分带分隔符的字符串而不创建函数?,sql-server,Sql Server,我正在使用SQL Server数据库。我有一个包含分隔列表的列,我需要编写一个查询,将列表的值拆分为行。通过浏览StackOverflow和其他网页,我知道这是一个常见问题。事实上,我在这里发现了一个广泛的分析: 不幸的是,我在该站点和其他地方看到的每个解决方案都需要我创建一个函数。这不是我的选择-我缺乏使用CREATE命令所需的特权 没有CREATE,我知道我可以使用PARSENAME函数,这要感谢Nathan Bedford at.: 但是,PARSENAME仅适用于包含4项或更少项的列表。

我正在使用SQL Server数据库。我有一个包含分隔列表的列,我需要编写一个查询,将列表的值拆分为行。通过浏览StackOverflow和其他网页,我知道这是一个常见问题。事实上,我在这里发现了一个广泛的分析:

不幸的是,我在该站点和其他地方看到的每个解决方案都需要我创建一个函数。这不是我的选择-我缺乏使用CREATE命令所需的特权

没有CREATE,我知道我可以使用PARSENAME函数,这要感谢Nathan Bedford at.:

但是,PARSENAME仅适用于包含4项或更少项的列表。因此,我的问题是:如何编写一个查询来拆分包含4个以上项目的分隔字符串,而不在数据库中创建新对象

编辑:


谢谢大家的快速回答。我可能遗漏了一些重要信息——我正在通过ODBC连接与数据库交互。除了CREATE语句外,似乎还有其他语句不起作用。例如,我似乎不能在一条语句中使用DECLARE来定义将在另一条语句中使用的变量。尽可能地,我必须将所有内容都放在一个SELECT语句中,尽管WITH似乎也适用于声明公共表。不幸的是,迄今为止提出的所有解决方案似乎都需要SELECT语句之外的变量声明,这是行不通的。请耐心听我说,我边走边学。

你看过数字表了吗


使用内置master..spt_值表的示例

DECLARE @String VARCHAR(1000)
    SELECT @String ='1,4,77,88,4546,234,2,3,54,87,9,6,4,36,6,9,9,6,4,4,68,9,0,5'

    SELECT SUBSTRING(',' + @String + ',', Number + 1,
    CHARINDEX(',', ',' + @String + ',', Number + 1) - Number -1)AS VALUE
    FROM master..spt_values
    WHERE Type = 'P'
    AND Number <= LEN(',' + @String + ',') - 1
    AND SUBSTRING(',' + @String + ',', Number, 1) = ','
    GO

更多信息,请参见此处:

我将只使用创建表的众多函数中的一个,而不是让它返回值,并将其放入表变量中。然后使用table变量。下面是一个返回表的示例

使用XML的版本

declare @S varchar(100) = 'Hello John Smith'

select 
  n.r.value('.', 'varchar(50)')
from (select cast('<r>'+replace(@S, ' ', '</r><r>')+'</r>' as xml)) as s(XMLCol)
  cross apply s.XMLCol.nodes('r') as n(r)
不使用变量拆分字符串“Hello John Smith”

select 
  n.r.value('.', 'varchar(50)')
from (select cast('<r>'+replace('Hello John Smith', ' ', '</r><r>')+'</r>' as xml)) as s(XMLCol)
  cross apply s.XMLCol.nodes('r') as n(r)

您可以使用递归CTE逐步提取一项

样本表

create table aTable(a int identity primary key, b int, c varchar(100))
insert aTable values (1, 'this is a test string')
insert aTable values (1, 'this is another test string')
insert aTable values (2, 'here is a test string to put the others to shame')
insert aTable values (4, '')
insert aTable values (5, null)
insert aTable values (5, '-the end- ')
询问

;with tmp(a, b, c, position, single) as (
select a, b,
    STUFF(c, 1, CHARINDEX(' ', c + ' .'), ''),
    1,
    convert(nvarchar(max),left(c, CHARINDEX(' ', c + ' .') -1))
from aTable
union all
select a, b,
    STUFF(c, 1, CHARINDEX(' ', c + ' .'), ''),
    position+1,
    convert(nvarchar(max),left(c, CHARINDEX(' ', c + ' .') -1))
from tmp
where c > '')
select a, b, single, position
from tmp
order by a, position
注:

这里的分隔符是单个空格,这是CHARINDEX搜索的内容。“.”中的点是必需的,因为SQL Server通常认为尾随空格不重要。 原始表的所有列都可以保留在CTE中,只需添加它们即可。这里我展示了一个输出中保留的2列a和b的示例,其中c列被拆分为单个列和一个额外列以指示位置。
使用UDF是最有意义的,它对所有项目都是灵活的,我通常在我的博客上使用它

因为它是用来接受输入字符串和分隔符的,所以它可以是分隔符的任何单个字符。我在上面的链接上写了一篇,但后来我发现许多网站发布了类似的解决方案,所以部分内容可能是从我所属的论坛中得到启发的

它是有效的,希望它也能对你有效

按照建议编辑2017-08-09,我已经克隆了下面的代码块。谢谢

CREATE FUNCTION udfListToTable (@HList VarChar(2000), @Delimiter CHAR(1))
RETURNS @ListTable TABLE (Field1 VARCHAR(6))
  AS
  BEGIN
  --By: Francisco Tapia
  --Date: 2/1/2005
  --Purpose: To convert a Comma delimited text to a Temp Variable table To help avoid dynamic sql
  -- Instead you can join the temp table or use it in your where clause if a field is IN the subquery
       DECLARE @FieldText as VarChar(6)

       IF RIGHT(RTRIM(@HLIST),1) <>@Delimiter
       SET @HList = @HList + @Delimiter

       WHILE CHARINDEX(@Delimiter, @HList) > 0
       BEGIN
            IF CHARINDEX(@Delimiter, @HList) > 0
            BEGIN
                 SELECT @FieldText =LEFT(@HList, CHARINDEX(@Delimiter, @HList)-1)
            END
       ELSE 
       BEGIN
            SELECT @FieldText = RTRIM(LTRIM(@HList))
       END
       --Insert into Variable Table
       INSERT INTO @ListTable(Field1)
       SELECT RTRIM(LTRIM(@FieldText))
       --Remove Item from list
       SELECT @HList = RIGHT(RTRIM(@HList), LEN(RTRIM(@HList)) -
            CHARINDEX(@Delimiter, @HList))
       END 
       RETURN
  END

对于这个问题的后来居上者,本文提供了各种选项的出色性能分析。考虑的一些选项包括从站点复制以供参考:

CLR

.Net代码位于

XML


事实证明,最好的性能来自于使用CLR函数,而XML解决方案也做得很好。几乎在所有情况下,使用数字表时,上述方法没有重复,导致性能最差。

对于sql Server>=2016,您可以使用字符串分割,如下所示:

SELECT * FROM string_split('Hello John Smith', ' ')
输出

+-------+
| value |
+-------+
| Hello |
| John  |
| Smith |
+-------+

-如果您对代码理解有疑问,请告诉我。这里有一个用户定义的解析函数,它支持SQL Server,该函数的执行方式与VB拆分函数类似。为互动杠杆设计;例如,解析从外部API调用的存储过程中的数据


顺便说一句,第一个使用XML的系统性能很差,而且相当大arrays@Johnny_D可以通过不强制转换查询而使用xml变量来改进。不会像使用数字表那样快。我不擅长此道,如果您能演示如何在不施法的情况下完成此道,我将不胜感激。@Johnny\u只需在我最后的评论中点击SQL FIDLE链接即可。@MikaelEriksson如果有SQL FIDLE链接,请您更新一下。如果没有,请不要介意,谢谢你的回答。小心点。当我试图警告那篇文章的作者时,他对所有10000行测试数据使用了重复数据。这是现实生活中通常不会发生的事情,它使测试结果偏向于实际上非常缓慢的XML拆分器,顺便说一句,不处理XML特殊字符。如果有delimeter,则将其拆分为3个部分;如果没有,则仅在单个部分中。OP明确表示,他/她希望拆分文本,而不使用用户函数。
CREATE ASSEMBLY CLRUtilities FROM 'c:\DLLs\CLRUtilities.dll' 
  WITH PERMISSION_SET = SAFE;
GO

CREATE FUNCTION dbo.SplitStrings_CLR
(
   @List      NVARCHAR(MAX),
   @Delimiter NVARCHAR(255)
)
RETURNS TABLE ( Item NVARCHAR(4000) )
EXTERNAL NAME CLRUtilities.UserDefinedFunctions.SplitString_Multi;
GO
CREATE FUNCTION dbo.SplitStrings_XML
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO
CREATE FUNCTION dbo.SplitStrings_CTE
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS @Items TABLE (Item NVARCHAR(4000))
WITH SCHEMABINDING
AS
BEGIN
   DECLARE @ll INT = LEN(@List) + 1, @ld INT = LEN(@Delimiter);

   WITH a AS
   (
       SELECT
           [start] = 1,
           [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, @ld), 0), @ll),
           [value] = SUBSTRING(@List, 1, 
                     COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, @ld), 0), @ll) - 1)
       UNION ALL
       SELECT
           [start] = CONVERT(INT, [end]) + @ld,
           [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, [end] + @ld), 0), @ll),
           [value] = SUBSTRING(@List, [end] + @ld, 
                     COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, [end] + @ld), 0), @ll)-[end]-@ld)
       FROM a
       WHERE [end] < @ll
   )
   INSERT @Items SELECT [value]
   FROM a
   WHERE LEN([value]) > 0
   OPTION (MAXRECURSION 0);

   RETURN;
END
GO
CREATE FUNCTION dbo.SplitStrings_Moden
(
   @List NVARCHAR(MAX),
   @Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
  WITH E1(N)        AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                         UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                         UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
       E2(N)        AS (SELECT 1 FROM E1 a, E1 b),
       E4(N)        AS (SELECT 1 FROM E2 a, E2 b),
       E42(N)       AS (SELECT 1 FROM E4 a, E2 b),
       cteTally(N)  AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1))) 
                         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
       cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                         WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0))
  SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000))
    FROM cteStart s;
SELECT * FROM string_split('Hello John Smith', ' ')
+-------+
| value |
+-------+
| Hello |
| John  |
| Smith |
+-------+
 DECLARE @cols  AS NVARCHAR(max), 
        @Val   VARCHAR(100)='Hi- Hello break this-Wall', 
        @Deli  VARCHAR(50)='-', 
        @query AS NVARCHAR(max) 

SELECT @cols = Stuff((SELECT ',' + Quotename(id) 
                      FROM   (SELECT stringpieceid AS ID, 
                                     stringpiece 
                              FROM 
                     [Utility].[dbo].[Splitstringtotable](@Val, @Deli)) 
                             X 
                      FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, 
               '') 

SELECT @query = 
'SELECT * FROM (SELECT StringPieceID as ID,StringPiece from [Utility].[dbo].[SplitStringToTable](''' 
         + @Val + ''',''' + @Deli + '''))X PIVOT  (     MAX(StringPiece)     for [ID] in (' + @cols 
         + ') ) P' 

PRINT @query 

EXEC Sp_executesql 
  @query 
USE TRIAL
GO

CREATE TABLE DETAILS
(
ID INT,
NAME VARCHAR(50),
ADDRESS VARCHAR(50)
)

INSERT INTO DETAILS
VALUES (100, 'POPE-JOHN-PAUL','VATICAN CIT|ROME|ITALY')
,(240, 'SIR-PAUL-McARTNEY','NEWYORK CITY|NEWYORK|USA')
,(460,'BARRACK-HUSSEIN-OBAMA','WHITE HOUSE|WASHINGTON|USA')
,(700, 'PRESIDENT-VLADAMIR-PUTIN','RED SQUARE|MOSCOW|RUSSIA')
,(950, 'NARENDRA-DAMODARDAS-MODI','10 JANPATH|NEW DELHI|INDIA')

select [ID]
,[NAME]
,[ADDRESS]
,REPLACE(LEFT(NAME, CHARINDEX('-', NAME)),'-',' ') as First_Name
,CASE 
WHEN CHARINDEX('-',REVERSE(NAME))+ CHARINDEX('-',NAME) < LEN(NAME)
THEN  SUBSTRING(NAME, CHARINDEX('-', (NAME)) + 1, LEN(NAME) - CHARINDEX('-' 
, REVERSE(NAME)) - CHARINDEX('-', NAME))
  ELSE 'NULL
  END AS Middle_Name
,REPLACE(REVERSE( SUBSTRING( REVERSE(NAME), 1, CHARINDEX('- 
',REVERSE(NAME)))), '-','') AS Last_Name 
,REPLACE(LEFT(ADDRESS, CHARINDEX('|', ADDRESS)),'|',' ') AS Locality
,CASE 
    WHEN CHARINDEX('|',REVERSE(ADDRESS))+ CHARINDEX('|',ADDRESS) < 
  LEN(ADDRESS) 
 THEN SUBSTRING(ADDRESS, CHARINDEX('|', (ADDRESS))+1, LEN(ADDRESS)- 
CHARINDEX('|', REVERSE(ADDRESS))-CHARINDEX('|',ADDRESS))
  ELSE 'Null' 
END AS STATE
,REPLACE(REVERSE(SUBSTRING(REVERSE(ADDRESS),1 
,CHARINDEX('|',REVERSE(ADDRESS)))),'|','') AS Country
 FROM DETAILS
  SELECT CHARINDEX('-', REVERSE(NAME)) AS LAST,CHARINDEX('-',NAME)AS FIRST, 
 LEN(NAME) AS LENGTH
 FROM DETAILS