T-sql:从表中选择最具体的匹配

For*_*ine 5 sql-server query t-sql

为可怕的标题道歉。我想不出更好的方法来简要描述这个问题。

这是场景:

用户可以根据订单的客户、贷款类型、银行、定价区域和服务设置要附加到工作订单的服务说明。使用以下参数设置服务说明:

  1. 适用于所有客户或特定客户
  2. 适用于所有贷款类型或特定贷款类型
  3. 适用于所有银行或特定银行
  4. 适用于所有定价区域或特定定价区域
  5. 适用于特定服务

下面描述的 service_instruction 表的列中的值 1 等同于“全部”。

表结构(相关部分无论如何):

order (order_num INT PRIMARY KEY, client_num INT, loan_type INT, 
       bank_num INT, pricing_region INT)

work_order_line(work_order_line_num INT PRIMARY KEY, order_num INT, service_num INT,
                description TEXT)

service_instruction(instruction_num INT PRIMARY KEY, service_num INT,
                    service_description TEXT, client_num INT NULL,
                    bank_num INT NULL, region_num INT, loan_type_cd TINYINT) 
Run Code Online (Sandbox Code Playgroud)

我需要编写一个脚本,该脚本将为服务选择最具体的指令(如果存在)。例如:

用户已设置如下服务说明:

  1. (所有客户)、(所有贷款类型)、(所有银行)、(所有定价区域)、(服务 A)、说明:做工作 A
  2. (客户 1)、(所有贷款类型)、(所有银行)、(区域 3)、(服务 A)、说明:做工作 B

客户端 1、区域 3 和服务 A 的订单应附加“Do work B”。客户端 2、区域 3 和服务 A 的订单应附加“Do work A”。服务 B 的订单不应附加任何内容。等等。

目前这是在代码中完成的,但情况需要编写脚本。有没有一种方法可以比我正在处理的 IF..ELSE 和 ISNULL 的当前混乱更优雅(并且可能具有更好的性能)?

编辑:这是一些示例数据。

服务指令:

instruction_num | service_num | instruction_desc | bank_num | region_num | loan_type_cd | client_num
       3               251    'Take the photos, yeah'  17        96            3             1
       4               251  'Bid for debris removal.'  1         471           1             7
       7               251  'Bid for debris removal.   1          3            1             1                                    
                                 Do not perform'    
Run Code Online (Sandbox Code Playgroud)

[命令]:

order_num | client_num | loan_type | bank_num | pricing_region
    1           3            1          1             3
    2           7            3          1            471
    3           2            3          17            96
    4           5            2          6             17
Run Code Online (Sandbox Code Playgroud)

工作订单行:

work_order_line_num | order_num | service_num | description
         20              1             251         NULL
         21              2             251         NULL
         22              3             251         NULL
         26              4             251         NULL
Run Code Online (Sandbox Code Playgroud)

最终结果将是:

工作订单行:

work_order_line_num | order_num | service_num | description
         20              1             251    'Bid for debris removal. Do not perform.'
         21              2             251    'Bid for debris removal.'
         22              3             251    'Take the photos, yeah'
         26              4             251    'Bid for debris removal. Do not perform.'
Run Code Online (Sandbox Code Playgroud)

service_instruction 中client_num、bank_num 或loan_type_cd 的值为1 表示它分别适用于所有客户、银行或贷款类型。更复杂的是,service_instruction 中的定价区域取决于客户端。例如,当client_num 为1(“所有客户端”)时,region_num 为3 表示“所有区域”。当client_num 为3 时,region_num 39 为“所有区域”。我并不是真的在寻找某人为我写出脚本,更多的是在正确的概念方向上的指针,因此现在可能可以忽略这种复杂性。

这是我一直在努力解决这个问题,为简洁起见进行了一些编辑。是的,里面有一个光标。是的,它将被放置在生产环境中。不,我不高兴。这就是我来这里的原因!

--Table var. containing orders we need to update
DECLARE @order_numbers TABLE (order_num INT PRIMARY KEY
    , client_num SMALLINT NOT NULL
    , order_type_cd TINYINT NOT NULL
    , loan_type_cd TINYINT NOT NULL
    , bank_num TINYINT NOT NULL
    , pricing_region INT NOT NULL
    , service_num INT NOT NULL
    , service_instruction_num SMALLINT NULL);

--Table containing the match data
DECLARE @instruction_matches TABLE (instruction_num SMALLINT PRIMARY KEY
        , client_match TINYINT
    , loan_type_match BIT
    , bank_match BIT
    , region_match BIT
    , total_matches TINYINT);

DECLARE @best_instruction SMALLINT
    , @order_num INT
    , @client_num SMALLINT
    , @loan_type_cd TINYINT
    , @bank_num TINYINT
    , @region_num INT
    , @service_num INT;

--Get the orders to update
INSERT INTO @order_numbers (order_num
    , client_num
    , order_type_cd
    , loan_type_cd
    , bank_num
    , pricing_region
    , service_num)
SELECT o.order_num, oc.client_num, o.order_type_cd, oc.loan_type_cd, oc.bank_num, oc.pricing_region, wol.service_num 
FROM [order] o
    inner join order_context oc ON o.context_num = oc.context_num
    inner join work_order_line wol ON o.order_num = wol.order_num
WHERE o.status_cd = 1 AND o.exchange_status_cd = 2 AND o.received_from_hub_ind = 1
    AND o.viewed_dt IS NULL

