在SQL Developer中提高存储过程的性能 - 嵌套循环

时间:2015-01-26 18:42:20

标签: sql oracle performance

我有一个程序,为员工分配“首选车辆”,最后我确保没有员工拥有相同的车辆。

第一个循环得到一个多次出现的所有首选车辆的列表,嵌套循环遍历每个拥有该车辆的员工。最后,只有一名员工将其设置为他/她的首选车辆

--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是一个包含数千行的表...我的目标是消除嵌套循环......我可以这样做吗?我仍然是sql的新手,所以我真的很感激任何建议/指出我错过的任何东西

1 个答案:

答案 0 :(得分:3)

首先,插入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的那个领带之外的每个成员都将被设置为空。

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


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