Tại sao điều này tham gia ước tính cardinality rất lớn?


18

Tôi đang trải nghiệm những gì tôi nghĩ là một ước tính cardinality cao không tưởng cho truy vấn sau:

SELECT dm.PRIMARY_ID
FROM
(
    SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

Kế hoạch ước tính là ở đây . Tôi đang làm việc trên một bản sao thống kê của các bảng để tôi không thể bao gồm một kế hoạch thực tế. Tuy nhiên, tôi không nghĩ rằng nó rất phù hợp cho vấn đề này.

SQL Server ước tính rằng 481577 hàng sẽ được trả về từ bảng dẫn xuất "dm". Sau đó, nó ước tính rằng 4528030000 hàng sẽ được trả về sau khi kết nối với X_LAST_TABLE được thực hiện, nhưng THAM GIA là khóa chính của X_LAST_TIME. Tôi sẽ mong đợi một ước tính cardinality tham gia giữa 0 và 481577 hàng. Thay vào đó, ước tính hàng dường như là 10% số lượng hàng tôi sẽ nhận được khi tham gia chéo các bảng bên ngoài và bên trong. Toán học cho việc này diễn ra với làm tròn: 481577 * 94025 * 0.1 = 45280277425 được làm tròn thành 4528030000.

Tôi chủ yếu tìm kiếm một nguyên nhân gốc rễ cho hành vi này. Tôi cũng quan tâm đến cách giải quyết đơn giản, nhưng vui lòng không đề xuất thay đổi mô hình dữ liệu hoặc sử dụng bảng tạm thời. Truy vấn này là một sự đơn giản hóa logic trong một khung nhìn. Tôi biết rằng thực hiện COALESCE trên một vài cột và tham gia vào chúng không phải là một thực hành tốt. Một phần của mục tiêu của câu hỏi này là tìm hiểu xem tôi có cần đề xuất rằng mô hình dữ liệu được thiết kế lại không.

Tôi đang thử nghiệm trên Microsoft SQL Server 2014 với công cụ ước tính thẻ kế thừa được bật. TF 4199 và những người khác đang trên. Tôi có thể cung cấp một danh sách đầy đủ các cờ theo dõi nếu kết thúc có liên quan.

Dưới đây là định nghĩa bảng có liên quan nhất:

CREATE TABLE X_LAST_TABLE (
JOIN_ID NUMERIC(18, 0) NOT NULL
    CONSTRAINT PK_X_LAST_TABLE PRIMARY KEY CLUSTERED (JOIN_ID ASC)
);

Tôi cũng đã viết ra tất cả các kịch bản tạo bảng cùng với số liệu thống kê của họ nếu có ai muốn tái tạo vấn đề trên một trong các máy chủ của họ.

Để thêm một vài quan sát của tôi, sử dụng TF 2312 sửa chữa ước tính, nhưng đó không phải là một lựa chọn cho tôi. TF 2301 không sửa lỗi ước tính. Loại bỏ một trong các bảng sửa lỗi ước tính. Thật kỳ lạ, việc thay đổi thứ tự tham gia của X_DETAIL_LINK cũng sửa lỗi ước tính. Bằng cách thay đổi thứ tự tham gia, tôi có nghĩa là viết lại truy vấn và không buộc thứ tự tham gia với một gợi ý. Dưới đây là một kế hoạch truy vấn ước tính khi chỉ thay đổi thứ tự của các phép nối.


PS Nếu bạn có thể chuyển sang bất kỳ cách nào bigintthay vì decimal(18, 0)bạn sẽ nhận được lợi ích: 1) sử dụng 8 byte thay vì 9 cho mỗi giá trị và 2) sử dụng loại dữ liệu có thể so sánh byte thay vì loại dữ liệu đóng gói, có thể có hàm ý cho thời gian CPU khi so sánh các giá trị.
ErikE

@ErikE Cảm ơn vì tiền boa nhưng tôi đã biết điều đó. Thật không may, chúng tôi bị mắc kẹt với NUMERIC (18,0) so với BIGINT vì lý do di sản.
Joe Obbish

Đó là giá trị một shot!
ErikE

Bạn có cần X_DETAIL2X_DETAIL3tất cả các bảng nếu JOIN_IDkhông phải là null X_DETAIL1?
ErikE

@ErikE Đây là một MCVE vì vậy truy vấn không chính xác có ý nghĩa tại thời điểm này.
Joe Obbish

Câu trả lời:


14

