简体   繁体   中英

How do I loop through a temp table in a stored procedure with no pk

Problem:

I need to loop through the records in one table, pulling the employee number and comparing this employee number against another table to see if they're still active employees. If they are no longer active employees I need to pass the data from this row into another stored proc.

Research:

I've googled around quite a bit and realize that I shouldn't use cursors for this. I did however, find the following examples:

  1. http://ask.sqlservercentral.com/questions/7969/loop-through-records-in-a-temporary-table.html
  2. http://eedle.com/2010/11/11/looping-through-records-in-sql-server-stored-procedure/

However, it seems like they use a pk to loop through the records. Employee numbers can be the same for multiple recods in my scenario

Questions:

  1. Is it possible to achieve what I'm attempting without cursors?
  2. If it is possible, how would I go about fetching each row with a non unique column?

Since you haven't given us a full description of your situation, we cannot give a complete answer, however, in general, it's Loops that you want to avoid in a set-based language like SQL and not Cursors per se (the problem with Cursosr is that they require the Loops).

In your comments you provide a little bit more information in that you want to " loop through first table, compare to second table, and when the compare fails i delete records from first table. In essense I'm deleting recods from the first table of employees who are no longer with the company. "

Here is how you can do this in SQL:

DELETE  From FirstTable
WHERE   FirstTable.EmployeeID NOT IN
    (
        SELECT  SecondTable.EmployeeID 
        FROM    SecondTable
        WHERE   SecondTable.Flag = 'Y'
    )

No Loops are needed ...


If the issue then is that you want to use a pre-existing Stored Procedure to do the deletion, then there are a several possibilities:

First, you can extract the contents of the Stored Procedure and re-write them for these preceding WHERE conditions. I understand that this is code duplication and that it violates some people's DRY instincts, however, understand that SQL is NOT an Object-Oriented development environment and that sometimes code duplication has to happen.

The second option would be to refactor the stored procedure so that it could accept a TableParameter for its EmployeeId's to Delete. This is complicated though, and we would need to see that stored procedure to advise you on it.

The third option would be to use string aggregation to build dynamic SQL to call the stored procedure for each EmployeeID to be deleted like so:

DECLARE @sql As NVarchar(MAX);  
SET     @sql = N'';

SELECT  @sql = @sql + ' 
    EXEC YourProc ''' + CAST(EmployeeID As NVARCHAR(MAX)) + '''; '
FROM    FirstTable
WHERE   FirstTable.EmployeeID IN
    (
        SELECT  SecondTable.EmployeeID 
        FROM    SecondTable
        WHERE   SecondTable.Flag = 'Y'
    )

EXEC(@sql);

This avoids both the Looping and the Cusror problems, though many dislike it also. I prefer this solution myself, largely because of its generality.

This will delete all records from your employee data table if there are no matching rows in your current employees table.

I'd sugest you replace the DELETE FROM with SELECT * FROM and then when you're happy to delete the results change it back to DELETE

DELETE FROM
    EmployeeDataTable
WHERE
    NOT EXISTS
    (SELECT 
        NULL
    FROM
        CurrentEmployees
    WHERE
        EmployeeDataTable.EmployeeID = CurrentEmployees.EmployeeID
    )

EDIT: Just saw your comment about the Active flag, this means the query can be changed to

DELETE FROM
    EmployeeDataTable
WHERE
    EXISTS
    (SELECT 
        NULL
    FROM
        CurrentEmployees
    WHERE
        EmployeeDataTable.EmployeeID = CurrentEmployees.EmployeeID
        CurrentEmployees.IsActive <> 'Y'
    )

I would do this by looping through a cursor. You can also add a unique ID to the cursor so you know which row you are currently on.

DECLARE @id uniqueidentifier 
DECLARE @empName   VARCHAR(50)

SELECT newId() AS Id, *
    INTO #mytemp
    FROM MyEmployees
    ORDER BY EmpName

while EXISTS(SELECT TOP 1 1 FROM #mytemp)
BEGIN
    --Get row from cursor to process
    SELECT TOP 1 @id = Id, @empName = EmpName FROM #mytemp  

    --Do you processing here. Call other stored proc.

    --Remove processed row from cursor.
    DELETE FROM #mytemp WHERE Id = @id  
END
DROP TABLE #mytemp

Have you considered using the MERGE statement : http://technet.microsoft.com/en-us/library/bb510625.aspx

You have clauses for :

  1. When the data does match between both table
  2. When the data exists in source, but not in target
  3. When the data exists in target, but not in source

You can then insert, update or delete records depending on the match type. You can also output the action from match to a temporary table for additional custom operations.

For instance, you can do :

MERGE INTO Table1
USING Table2 ON Table1.EmployeeID = Table2.EmployeeID
WHEN MATCHED 
    THEN UPDATE SET Table1.SomeField = Table2.SomeOtherField
WHEN NOT MATCHED BY SOURCE
    THEN DELETE
WHEN NOT MATCHED BY TARGET
    THEN Insert (Name, Status) VALUES (EmployeeName, 'Active')
  1. It would be possible if you took the content from the sproc you are wanting to call inside your cursor and implemented that logic within your current sproc. At this point, you should be able to use set-based logic. Is the sproc you are calling very complex?
  2. What about adding an identity column to the temp table?

Sometimes, cursors are the right solution for a variety of reasons.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM