Azure SQL数据仓库是否有分割字符串的方法?

时间:2017-08-11 12:36:42

标签: sql-server azure-sql-database data-warehouse

进行一些研究,我发现在Azure SQL数据仓库中拆分字符串没有很好的选择。它没有新的STRING_SPLIT()函数或OPENJSON()函数。它也不允许用户定义函数中的SELECT语句尝试创建自己的,就像社区所做的许多自定义拆分器功能一样。

因此,我想我会提出一些问题:SQL数据仓库是否有分割字符串的方法以及最佳选择?

使用案例

SQL表中有一个字段,其值为" My_Value_Is_Good"。目标是在SELECT语句中使用分隔符下划线将每个段拆分为单独的字段,或者最多写入新表。

我使用的解决方案

对我来说,主要的是在数据进入数据仓库之前转换数据。我使用Python来解析数据。但是,更大的数据集会降低速度,并将其更多地分离到系统中的特定记录中。

1 个答案:

答案 0 :(得分:5)

与普通SQL Server或Azure SQL数据库相比,Azure SQL数据仓库的T-SQL表面积减少了。它没有任何花哨的技巧,如STRING_SPLIT,表值函数,CLR,XML;甚至不允许使用游标。实际上,对于关于此主题的一篇文章(pre-SQL 2016)“Split strings the right way - or the next best way”中的所有技术,除数字表外,您不能使用其中任何一种技术。

因此,我们需要更多程序性的东西,避免任何类型的循环。我使用上面的文章来获取灵感,使用了测试数据脚本的改编版本和this approach

-- Create one million guids
IF OBJECT_ID('dbo.numbers') IS NOT NULL DROP TABLE dbo.numbers
IF OBJECT_ID('dbo.guids_split') IS NOT NULL DROP TABLE dbo.guids_split
IF OBJECT_ID('dbo.guids') IS NOT NULL DROP TABLE dbo.guids
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL DROP TABLE #tmp
GO


CREATE TABLE dbo.Numbers (
    Number  INT NOT NULL
)
WITH
(
    DISTRIBUTION = ROUND_ROBIN,     --!!TODO try distibuting?
    CLUSTERED INDEX ( Number )
)
GO


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 x
INTO #tmp
FROM n
WHERE x BETWEEN 1 AND @UpperLimit
GO

INSERT INTO dbo.Numbers ( Number )
SELECT x
FROM #tmp
GO


CREATE TABLE dbo.guids (
    rn  INT IDENTITY,
    xguid   CHAR(36) NOT NULL
)
WITH
(
    DISTRIBUTION = HASH(xguid),
    CLUSTERED COLUMNSTORE INDEX
)
GO

INSERT INTO dbo.guids ( xguid )
SELECT NEWID() xguid
FROM dbo.Numbers
GO -- 10    -- scale up 10 to 100, 1,000 etc

ALTER INDEX ALL ON dbo.guids REBUILD 
GO


-- Create the stats
CREATE STATISTICS _st_numbers_number ON dbo.numbers (number);
CREATE STATISTICS _st_guids_rn ON dbo.guids (rn);
CREATE STATISTICS _st_guids_xguid ON dbo.guids (xguid);
GO
-- multi-col stat?
:exit


-- NB The length of the guid; so we don't have to use VARCHAR(MAX)
DECLARE @delimiter VARCHAR(1) = '-';

CREATE TABLE dbo.guids_split
WITH
(
    DISTRIBUTION = HASH(xguid),
    HEAP
)
AS
SELECT
    s.rn,
    n.Number n,
    originalid AS xguid,
    LTRIM( RTRIM( SUBSTRING( s.xguid, n.Number + 1, CHARINDEX( @delimiter, s.xguid, n.Number + 1 ) - n.Number - 1 ) ) ) AS split_value
FROM (
    SELECT
        rn,
        xguid AS originalid,
        CAST( CAST( @delimiter AS VARCHAR(38) ) + CAST( xguid AS VARCHAR(38) ) + CAST( @delimiter AS VARCHAR(38) ) AS VARCHAR(38) ) AS xguid
        FROM dbo.guids
        ) s
    CROSS JOIN dbo.Numbers n
WHERE n.Number < LEN( s.xguid )
  AND SUBSTRING( s.xguid, n.Number, 1 ) = @delimiter;
GO


/*
SELECT TOP 10 * FROM dbo.guids ORDER BY rn;

SELECT *
FROM dbo.guids_split
WHERE rn In ( SELECT TOP 10 rn FROM dbo.guids ORDER BY rn )
ORDER BY 1, 2;
GO

*/

该脚本现在在ADW上进行了测试,并且令人满意地工作了超过1亿条记录。这只用了4分钟,只有DWU 400(至少一次我添加了统计数据并删除了varchar(max) :)。然而,guids是一个略微人为的例子,因为数据大小一致,并且总是只有5个部分要分开。

从Azure SQL数据仓库中获得良好的性能实际上与通过良好的哈希分发密钥最小化数据移动有关。因此,请发布一些实际的样本数据。

另一种选择是Azure Data Lake Analytics。 ADLA支持联合查询“查询它所在的数据”,因此您可以使用U-SQL查询原始表,使用本机.net方法将其拆分并输出 可以使用Polybase轻松导入。如果您需要更多关于此方法的帮助,请告诉我,我会举一个例子。

SQLCat团队已经发布了关于SQL数据仓库的反模式的这篇文章,这种类型的字符串处理可能被认为是一个例子。请阅读这篇文章:

https://blogs.msdn.microsoft.com/sqlcat/2017/09/05/azure-sql-data-warehouse-workload-patterns-and-anti-patterns/