sql >> Databáze >  >> RDS >> Sqlserver

Jak převést dotaz založený na CURSORu na dotaz založený na SET

INSERT INTO T_PROJECTGROUPSDATA (uploadID, fieldA,fieldB,......fieldN )
SELECT t.ID,p.fieldA,mg.fieldB,......mgc.fieldN
FROM T_Projects mp
INNER JOIN T_UPLOADS t mp.ID = t.project_savix_ID
INNER JOIN  SG_Dynamic_Forms..v_projects p ON p.LegacyProjectId = mp.ID 
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 
WHERE mp.SyncMode = 1
AND t.[STATUS] = 3 AND t.UPLOADFILENAME = 'Automatic upload from web MIS'
AND mg.Grp_IsDeleted = 0 AND mgd.Gd_IsDeleted != 1


UPDATE t SET 
TOTALEXPENDITURES = CASE WHEN DATEDIFF(d,mp.PROJECTESTABLISHEDDATE,mp.EndDate) != 0 
    THEN p.Cost_Until_Today*DATEDIFF(d,mp.PROJECTESTABLISHEDDATE,dbo.fn_GetEndOfPeriod(t.Period_ID))/DATEDIFF(d,mp.PROJECTESTABLISHEDDATE,mp.EndDate)/dbo.fn_RateAtDate(p.PROJECTCURRENCY_ID,dbo.fn_GetEndOfPeriod(t.Period_ID))
    ELSE 0 
    END,
TotalExpendituresNative = CASE WHEN DATEDIFF(d,mp.PROJECTESTABLISHEDDATE,mp.EndDate) != 0 
    THEN p.Cost_Until_Today*DATEDIFF(d,mp.PROJECTESTABLISHEDDATE,dbo.fn_GetEndOfPeriod(t.Period_ID))/DATEDIFF(d,mp.PROJECTESTABLISHEDDATE,mp.EndDate)
    ELSE 0 
    END
FROM T_UPLOADS t
JOIN T_Projects mp ON mp.ID = t.project_savix_ID AND mp.SyncMode = 1
WHERE t.[STATUS] = 3 AND t.UPLOADFILENAME = 'Automatic upload from web MIS'

Abych vysvětlil, jak jsem na to přišel, níže je upravená verze kódu. Nemohu zaručit, že jsem dostal všechno, musel jsem si udělat nějaké předpoklady. Chyběly ukázkové údaje a chybí všechny tabulky savix. Toto je nejlepší odpověď, se kterou mohu přijít s ohledem na informace a omezení mého času na méně než 4 hodiny investovaného času. Mohl bych udělat mnohem víc, ale ty bys mi musel dát víc než jen internetovou slávu.

--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, 
    @MINIDprojects INT,
    @MAXIDprojects INT,
    @MINIDdatasets INT,
    @MAXIDdatasets INT,
    @MINIDperiods INT,
    @MAXIDperiods INT

Je obtížné pracovat s kurzory a testovat je, a tak jsem je nahradil (projekty, projekty1, datové sady a období) dočasnými tabulkami obsahujícími identitu a procházel jsem je.

IF OBJECT_ID('tempdb..#projects') IS NOT NULL DROP TABLE #projects
IF OBJECT_ID('tempdb..#projects1') IS NOT NULL DROP TABLE #projects1
IF OBJECT_ID('tempdb..#datasets') IS NOT NULL DROP TABLE #datasets
IF OBJECT_ID('tempdb..#period') IS NOT NULL DROP TABLE #period

CREATE TABLE #projects
(
    [ProjectID] [INT],
    [Title] [varchar](255) ,
    [currency] [int] ,
    [Cost_Until_Today] [float] ,
    [StartDate] [datetime] ,
    [EndDate] [datetime] ,
    [MisID] [uniqueidentifier] ,
    [SystemStatus] [int] ,
    [FacilitatingAgency] [uniqueidentifier] ,
    [SyncMode] [int] 
)

CREATE TABLE #projects1
(
    ID INT IDENTITY(1,1),
    [ProjectID] [INT],
    [FacilitatingAgency] [uniqueidentifier]
)

CREATE TABLE #datasets
(
    ID INT IDENTITY(1,1),
    Gd_ID [uniqueidentifier], 
    Grp_ID [uniqueidentifier], 
    Gd_CollectionDate DATETIME, 
    Gd_IsDeleted BIT, 
    Gd_CreateDate DATETIME
)

