LINQ ไปยัง SQL Left Outer เข้าร่วม


140

แบบสอบถามนี้เทียบเท่ากับการLEFT OUTERเข้าร่วมหรือไม่

//assuming that I have a parameter named 'invoiceId' of type int
from c in SupportCases
let invoice = c.Invoices.FirstOrDefault(i=> i.Id == invoiceId)
where (invoiceId == 0 || invoice != null)    
select new 
{
      Id = c.Id
      , InvoiceId = invoice == null ? 0 : invoice.Id
}

คำตอบ:


167

ไม่มาก - เนื่องจากแต่ละแถว "ซ้าย" ในการเข้าร่วมจากด้านนอกจะตรงกับ 0-n "ขวา" แถว (ในตารางที่สอง) โดยที่การจับคู่ของคุณเพียง 0-1 ในการเข้าร่วมด้านนอกคุณต้องSelectManyและDefaultIfEmptyตัวอย่างเช่น:

var query = from c in db.Customers
            join o in db.Orders
               on c.CustomerID equals o.CustomerID into sr
            from x in sr.DefaultIfEmpty()
            select new {
               CustomerID = c.CustomerID, ContactName = c.ContactName,
               OrderID = x == null ? -1 : x.OrderID };   

( หรือผ่านวิธีการขยาย )


19
บางคนสามารถอธิบายวิธีการทำงานของไวยากรณ์บ้านี้ ฉันไม่เห็นว่าคำหลักใดที่ทำให้การเข้าร่วมด้านซ้ายนั้นน่าอัศจรรย์ "สู่ sr" ทำอะไรได้บ้าง Linq ทำให้ฉันหงุดหงิดบางครั้ง :)
Joe Phillips

2
@ JoePhillips ฉันมีประสบการณ์ SQL มากมาย แต่การพยายามเรียนรู้ LINQ ก็เหมือนการลุยโคลน ฉันเห็นด้วยว่ามันบ้าอย่างแน่นอน
Nick.McDermaid

@ marc-gravell: คุณสามารถช่วยฉันในการแก้แบบสอบถาม sql ของฉันเพื่อการแปลง linq: stackoverflow.com/questions/28367941//
Vishal I Patil

@VishalIPatil ทำไมคุณต้องการแปลงจาก SQL เป็น LINQ SQL ทำงานได้ดีและสามารถคาดเดาได้และมีประสิทธิภาพมากขึ้น ...
Marc Gravell

1
@VishalIPatil แล้ว ... ทำไมล่ะ เครื่องมือ LINQ เกือบทุกตัวมีความสามารถในการเรียกใช้ SQL ที่เขียนด้วยมือ ทำไมไม่ทำอย่างนั้นล่ะ?
Marc Gravell

216

คุณไม่ต้องการคำสั่งเป็น:

var query = 
    from customer in dc.Customers
    from order in dc.Orders
         .Where(o => customer.CustomerId == o.CustomerId)
         .DefaultIfEmpty()
    select new { Customer = customer, Order = order } 
    //Order will be null if the left join is null

และใช่แบบสอบถามด้านบนสร้างการเข้าร่วมด้านซ้ายอย่างแท้จริง

เชื่อมโยงไปยังคำถามที่คล้ายกันซึ่งจัดการการรวมหลายด้านซ้าย: Linq ถึง Sql: การรวมภายนอกหลายรายการ


14
ในขณะที่ฉันรู้ว่าคำตอบของ @Marc Gravvel ทำงานได้ดีฉันชอบวิธีนี้มากกว่าเพราะ IMO รู้สึกว่าสอดคล้องกับลักษณะการรวมด้านซ้ายมากขึ้น
llaughlin

1
คำตอบที่ยอดเยี่ยม กำลังค้นหา google มากกว่า 5 ชั่วโมง นี่เป็นวิธีเดียวที่ทำให้ SQL จะเข้าร่วมได้
Faisal Mq

1
ขอบคุณมาก .... ฉันกำลังค้นหาวิธีแก้ปัญหาสำหรับช่วงบ่ายนี้และรหัสของคุณถูกจับ (และรู้สึกเป็นธรรมชาติในการบูต) หวังว่าฉันจะสามารถโหวตได้หลายครั้ง
Jim

2
@Jim ขอบคุณ :-) ฉันดีใจที่ devs ยังคงได้รับไมล์สะสมจากคำตอบนี้ ฉันยอมรับอย่างสมบูรณ์ว่า DefaultIfEmpty () ให้ความรู้สึกเป็นธรรมชาติมากกว่าการใช้คำสั่งลงใน
อาเมียร์

7
แค่โน้ตสำหรับคนอื่นที่พบสิ่งนี้เหมือนที่ฉันเพิ่งทำผลลัพธ์นี้จะเป็นการเข้าร่วมด้านซ้ายด้านนอกใน CROSS APPLY ซึ่งหมายความว่าคุณจะได้รับข้อมูลซ้ำหากมีการแข่งขันหลายรายการทางด้านขวามือของการเข้าร่วม โซลูชันของ Marc Gravell ในขณะที่ไม่ใช่ "สวย" ให้ผลลัพธ์ SQL ที่ถูกต้องและชุดผลลัพธ์ที่ฉันค้นหา
Mike U

13
Public Sub LinqToSqlJoin07()
Dim q = From e In db.Employees _
        Group Join o In db.Orders On e Equals o.Employee Into ords = Group _
        From o In ords.DefaultIfEmpty _
        Select New With {e.FirstName, e.LastName, .Order = o}

ObjectDumper.Write(q) End Sub

ตรวจสอบhttp://msdn.microsoft.com/en-us/vbasic/bb737929.aspx


