T-SQL: Wählen Sie die spezifische Übereinstimmung aus der Tabelle aus
-
16-10-2019 - |
Frage
Entschuldigung für den schrecklichen Titel. Ich konnte mich nicht besser einfallen lassen, um das Problem kurz zu beschreiben.
Hier ist das Szenario:
Benutzer können Serviceanweisungen einrichten, die an einer Arbeitsauftrag basieren, die auf dem Kunden der Bestellung, dem Kredittyp, der Bank, der Preisregion und Service der Bestellung basiert. Die Serviceanweisung wird mit den folgenden Parametern eingerichtet:
- Gilt für alle Kunden oder einen bestimmten Kunden
- Gilt für alle Kreditarten oder einen bestimmten Kredittyp
- Gilt für alle Banken oder eine bestimmte Bank
- Gilt für alle Preisregionen oder eine bestimmte Preisregion
- Gilt für einen bestimmten Dienst
Ein Wert von 1 in einer Spalte in der nachstehend beschriebenen Service_instruction -Tabelle entspricht "All".
Die Tabellenstruktur (die relevanten Teile sowieso):
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)
Ich muss ein Skript schreiben, das den spezifischsten Anweisungen für einen Dienst auswählt, falls vorhanden. Zum Beispiel:
Der Benutzer hat die folgenden Serviceanweisungen eingerichtet:
- (Alle Kunden), (alle Kreditarten), (alle Banken), (alle Preisregionen), (Service A), Anweisungen: Arbeit A.
- (Kunde 1), (alle Kreditarten), (alle Banken), (Region 3), (Service A), Anweisungen: Arbeit b
Eine Bestellung, die für Client 1, Region 3 und Service A eintritt, sollte "Arbeit b" angehängt haben. Eine Bestellung, die für Client 2, Region 3 und Service A eintritt, sollte "Arbeit ein" angehängt haben. Eine Bestellung, die für Service B kommt, sollte nichts angehängt haben. Usw.
Derzeit erfolgt dies in Code, aber die Umstände erfordern das Scripting. Gibt es eine Möglichkeit, dies zu tun, das eleganter ist (und vielleicht bessere Leistung) als das aktuelle Chaos von if ... Else und Isnulls, an dem ich arbeite?
Bearbeiten: Hier sind einige Beispieldaten.
service_instruction:
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'
bestellen]:
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
Work_order_line:
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
Das Endergebnis wäre:
Work_order_line:
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.'
Ein Wert von 1 für client_num, bank_num oder links_type_cd in service_instruction bedeutet, dass er für alle Kunden, Banken oder Kredittypen gilt. Um die Dinge weiter zu komplizieren, hängt die PREISBILDUNG IN SERVICE_Instruction vom Client ab. Wenn beispielsweise das Client_num 1 ist ("alle Clients"), zeigt ein Region_Num von 3 "alle Regionen" an. Wenn das Client_Num 3 ist, ist Region_Num 39 "alle Regionen". Ich bin nicht wirklich auf der Suche nach jemandem, der das Skript für mich aufschreibt, eher einen Zeiger in die richtige konzeptionelle Richtung, so dass die Komplikation wahrscheinlich vorerst ignoriert werden kann.
Hier ist, woran ich gearbeitet habe, um dies zu lösen und etwas für Kürze zu bearbeiten. Ja, es gibt einen Cursor. Ja, es wird in eine Produktionsumgebung versetzt. Nein, ich bin nicht glücklich darüber. Deshalb bin ich hierher gekommen!
--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;
Lösung
Mein erster Schritt wäre, die zu bewerten service_instruction
nach Spezifität/Allgemeinheit - Ich glaube nicht 1
S und Krawatten werden gebrochen von: bank_num
ist spezifischer als loan_type_cd
ist spezifischer als 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;
produziert:
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
Dies kann dann verbunden werden order
und work_order_line
und für die Zeile für jeden gefiltert work_order_line_num
mit der niedrigsten Allgemeinheit (gen
), vielleicht so etwas:
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
was produziert:
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
Anmerkungen:
- Ich habe angenommen, dass Sie sich partitionieren müssen
work_order_line_num
,order_num
undservice_num
Aber vielleicht sind nicht alle je nach PK von notwendigwork_order_line
- Ich habe das ignoriert
pricing_region
Die Komplexität, die Sie erwähnen, aber nicht vollständig angeben - hoffentlich können Sie sie auf ähnliche Weise wie die anderen in die Abfrage einarbeiten - Sie müssen den CTE wahrscheinlich mit Ihren Regeln für Spezifität/Allgemeinheit beheben
- Ich habe auf 2008R2 - YMMV getestet, wenn Sie in einer früheren Version sind