CREATE TABLE #period
(
    ID INT IDENTITY(1,1),
    IDPeriod INT,
    EndDate DATETIME
)

 INSERT #projects ( [ProjectID], [Title], {currency], [Cost_Until_Today], [StartDate], [EndDate], [MisID], [SystemStatus], [FacilitatingAgency], [SyncMode] )
 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*/

Projects obsahuje všechna ID ( projectID ) z T_Projects, jediná pole, která jsou použita z této tabulky, jsou ID, projectcurrency, projectestablisheddate ( startdate ), enddate a syncmode ( musí být =1, uvidíme později). Stav systému můžeme ignorovat, protože je konstanta.

Zde jsou datové sady tabulky zrušeny a načteny. Zajímá nás pouze gd_id z tabulky Savix_Service_Group..Group_Data, ale stále se potřebujeme připojit k ostatním tabulkám v případě, že odfiltrují hodnoty, které nechceme. Tato logika se opět používá níže při vkládání záznamů.

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

Zde vkládáme do projektů1 odlišnou hodnotu ID projektu. Bez dalších dat předpokládám, že ID je v tabulce T_projects odlišné, a proto je tento krok poněkud zbytečný kromě odfiltrování pomocí SyncMode =1. Pamatujte, že SystemStatus je konstanta a naplnili jsme ji hodnotou '4', toto kritéria mohla být odstraněna bez účinku.

INSERT INTO #projects1 ( ProjectID, FacilitatingAgency )
SELECT DISTINCT ProjectID, isnull([FacilitatingAgency], '00000000-0000-0000-0000-000000000000')  
FROM #projects p
WHERE SystemStatus = 4 AND SyncMode = 1

SELECT @MINIDprojects = MIN(ID), @MAXIDprojects = MAX(ID) FROM #projects1
WHILE @MINIDprojects < @MAXIDprojects + 1
BEGIN
--    BEGIN TRY
--        BEGIN TRAN

SELECT @ProjectID = ProjectID, @FAID = FacilitatingAgency FROM #projects1 WHERE ID = @MINIDprojects

        --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

Zde vkládáme do #datasets filtrování podle T_Project.ID A Savix_Service_Group..Groups.Grp_IsDeleted =0 AND Savix_Service_Group..Group_Data.Gd_IsDeleted !=1

        INSERT INTO #datasets ( Gd_ID, Grp_ID, Gd_CollectionDate, Gd_IsDeleted, Gd_CreateDate )
        SELECT Gd_ID, Grp_ID, Gd_CollectionDate, Gd_IsDeleted, Gd_CreateDate FROM datasets WHERE LegacyProjectId = @ProjectID AND Grp_IsDeleted = 0 AND Gd_IsDeleted != 1 

        SELECT @MINIDdatasets = MIN(ID), @MAXIDdatasets = MAX(ID) FROM #datasets

        WHILE @MINIDdatasets < @MAXIDdatasets + 1
        BEGIN

            SELECT @DataID = Gd_ID, @GroupID = Grp_ID, @CollectionDate = Gd_CollectionDate, @Status = Gd_IsDeleted, @Createdate = Gd_CreateDate FROM #datasets WHERE ID = @MINIDdatasets

            --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

Tady jde asi o nejhorší použití kurzoru. Načteme všechna období a projdeme je. Nakonec se staráme pouze o období, která jsou v tabulce T_UPLOADS.

            INSERT INTO #period ( IDPeriod, EndDate ) SELECT ID, dbo.fn_GetEndOfPeriod(ID) FROM T_PERIODS WHERE DATEDIFF(dd,@CollectionDate,dbo.fn_GetEndOfPeriod(ID)) >= 0
            SELECT @MINIDperiods = MIN(ID), @MAXIDperiods = MAX(ID) FROM #period

            WHILE @MINIDperiods < @MAXIDperiods + 1
            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

Zde přeskočíme všechna období, kde UPLOADFILENAME !='Automatické nahrávání z webového MIS'

                --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

