สำหรับสคีมาที่สมมติและข้อมูลตัวอย่างต่อไปนี้
CREATE TABLE dbo.RecipeIngredients
(
RecipeId INT NOT NULL ,
IngredientID INT NOT NULL ,
Quantity INT NOT NULL ,
UOM INT NOT NULL ,
CONSTRAINT RecipeIngredients_PK
PRIMARY KEY ( RecipeId, IngredientID ) WITH (IGNORE_DUP_KEY = ON)
) ;
INSERT INTO dbo.RecipeIngredients
SELECT TOP (210000) ABS(CRYPT_GEN_RANDOM(8)/50000),
ABS(CRYPT_GEN_RANDOM(8) % 100),
ABS(CRYPT_GEN_RANDOM(8) % 10),
ABS(CRYPT_GEN_RANDOM(8) % 5)
FROM master..spt_values v1,
master..spt_values v2
SELECT DISTINCT RecipeId, 'X' AS Name
INTO Recipes
FROM dbo.RecipeIngredients
มีแถวส่วนผสม 205,009 แถวและสูตร 42,613 สูตร สิ่งนี้จะแตกต่างกันเล็กน้อยในแต่ละครั้งเนื่องจากองค์ประกอบแบบสุ่ม
มันจะถือว่ามีจำนวนไม่มากนัก (ผลลัพธ์หลังจากการรันตัวอย่างคือ 217 กลุ่มสูตรซ้ำที่มีสองหรือสามสูตรต่อกลุ่ม) กรณีทางพยาธิวิทยาที่มากที่สุดตามตัวเลขใน OP จะเป็น 48,000 รายการที่ซ้ำกัน
สคริปต์ในการตั้งค่านั้นคือ
DROP TABLE dbo.RecipeIngredients,Recipes
GO
CREATE TABLE Recipes(
RecipeId INT IDENTITY,
Name VARCHAR(1))
INSERT INTO Recipes
SELECT TOP 48000 'X'
FROM master..spt_values v1,
master..spt_values v2
CREATE TABLE dbo.RecipeIngredients
(
RecipeId INT NOT NULL ,
IngredientID INT NOT NULL ,
Quantity INT NOT NULL ,
UOM INT NOT NULL ,
CONSTRAINT RecipeIngredients_PK
PRIMARY KEY ( RecipeId, IngredientID )) ;
INSERT INTO dbo.RecipeIngredients
SELECT RecipeId,IngredientID,Quantity,UOM
FROM Recipes
CROSS JOIN (SELECT 1,1,1 UNION ALL SELECT 2,2,2 UNION ALL SELECT 3,3,3 UNION ALL SELECT 4,4,4) I(IngredientID,Quantity,UOM)
เรื่องต่อไปนี้เสร็จสมบูรณ์ในเวลาไม่ถึงวินาทีบนเครื่องของฉันสำหรับทั้งสองกรณี
CREATE TABLE #Concat
(
RecipeId INT,
concatenated VARCHAR(8000),
PRIMARY KEY (concatenated, RecipeId)
)
INSERT INTO #Concat
SELECT R.RecipeId,
ISNULL(concatenated, '')
FROM Recipes R
CROSS APPLY (SELECT CAST(IngredientID AS VARCHAR(10)) + ',' + CAST(Quantity AS VARCHAR(10)) + ',' + CAST(UOM AS VARCHAR(10)) + ','
FROM dbo.RecipeIngredients RI
WHERE R.RecipeId = RecipeId
ORDER BY IngredientID
FOR XML PATH('')) X (concatenated);
WITH C1
AS (SELECT DISTINCT concatenated
FROM #Concat)
SELECT STUFF(Recipes, 1, 1, '')
FROM C1
CROSS APPLY (SELECT ',' + CAST(RecipeId AS VARCHAR(10))
FROM #Concat C2
WHERE C1.concatenated = C2.concatenated
ORDER BY RecipeId
FOR XML PATH('')) R(Recipes)
WHERE Recipes LIKE '%,%,%'
DROP TABLE #Concat
หนึ่งข้อแม้
ฉันคิดว่าความยาวของสตริงที่ต่อกันนั้นจะต้องไม่เกิน 896 ไบต์ หากทำเช่นนี้จะทำให้เกิดข้อผิดพลาดในเวลาทำงานแทนที่จะล้มเหลวอย่างเงียบ ๆ คุณจะต้องลบคีย์หลัก (และดัชนีที่สร้างโดยนัย) ออกจาก#temp
ตาราง ความยาวสูงสุดของสตริงที่ต่อกันในการตั้งค่าการทดสอบของฉันคือ 125 ตัวอักษร
หากสตริงที่ต่อกันยาวเกินไปที่จะจัดทำดัชนีดังนั้นประสิทธิภาพของการXML PATH
สืบค้นสุดท้ายที่รวมสูตรที่เหมือนกันอาจไม่ดีนัก การติดตั้งและใช้การรวมสตริง CLR ที่กำหนดเองจะเป็นวิธีการหนึ่งในการแก้ปัญหาที่สามารถทำการเชื่อมต่อกับหนึ่งรอบของข้อมูลมากกว่าการเข้าร่วมด้วยตนเองที่ไม่ได้จัดทำดัชนี
SELECT YourClrAggregate(RecipeId)
FROM #Concat
GROUP BY concatenated
ฉันก็ลอง
WITH Agg
AS (SELECT RecipeId,
MAX(IngredientID) AS MaxIngredientID,
MIN(IngredientID) AS MinIngredientID,
SUM(IngredientID) AS SumIngredientID,
COUNT(IngredientID) AS CountIngredientID,
CHECKSUM_AGG(IngredientID) AS ChkIngredientID,
MAX(Quantity) AS MaxQuantity,
MIN(Quantity) AS MinQuantity,
SUM(Quantity) AS SumQuantity,
COUNT(Quantity) AS CountQuantity,
CHECKSUM_AGG(Quantity) AS ChkQuantity,
MAX(UOM) AS MaxUOM,
MIN(UOM) AS MinUOM,
SUM(UOM) AS SumUOM,
COUNT(UOM) AS CountUOM,
CHECKSUM_AGG(UOM) AS ChkUOM
FROM dbo.RecipeIngredients
GROUP BY RecipeId)
SELECT A1.RecipeId AS RecipeId1,
A2.RecipeId AS RecipeId2
FROM Agg A1
JOIN Agg A2
ON A1.MaxIngredientID = A2.MaxIngredientID
AND A1.MinIngredientID = A2.MinIngredientID
AND A1.SumIngredientID = A2.SumIngredientID
AND A1.CountIngredientID = A2.CountIngredientID
AND A1.ChkIngredientID = A2.ChkIngredientID
AND A1.MaxQuantity = A2.MaxQuantity
AND A1.MinQuantity = A2.MinQuantity
AND A1.SumQuantity = A2.SumQuantity
AND A1.CountQuantity = A2.CountQuantity
AND A1.ChkQuantity = A2.ChkQuantity
AND A1.MaxUOM = A2.MaxUOM
AND A1.MinUOM = A2.MinUOM
AND A1.SumUOM = A2.SumUOM
AND A1.CountUOM = A2.CountUOM
AND A1.ChkUOM = A2.ChkUOM
AND A1.RecipeId <> A2.RecipeId
WHERE NOT EXISTS (SELECT *
FROM (SELECT *
FROM RecipeIngredients
WHERE RecipeId = A1.RecipeId) R1
FULL OUTER JOIN (SELECT *
FROM RecipeIngredients
WHERE RecipeId = A2.RecipeId) R2
ON R1.IngredientID = R2.IngredientID
AND R1.Quantity = R2.Quantity
AND R1.UOM = R2.UOM
WHERE R1.RecipeId IS NULL
OR R2.RecipeId IS NULL)
วิธีนี้ใช้ได้ผลดีเมื่อมีการทำซ้ำค่อนข้างน้อย (น้อยกว่าหนึ่งวินาทีสำหรับข้อมูลตัวอย่างแรก) แต่ทำงานได้ไม่ดีในกรณีที่เกิดพยาธิสภาพเนื่องจากการรวมครั้งแรกส่งคืนผลลัพธ์ที่เหมือนกันทุกครั้งRecipeID
ดังนั้นจึงไม่สามารถลดจำนวน เปรียบเทียบเลย