จะปลอดภัยหรือไม่ที่จะพึ่งพาคำสั่งของส่วนคำสั่งเอาท์พุทของ INSERT?


19

รับตารางนี้:

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);

ในสองสถานการณ์ที่แตกต่างกันเล็กน้อยฉันต้องการแทรกแถวและคืนค่าจากคอลัมน์ข้อมูลประจำตัว

สถานการณ์ 1

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
   (VALUES
      ('Blue', 'New', 1234),
      ('Blue', 'Cancel', 4567),
      ('Red', 'New', 5678)
   ) t (Color, Action, Code)
;

สถานการณ์ที่ 2

CREATE TABLE #Target (
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL,
   Code int NOT NULL,
   PRIMARY KEY CLUSTERED (Color, Action)
);

-- Bulk insert to the table the same three rows as above by any means

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;

คำถาม

ฉันสามารถพึ่งพาค่าเอกลักษณ์ที่ส่งคืนจากการdbo.Targetแทรกตารางเพื่อส่งคืนตามลำดับที่มีอยู่ใน 1) VALUESส่วนคำสั่งและ 2) #Targetตารางเพื่อให้ฉันสามารถเชื่อมโยงพวกเขาตามตำแหน่งของพวกเขาในชุดแถวเอาท์พุท

สำหรับการอ้างอิง

นี่คือโค้ด C # ที่ถูกลดขนาดบางส่วนที่แสดงให้เห็นถึงสิ่งที่เกิดขึ้นในแอปพลิเคชัน (สถานการณ์ที่ 1 ซึ่งเร็ว ๆ นี้จะถูกแปลงเป็นใช้SqlBulkCopy):

public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
   var targetList = targets.ToList();
   const string insertSql = @"
      INSERT dbo.Target (
         CoreItemId,
         TargetDateTimeUtc,
         TargetTypeId,
      )
      OUTPUT
         Inserted.TargetId
      SELECT
         input.CoreItemId,
         input.TargetDateTimeUtc,
         input.TargetTypeId,
      FROM
         (VALUES
            {0}
         ) input (
            CoreItemId,
            TargetDateTimeUtc,
            TargetTypeId
         );";
   var results = Connection.Query<DbTargetInsertResult>(
      string.Format(
         insertSql,
         string.Join(
            ", ",
            targetList
               .Select(target => $@"({target.CoreItemId
                  }, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
                  }', {(byte) target.TargetType
                  })";
               )
         )
      )
      .ToList();
   return targetList
      .Zip( // The correlation that relies on the order of the two inputs being the same
         results,
         (inputTarget, insertResult) => new Target(
            insertResult.TargetId, // with the new TargetId to replace null.
            inputTarget.TargetDateTimeUtc,
            inputTarget.CoreItemId,
            inputTarget.TargetType
         )
      )
      .ToList()
      .AsReadOnly();
}

คำตอบ:


22

ฉันสามารถพึ่งพาค่า identity ที่ส่งคืนจากการแทรกตาราง dbo.Target ที่จะส่งคืนตามลำดับที่มีอยู่ใน 1) VALUES clause และ 2) #Target table เพื่อให้ฉันสามารถสัมพันธ์กับตำแหน่งใน rowset เอาต์พุต ไปที่อินพุตต้นฉบับหรือไม่

ไม่คุณไม่สามารถพึ่งพาสิ่งใดที่จะรับประกันได้หากไม่มีการรับรองเอกสารจริง เอกสารระบุอย่างชัดเจนว่าไม่มีการรับประกันดังกล่าว

SQL Server ไม่รับประกันลำดับที่แถวถูกประมวลผลและส่งคืนโดยคำสั่ง DML โดยใช้คำสั่ง OUTPUT ขึ้นอยู่กับแอปพลิเคชันที่จะรวมส่วนคำสั่ง WHERE ที่เหมาะสมที่สามารถรับประกันความหมายที่ต้องการหรือเข้าใจว่าเมื่อหลายแถวอาจมีคุณสมบัติสำหรับการดำเนินการ DML ไม่มีคำสั่งที่รับประกัน