Tôi biết rằng làm COALESCEtrên một vài cột và tham gia vào chúng không phải là một thực hành tốt.

Tạo ước lượng phân phối và ước lượng phân phối tốt là đủ khó khi lược đồ là 3NF + (với các khóa và ràng buộc) và truy vấn có quan hệ và chủ yếu là SPJG (nhóm lựa chọn-chiếu-tham gia theo nhóm). Mô hình CE được xây dựng trên những nguyên tắc đó. Các tính năng khác thường hơn hoặc không liên quan có trong một truy vấn, người ta càng tiến gần đến ranh giới của những gì mà khung tim và tính chọn lọc có thể xử lý. Đi quá xa và CE sẽ bỏ cuộc và đoán .

Hầu hết các ví dụ MCVE là SPJ đơn giản (không có G), mặc dù với các đẳng thức bên ngoài chủ yếu (được mô hình hóa như là nối bên trong cộng với chống semijoin) chứ không phải là Equijoin bên trong đơn giản hơn (hoặc semijoin). Tất cả các mối quan hệ đều có khóa, mặc dù không có khóa ngoại hoặc các ràng buộc khác. Tất cả trừ một trong những người tham gia là một-nhiều, điều đó là tốt.

Ngoại lệ là sự kết hợp nhiều-nhiều bên ngoài giữa X_DETAIL_1X_DETAIL_LINK. Chức năng duy nhất của phép nối này trong MCVE là có khả năng trùng lặp các hàng trong X_DETAIL_1. Đây là một loại khác thường của một điều.

Các biến vị ngữ đẳng thức đơn giản (các lựa chọn) và toán tử vô hướng cũng tốt hơn. Ví dụ: thuộc tính so sánh bằng / thuộc tính thường xuyên hoạt động tốt trong mô hình. Nó là tương đối "dễ dàng" để sửa đổi biểu đồ và thống kê tần số để phản ánh việc áp dụng các vị từ đó.

COALESCEđược xây dựng trên CASE, lần lượt được triển khai trong nội bộ như IIF(và điều này cũng đúng trước khi IIFxuất hiện trong ngôn ngữ Transact-SQL). Các mô hình CE IIFnhư là một UNIONvới hai đứa trẻ loại trừ lẫn nhau, mỗi đứa trẻ bao gồm một dự án về một lựa chọn về mối quan hệ đầu vào. Mỗi thành phần được liệt kê đều có hỗ trợ mô hình, vì vậy việc kết hợp chúng là tương đối đơn giản. Mặc dù vậy, càng nhiều lớp trừu tượng, kết quả cuối cùng càng kém chính xác - một lý do tại sao các kế hoạch thực hiện lớn hơn có xu hướng kém ổn định và đáng tin cậy.

ISNULLmặt khác, là nội tại của động cơ. Nó không được xây dựng bằng cách sử dụng bất kỳ thành phần cơ bản hơn. Áp dụng hiệu ứng của ISNULLbiểu đồ, ví dụ, đơn giản như thay thế bước cho NULLcác giá trị (và nén khi cần thiết). Nó vẫn còn tương đối mờ, như các toán tử vô hướng đi, và vì vậy tốt nhất nên tránh khi có thể. Tuy nhiên, nói chung là thân thiện với trình tối ưu hóa (ít tối ưu hóa-không thân thiện) hơn làCASE thay thế dựa trên cơ sở.

CE (70 và 120+) rất phức tạp, thậm chí theo tiêu chuẩn SQL Server. Đây không phải là trường hợp áp dụng logic đơn giản (với công thức bí mật) cho mỗi toán tử. CE biết về các khóa và phụ thuộc chức năng; nó biết cách ước tính bằng cách sử dụng tần số, thống kê đa biến và biểu đồ; và có rất nhiều trường hợp đặc biệt, tinh chỉnh, kiểm tra và cân bằng, và các cấu trúc hỗ trợ. Nó thường ước tính ví dụ tham gia theo nhiều cách (tần số, biểu đồ) và quyết định kết quả hoặc điều chỉnh dựa trên sự khác biệt giữa hai cách.

Một điều cơ bản cuối cùng cần trình bày: Ước tính cardinality ban đầu chạy cho mọi hoạt động trong cây truy vấn, từ dưới lên. Tính chọn lọc và cardinality được lấy từ các toán tử lá trước (quan hệ cơ sở). Biểu đồ đã sửa đổi và thông tin mật độ / tần số được lấy từ các toán tử cha. Cây càng đi xa, chất lượng ước tính càng thấp có xu hướng tích lũy.

