SQL — Назначить задания работникам — альтернатива циклу?

#sql #sql-server #sql-server-2019

#sql #sql-сервер #sql-server-2019

Вопрос:

В SQL Server 2019 у меня есть таблица неназначенных задач и другая таблица рабочих, которые доступны для работы над этими задачами. Мое требование состоит в том, чтобы распределить работников равномерно по доступным задачам, обновив WorkerID столбец в Tasks таблице с ID помощью работника, который будет выполнять задачу.

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

Учитывая таблицы со следующими структурами, я хотел бы знать, как выполнять эти назначения без использования цикла. Я подозреваю, что это можно сделать с помощью номеров строк, ранга или какой-либо другой подобной магии SQL Server, но я не смог в этом разобраться. Спасибо!

Таблица задач:

 JobID    JobName           WorkerID
------------------------------------
23       Carry Groceries   NULL
1234     Drive             NULL
6543     Dig               NULL
234567   Walk              NULL
78       Clean Room        NULL
54       Cook Dinner       NULL
2        Move Logs         NULL
34       Cut Grass         NULL
99       Milk Chickens     NULL
  

Таблица рабочих:

 WorkerID   WorkerName
---------------------
67         Larry
42         Sue
10         Peter
45         Steve
  

Ожидаемые результаты для Tasks таблицы:

 JobID    JobName           WorkerID
--------------------------------------
23       Carry Groceries   67
1234     Drive             42
6543     Dig               10
234567   Walk              45
78       Clean Room        67
54       Cook Dinner       42
2        Move Logs         10
34       Cut Grass         45
99       Milk Chickens     67
  

Ответ №1:

Вы можете назначить номер с помощью оконных функций, а затем join . Метод «циклического перебора»:

 with toupdate as (
      select t.*, row_number() over (order by (select null)) as seqnum
      from tasks t
     )
update toupdate
     from toupdate join
          (select w.*,
                  row_number() over (order by (select null)) as seqnum,
                  count(*) over () as cnt
           from workers w
          ) w
          on w.seqnum = ( (toupdate.seqnum - 1) % w.cnt)   1;
  

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

1. Спасибо, Гордон Линофф! Это отлично работает, хотя я обнаружил, что первым назначенным работником был второй работник, выбранный из рабочих. Поскольку они все равно просто выполняют цикл, это все равно соответствует спецификации, но для понимания будущего сопровождающего я вычел 1 из toupdate.seqnum перед тем, как применить его к модулю, и, похоже, он скорректировал его так, чтобы первый назначенный рабочий был первым выбранным рабочим: на w.seqnum= ((toupdate.seqnum — 1) % w.cnt) 1;

2. @user1876354 . . . Это кажется разумным, и ваше предложение — прекрасный способ его решения.