ลองดี แต่ดูเหมือนว่า OP กำลังใช้ c # ไวยากรณ์ VB แตกต่างกันอย่างผิดปกติ
Levitikon

5

ฉันพบวิธีแก้ปัญหา 1 ข้อ ถ้าต้องการแปล SQL ชนิดนี้ (การรวมซ้าย) เป็น Linq Entity ...

SQL:

SELECT * FROM [JOBBOOKING] AS [t0]
LEFT OUTER JOIN [REFTABLE] AS [t1] ON ([t0].[trxtype] = [t1].[code])
                                  AND ([t1]. [reftype] = "TRX")

LINQ:

from job in JOBBOOKINGs
join r in (from r1 in REFTABLEs where r1.Reftype=="TRX" select r1) 
          on job.Trxtype equals r.Code into join1
from j in join1.DefaultIfEmpty()
select new
{
   //cols...
}

ดูความคิดเห็นนี้หน่วยงาน Linq เพื่อ SQL DefaultIfEmptyไม่สนับสนุน
TJ Crowder

2

ฉันต้องการเพิ่มอีกหนึ่งสิ่ง ใน LINQ ไปยัง SQL ถ้าฐานข้อมูลของคุณถูกสร้างขึ้นอย่างถูกต้องและตารางของคุณมีความเกี่ยวข้องผ่านข้อ จำกัด ของคีย์ต่างประเทศคุณไม่จำเป็นต้องเข้าร่วมเลย

ใช้ LINQPad ฉันสร้างแบบสอบถาม LINQ ต่อไปนี้:

//Querying from both the CustomerInfo table and OrderInfo table
from cust in CustomerInfo
where cust.CustomerID == 123456
select new {cust, cust.OrderInfo}

ซึ่งถูกแปลไปยังแบบสอบถาม (ตัดทอนเล็กน้อย) ด้านล่าง

 -- Region Parameters
 DECLARE @p0 Int = 123456
-- EndRegion
SELECT [t0].[CustomerID], [t0].[AlternateCustomerID],  [t1].[OrderID], [t1].[OnlineOrderID], (
    SELECT COUNT(*)
    FROM [OrderInfo] AS [t2]
    WHERE [t2].[CustomerID] = [t0].[CustomerID]
    ) AS [value]
FROM [CustomerInfo] AS [t0]
LEFT OUTER JOIN [OrderInfo] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[CustomerID], [t1].[OrderID]

สังเกตเห็นLEFT OUTER JOINข้างต้น


1

ดูแลการแสดง:

ฉันพบว่าอย่างน้อยที่สุดกับEF Coreคำตอบที่ต่างออกไปอาจทำให้ประสิทธิภาพแตกต่างกัน ฉันรู้ว่า OP ถามเกี่ยวกับ Linq ถึง SQL แต่สำหรับฉันแล้วคำถามเดียวกันก็เกิดขึ้นกับ EF Core เช่นกัน

ในกรณีเฉพาะที่ฉันต้องจัดการข้อเสนอแนะ (syntactically nicer) โดย Marc Gravell ทำให้เกิดการรวมซ้ายใน cross ใช้ - คล้ายกับสิ่งที่ Mike U อธิบาย - ซึ่งมีผลที่ค่าใช้จ่ายโดยประมาณสำหรับการค้นหานี้เฉพาะสอง ครั้งตามที่สูงเมื่อเทียบกับแบบสอบถามที่ไม่มีการข้ามร่วม เวลาการดำเนินการของเซิร์ฟเวอร์ที่แตกต่างกันโดยปัจจัยที่ 3 [1]

การแก้ปัญหาโดย Marc Gravell ทำให้เกิดการสืบค้นโดยไม่มีการรวมข้าม

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

ค่าใช้จ่ายของผู้ให้บริการโดยประมาณ:

  • ด้วยครอสใช้: 0.2534
  • 0.0991 โดยไม่ต้องข้าม

เวลาดำเนินการของเซิร์ฟเวอร์หน่วยเป็นมิลลิวินาที (แบบสอบถามดำเนินการ 10 ครั้งวัดโดยใช้เวลาตั้งค่าสถิติเปิด):

  • ใช้กากบาท: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • โดยไม่ต้องข้ามใช้: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(การเรียกใช้ครั้งแรกช้ากว่าสำหรับข้อความค้นหาทั้งสองดูเหมือนว่ามีบางสิ่งในแคช)

ขนาดโต๊ะ:

  • ตารางหลัก: 87 แถว
  • ตารางแรกสำหรับการเข้าร่วมทางซ้าย: 179 แถว;
  • ตารางที่สองสำหรับการเข้าร่วมทางซ้าย: 7 แถว

รุ่น EF Core: 2.2.1

รุ่นของ SQL Server: MS SQL Server 2017 - 14 ... (บน Windows 10)

ตารางที่เกี่ยวข้องทั้งหมดมีดัชนีในคีย์หลักเท่านั้น

ข้อสรุปของฉัน: ขอแนะนำเสมอให้ดู SQL ที่สร้างขึ้นเนื่องจากอาจแตกต่างกันจริงๆ


[1] น่าสนใจพอเมื่อตั้งค่า 'สถิติลูกค้า' ใน MS SQL Server Management Studio บนฉันจะเห็นแนวโน้มตรงกันข้าม กล่าวคือการดำเนินการครั้งสุดท้ายของการแก้ปัญหาโดยไม่ต้องใช้ cross นั้นใช้เวลามากกว่า 1 วินาที ฉันคิดว่ามีบางอย่างผิดปกติที่นี่ - อาจเป็นเพราะการตั้งค่าของฉัน

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