SQL Server: запустить хранимую процедуру в другой хранимой процедуре с помощью WHILE

#sql #sql-server #stored-procedures

#sql #sql-сервер #хранимые процедуры

Вопрос:

У меня есть база данных Office employees с информацией об этих сотрудниках, их JobName, их образовании (курсах) и курсах, необходимых для правильного выполнения их РАБОТЫ. Просто.

Я написал 2 хранимые процедуры SQL Server, которые работают хорошо.

  • Хранимые процедуры #1 принимают a Jobname и возвращают имена пользователей с этим jobname
  • Хранимые процедуры # 2 принимают Name и возвращают имена курсов (необходимых), которые не были завершены сотрудником

Что я ищу: запуск хранимой процедуры # 1 (с использованием @Jobname ), чтобы сначала получить имя сотрудника, передать это хранимой процедуре # 2 (as @Name ), а затем возвращает результаты — затем возьмите следующее имя из результата процедуры # 1 и так далее.

Это мои процедуры:

Процедура #1:

 ALTER PROCEDURE [dbo].[Functie] 
    @JobName AS NVARCHAR(50)
AS
BEGIN
    SELECT JobName, Name
    FROM Employers
    WHERE JobName = @Functie
END
  

Процедура #2:

 ALTER PROCEDURE [dbo].[ExceptTest] 
    (@Name AS NVARCHAR(50))
BEGIN
    SELECT coursename 
    FROM CourseNeeded
    WHERE CourseNeeded.coursename = @Name

    EXCEPT

    SELECT coursename 
    FROM CourseDone
    WHERE CourseDone.coursename = @Name
END
  

Я попробовал это:

 ALTER PROCEDURE [dbo].[Functie] 
    @JobName AS NVARCHAR(50)
BEGIN
    SELECT JobName, Name
    FROM Employers
    WHERE JobName = @Functie

    WHILE @JobName <> 0
    BEGIN
        EXEC Procedure2 @Name = Name
END
  

Комментарии:

1. Вы уверены , что эти процедуры работают? Например, в вашей первой процедуре есть предложение JobName = @Functie , однако @Functie в вашей процедуре не определен параметр (или переменная). Однако это похоже на проблему XY . Циклирование (в данном случае a WHILE ) — одна из последних вещей, которые вы должны делать в SQL. SQL — это язык, основанный на множествах, и вы должны использовать решения, основанные на множествах.

Ответ №1:

 ALTER PROCEDURE [dbo].[Functie] 
@JobName AS NVARCHAR(50)
AS
BEGIN
declare @Name AS NVARCHAR(50) 
SELECT @Name = Name
FROM Employers
WHERE JobName = @JobName

SELECT coursename 
FROM CourseNeeded
WHERE CourseNeeded.coursename = @Name

EXCEPT

SELECT coursename 
FROM CourseDone
WHERE CourseDone.coursename = @Name
END
  

Ответ №2:

На самом деле это должно выполняться как «набор», а не как цикл. Представьте, что если бы у вас здесь был миллион человек — ему нужно было бы выполнить много-много циклов. Вместо этого сила баз данных заключается в их способности выполнять все операции со всеми строками одновременно.

Таким образом, я сосредоточился на подходе, основанном на множествах, в качестве ответа и на том, как перейти от «циклического» подхода к подходу, основанному на множествах. Если вы действительно хотите выполнить цикл WHILE, выполните поиск Stack Overflow для «WHILE LOOP SQL» или аналогичный.

С точки зрения мышления и планирования, нет ничего плохого в том, чтобы начинать с циклов (действительно, для многих людей более естественно думать о циклическом процессе). Однако, прежде чем кодировать ее в SQL, попробуйте преобразовать «цикл» в ответ «на основе набора». Один из способов — заменить подход «для каждого человека, сделайте это» на «для всех, сделайте это». Затем вам нужно просто решить, как туда добраться.

Вы на полпути к своему ответу — еще несколько шагов, и это будет сделано.

Ниже приведен пример того, как вы могли бы подойти к этому.

Кроме того, поскольку вы не предоставили нам структуры данных и т. Д. — Только некоторые имена — я создал свои собственные структуры данных для ясности. Кроме того, для большей части ответа я только что сообщил Employee_ID, Job_ID и т. Д., А не имена — вы можете легко получить их, сославшись на таблицы.

Я разделил операторы для примера, но (как показано в конце) вы сможете свернуть / объединить многие из них в один оператор, который даст вам ваш ответ.