Ước tính toàn diện ban đầu này cung cấp một điểm khởi đầu và diễn ra tốt trước khi bất kỳ xem xét nào được đưa ra cho một kế hoạch thực hiện cuối cùng (nó xảy ra một cách trước cả giai đoạn biên soạn kế hoạch tầm thường). Cây truy vấn tại thời điểm này có xu hướng phản ánh hình thức bằng văn bản của truy vấn khá chặt chẽ (mặc dù đã loại bỏ các truy vấn con và đơn giản hóa được áp dụng, v.v.)

Ngay sau khi ước tính ban đầu, SQL Server thực hiện sắp xếp lại tham gia heuristic, nói một cách lỏng lẻo cố gắng sắp xếp lại cây để đặt các bảng nhỏ hơn và tính chọn lọc cao sẽ tham gia trước. Nó cũng cố gắng định vị các liên kết bên trong trước khi tham gia bên ngoài và các sản phẩm chéo. Khả năng của nó không sâu rộng; những nỗ lực của nó không đầy đủ; và nó không xem xét chi phí vật lý (vì chúng chưa tồn tại - chỉ có thông tin thống kê và thông tin siêu dữ liệu). Sắp xếp lại heuristic là thành công nhất trên cây Equijoin đơn giản bên trong. Nó tồn tại để cung cấp một điểm khởi đầu "tốt hơn" để tối ưu hóa dựa trên chi phí.

Tại sao điều này tham gia ước tính cardinality rất lớn?

MCVE có một sự tham gia nhiều-nhiều-nhiều-nhiều-nhiều "bất thường" , và một đẳng thức tham gia với COALESCEvị ngữ. Cây toán tử cũng có một phép nối bên trong cuối cùng , thứ tự sắp xếp tham gia heuristic không thể di chuyển lên cây đến một vị trí ưa thích hơn. Bỏ qua tất cả các vô hướng và hình chiếu, cây tham gia là:

LogOp_Join [ Card=4.52803e+009 ]
    LogOp_LeftOuterJoin [ Card=481577 ]
        LogOp_LeftOuterJoin [ Card=481577 ]
            LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
                LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
                LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
            LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
        LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
    LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]

Lưu ý ước tính cuối cùng bị lỗi đã được đưa ra. Nó được in dưới dạng Card=4.52803e+009và được lưu trữ bên trong dưới dạng giá trị dấu phẩy động chính xác kép 4.5280277425e + 9 (4528027742.5 theo số thập phân).

Bảng dẫn xuất trong truy vấn ban đầu đã bị xóa và các phép chiếu được chuẩn hóa. Một đại diện SQL của cây trên đó ước tính độ chính xác và độ chọn lọc ban đầu được thực hiện là:

SELECT 
    PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
    ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk 
    ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2 
    ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3 
    ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst 
    ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)

(Bên cạnh đó, sự lặp lại COALESCEcũng có mặt trong kế hoạch cuối cùng - một lần trong tính toán vô hướng cuối cùng, và một lần ở phía bên trong của phép nối bên trong).

Thông báo tham gia cuối cùng. Phép nối bên trong này là (theo định nghĩa) sản phẩm cartesian X_LAST_TABLEvà đầu ra nối trước đó, với một lựa chọn (biến vị ngữ nối) lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)được áp dụng. Cardinality của sản phẩm cartesian chỉ đơn giản là 481577 * 94025 = 45280277425.

Để làm được điều đó, chúng ta cần xác định và áp dụng tính chọn lọc của vị ngữ. Sự kết hợp của COALESCEcây mở rộng mờ đục (về UNIONIIF, nhớ) cùng với tác động đến thông tin chính, biểu đồ và tần số xuất phát của phép nối ngoài nhiều-nhiều-nhiều-nhiều-nhiều-dự phòng kết hợp trước đó có nghĩa là CE không thể rút ra một ước tính chấp nhận được trong bất kỳ cách thông thường nào.

Kết quả là, nó đi vào Logic Guess. Logic đoán phức tạp vừa phải, với các lớp thuật toán đoán "có giáo dục" và thuật toán đoán "không được giáo dục" đã thử. Nếu không tìm thấy cơ sở nào tốt hơn cho dự đoán, mô hình sử dụng dự đoán cuối cùng, để so sánh đẳng thức là: sqllang!x_Selectivity_Equal= cố định 0,1 độ chọn lọc (đoán 10%):

