SQL WHERE ID IN (id1, id2, …, idn)


170

ฉันต้องเขียนแบบสอบถามเพื่อดึงรายการรหัสขนาดใหญ่

เรารองรับแบ็กเอนด์จำนวนมาก (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ... ) ดังนั้นฉันต้องเขียน SQL มาตรฐาน

ขนาดของชุด id อาจมีขนาดใหญ่แบบสอบถามจะถูกสร้างขึ้นโดยทางโปรแกรม ดังนั้นวิธีที่ดีที่สุดคืออะไร?

1) การเขียนแบบสอบถามโดยใช้ IN

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

คำถามของฉันที่นี่คือ จะเกิดอะไรขึ้นถ้า n ใหญ่มาก? นอกจากนี้สิ่งที่เกี่ยวกับประสิทธิภาพ?

2) การเขียนแบบสอบถามโดยใช้ OR

SELECT * FROM TABLE WHERE ID = id1 OR ID = id2 OR ... OR ID = idn

ฉันคิดว่าวิธีการนี้ไม่มีขีด จำกัด n แต่จะเกี่ยวกับประสิทธิภาพหาก n มีขนาดใหญ่มาก

3) การเขียนวิธีแก้ปัญหาแบบเป็นโปรแกรม:

  foreach (var id in myIdList)
  {
      var item = GetItemByQuery("SELECT * FROM TABLE WHERE ID = " + id);
      myObjectList.Add(item);
  }

เราประสบปัญหาบางอย่างกับวิธีการนี้เมื่อเซิร์ฟเวอร์ฐานข้อมูลถูกสอบถามผ่านเครือข่าย โดยปกติแล้วจะดีกว่าหากทำแบบสอบถามหนึ่งรายการที่ดึงผลลัพธ์ทั้งหมดเมื่อเทียบกับสร้างแบบสอบถามจำนวนน้อย บางทีฉันผิด

อะไรจะเป็นทางออกที่ถูกต้องสำหรับปัญหานี้


1
ตัวเลือก 1 ลดเวลาตอบสนองของเซิร์ฟเวอร์ SQL ลงอย่างมากโดยเลือก 7k ID ซึ่งไม่มีอยู่จริง ปกติแบบสอบถามใช้เวลาประมาณ 1300ms จะช่วยลดการใช้ 80 มิลลิวินาทีIN! ฉันทำของฉันเป็นวิธีแก้ปัญหาของคุณ 1 + 3 เพียงแค่แบบสอบถามสุดท้ายเป็นหนึ่งสตริงแบบสอบถามยาวส่งไปยัง SQL เพื่อดำเนินการ
Piotr Kula

คำตอบ:


108

ตัวเลือกที่ 1 เป็นทางออกที่ดีเท่านั้น

ทำไม?

  • ตัวเลือกที่ 2 ทำเช่นเดียวกัน แต่คุณทำซ้ำชื่อคอลัมน์หลายครั้ง นอกจากนี้โปรแกรม SQL ไม่ทราบทันทีว่าคุณต้องการตรวจสอบว่าค่าเป็นหนึ่งในค่าในรายการคงที่หรือไม่ อย่างไรก็ตามเอ็นจิ้น SQL ที่ดีสามารถเพิ่มประสิทธิภาพให้มีประสิทธิภาพเท่าเทียมกันINได้ ยังมีปัญหาการอ่านแม้ว่า ...

  • ตัวเลือกที่ 3 เป็นเรื่องน่ากลัวในเรื่องประสิทธิภาพ มันจะส่งแบบสอบถามทุกวงและค้อนฐานข้อมูลที่มีแบบสอบถามขนาดเล็ก นอกจากนี้ยังป้องกันไม่ให้ใช้การปรับให้เหมาะสมสำหรับ "value เป็นหนึ่งในรายการที่กำหนด"


2
ฉันเห็นด้วย แต่โปรดทราบว่าในรายการมีข้อ จำกัด ใน RDMS จำนวนมากดังนั้นคุณจะต้องให้เราใช้วิธีการแก้ปัญหาของ @Ed Guiness แต่ที่นี่ตารางชั่วคราวแตกต่างกันระหว่าง RDBMS (มีประสิทธิภาพสำหรับปัญหาที่ซับซ้อนคุณไม่สามารถใช้ SQL มาตรฐานที่บริสุทธิ์ได้)
mmmmmm

28

แนวทางอื่นอาจใช้ตารางอื่นเพื่อเก็บค่า id ตารางอื่นนี้สามารถเข้าร่วม Inner Table ของคุณเพื่อ จำกัด แถวที่ส่งคืน สิ่งนี้จะมีข้อได้เปรียบที่สำคัญซึ่งคุณไม่จำเป็นต้องใช้ SQL แบบไดนามิก (เป็นปัญหาในช่วงเวลาที่ดีที่สุด) และคุณจะไม่มีข้อต่อ IN ยาว ๆ

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

อัปเดต : แม้ว่าคุณจะสามารถใช้ตารางชั่วคราวได้ แต่ฉันก็ไม่ได้หมายความว่าคุณต้องหรือควร ตารางถาวรที่ใช้สำหรับข้อมูลชั่วคราวเป็นโซลูชันทั่วไปที่มีข้อดีนอกเหนือจากที่อธิบายไว้ที่นี่


