我不是 SQL 专家,我正在编写一个庞大的 SQL 代码,不幸的是,它有一个 CURSOR 处理其中的另外两个嵌套 CURSORS(存储过程中总共三个游标),它处理数百万要删除的数据,更新和插入。
由于逐行执行,这需要大量时间(超过 13 小时并最终得到错误“光标名称数据集已存在”),我正在尝试将其修改为基于 SET 的方法,但我被卡住了在转换为基于 SET 的方法时执行 INSERT、DELETE 和 UPDATE。或者换句话说,我不能通过在 JOINS 中替换它们来完成所有的 INSERT、DELETE 和 UPDATE。
我知道这是一个大代码,可能不适合在这里发布,但我已经以一种简单的方式将它放在另一篇帖子中,人们不明白我的问题,所以我把实际代码放在这里。
这是我正在处理的代码:
ALTER PROCEDURE [dbo].[proc_Upload] WITH RECOMPILE
as
set NoCount on
DECLARE
@StartTime datetime,
@EndTime datetime,
@DataID uniqueidentifier,
@CollectionDate datetime,
@Status int,
@PeriodID int,
@EndDate datetime,
@GroupID uniqueidentifier,
@ProjectID INT,
@FAID uniqueidentifier,
@UploadID int ,
@Createdate datetime
declare @projects table(
[ProjectID] [INT],
[Title] [varchar](255) ,
[currency] [int] ,
[Cost_Until_Today] [float] ,
[StartDate] [datetime] ,
[EndDate] [datetime] ,
[MisID] [uniqueidentifier] ,
[SystemStatus] [int] ,
[FacilitatingAgency] [uniqueidentifier] ,
[SyncMode] [int] )
INSERT @projects
SELECT ID, PROJECTNAME, PROJECTCURRENCY_ID, Cost_Until_Today, PROJECTESTABLISHEDDATE, EndDate, MisID, 4, FacilitatingAgency, SyncMode
FROM [dbo].[T_PROJECTS] /*thsi is the source table where every projectIDs need to be processed*/
IF exists ( select *
from dbo.sysobjects
where id = object_id(N'[dbo].[datasets]') and objectproperty(id, N'IsTable') = 1 )
DROP Table [dbo].datasets
SELECT mgd.Gd_ID, mg.Grp_ID, mgd.Gd_CollectionDate, mgd.Gd_IsDeleted, mgd.Gd_CreateDate, mg.Grp_Project, mg.Grp_IsDeleted , mg.Grp_Legacy_ID, p.LegacyProjectId
INTO datasets
FROM Savix_Service_Group..Group_Data mgd
INNER JOIN Savix_Service_Group..Group_Cycle mgc ON mgc.Gc_ID = mgd.Gd_CycleID
INNER JOIN Savix_Service_Group..Groups mg ON mg.Grp_ID = mgc.Gc_GroupID
inner join SG_Dynamic_Forms..v_projects p ON p.ProjectID = mg.Grp_Project
DECLARE projects1 CURSOR LOCAL FOR Select distinct ProjectID, isnull([FacilitatingAgency], '00000000-0000-0000-0000-000000000000') from @projects P WHERE P.SystemStatus = 4 AND P.SyncMode = 1
/*First cursor - fetch the cursor from ProjectaTable*/
OPEN projects1
FETCH NEXT FROM projects1 INTO @ProjectID, @FAID
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
BEGIN TRAN
DELETE FROM T_PROJECTGROUPSDATA
WHERE T_PROJECTGROUPSDATA.UPLOAD_ID IN (SELECT ID FROM T_UPLOADS WHERE project_savix_ID = @ProjectID AND UPLOADFILENAME = 'Automatic upload from web MIS')
DECLARE datasets CURSOR LOCAL FAST_FORWARD FOR SELECT Gd_ID, Grp_ID, Gd_CollectionDate, Gd_IsDeleted, Gd_CreateDate
FROM datasets
WHERE LegacyProjectId = @ProjectID AND Grp_IsDeleted = 0 AND Gd_IsDeleted != 1
/*Second cursor - this will get the 'collectionDate'field from datasetsTable for every project fetched in above cursor and also get @dataID which is used to insert value in to other table-T_PROJECTGROUPSDATA*/
OPEN datasets
FETCH NEXT FROM datasets INTO @DataID, @GroupID, @CollectionDate, @Status, @Createdate
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE period CURSOR LOCAL FAST_FORWARD FOR SELECT ID, dbo.fn_GetEndOfPeriod(ID) FROM T_PERIODS
/* dbo.fn_GetEndOfPeriod(ID) - this function will give the end of the date of that specifc quarter for any given date*/
WHERE DATEDIFF(dd,@CollectionDate,dbo.fn_GetEndOfPeriod(ID)) >= 0
ORDER BY [YEAR],[Quarter]
/*Third Cursor - this will process the records from another table called period with above fetched @collectionDate*/
OPEN period
FETCH NEXT FROM period INTO @PeriodID, @EndDate
WHILE @@FETCH_STATUS = 0
BEGIN
IF EXISTS (SELECT * FROM Savix_Service_Group..Group_Data mgd
INNER JOIN Savix_Service_Group..Group_Cycle mgc ON mgc.Gc_ID = mgd.Gd_CycleID
WHERE mgc.Gc_GroupID = @GroupID
AND DATEDIFF(dd,mgd.Gd_CollectionDate,@EndDate) >= 0
AND (mgd.Gd_CollectionDate > @CollectionDate )
AND mgd.Gd_IsDeleted != 1)
BEGIN
BREAK
END
IF EXISTS (SELECT ID FROM T_UPLOADS u
WHERE u.project_savix_ID = @ProjectID AND u.PERIOD_ID = @PeriodID AND u.STATUS = 3 AND UPLOADFILENAME != 'Automatic upload from web MIS')
BEGIN
FETCH NEXT FROM period INTO @PeriodID, @EndDate
CONTINUE
END
SET @UploadID = (SELECT ID FROM T_UPLOADS u WHERE u.project_savix_ID = @ProjectID AND u.PERIOD_ID = @PeriodID AND u.STATUS = 3)
/*If T_uploads doesn't have appropirate period ID from cursor fetch then create a new entry in T_uploads with current projectID*/
IF @UploadID IS NULL
BEGIN
declare @Project_ID_Legacy int = ISNULL((select distinct PROJECT_ID from T_UPLOADS where project_savix_ID = @ProjectID),@ProjectID)
INSERT INTO T_UPLOADSFIRSTSTEP
(PROJECT_ID
,UPLOADDATE
,UPLOADFILENAME
,UPLOADUSER_ID
,CURRENTSTEP
,STATUS
,Project_ID_MIS)
SELECT @Project_ID_Legacy , --mm 06/12
GETDATE(),
'Automatic upload from web MIS',
2,
2,
0,
@ProjectID
INSERT INTO T_UPLOADS
(ID, periodID, projectID,UPLOADDATE,UPLOADFILENAME,UPLOADUSER_ID )
SELECT uf.ID,
@PeriodID,
uf.PROJECT_ID,
uf.UPLOADDATE,
uf.UPLOADFILENAME,
uf.UPLOADUSER_ID
FROM T_UPLOADSFIRSTSTEP uf
INNER JOIN @projects mp ON uf.Project_ID_MIS = mp.ProjectID
WHERE uf.Project_ID_MIS = @ProjectID AND uf.STATUS = 0
AND NOT EXISTS (SELECT * FROM T_UPLOADS u WHERE u.PROJECT_ID = uf.PROJECT_ID AND u.PERIOD_ID = @PeriodID AND u.STATUS = 3)
DELETE FROM T_UPLOADSFIRSTSTEP WHERE STATUS = 0 AND PROJECT_ID = @Project_ID_Legacy
SET @UploadID = (SELECT ID FROM T_UPLOADS u WHERE u.project_savix_ID = @ProjectID AND u.PERIOD_ID = @PeriodID AND u.STATUS = 3)
END ELSE
UPDATE T_UPLOADS SET
TOTALEXPENDITURES = CASE WHEN DATEDIFF(d,mp.StartDate,mp.EndDate) != 0
THEN mp.Cost_Until_Today*DATEDIFF(d,mp.StartDate,dbo.fn_GetEndOfPeriod(@PeriodID))/DATEDIFF(d,mp.StartDate,mp.EndDate)/dbo.fn_RateAtDate(mp.Currency,dbo.fn_GetEndOfPeriod(@PeriodID))
ELSE 0 END,
TotalExpendituresNative = CASE WHEN DATEDIFF(d,mp.StartDate,mp.EndDate) != 0
THEN mp.Cost_Until_Today*DATEDIFF(d,mp.StartDate,dbo.fn_GetEndOfPeriod(@PeriodID))/DATEDIFF(d,mp.StartDate,mp.EndDate)
ELSE 0 END
FROM @projects mp
WHERE T_UPLOADS.ID = @UploadID AND mp.ProjectID = @ProjectID
INSERT INTO T_PROJECTGROUPSDATA
(uploadID, fieldA,fieldB,......fieldN )
SELECT @UploadID,p.fieldA,mg.fieldB,......mgc.fieldN
FROM @projects mp
inner join SG_Dynamic_Forms..v_projects p ON p.LegacyProjectId = mp.projectID
inner join Savix_Service_Group..Groups mg ON mg.Grp_Project = p.ProjectID
INNER JOIN Savix_Service_Group..Group_Cycle mgc ON mgc.Gc_GroupID = mg.Grp_ID
INNER JOIN Savix_Service_Group..Group_Data mgd ON mgd.Gd_CycleID = mgc.Gc_ID
LEFT JOIN Savix_Service_Trainers..Trainers me ON me.Tr_ID = mgc.Gc_MonitoredBy
LEFT JOIN Savix_Service_Dictionaries..Dictionary mgt ON mgt.Dny_ID = me.Tr_Type
--left join v1_Report_UDF_Data_UploadToSavix udf on udf.DataID = mgd.Gd_ID
WHERE mgd.Gd_ID = @DataID
FETCH NEXT FROM period INTO @PeriodID, @EndDate
END
CLOSE period
DEALLOCATE period
FETCH NEXT FROM datasets INTO @DataID, @GroupID, @CollectionDate, @Status, @Createdate
END
CLOSE datasets
DEALLOCATE datasets
COMMIT
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE(),@PeriodID, @ProjectID, @UploadID,@DataID
IF CURSOR_STATUS('global' , 'period') >= 0
BEGIN
CLOSE period
DEALLOCATE uploadID
END
IF CURSOR_STATUS('global' , 'datasets') >= 0
BEGIN
CLOSE datasets
DEALLOCATE datasets
END
IF @@TRANCOUNT > 0
ROLLBACK
INSERT INTO error_catch_UploadtoSavix
SELECT cast(ERROR_NUMBER() as nvarchar), ERROR_MESSAGE(),@PeriodID, @ProjectID, @UploadID,@DataID, getdate()
END CATCH
FETCH NEXT FROM projects1 INTO @ProjectID, @FAID
END
CLOSE projects1
DEALLOCATE projects1
SELECT 1 as success
这是我尝试做的并且陷入中间和不完整的事情:
select s.ID,u.*
from T_PROJECTS_TEMP pt
INNER JOIN
(SELECT mgd.Gd_ID, mg.Grp_ID, mgd.Gd_CollectionDate, mgd.Gd_IsDeleted, mgd.Gd_CreateDate, mg.Grp_Project, mg.Grp_IsDeleted , mg.Grp_Legacy_ID, p.LegacyProjectId
FROM Savix_Service_Group..Group_Data mgd
INNER JOIN Savix_Service_Group..Group_Cycle mgc ON mgc.Gc_ID = mgd.Gd_CycleID
INNER JOIN Savix_Service_Group..Groups mg ON mg.Grp_ID = mgc.Gc_GroupID
inner join SG_Dynamic_Forms..v_projects p ON p.ProjectID = mg.Grp_Project
WHERE LegacyProjectId = 5047 AND Grp_IsDeleted = 0 AND Gd_IsDeleted != 1 ) dataset on pt.ID = dataset.LegacyProjectId
INNER JOIN T_PERIODS s ON DATEDIFF(DAY,dataset.Gd_CollectionDate,dbo.fn_GetEndOfPeriod(s.ID)) >= 0
LEFT JOIN T_UPLOADS u ON u.project_savix_ID = pt.ID AND u.PERIOD_ID = s.ID AND u.STATUS = 3 --AND u.UPLOADFILENAME != 'Automatic upload from web MIS'
WHERE NOT EXISTS (SELECT * FROM Savix_Service_Group..Group_Data mgd
INNER JOIN Savix_Service_Group..Group_Cycle mgc ON mgc.Gc_ID = mgd.Gd_CycleID
WHERE mgc.Gc_GroupID = Grp_ID
AND DATEDIFF(dd,mgd.Gd_CollectionDate,(select dbo.fn_GetEndOfPeriod(s.ID))) >= 0
AND (mgd.Gd_CollectionDate > Gd_CollectionDate) AND mgd.Gd_IsDeleted != 1 )
AND NOT EXISTS (SELECT ID FROM T_UPLOADS u
WHERE u.project_savix_ID = pt.ID AND u.PERIOD_ID = s.ID AND u.STATUS = 3 AND UPLOADFILENAME != 'Automatic upload from web MIS')
ORDER BY s.ID
样本数据
T_Projects 和数据集的表和数据 - http://www.sqlfiddle.com/#!18/00205/2
T_Periods 的表格和数据 - http://www.sqlfiddle.com/#!18/3b6b1/2
T_uploads 的表和数据(在游标运行之前)- http://www.sqlfiddle.com/#!18/7d12f3/1
其他表数据 1 - http://www.sqlfiddle.com/#!18/6e499
其他表数据 2 - http://www.sqlfiddle.com/#!18/30e3b/3
函数 fn_getEndOFperiod - http://www.sqlfiddle.com/#!18/54d66
请注意 - 上述任何表格中都会有不止一行,但是我给出的示例只有一条记录。
例外数据
T_uploads 表 - http://sqlfiddle.com/#!18/027cc/1
T_ProjectsGroupData 总共将有 15 条记录,即对于每个 T_uploads.ID(与 T_ProjectsGroupData .UploadID 字段相关),T_ProjectsGroupData 中都会有一个条目
我除了将这个基于 CURSOR 的代码转换成一个简单的基于 SET 的方法之外,任何改进这个代码的帮助和建议都将是非常可观的。