Cuộc gọi ngăn xếp

-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal

Kết quả là 0,1 độ chọn lọc trên sản phẩm cartesian: 481577 * 94025 * 0.1 = 4528027742.5 (~ 4.52804e + 009) như đã đề cập trước đó.

Viết lại

Khi tham gia có vấn đề được nhận xét , một ước tính tốt hơn được tạo ra bởi vì "dự đoán cuối cùng" có tính chọn lọc cố định được tránh (thông tin chính được giữ lại bởi các phép nối 1 M). Chất lượng của ước tính vẫn có độ tin cậy thấp, bởi vì một COALESCEvị từ tham gia hoàn toàn không thân thiện với CE. Ước tính sửa đổi ít nhất trông hợp lý hơn với con người, tôi cho rằng.

Khi truy vấn được viết với phép nối ngoài để X_DETAIL_LINK đặt cuối cùng , thứ tự heuristic có thể hoán đổi nó với phép nối bên trong cuối cùng thành X_LAST_TABLE. Đặt liên kết bên trong ngay bên cạnh tham gia bên ngoài có vấn đề hạn chế khả năng sắp xếp lại sớm cơ hội để cải thiện ước tính cuối cùng, vì các tác động của liên kết ngoài nhiều "bất thường" nhiều dự phòng đến sau khi ước tính chọn lọc khó khăn cho COALESCE. Một lần nữa, các ước tính tốt hơn một chút so với dự đoán cố định, và có lẽ sẽ không thể đứng lên để xác định kiểm tra chéo trong tòa án của pháp luật.

Sắp xếp lại một hỗn hợp các kết nối bên trong và bên ngoài là khó khăn và tốn thời gian (thậm chí tối ưu hóa toàn bộ giai đoạn 2 chỉ cố gắng một tập hợp con hạn chế của các bước di chuyển lý thuyết).

Đề ISNULLxuất lồng nhau trong câu trả lời của Max Vernon quản lý để tránh dự đoán cố định giải cứu, nhưng ước tính cuối cùng là một hàng 0 không thể thực hiện được (nâng lên một hàng cho độ chính xác). Đây cũng có thể là một phỏng đoán cố định của 1 hàng, cho tất cả các cơ sở thống kê mà phép tính có.

Tôi sẽ mong đợi một ước tính cardinality tham gia giữa 0 và 481577 hàng.

Đây là một kỳ vọng hợp lý, ngay cả khi người ta chấp nhận rằng ước tính cardinality có thể xảy ra ở các thời điểm khác nhau (trong quá trình tối ưu hóa dựa trên chi phí) về các khác nhau về mặt vật lý, nhưng giống hệt nhau về mặt ngữ nghĩa - với kế hoạch cuối cùng là một loại tốt nhất của nhau tốt nhất (mỗi nhóm ghi nhớ). Việc thiếu một đảm bảo tính nhất quán trên toàn kế hoạch không có nghĩa là một cá nhân tham gia sẽ có thể thể hiện sự tôn trọng, tôi hiểu điều đó.

Mặt khác, nếu chúng ta kết thúc với dự đoán về biện pháp cuối cùng , hy vọng đã bị mất, vậy tại sao phải bận tâm. Chúng tôi đã thử tất cả các thủ thuật mà chúng tôi biết, và đã từ bỏ. Nếu không có gì khác, ước tính cuối cùng hoang dã là một dấu hiệu cảnh báo tuyệt vời rằng không phải mọi thứ đều diễn ra tốt đẹp trong CE trong quá trình biên dịch và tối ưu hóa truy vấn này.

Khi tôi thử MCVE, 120+ CE đã tạo ra ước tính cuối cùng của hàng không (= 1) (giống như lồng nhau ISNULL) cho truy vấn ban đầu, điều này không thể chấp nhận được theo cách nghĩ của tôi.

Giải pháp thực sự có thể liên quan đến thay đổi thiết kế, cho phép các phép nối đơn giản không có COALESCEhoặc ISNULL, và lý tưởng là các khóa ngoại và các ràng buộc khác hữu ích cho việc biên dịch truy vấn.


10

Tôi tin rằng các Compute Scalarnhà điều hành kết quả từ việc COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)tham gia X_LAST_TABLE.JOIN_IDlà nguyên nhân gốc rễ của vấn đề. Trong lịch sử, tính toán vô hướng rất khó để chi phí chính xác 1 , 2 .

