Sql 将字符串拆分为多行和多列

Sql 将字符串拆分为多行和多列,sql,sql-server,azure-sql-database,Sql,Sql Server,Azure Sql Database,我有一个类似于下面的数据集,有更多的列,但只是出于示例目的使用这些列 拟人 位置ID 起始日期 出席人数 123 987 2018-09-01 xxxxzzppll 234 678 2018-10-01 ppllzzxx 我将其重写为基于集合的查询,而不是游标,整个脚本(包括生成100k测试记录)在我的Azure SQL DB上运行约40秒,这是一个无服务器的第5代,只有1个vCore。仔细阅读脚本以确保您理解它 注意:我正在删除表格,因为这是一个试验台,而不是生产代码: -----------

我有一个类似于下面的数据集,有更多的列,但只是出于示例目的使用这些列

拟人 位置ID 起始日期 出席人数 123 987 2018-09-01 xxxxzzppll 234 678 2018-10-01 ppllzzxx
我将其重写为基于集合的查询,而不是游标,整个脚本(包括生成100k测试记录)在我的Azure SQL DB上运行约40秒,这是一个无服务器的第5代,只有1个vCore。仔细阅读脚本以确保您理解它

注意:我正在删除表格,因为这是一个试验台,而不是生产代码:

------------------------------------------------------------------------------------------------
-- Setup START
------------------------------------------------------------------------------------------------

DROP TABLE IF EXISTS dbo.sourceTable
DROP TABLE IF EXISTS dbo.finalTable
GO

CREATE TABLE dbo.sourceTable
(
    PersonId            INT IDENTITY PRIMARY KEY,
    LocationId          INT,
    StartDate           DATE,
    AttendanceString    VARCHAR(1000)
)
GO

CREATE TABLE dbo.finalTable
(
    DayDate         DATE, 
    LocationId      INT, 
    PersonId        INT, 
    MorningValue    CHAR(1), 
    AfternoonValue  CHAR(1)
)
GO

-- Generate some test data
SET IDENTITY_INSERT dbo.sourceTable ON

INSERT INTO dbo.sourceTable ( PersonId, LocationId, StartDate, AttendanceString )
VALUES
    ( 123, 987, '2018-09-01', 'XXXXZZZZ######PPLL' ),
    ( 234, 678, '2018-10-01', 'PPPPLL######ZZZZXX' ),
    ( 567, 999, '2018-10-01', 'abcdefghijklmnopqr' )

SET IDENTITY_INSERT dbo.sourceTable OFF
GO

-- Setup END
------------------------------------------------------------------------------------------------



------------------------------------------------------------------------------------------------
-- Test Data START
------------------------------------------------------------------------------------------------

;WITH cte AS (
SELECT 1 rn, 1 locationId, CAST( '1 Jan 2018' AS DATE ) startDate, REPLACE( NEWID(), '-', '' ) AttendanceString
UNION ALL
SELECT rn + 1, rn % 42, DATEADD( day, 1, startDate ), REPLACE( NEWID(), '-', '' )
FROM cte
WHERE rn < 100
)
INSERT INTO dbo.sourceTable ( LocationId, StartDate, AttendanceString )
SELECT LocationId, StartDate, AttendanceString 
FROM cte
ORDER BY 1;
GO 1000


-- Test Data END
------------------------------------------------------------------------------------------------


------------------------------------------------------------------------------------------------
-- Rewritten query START
------------------------------------------------------------------------------------------------





DROP TABLE IF EXISTS #tmp;

;WITH cte AS (
SELECT 1 n, 1 x, 2 y
UNION ALL
SELECT n + 1, x + 2, y + 2
FROM cte
WHERE n < 20
)
SELECT
    personId,
    locationId,
    DATEADD( day, c.n - 1, startDate ) xdate,
    SUBSTRING ( attendanceString, c.x, 1 ) a,
    SUBSTRING ( attendanceString, c.y, 1 ) b

INTO #tmp

FROM dbo.sourceTable s
    CROSS APPLY cte c
WHERE c.y <= LEN(attendanceString);


select *
from sourceTable
WHERE personId = 999

select *
from #tmp
WHERE personId = 999

select *
from #tmp
WHERE locationId = 999

-- Rewritten query END
------------------------------------------------------------------------------------------------

修改了脚本的版本以延长访问时间。

我将其改写为基于集合的查询,而不是游标,整个脚本(包括生成100k测试记录)在我的Azure SQL DB上运行约40秒,这是一个无服务器的第5代,只有1个vCore。仔细阅读脚本以确保您理解它

注意:我正在删除表格,因为这是一个试验台,而不是生产代码:

------------------------------------------------------------------------------------------------
-- Setup START
------------------------------------------------------------------------------------------------

DROP TABLE IF EXISTS dbo.sourceTable
DROP TABLE IF EXISTS dbo.finalTable
GO

CREATE TABLE dbo.sourceTable
(
    PersonId            INT IDENTITY PRIMARY KEY,
    LocationId          INT,
    StartDate           DATE,
    AttendanceString    VARCHAR(1000)
)
GO

