Да, рекламная площадка из моей рассылки получилась отличная - предложений нет.
"Графа Монте-Кристо из меня не поучилось - придется переквалифицироваться в управдомы..."
Есть о чем подумать. В связи с этим сообщаю вам, что после этого "юбилейного" выпуска
ведущий рассылки уходит в отпуск до августа.
Напоследок предлагаю небольшой, но, как мне кажется, интересный выпуск, посвященный
практическому использованию некоторых аналитических функций. По мотивам ответа
Тома Кайта на вопрос, заданный в мае 2003 года.
И небольшой стишок Егора Летова, так, для медитации.
Балансировка нагрузки на сотрудников - снова аналитические функции
Том,
У меня возникла следующая проблема с оператором update:
Есть таблица кредитов (loan):
state_cd,
user_id,
status_cd,
...
И таблица user_state_served:
user_id,
state_cd,
last_asgnmt_dt
Хотелось бы распределить кредиты равномерно среди сотрудников, имеющих право работать в
соответствующем штате. Для этого я изменяю last_asgnmt_dt = sysdate и
беру в качестве следующего ответственного за кредит user_id с min(last_asgnmt_dt).
Однако после первоначального распределения нагрузка сотрудников может оказаться разной,
в зависимости от количества кредитов, выданных в штате. Как можно было бы взять
среднее количество кредитов в штате и поровну распределить их между сотрудниками?
Например, в штате TX выдано 400 кредитов. Этим штатом сейчас занимается 4 сотрудника, нагрузка
среди которых распределена так:
Я хочу поровну поделить все активные кредиты между 4 сотрудниками.
Ответ Тома Кайта
Как вам такое решение:
ops$tkyte@ORA920LAP> create table user_state_served
2 as
3 select rownum user_id, 'TX' state_cd from all_objects where rownum <= 4
4 union all
5 select rownum+4 user_id, 'VA' state_cd from all_objects where rownum <= 3;
Table created.
ops$tkyte@ORA920LAP> create table loan_table
2 as
3 select 'TX' state_cd, 1 user_id from all_objects where rownum <= 150
4 union all
5 select 'TX', 2 from all_objects where rownum <= 50
6 union all
7 select 'TX', 3 from all_objects where rownum <= 75
8 union all
9 select 'TX', null from all_objects where rownum <= 125;
Table created.
ops$tkyte@ORA920LAP> insert into loan_table
2 select 'VA', 5 from loan_table
3 /
400 rows created.
ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
2 /
USER_ID COUNT(*)
---------- ----------
1 150
2 50
3 75
5 400
125
ops$tkyte@ORA920LAP> create or replace procedure redistribute_state(p_state_cd in varchar2)
2 as
3 type rowidArray is table of rowid INDEX BY BINARY_INTEGER;
4
5 l_user_id dbms_sql.number_table;
6 l_rowids rowidArray;
7 begin
8
9 select b.user_id, a.rid BULK COLLECT into l_user_id, l_rowids
10 from (
11 select state_cd,
12 ntile(buckets) over ( partition by buckets, state_cd order by state_cd ) idx,
13 loan_table.rowid rid
14 from loan_table,
15 (select count(*) BUCKETS
16 from user_state_served
17 where state_cd = p_state_cd)
18 where state_cd = p_state_cd
19 ) A,
20 (select user_id, row_number() over ( order by user_id ) rn
21 from user_state_served
22 where state_cd = p_state_cd ) b
23 where a.idx = b.rn;
24
25 forall i in 1 .. l_rowids.count
26 update loan_table set user_id = l_user_id(i) where rowid = l_rowids(i);
27 end;
28 /
Procedure created.
ops$tkyte@ORA920LAP> exec redistribute_state( 'TX' );
PL/SQL procedure successfully completed.
ops$tkyte@ORA920LAP> exec redistribute_state( 'VA' );
PL/SQL procedure successfully completed.
ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
2 /
USER_ID COUNT(*)
---------- ----------
1 100
2 100
3 100
4 100
5 134
6 133
7 133
7 rows selected.
Поскольку в версии 8i функцию ntile в plsql использовать нельзя, вот как
можно изменить это решение для версии Oracle 8i:
ops$tkyte@ORA920LAP> rollback;
Rollback complete.
ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
2 /
USER_ID COUNT(*)
---------- ----------
1 150
2 50
3 75
5 400
125
ops$tkyte@ORA920LAP> create or replace view V
2 as
3 select b.user_id, a.rid
4 from (
5 select state_cd,
6 ntile(buckets) over ( partition by buckets, state_cd order by state_cd ) idx,
7 loan_table.rowid rid
8 from loan_table,
9 (select count(*) BUCKETS
10 from user_state_served
11 where state_cd = userenv('client_info'))
12 where state_cd = userenv('client_info')
13 ) A,
14 (select user_id, row_number() over ( order by user_id ) rn
15 from user_state_served
16 where state_cd = userenv('client_info') ) b
17 where a.idx = b.rn;
View created.
ops$tkyte@ORA920LAP> create or replace procedure redistribute_state(p_state_cd in varchar2)
2 as
3 type rowidArray is table of rowid index by binary_integer;
4
5 l_user_id dbms_sql.number_table;
6 l_rowids rowidArray;
7 begin
8 dbms_application_info.set_client_info(p_state_cd);
9 select user_id, rid BULK COLLECT into l_user_id, l_rowids
10 from v;
11
12 forall i in 1 .. l_rowids.count
13 update loan_table set user_id = l_user_id(i) where rowid = l_rowids(i);
14 end;
15 /
Procedure created.
ops$tkyte@ORA920LAP> exec redistribute_state( 'TX' );
PL/SQL procedure successfully completed.
ops$tkyte@ORA920LAP> exec redistribute_state( 'VA' );
PL/SQL procedure successfully completed.
ops$tkyte@ORA920LAP> select user_id, count(*) from loan_table group by user_id
2 /
USER_ID COUNT(*)
---------- ----------
1 100
2 100
3 100
4 100
5 134
6 133
7 133
7 rows selected.
Комментарий читателя от 24 июня 2003 года
Том!
Хранимая процедура работает не совсем так, как мне хотелось бы. Например,
у меня есть 6 кредитов для в штате TX:
user_id loan_id
2 1
3 2
3 3
3 4
3 5
6
При выполнении твоей хранимой процедуры, она равномерно распределяет 6 кредитов среди
2 пользователей в группе, но меняет уже выделенные сотруднику задания. Например,
в данном случае она может взять кредит с идентификатором 1 и передать его
для работы сотруднику с user_id 3. Хотя этого не надо делать - надо
только забрать кредиты у перегруженных сотрудников и передать для управления
менее загруженным, не отбирая у них те кредиты, с которыми они уже работают.
Ответ Тома Кайта
Ну, идею вы поняли, так почему бы не попытаться решить самому?
Оригинал обсуждения этого вопроса можно найти
здесь.
Реструктуризация сайта проекта
Open Oracle. Дальнейшее развитие рассылки и, возможно, новые ответы от Тома Кайта.
Этот выпуск выйдет, вероятно, уже в начале августа. Я ухожу в отпуск. В свободное время
займусь подготовкой новых материалов и этой самой реструктуризацией сайта.
С 6 по 19 июля я буду находиться в урочище Лисья бухта в Крыму. Такой дядька с бородой
и в бандане. Можете спросить у Черного полковника, где стоит группа людей из
Вышгорода (Киева) и Питера, с детьми. Найдете, если что. Хотя, кому я нужен...
Напоследок обещанная цитата из Егора Летова ("Зря вы это все", альбом
"Сто лет одиночества"):
Что бы ни случилось - все к лучшему
Мертвые пчелы оглушительно гудят
Скошенные травы встают стеной
Съеденные птички упоительно поют
А только хорошо смеется тот, кто смеется последним
А только хорошо смеется тот, кто смеется последним
Что бы ни случилось - все к лучшему
Срубленные головы стремительно умнеют
Реки подо льдом кипят светло и зло
Выбитые зубы ослепительно скрипят
А только хорошо смеется тот, кто смеется последним
А только хорошо смеется тот, кто смеется последним
Что бы ни случилось - все к лучшему
Вырванные корни прорастают в небо
Пойманные рыбы ныряют ввысь
Срубленные головы пронзительно гудят
А только хорошо смеется тот, кто смеется последним
А только хорошо смеется тот, кто смеется последним
А только хорошо смеется тот, кто смеется последним
А только хорошо смеется тот, кто смеется последним