Vì bạn đã cung cấp một ví dụ có thể xác minh đầy đủ tối thiểu (cảm ơn!) Với các số liệu thống kê chính xác, tôi có thể viết lại truy vấn sao cho phép nối không còn yêu cầu CASEchức năng COALESCEđược mở rộng, dẫn đến ước tính hàng chính xác hơn và rõ ràng hơn chi phí tổng thể chính xác Xem phần phụ lục ở cuối. :

SELECT COALESCE(dm.d1ID, dm.d2ID, dm.d3ID)
FROM
(
    SELECT d1ID = d1.JOIN_ID
        , d2ID = d2.JOIN_ID
        , d3ID = d3.JOIN_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst 
    ON (dm.d1ID IS NOT NULL AND dm.d1ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NOT NULL AND dm.d2ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NULL AND dm.d3ID IS NOT NULL AND dm.d3ID = lst.JOIN_ID);

Mặc dù xID IS NOT NULLkhông bắt buộc về mặt kỹ thuật, vì ID = JOIN_IDsẽ không tham gia vào các giá trị null, tôi đã đưa chúng vào vì nó mô tả rõ hơn ý định này.

Kế hoạch 1Kế hoạch 2

Kế hoạch 1:

nhập mô tả hình ảnh ở đây

Kế hoạch 2:

nhập mô tả hình ảnh ở đây

Các truy vấn mới có lợi (?) Từ song song. Cũng cần lưu ý, truy vấn mới có số lượng đầu ra ước tính của các hàng là 1, trên thực tế có thể tệ hơn vào cuối ngày so với ước tính 4528030000 cho truy vấn ban đầu. Chi phí phụ cho toán tử chọn trên truy vấn mới xuất hiện ở mức 243210, trong khi đồng hồ gốc ở mức 536.535, rõ ràng là ít hơn. Phải nói rằng, tôi không tin ước tính đầu tiên là bất cứ nơi nào gần với thực tế.


Phụ lục 1.

Sau khi tham khảo thêm với nhiều người khác nhau về The Heap ™ được thúc đẩy bởi một cuộc thảo luận với @Lamak, có vẻ như truy vấn quan sát của tôi ở trên thực hiện rất khủng khiếp, ngay cả với sự song song. Một giải pháp cho phép cả hiệu suất tốt ước tính cardinality tốt bao gồm thay thế COALESCE(x,y,z)bằng một ISNULL(ISNULL(x, y), z), như trong:

SELECT dm.PRIMARY_ID
FROM
(
    SELECT ISNULL(ISNULL(d1.JOIN_ID, d2.JOIN_ID), d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

COALESCEđược chuyển đổi thành một CASEcâu lệnh "dưới vỏ bọc" bởi trình tối ưu hóa truy vấn. Như vậy, công cụ ước tính cardinality có một thời gian khó hơn để khám phá các số liệu thống kê đáng tin cậy cho các cột được chôn bên trong COALESCE. ISNULLlà một chức năng nội tại là "mở" hơn nhiều cho công cụ ước tính cardinality. Nó cũng không có giá trị gì ISNULLcó thể được tối ưu hóa nếu mục tiêu được biết là không có giá trị .

Kế hoạch cho ISNULLbiến thể trông giống như:

nhập mô tả hình ảnh ở đây

(Dán phiên bản Kế hoạch tại đây ).

FYI, đạo cụ cho Sentry One cho Plan Explorer tuyệt vời của họ, mà tôi đã sử dụng để sản xuất các kế hoạch đồ họa ở trên.


-1

Theo điều kiện tham gia của bạn, bảng có thể được sắp xếp theo nhiều cách, đó là "thay đổi theo một cách cụ thể" khắc phục kết quả.

Giả sử chỉ tham gia một bảng cho bạn kết quả chính xác.

SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID

Ở đây thay cho X_DETAIL_1, bạn có thể sử dụng một trong haiX_DETAIL_2 hoặc X_DETAIL_3.

Do đó mục đích nghỉ 2 bàn không rõ ràng.

Nó giống như bạn đã phá vỡ bảng X_DETAIL_1 thành 2 phần nữa.

Có lẽ hầu hết " có lỗi mà bạn đang Populating những bảng. " Lý tưởng nhất X_DETAIL_1, X_DETAIL_2X_DETAIL_3 nên chứa số tiền bằng hàng.

Nhưng một hoặc nhiều bảng chứa số lượng hàng không mong muốn.

Xin lỗi nếu tôi sai.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.