1
แต่คุณจะส่งรายการรหัสที่คุณต้องการได้อย่างไร (เนื่องจากคุณไม่สามารถเลือกช่วงหรืออะไรทำนองนั้น)
raam86

1
@ raam86: รายการ ID อาจได้รับโดยใช้selectคำสั่งในตารางอื่น รายการจะถูกส่งผ่านเป็นตารางอื่น ๆ ที่คุณกำลังinner joinต่อต้าน
bdforbes

19

สิ่งที่ Ed Guiness แนะนำคือผู้สนับสนุนประสิทธิภาพจริงๆฉันมีคำถามเช่นนี้

select * from table where id in (id1,id2.........long list)

ฉันทำอะไรลงไป :

DECLARE @temp table(
            ID  int
            )
insert into @temp 
select * from dbo.fnSplitter('#idlist#')

จากนั้นด้านในเข้าร่วมชั่วคราวกับตารางหลัก:

select * from table inner join temp on temp.id = table.id

และปรับปรุงประสิทธิภาพอย่างมาก


1
สวัสดี fnSplitter เป็นฟังก์ชั่นจาก MSSQL หรือไม่? เพราะฉันหามันไม่เจอ
WiiMaxx

มันไม่ได้เป็นสิ่งมาตรฐาน พวกเขาต้องหมายความว่าพวกเขาเขียนฟังก์ชันนั้นเพื่อจุดประสงค์นี้หรือมีแอปพลิเคชันที่จัดหามาให้แล้ว
underscore_d

fnSplitter เป็นฟังก์ชั่นที่สร้างขึ้นโดย Ritu คุณสามารถค้นหาบนอินเทอร์เน็ต / google ที่คล้ายกันของมัน
Bashar Abu Shamaa

9

ตัวเลือกแรกเป็นตัวเลือกที่ดีที่สุดแน่นอน

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

อย่างไรก็ตามเมื่อพิจารณาว่ารายการรหัสมีขนาดใหญ่มากพูดได้หลายล้านรายการคุณควรพิจารณาขนาดก้อนตามด้านล่าง:

  • แบ่งรายชื่อ Ids ของคุณเป็นจำนวนคงที่พูด 100
  • ขนาดของก้อนควรตัดสินใจตามขนาดหน่วยความจำของเซิร์ฟเวอร์ของคุณ
  • สมมติว่าคุณมี 10,000 รหัสคุณจะมี 10,000/100 = 100 ชิ้น
  • ประมวลผลทีละอันส่งผลให้มีการเรียกใช้ฐานข้อมูล 100 ครั้งเพื่อเลือก

ทำไมคุณควรแบ่งเป็นกลุ่ม?

คุณจะไม่ได้รับข้อยกเว้นหน่วยความจำล้นซึ่งเป็นเรื่องปกติในสถานการณ์เช่นคุณ คุณจะได้รับจำนวนการโทรฐานข้อมูลที่ดีที่สุดส่งผลให้ประสิทธิภาพดีขึ้น

มันได้ผลเสมอเหมือนมีเสน่ห์สำหรับฉัน หวังว่ามันจะใช้ได้กับเพื่อนนักพัฒนาของฉันด้วย :)


4

การดำเนินการ SELECT * จาก MyTable โดยที่ id in () คำสั่งบนตาราง Azure SQL ที่มี 500 ล้านเร็กคอร์ดส่งผลให้เวลารอ> 7 นาที!

ทำสิ่งนี้แทนผลลัพธ์ที่ส่งคืนทันที:

select b.id, a.* from MyTable a
join (values (250000), (2500001), (2600000)) as b(id)
ON a.id = b.id

ใช้การเข้าร่วม


3

ในระบบฐานข้อมูลส่วนใหญ่IN (val1, val2, …)และชุดของได้ORรับการปรับให้เหมาะกับแผนเดียวกัน

วิธีที่สามจะนำเข้ารายการค่าลงในตารางชั่วคราวและเข้าร่วมซึ่งมีประสิทธิภาพมากขึ้นในระบบส่วนใหญ่หากมีค่าจำนวนมาก

คุณอาจต้องการอ่านบทความนี้:


3

ตัวอย่างที่ 3 จะเป็นนักแสดงที่แย่ที่สุดเพราะคุณตีฐานข้อมูลนับครั้งไม่ถ้วนด้วยเหตุผลที่ไม่ชัดเจน

การโหลดข้อมูลลงในตารางชั่วคราวแล้วเข้าร่วมในนั้นจะเร็วที่สุด หลังจากนั้น IN ควรทำงานได้เร็วกว่ากลุ่ม OR เล็กน้อย


2

ฉันคิดว่าคุณหมายถึง SqlServer แต่ใน Oracle คุณมีข้อ จำกัด ที่เข้มงวดว่าคุณสามารถระบุองค์ประกอบ IN ได้มากถึง 1,000 รายการ


1
แม้แต่ SQL Server ก็หยุดทำงานหลังจากองค์ประกอบ ~ 40k IN ตาม MSDN: การรวมค่าจำนวนมาก (หลายพันรายการ) ในส่วนคำสั่ง IN สามารถใช้ทรัพยากรและส่งคืนข้อผิดพลาด 8623 หรือ 8632 เพื่อแก้ไขปัญหานี้ให้จัดเก็บรายการในรายการ IN ในตาราง
jahav
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.