สิ่งนี้จะขึ้นอยู่กับสมมติฐานที่ไม่มีเอกสารจำนวนมาก

  1. ลำดับที่แถวจะถูกส่งออกจากการสแกนคงที่อยู่ในลำดับเดียวกันกับค่ามาตรา (ฉันไม่เคยเห็นพวกเขาแตกต่างกัน แต่ AFAIK นี้ไม่รับประกัน)
  2. ลำดับที่แถวถูกแทรกจะเหมือนกับลำดับที่พวกมันออกจากการสแกนค่าคงที่ (ไม่แน่นอนเสมอไป)
  3. หากใช้แผนการดำเนินการ "wide" (ต่อดัชนี) ค่าจากส่วนคำสั่งย่อยจะถูกดึงจากตัวดำเนินการปรับปรุงดัชนีแบบคลัสเตอร์และไม่ใช่ของดัชนีรองใด ๆ
  4. ว่าคำสั่งมีการประกันเพื่อที่จะรักษาหลังจากนั้น - เช่นเมื่อบรรจุภัณฑ์แถวขึ้นสำหรับการส่งผ่านเครือข่าย
  5. แม้ว่าคำสั่งซื้อจะปรากฏขึ้นในขณะนี้การใช้งานการเปลี่ยนแปลงคุณสมบัติเช่นการแทรกแบบขนานจะไม่เปลี่ยนลำดับในอนาคต (ในปัจจุบันหากคำสั่ง OUTPUT ถูกระบุไว้ในคำสั่ง INSERT … SELECT เพื่อส่งคืนผลลัพธ์ให้กับลูกค้า ปิดใช้งานโดยทั่วไปรวมถึง INSERTs )

ตัวอย่างของจุดที่สองที่ล้มเหลว (สมมุติว่า PK ของคลัสเตอร์(Color, Action)) สามารถมองเห็นได้ถ้าคุณเพิ่ม 600 แถวในVALUESประโยค จากนั้นแผนจะมีตัวดำเนินการเรียงลำดับก่อนที่จะแทรกดังนั้นการสูญเสียลำดับเดิมของคุณในVALUESข้อ

มีวิธีการจัดทำเอกสารเพื่อบรรลุเป้าหมายของคุณและนี่คือการเพิ่มหมายเลขลงในแหล่งที่มาและใช้MERGEแทนINSERT

MERGE dbo.Target
USING (VALUES (1, 'Blue', 'New', 1234),
              (2, 'Blue', 'Cancel', 4567),
              (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ON 1 = 0
WHEN NOT MATCHED THEN
  INSERT (Color,
          Action,
          Code)
  VALUES (Color,
          Action,
          Code)
OUTPUT t.SourceId,
       inserted.TargetId; 

ป้อนคำอธิบายรูปภาพที่นี่

@a_horse_with_no_name

การรวมจำเป็นหรือไม่ คุณไม่สามารถทำได้insert into ... select ... from (values (..)) t (...) order by sourceidหรือ

ใช่คุณทำได้ การรับประกันการสั่งซื้อใน SQL Server ...ระบุว่า

คำสั่ง INSERT ที่ใช้ SELECT กับ ORDER BY เพื่อเติมแถวจะช่วยรับประกันว่าจะคำนวณค่าตัวตนได้อย่างไร แต่ไม่ใช่ลำดับที่แทรกแถว

ดังนั้นคุณสามารถใช้

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES (1, 'Blue', 'New', 1234),
        (2, 'Blue', 'Cancel', 4567),
        (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ORDER BY t.SourceId

ป้อนคำอธิบายรูปภาพที่นี่

สิ่งนี้จะรับประกันได้ว่าค่าประจำตัวจะได้รับมอบหมายตามลำดับt.SourceIdแต่ไม่ใช่ว่าจะส่งออกในลำดับใด ๆ หรือว่าค่าคอลัมน์ข้อมูลประจำตัวที่กำหนดไม่มีช่องว่าง (เช่นถ้าพยายามแทรกพร้อมกัน)


2
บิตสุดท้ายนี้เกี่ยวกับศักยภาพของช่องว่างและเอาต์พุตที่ไม่ได้อยู่ในลำดับเฉพาะทำให้สิ่งต่าง ๆ น่าสนใจยิ่งขึ้นเมื่อพยายามเชื่อมโยงกลับไปยังอินพุต ผมคิดว่าคำสั่งซื้อโดยในใบสมัครจะทำผลงานได้ MERGEแต่ดูเหมือนว่าปลอดภัยและชัดเจนเพียงแค่ใช้
ErikE

ใช้OUTPUT ... INTO [#temp]ไวยากรณ์แล้วSELECT ... FROM [#temp] ORDER BYรับประกันการส่งออก
Max Vernon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.