T SQL Tablosu Değerli virgül üzerinde bir sütun bölme işlevi


10

Her değer için ayrı satırlar tükürmek için bir veritabanında virgülle ayrılmış bir sütun almak için Microsoft SQL Server 2008'de bir Tablo Değerli İşlev yazdı.

Örn: "bir, iki, üç, dört", yalnızca bir sütun aşağıdaki değerleri içeren yeni bir tablo döndürür:

one
two
three
four

Bu kod size eğilimli bir hata mı görünüyor? İle test ettiğimde

SELECT * FROM utvf_Split('one,two,three,four',',') 

sadece sonsuza dek çalışır ve hiçbir şey döndürmez. Özellikle MSSQL sunucusunda (NEDEN NEDEN ?!) bölünmüş işlevler bulunmadığından ve web'de bulduğum tüm benzer işlevlerin mutlak çöp olduğu veya yapmaya çalıştığım şeyle alakasız olduğu için bu gerçekten cesaret kırıcı oluyor. .

İşte işlevi:

USE *myDBname*
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[utvf_SPlit] (@String VARCHAR(MAX), @delimiter CHAR)

RETURNS @SplitValues TABLE
(
    Asset_ID VARCHAR(MAX) NOT NULL
)

AS
BEGIN
            DECLARE @FoundIndex INT
            DECLARE @ReturnValue VARCHAR(MAX)

            SET @FoundIndex = CHARINDEX(@delimiter, @String)

            WHILE (@FoundIndex <> 0)
            BEGIN
                  DECLARE @NextFoundIndex INT
                  SET @NextFoundIndex = CHARINDEX(@delimiter, @String, @FoundIndex+1)
                  SET @ReturnValue = SUBSTRING(@String, @FoundIndex,@NextFoundIndex-@FoundIndex)
                  SET @FoundIndex = CHARINDEX(@delimiter, @String)
                  INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
            END

            RETURN
END

Yanıtlar:


1

Biraz daha çalıştı ...

DECLARE @FoundIndex INT
DECLARE @ReturnValue VARCHAR(MAX)

SET @FoundIndex = CHARINDEX(@delimiter, @String)

WHILE (@FoundIndex <> 0)
BEGIN
      SET @ReturnValue = SUBSTRING(@String, 0, @FoundIndex)
      INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
      SET @String = SUBSTRING(@String, @FoundIndex + 1, len(@String) - @FoundIndex)
      SET @FoundIndex = CHARINDEX(@delimiter, @String)
END

INSERT @SplitValues (Asset_ID) VALUES (@String)

RETURN

20

Bunu bir döngü ile yapmazdım; çok daha iyi alternatifler var. Ne zaman iyi Uzak, sahip bölünmeye, CLR ve Adam Machanic yaklaşımı Ive'sınav en hızlı .

Bir sonraki en iyi yaklaşım IMHO, CLR'yi uygulayamıyorsanız, bir sayılar tablosu:

SET NOCOUNT ON;

DECLARE @UpperLimit INT = 1000000;

WITH n AS
(
    SELECT
        x = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM       sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    CROSS JOIN sys.all_objects AS s3
)
SELECT Number = x
  INTO dbo.Numbers
  FROM n
  WHERE x BETWEEN 1 AND @UpperLimit
  OPTION (MAXDOP 1); -- protecting from Paul White's observation

GO
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(Number) 
    --WITH (DATA_COMPRESSION = PAGE);
GO

... bu fonksiyona izin verir:

CREATE FUNCTION dbo.SplitStrings_Numbers
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN
   (
       SELECT Item = SUBSTRING(@List, Number, 
         CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)
       FROM dbo.Numbers
       WHERE Number <= CONVERT(INT, LEN(@List))
         AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
   );
GO

Ben bunların hepsi, sahip fonksiyonun daha iyi performans göstereceği inanıyoruz zaman onlar satır içi yerine çoklu ifadenin özellikle beri, çalışıp çalışmadıklarını olsun. Sizinkinin neden çalışmadığını araştırmadım, çünkü bu işlevi çalıştırmanın buna değeceğini düşünmüyorum.

Ama hepsi dedi ki ...

SQL Server 2008 kullandığınızdan, ilk etapta bölmeniz gereken bir neden var mı? Bunun için bir TVP kullanmayı tercih ederim:

CREATE TYPE dbo.strings AS TABLE
(
  string NVARCHAR(4000)
);

Şimdi bunu saklı yordamlarınız için bir parametre olarak kabul edebilir ve içeriği bir TVF'de kullandığınız gibi kullanabilirsiniz:

CREATE PROCEDURE dbo.foo
  @strings dbo.strings READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT Asset_ID = string FROM @strings;
  -- SELECT Asset_ID FROM dbo.utvf_split(@other_param, ',');
END

Ve bir TVP'yi DataTable olarak doğrudan C # vb. Bu, özellikle uygulamanızda virgülle ayrılmış bir dize oluşturuyorsanız, özellikle saklanan prosedürünüzün tekrar ayrılması için bir TVP'yi çağırabilmesi için, yukarıdaki çözümlerden herhangi birinden kesinlikle daha iyi performans gösterecektir. TVP'ler hakkında daha fazla bilgi için Erland Sommarskog'un harika makalesine bakın .

Daha yakın zamanda, dizeleri bölme hakkında bir dizi yazdım:

SQL Server 2016 veya daha yenisini (veya Azure SQL Veritabanı) kullanıyorsanız, burada blogladığım yeni bir STRING_SPLITişlev var:


7

SQL Server 2016, STRING_SPLIT () işlevini tanıttı . İki parametreye sahiptir - kesilecek dize ve ayırıcı. Çıktı, döndürülen değer başına bir satırdır.

Verilen örnek için

SELECT * FROM string_split('one,two,three,four', ',');

geri dönücek

value
------------------
one
two
three
four

1

Jeff Moden'in dize ayırıcısını çıktığı günden beri kullanıyorum ve sevdim.

Tally OH! Geliştirilmiş SQL 8K “CSV Splitter” İşlevi

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  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
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;

-2
CREATE FUNCTION [dbo].[fnSplit]
(

    @sInputList VARCHAR(8000),         -- List of delimited items

    @sDelimiter VARCHAR(8000) = ','    -- delimiter that separates items

)
RETURNS @List TABLE (colData VARCHAR(8000))

BEGIN

DECLARE @sItem VARCHAR(8000)

    WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0

    BEGIN

        SELECT @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX
(@sDelimiter,@sInputList,0)-1))),

        @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)
+LEN(@sDelimiter),LEN(@sInputList))))

        IF LEN(@sItem) > 0
            INSERT INTO @List SELECT @sItem
        END

        IF LEN(@sInputList) > 0
            INSERT INTO @List SELECT @sInputList -- Put the last item in
        RETURN
    END

--TEST

--Example 1: select * from fnSplit('1,22,333,444,,5555,666', ',')

--Example 2: select * from fnSplit('1##22#333##444','##')  --note second colData has embedded #

--Example 3: select * from fnSplit('1 22 333 444  5555 666', ' ')

resim açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.