[英]Split string into multiple rows and columns
我有一个类似于下面的数据集(有更多列,但仅将这些列用于示例目的)。
个人身份 | 位置 ID | 开始日期 | 出勤字符串 |
---|---|---|---|
123 | 987 | 2018-09-01 | XXXXZZZZ######PPLL |
234 | 678 | 2018-10-01 | PPPPLL######ZZZZXX |
我需要实现的是将AttendanceString列拆分为多行和多列。 考勤字符串需要每 2 个字符分解一次,并分成 2 个不同的列,分别代表一个上午和下午的时段。 一个例子会让这个更清楚,所以让我们使用第一条记录。 期望的结果是:
个人身份 | 位置 ID | 开始日期 | 早上出勤字符串 | 下午出勤字符串 |
---|---|---|---|---|
123 | 987 | 2018-09-01 | X | X |
123 | 987 | 2018-09-02 | X | X |
123 | 987 | 2018-09-03 | Z | Z |
123 | 987 | 2018-09-04 | Z | Z |
123 | 987 | 2018-09-05 | # | # |
对于每个字符串,我们需要迭代直到到达最后一个字符,将新记录添加到对应于不同日期的表中,并在早上/下午分别记录。
我可以使用本文末尾的代码实现所需的逻辑。 但是,因为这可能涉及大约 90/100k 条记录,并且其中大部分需要分解为 365 条记录,所以我们谈论的是需要创建 33-35M 条记录。 我正在使用带有一段时间周期的 cursor 来获得这些结果,即使通常要避免使用游标,我也不认为这是这里的问题。 这通常需要大约 30 分钟才能在 S6 标准层中的 Azure SQL 数据库上运行。
我是否缺少任何选项来提高效率? 理想情况下,我想减少处理数据所需的时间。 我不能真正使用 split_string 因为它需要一个特定的字符来破坏字符串。
DECLARE @LocationID as int;
DECLARE @AttendanceString as varchar(1000);
DECLARE @StartDate as date;
DECLARE @PersonId as int;
DECLARE @MorningValue as char(1);
DECLARE @AfternoonValue as char(1);
DECLARE @DayDate as date;
DECLARE @AttendanceCursor as cursor;
DECLARE @i as int;
SET @AttendanceCursor = CURSOR LOCAL FAST_FORWARD FOR
SELECT
PersonId,
LocationId,
StartDate,
AttendanceString
FROM
SourceTable
WHERE
StartDate >= '2019-08-01'
BEGIN
SET NOCOUNT ON
OPEN @AttendanceCursor
FETCH NEXT FROM @AttendanceCursor INTO @PersonId , @AttendanceString, @StartDate, @LocationId;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @i = 1;
SET @DayDate = @StartDate;
WHILE (@i < len(@AttendanceString))
BEGIN
SET @MorningValue = SUBSTRING(@AttendanceString,@i,1);
SET @AfternoonValue = SUBSTRING(@AttendanceString,@i+1,1);
BEGIN TRY
INSERT INTO FinalTable
SELECT @DayDate , @LocationId, @PersonId, @MorningValue @AfternoonValue
END TRY
BEGIN CATCH
...
END CATCH
SET @i = @i+2;
SET @DayDate = DATEADD(DD,1,@DayDate );
END
FETCH NEXT FROM @AttendanceCursor INTO @PersonId , @AttendanceString, @StartDate, @LocationId;
END
CLOSE @AttendanceCursor ;
DEALLOCATE @AttendanceCursor ;
我将其重写为基于集合的查询,而不是Z1791A97A8403730EEE0760489A2AEB992Z和整个脚本,其中包括100K测试记录在我的Z3A580F14220367F1F1F1F0BC3089898989898989898987777777777777777777777777778787878787878787878787878787878787878787878787878787878787878787878787878787878777.77878787878787878787878787878. 通读脚本以确保您理解它。
注意我要放弃表格,因为这是一个测试台 - 不是生产代码:
------------------------------------------------------------------------------------------------
-- 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
------------------------------------------------------------------------------------------------
此处为更长的出勤ID 的脚本的更改版本。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.