CREATE TABLE dbo.finalTable
(
    DayDate         DATE, 
    LocationId      INT, 
    PersonId        INT, 
    MorningValue    CHAR(1), 
    AfternoonValue  CHAR(1)
)
GO

-- Generate some test data
SET IDENTITY_INSERT dbo.sourceTable ON

INSERT INTO dbo.sourceTable ( PersonId, LocationId, StartDate, AttendanceString )
VALUES
    ( 123, 987, '2018-09-01', 'XXXXZZZZ######PPLL' ),
    ( 234, 678, '2018-10-01', 'PPPPLL######ZZZZXX' ),
    ( 567, 999, '2018-10-01', 'abcdefghijklmnopqr' )

SET IDENTITY_INSERT dbo.sourceTable OFF
GO

-- Setup END
------------------------------------------------------------------------------------------------



------------------------------------------------------------------------------------------------
-- Test Data START
------------------------------------------------------------------------------------------------

;WITH cte AS (
SELECT 1 rn, 1 locationId, CAST( '1 Jan 2018' AS DATE ) startDate, REPLACE( NEWID(), '-', '' ) AttendanceString
UNION ALL
SELECT rn + 1, rn % 42, DATEADD( day, 1, startDate ), REPLACE( NEWID(), '-', '' )
FROM cte
WHERE rn < 100
)
INSERT INTO dbo.sourceTable ( LocationId, StartDate, AttendanceString )
SELECT LocationId, StartDate, AttendanceString 
FROM cte
ORDER BY 1;
GO 1000


-- Test Data END
------------------------------------------------------------------------------------------------


------------------------------------------------------------------------------------------------
-- Rewritten query START
------------------------------------------------------------------------------------------------





DROP TABLE IF EXISTS #tmp;

;WITH cte AS (
SELECT 1 n, 1 x, 2 y
UNION ALL
SELECT n + 1, x + 2, y + 2
FROM cte
WHERE n < 20
)
SELECT
    personId,
    locationId,
    DATEADD( day, c.n - 1, startDate ) xdate,
    SUBSTRING ( attendanceString, c.x, 1 ) a,
    SUBSTRING ( attendanceString, c.y, 1 ) b

INTO #tmp

FROM dbo.sourceTable s
    CROSS APPLY cte c
WHERE c.y <= LEN(attendanceString);


select *
from sourceTable
WHERE personId = 999

select *
from #tmp
WHERE personId = 999

select *
from #tmp
WHERE locationId = 999

-- Rewritten query END
------------------------------------------------------------------------------------------------

修改脚本的版本以获得更长的AttendanceId。

AttendanceString的长度始终是固定的还是可变的?游标解决方案在同一台服务器上仅用1小时26分钟就完成了。选择…进入在我的服务器上2秒钟内完成。这对我来说似乎是一个重大的改进。它不是固定的,它可以有不同的长度,通常大约730个字符是AttendanceString的长度总是固定的还是可变的?光标解决方案在同一台服务器上仅用1小时26分钟就完成了。选择…进入在我的服务器上2秒钟内完成。这对我来说似乎是一个重大的改进。它不是固定的,它可以有不同的长度,通常大约730个字符。对于这个wBob,我理解你在这里做了什么,以及你为什么问字符串的长度。将需要调整cte中生成的数字的数量,因为它们稍微大一点,但将使其进行尝试并更新,其结果与charm wBob一样,在2m30s内处理3400万行。如果我把它和我所拥有的相比,我会羞愧地低下头!好东西。我已经添加了一个经过修改的测试设备,它将考勤字符串的长度扩展到960,并拆分了映射表的创建,我认为这是一个好主意。请进行一些测试,以确保新解决方案在功能上与旧解决方案相同。至少你有新的和旧的结果要做比较。谢谢,这就是我回来之前测试的结果。这比我所拥有的有了巨大的进步,正如我所说的,考虑到我最初的做法,这让我看起来很糟糕:还不能提高投票率,需要更多的声望点来做到这一点。将大约93k条记录转换为大约33-34M条记录的原始运行时间大约需要30分钟。第一种方法大约需要2米/2米30秒,如果分割贴图表的创建,它将下降到1米30秒。对于这个wBob,我理解您在这里做了什么,以及为什么您要询问字符串的长度。将需要调整cte中生成的数字的数量,因为它们稍微大一点,但将使其进行尝试并更新,其结果与charm wBob一样,在2m30s内处理3400万行。如果我把它和我所拥有的相比,我会羞愧地低下头!好东西。我已经添加了一个经过修改的测试设备,它将考勤字符串的长度扩展到960,并拆分了映射表的创建,我认为这是一个好主意。请进行一些测试,以确保新解决方案在功能上与旧解决方案相同。至少你有新的和旧的结果要做比较。谢谢,这就是我回来之前测试的结果。这比我所拥有的有了巨大的进步,正如我所说的,考虑到我最初的做法,这让我看起来很糟糕:还不能提高投票率,需要更多的声望点来做到这一点。将大约93k条记录转换为大约33-34M条记录的原始运行时间大约需要30分钟。第一种方法大约需要2米/2米30秒,如果分割地图表的创建,它将下降到1米30秒