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

Does Azure SQL Data Warehouse have a way to split strings?

经过一些研究,我发现在 Azure SQL 数据仓库中没有很好的拆分字符串的选项。它没有新的 STRING_SPLIT() 函数或 OPENJSON() 函数。它还不允许 SELECT 用户定义函数中的语句尝试创建您自己的语句,就像社区制作的许多自定义拆分器函数一样。

因此,我想我会提出以下问题:SQL Data Warehouse 有拆分字符串的方法吗?最好的选择是什么?

用例

您在 SQL table 中有一个值为 "My_Value_Is_Good" 的字段。 objective 是在 SELECT 语句中使用分隔符下划线将每个段拆分为单独的字段,或者至多写入新的 table.

我用过的解决方案

对我来说最主要的是在数据进入数据仓库之前对其进行转换。我使用 Python 来解析数据。然而,更大的数据集确实会减慢这种速度,并将其更多地隔离到系统中的特定记录中。

2019 年 7 月更新 - 根据 hereSTRING_SPLIT 现在可在 Azure SQL 数据仓库中使用。所以在我下面的例子中,代码会更像这样:

DECLARE @delimiter CHAR(1) = '-';

CREATE TABLE dbo.guids_split
WITH
(
    DISTRIBUTION = HASH(xguid),
    HEAP
)
AS
SELECT *
FROM dbo.guids g
    CROSS APPLY STRING_SPLIT ( xguid, @delimiter );
与普通 SQL 服务器或 Azure SQL 数据库相比,

Azure SQL 数据仓库的 T-SQL 表面积减少了。它没有任何花哨的技巧,例如 STRING_SPLIT、table 值函数、CLR、XML;甚至不允许游标。事实上,对于其中一篇关于该主题的热门文章(pre-SQL 2016)'Split strings the right way - or the next best way' 中的所有技术,您不能使用其中任何一个,除了数字table.

因此我们需要一些更具程序性的东西,避免任何类型的循环。我使用上面的文章作为灵感,使用了测试数据脚本的改编版本和 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 数据仓库中获得良好的性能实际上是通过良好的散列分布密钥最大限度地减少数据移动。因此请 post 一些实际的样本数据。

另一种选择是 Azure Data Lake Analytics。 ADLA 支持联合查询 "query data where it lives",因此您可以使用 U-SQL 查询原始 table,使用本机 .net 方法拆分它并输出一个 可以使用 Polybase 轻松导入。如果您需要有关此方法的更多帮助,请告诉我,我会举一个例子。

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

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