DECLARE o_cursor CURSOR LOCAL
FOR SELECT order_num FROM @order_numbers;
OPEN o_cursor;
FETCH NEXT FROM o_cursor
INTO @order_num;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @service_num = (SELECT service_num FROM @order_numbers WHERE order_num = @order_num);
    SET @client_num = (SELECT client_num FROM @order_numbers WHERE order_num = @order_num);
    SET @loan_type_cd = (SELECT loan_type_cd FROM @order_numbers WHERE order_num = @order_num);
    SET @bank_num = (SELECT bank_num FROM @order_numbers WHERE order_num = @order_num);
    SET @region_num = (SELECT pricing_region FROM @order_numbers WHERE order_num = @order_num);

    INSERT INTO @instruction_matches (instruction_num, client_match, loan_type_match, bank_match, region_match)
    SELECT
        instruction_num
        , CASE 
            WHEN client_num = @client_num OR client_num = 1
                THEN 1
            ELSE 0
          END
        , CASE
            WHEN loan_type_cd = @loan_type_cd OR loan_type_cd = 1
                THEN 1
            ELSE 0
          END
        , CASE
            WHEN bank_num = @bank_num OR bank_num = 1
                THEN 1
            ELSE 0
          END
        , CASE -- region_num = 4 means INSPECTIONS ONLY. will need to be re-written for pres.
            WHEN region_num = @region_num
                OR (client_num != 1 AND region_num = (SELECT region_num FROM region 
                                                      WHERE client_num = @client_num
                                                        AND region_type_cd = 2 
                                                        AND order_item_type_cd = 2
                                                        AND region_id = 1)
                )
                THEN 2
            WHEN client_num = 1 AND region_num = 4
                THEN 1
            ELSE 0
          END
    FROM service_instruction
    WHERE service_num = @service_num;

    UPDATE @instruction_matches
    SET total_matches = (client_match + loan_type_match + bank_match + region_match);

    SET @best_instruction = (SELECT TOP(1) instruction_num FROM @instruction_matches ORDER BY total_matches DESC);

    UPDATE @order_numbers
    SET service_instruction_num = @best_instruction 
    WHERE order_num = @order_num;

    FETCH NEXT FROM o_cursor
    INTO @order_num;
END;
Run Code Online (Sandbox Code Playgroud)

Jac*_*las 2

我的第一步是service_instruction按特异性/一般性进行排名 - 我认为您没有提供足够的信息来确定您所追求的内容,但假设它是基于1s 的数量并且关系被打破:bank_num更具体比loan_type_cd更具体client_num

select *, row_number() over ( order by case when bank_num=1 then 1 else 0 end+
                                       case when loan_type_cd=1 then 1 else 0 end+
                                       case when client_num=1 then 1 else 0 end,
                                     case when bank_num=1 then 1 else 0 end,
                                     case when loan_type_cd=1 then 1 else 0 end,
                                     case when client_num=1 then 1 else 0 end ) as gen
from service_instruction;
Run Code Online (Sandbox Code Playgroud)

产生:

instruction_num service_num instruction_desc    bank_num    region_num  loan_type_cd    client_num  gen
3   251 Take the photos, yeah   17  96  3   1   1
4   251 Bid for debris removal. 1   471 1   7   2
7   251 Bid for debris removal. Do not perform  1   3   1   1   3
Run Code Online (Sandbox Code Playgroud)

然后可以将其连接到orderand并过滤每个具有最低通用性 ( )work_order_line的行,也许是这样的:work_order_line_numgen

with w as (
    select *, row_number() over ( order by case when bank_num=1 then 1 else 0 end+
                                           case when loan_type_cd=1 then 1 else 0 end+
                                           case when client_num=1 then 1 else 0 end,
                                         case when bank_num=1 then 1 else 0 end,
                                         case when loan_type_cd=1 then 1 else 0 end,
                                         case when client_num=1 then 1 else 0 end ) as gen
    from service_instruction )
select *
from( select l.*, instruction_desc, row_number() over ( partition by l.work_order_line_num, 
                                                                     l.order_num, 
                                                                     l.service_num
                                                        order by gen ) as gen_rank
      from work_order_line l join [order] o on(o.order_num=l.order_num)
           join w on( l.service_num=w.service_num 
                      and (o.bank_num=w.bank_num or w.bank_num=1) 
                      and (o.loan_type_cd=w.loan_type_cd or w.loan_type_cd=1)
                      and (o.client_num=w.client_num or w.client_num=1)) ) z
where gen_rank=1
Run Code Online (Sandbox Code Playgroud)

其产生:

work_order_line_num order_num   service_num instruction_desc    gen_rank
20  1   251 Bid for debris removal. Do not perform  1
21  2   251 Bid for debris removal. 1
22  3   251 Take the photos, yeah   1
26  4   251 Bid for debris removal. Do not perform  1
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 我假设您需要按 进行分区work_order_line_numorder_numservice_num也许并非全部都是必要的,具体取决于 PKwork_order_line
  2. 我忽略了pricing_region您提到的复杂性,但没有完全指定 - 希望您能够以与其余部分类似的方式将其处理到查询中
  3. 您可能需要使用您的特殊性/通用性规则来修复 CTE
  4. 我在 2008R2 - YMMV 上进行了测试(如果您使用的是早期版本)