S ohledem na to nás zajímá pouze to, kde status =3 a UPLOADFILENAME ='Automatické nahrání z webového MIS'

                SET @UploadID = (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')

                /*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 NOT NULL
                        BEGIN

                            --declare @Project_ID_Legacy int = ISNULL((select distinct PROJECT_ID from T_UPLOADS where project_savix_ID = @ProjectID),@ProjectID)

Proč používají druhou tabulku ( T_UPLOADFIRSTSTEP ) ke generování záznamu, kde používají pouze id, a pak záznam s použitím tohoto ID při vytváření záznamu T_Uploads smažou, je mimo mé chápání a zdá se mi to jako hrozné kódování.

    --                            INSERT INTO T_UPLOADSFIRSTSTEP
    --                                   (PROJECT_ID
    --                                   --,UPLOADDATE
    --                                   --,UPLOADFILENAME
    --                                   --,UPLOADUSER_ID
    --                                   --,CURRENTSTEP
    --                                   ,[STATUS]
    --                                   ,Project_ID_MIS)
    --                             SELECT ISNULL((select distinct PROJECT_ID from T_UPLOADS where project_savix_ID = @ProjectID),@ProjectID),
    --                                    --GETDATE(),
    --                                    --'Automatic upload from web MIS',
    --                                    --2,
    --                                    --2,
    --                                    0,
    --                                    @ProjectID

Vložení do T_UPLOADS se vždy nezdaří, protože existují pole, která nemohou mít hodnotu null, která nejsou v seznamu vložení a nemají přiřazenou výchozí hodnotu.

    --                            INSERT INTO T_UPLOADS ( ID, periodID, projectID,UPLOADDATE,UPLOADFILENAME,UPLOADUSER_ID )
    --                             SELECT uf.ID,
    --                                    @PeriodID,
    --                                    ISNULL((select distinct PROJECT_ID from T_UPLOADS where project_savix_ID = @ProjectID),@ProjectID),
    --                                    GETDATE(),
    --                                    'Automatic upload from web MIS',
    --                                    2
    --                             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 = ISNULL((select distinct PROJECT_ID from T_UPLOADS where project_savix_ID = @ProjectID),@ProjectID)

    --                            --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

Takže tady je skutečná logika, která se provádí uvnitř těchto 3 kurzorů. Když pochopíme, že nás nezajímají období, logika je všechny prochází. Záleží nám pouze na tom, abychom museli filtrovat určitá kritéria ( status =3 a UPLOADFILENAME ='Automatické nahrání z webového MIS') a aby měly odpovídající tabulku T_Project.ID s kritérii T_Projects.SyncMode =1

                            UPDATE t SET 
                                    TOTALEXPENDITURES = CASE WHEN DATEDIFF(d,mp.StartDate,mp.EndDate) != 0 
                                            THEN mp.Cost_Until_Today*DATEDIFF(d,mp.StartDate,dbo.fn_GetEndOfPeriod(t.Period_ID))/DATEDIFF(d,mp.StartDate,mp.EndDate)/dbo.fn_RateAtDate(mp.Currency,dbo.fn_GetEndOfPeriod(t.Period_ID))
                                            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(t.Period_ID))/DATEDIFF(d,mp.StartDate,mp.EndDate)
                                            ELSE 0 
                                            END
                            FROM T_UPLOADS t
                            JOIN #projects mp ON mp.ProjectID = t.project_savix_ID
                            WHERE 1=1 -- t.ID = @UploadID 
                            --AND t.project_savix_ID = @ProjectID AND t.PERIOD_ID = @PeriodID 
                            AND t.[STATUS] = 3 AND t.UPLOADFILENAME = 'Automatic upload from web MIS'

Vložení do T_PROJECTGROUPDATA duplikovalo logiku použitou k vytvoření proměnné @dataid odvozené z #datasets. Který je odvozen z tabulky datových sad, kterou jsme zrušili a vytvořili výše.

                        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
                SET @MINIDperiods = @MINIDperiods + 1
            END
            --CLOSE period
            --DEALLOCATE period

            --FETCH NEXT FROM datasets INTO @DataID, @GroupID, @CollectionDate, @Status, @Createdate
            SET @MINIDdatasets = @MINIDdatasets + 1
        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

    SET @MINIDprojects = @MINIDprojects + 1
    --FETCH NEXT FROM projects1 INTO @ProjectID, @FAID
END


--CLOSE projects1
--DEALLOCATE projects1

--SELECT 1 as success

Tady to máš. Zhuštění téměř 300 řádků a 3 kurzorů na 30 řádků a žádné kurzory.




  1. Selektivně odstraňte většinou duplicitní záznamy z MySQL

  2. Nejlepší způsob, jak získat PK Guid vloženého řádku

  3. ORA-01843:neplatný měsíc

  4. Chyba v příkazu SQL pomocí sloupce INSERT a AUTO-INCREMENT