PS Я предположил, что вы имели в виду «сотрудника», а не «работодателя» (т. Е. Вы говорите о человеке, а не о компании).

Настройка данных

 -- Tables holding base data about employees, jobs and courses. 
-- Note that an employee can only have one job in this model.

CREATE TABLE #Employees (Employee_ID int PRIMARY KEY, Employee_Name nvarchar(100), Job_ID int)

CREATE TABLE #Jobs (Job_ID int PRIMARY KEY, Job_Name nvarchar(100))

CREATE TABLE #Courses (Course_ID int PRIMARY KEY, Course_Name nvarchar(100))


-- Tables holding links between these 
-- - the courses already completed by employees, and 
-- - the courses required for each job

CREATE TABLE #Employee_Course (Employee_ID int, Course_ID int, PRIMARY KEY(Employee_ID, Course_ID))

CREATE TABLE #Job_Course (Job_ID int, Course_ID int, PRIMARY KEY(Job_ID, Course_ID))

-- Note I haven't set up data or other things like Foreign Keys etc - to keep this as focussed as possible
  

Теперь для обработки.

Ваш подход заключался в том, чтобы сначала выяснить (для данной работы), кем были соответствующие сотрудники. Оттуда вы проверили каждого сотрудника, чтобы узнать, прошли ли они все необходимые курсы.

Мы немного изменим это — вместо проверки каждого сотрудника мы будем проверять всех сотрудников сразу.

 -- Step 1 - Identify employees in job

    CREATE TABLE #Emps_in_job (Employee_ID int)
    
    INSERT INTO #Emps_in_job (Employee_ID)
        SELECT  E.Employee_ID
        FROM    #Employees E
        WHERE   E.Job_ID = @Job_ID
    
    SELECT * FROM #Emps_in_job ORDER BY Employee_ID
  
 -- Step 2 - For all employees in that list, find the courses they need (based on their job)
-- Note this hasn't yet checked whether they have completed that course or not.

    CREATE TABLE #Emp_Course_Requirements (Employee_ID int, Course_ID int)

    INSERT INTO #Emp_Course_Requirements (Employee_ID, Course_ID)
        SELECT  E.Employee_ID, JC.Course_ID
        FROM    #Employees E
                INNER JOIN #Job_Course JC ON E.Job_ID = JC.Job_ID

    SELECT * FROM #Emp_Course_Requirements
  
 -- Step 3 - Find out which courses are missing and report on them
-- There are three options here - all should work (select whichever you prefer)

    -- Option 1 (using EXCEPT clause - easy to read but not frequently used)
    SELECT      ECR.Employee_ID, ECR.Course_ID
        FROM    #Emp_Course_Requirements ECR
      EXCEPT
    SELECT      EC.Employee_ID, EC.Course_ID
        FROM    #Employee_Course EC

    -- Option 2 (using LEFT OUTER JOIN, and a NULL result in that joined table indicating they don't have it)
    SELECT      ECR.Employee_ID, ECR.Course_ID
        FROM    #Emp_Course_Requirements ECR
                LEFT OUTER JOIN #Employee_Course EC
                        ON ECR.Course_ID = EC.Course_ID
                        AND ECR.Employee_ID = EC.Employee_ID
        WHERE   EC.Employee_ID IS NULL

    -- Option 3 (Delete completed employee/courses from the list. Any remaining must still be required)
    DELETE FROM ECR
        FROM    #Emp_Course_Requirements ECR
                INNER JOIN #Employee_Course EC
                        ON ECR.Course_ID = EC.Course_ID
                        AND ECR.Employee_ID = EC.Employee_ID
    SELECT      * 
        FROM    #Emp_Course_Requirements ECR
  

Однако даже в приведенном выше случае требуется дополнительная обработка. В приведенном выше примере мы создаем временные таблицы, заполняем их и используем только на следующем шаге. Вместо этого просто поместите исходный запрос во второй запрос (и так далее), пропуская необходимость создания временной таблицы.

Конечный результат может выглядеть следующим образом

 CREATE PROCEDURE #JobCourseCheck 
        @Job_ID int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT      E.Employee_Name, C.Course_Name
        FROM    #Employees E
                INNER JOIN #Job_Course JC ON E.Job_ID = JC.Job_ID
                INNER JOIN #Courses C ON JC.Course_ID = C.Course_ID
                LEFT OUTER JOIN #Employee_Course EC
                        ON E.Employee_ID = EC.Employee_ID
                        AND JC.Course_ID = EC.Course_ID
        WHERE   E.Job_ID = @Job_ID
                AND EC.Employee_ID IS NULL

END