提高 SQL 开发人员中存储过程的性能 - 嵌套循环

improve performance of stored procedure in SQL Developer - nested loop

我有一个为员工分配 "preferred vehicle" 的程序,最后我确保没有员工拥有相同的车辆。

第一个循环获取出现不止一次的所有首选车辆的列表,嵌套循环遍历拥有该车辆的每个员工。最后,只有一名员工将其设置为 his/her 首选车辆

--Loop through to see if a pref_veh occurs more than once 
FOR every_duplicate_veh IN 
(SELECT PREFERRED_VEH FROM FLEET_USER GROUP BY PREFERRED_VEH HAVING COUNT (PREFERRED_VEH) > 1)
LOOP      
  max_count:=0; 

  --Loop through all the employees with the duplicate vehicle
  FOR every_employee IN (SELECT EMPLOYEE_ID FROM FLEET_USER WHERE PREFERRED_VEH=every_duplicate_veh.PREFERRED_VEH)
  LOOP

    --Find which employee is assigned the vehicle the most 
    SELECT COUNT(ASSIGN_VEH_ID) INTO assigned_veh_count FROM FLEET_TRANSACTION 
    WHERE ASSIGN_VEH_ID=every_duplicate_veh.PREFERRED_VEH AND DRIVER_EMP_ID=every_employee.EMPLOYEE_ID
    AND SYSDATE - 30 <= RESERV_START_DT;   

    IF assigned_veh_count>max_count THEN
      max_count:=assigned_veh_count; 
      preferred_employee_id:=every_employee.EMPLOYEE_ID; 
    END IF; 

    --Reset the employee's preferred vehicle to NULL
    UPDATE FLEET_USER SET PREFERRED_VEH = NULL WHERE EMPLOYEE_ID = every_employee.EMPLOYEE_ID;
    INSERT INTO FLEET_PREF_VEH_LOG VALUES (SYSDATE, every_employee.EMPLOYEE_ID, NULL);

  END LOOP;

  --One employee will get the preferred vehicle 
  UPDATE FLEET_USER SET PREFERRED_VEH = every_duplicate_veh.PREFERRED_VEH  WHERE EMPLOYEE_ID = preferred_employee_id;
  INSERT INTO FLEET_PREF_VEH_LOG VALUES (SYSDATE, preferred_employee_id, every_duplicate_veh.PREFERRED_VEH);
  COMMIT;

END LOOP;

FLEET_USER 是具有数千行的 table...我的目标是消除嵌套循环...我可以这样做吗?我对 sql 还是很陌生,所以我非常感谢任何 advice/point 我错过的任何东西

首先,FLEET_PREF_VEH_LOG 中的插入让我觉得更适合用作触发器,而不是一个过程。以这种方式卸载后,我认为您可以将其减少为单个 SQL 语句。

我没有你的对象或数据,所以这完全未经测试,但我认为它与你现有的代码非常接近,应该会提高性能。

MERGE INTO fleet_user fu_m
USING      (SELECT employee_id,
                   cnt,
                   MAX (cnt) OVER (PARTITION BY fm_s.preferred_veh) 
                       AS max_cnt
            FROM   (SELECT   fu.employee_id, fu.preferred_veh, COUNT (*) AS cnt
                    FROM     fleet_user fu
                             JOIN fleet_transaction ft
                                ON     ft.assign_veh_id = fu.preferred_veh
                                   AND ft.driver_emp_id = fu.employee_id
                    WHERE        EXISTS
                                    (SELECT   preferred_veh
                                     FROM     fleet_user fu2
                                     WHERE    fu2.preferred_veh =
                                                 fu.preferred_veh
                                     GROUP BY preferred_veh
                                     HAVING   COUNT (preferred_veh) > 1)
                             AND SYSDATE - 30 <= ft.reserv_start_dt
                    GROUP BY fu.employee_id, fu.preferred_veh)) fm_s
ON         (fm_m.employee_id = fm_s.employee_id)
WHEN MATCHED THEN
   UPDATE SET preferred_veh = NULL
      WHERE      max_cnt <> cnt;

此方法的另一个优点是可以防止将不必要的记录写入FLEET_PREF_VEH_LOG。使用您当前的代码(根据我的阅读),首选用户的首选项将被清除,然后设置回其原始值。当实际上没有任何更改时,这会导致两个 FLEET_PREF_VEH_LOG 记录。


正如评论中所指出的,此答案不包括并列计数。问题中的代码确实如此,但它是不确定的:在平局的情况下,最后处理的员工获胜。由于数据库变幻莫测,缺少ORDER BY,不能保证每次都一样

我更愿意添加确定性方法来打破关系,即使它是任意的:

MERGE INTO fleet_user fu_m
USING      (SELECT employee_id,
                   cnt,
                   MAX (cnt) OVER (PARTITION BY fm_s.preferred_veh) 
                       AS max_cnt,
                   ROW_NUMBER () OVER (PARTITION BY cnt ORDER BY employee_id) 
                       AS tie_order
            FROM   (SELECT   fu.employee_id, fu.preferred_veh, COUNT (*) AS cnt
                    FROM     fleet_user fu
                             JOIN fleet_transaction ft
                                ON     ft.assign_veh_id = fu.preferred_veh
                                   AND ft.driver_emp_id = fu.employee_id
                    WHERE        EXISTS
                                    (SELECT   preferred_veh
                                     FROM     fleet_user fu2
                                     WHERE    fu2.preferred_veh =
                                                 fu.preferred_veh
                                     GROUP BY preferred_veh
                                     HAVING   COUNT (preferred_veh) > 1)
                             AND SYSDATE - 30 <= ft.reserv_start_dt
                    GROUP BY fu.employee_id, fu.preferred_veh)) fm_s
ON         (fm_m.employee_id = fm_s.employee_id)
WHEN MATCHED THEN
   UPDATE SET preferred_veh = NULL
      WHERE      max_cnt <> cnt or tie_order <> 1;

ROW_NUMBER () OVER (PARTITION BY cnt order by employee_id) 将为具有相同计数的每个员工提供一个连续编号,从最低 employee_id 到最高。当没有平局时,所有值都将为 1,因此查询将与以前完全一样。但是当出现平局时,平局中除了最低 employee_id 的每个成员都将被设置为 null。

显然,您可以将 ORDER BY 更改为您选择的任意标准。


我已经更改了上述两个查询以满足评论中提出的最新问题(我认为)。通过仅获取最大的 count 车辆编号,我们确保我们分别处理每辆车。