2009-07-31 14 views
11

के बिना न्यूनतम/अधिकतम से जुड़ी पंक्ति खोजें, मेरे पास टी-एसक्यूएल और एसक्यूएल सर्वर से संबंधित एक प्रश्न है।न्यूनतम लूप

मान लीजिए कि मैं 2 कॉलम के साथ एक मेज आदेश करते हैं:

  • productId पूर्णांक
  • ग्राहक आईडी पूर्णांक
  • तिथि दिनांक

मैं हर उत्पाद के लिए पहले के आदेश की तारीख चाहते हैं , इसलिए मैं इस प्रकार की क्वेरी निष्पादित करता हूं:

SELECT ProductId, MIN(Date) AS FirstOrder 
FROM Orders 
GROUP BY ProductId 

मेरे पास ProductId पर एक सूचकांक है, जिसमें क्वेरी CustomerId और Date शामिल हैं, क्वेरी को तेज करने के लिए (IX_Orders)। क्वेरी प्लान IX_Orders पर एक गैर-क्लस्टर इंडेक्स स्कैन की तरह दिखता है, इसके बाद एक धारा कुल (सूचकांक के लिए कोई तरह का धन्यवाद नहीं) होता है।

अब मेरी समस्या यह है कि मैं प्रत्येक उत्पाद के लिए पहले क्रम से जुड़े CustomerId को भी पुनर्प्राप्त करना चाहता हूं (उत्पाद 26 को पहले मंगलवार 25 को ग्राहक द्वारा 12 का आदेश दिया गया था)। मुश्किल हिस्सा यह है कि मैं निष्पादन योजना में कोई आंतरिक पाश नहीं चाहता हूं, क्योंकि इसका मतलब तालिका में ProductId प्रति अतिरिक्त पढ़ना होगा, जो अत्यधिक अक्षम है।

यह एक ही गैर-क्लस्टर इंडेक्स स्कैन का उपयोग करके संभव होना चाहिए, इसके बाद धारा समेकित हो, हालांकि मुझे ऐसा कोई प्रश्न नहीं मिल रहा है जो ऐसा करेगा। कोई उपाय?

धन्यवाद

उत्तर

0
SELECT 
    o1.productid, 
    o1.date, 
    o1.customerid 
FROM 
    Orders o1 
JOIN 
    (select productid, min(date) as orderDate 
    from Orders 
    group by productid 
    ) firstOrder 
ON o1.productid = firstOrder.productid 

यह सबसे अच्छा मैं के साथ हालांकि ईमानदार होना करने के लिए आ सकते हैं, मैं नहीं जानता कि क्या इस क्वेरी के प्रदर्शन विशेषताएं हैं। यदि यह अच्छा नहीं है, तो शायद मैं आपके इच्छित जानकारी प्राप्त करने के लिए दो प्रश्नों का पालन करने का सुझाव दूंगा।

+0

+1: मैं आप के अंदर मिनट (तारीख) के लिए उपनाम परिभाषित करने की जरूरत लगता है कि आपके गुमनाम शामिल; अन्यथा, यह वही है जो मुझे मिला। यह जानना अच्छा होगा कि इसके लिए बेहतर दृष्टिकोण है या नहीं। – butterchicken

+2

यह क्वेरी गलत जवाब प्राप्त करती है क्योंकि इसमें o1 और firstOrder –

+0

के बीच शामिल होने की तिथि शामिल नहीं है आपको शामिल होने की तिथि शामिल करने की आवश्यकता नहीं है। आपको केवल उप-क्वेरी की उत्पाद आईडी की आवश्यकता है, क्योंकि इसमें न्यूनतम तिथि है। वास्तविक परिणाम लौटने का चयन तिथि लौटाता है। –

2
declare @Orders table (
    ProductId int, 
    CustomerId int, 
    Date datetime 
) 

insert into @Orders values (1,1,'20090701') 
insert into @Orders values (2,1,'20090703') 
insert into @Orders values (3,1,'20090702') 
insert into @Orders values (1,2,'20090704') 
insert into @Orders values (4,2,'20090701') 
insert into @Orders values (1,3,'20090706') 
insert into @Orders values (2,3,'20090704') 
insert into @Orders values (4,3,'20090702') 
insert into @Orders values (5,5,'20090703') 

select O.* from @Orders O inner join 
(
    select ProductId, 
    MIN(Date) MinDate 
    from @Orders 
    group by ProductId 
) FO 
on FO.ProductId = O.ProductId and FO.MinDate = O.Date 

इस के लिए अनुमानित क्वेरी योजना के रूप में मैं इसे तालिका चर के साथ मजाक कर रहा हूँ बेकार है, लेकिन अनाम भीतरी में शामिल होने के एक subselect से अधिक अनुकूलित किया जाना चाहिए।

+1

आपके चयन को FO.MinDate को शामिल करने की आवश्यकता है। – pjp

+0

मैंने कभी "अज्ञात" शामिल होने के बारे में नहीं सुना है, मैंने हमेशा व्युत्पन्न तालिका का उपयोग किया है। –

+1

यदि यह वही उत्पाद के लिए एक ही मिनट की तारीख के साथ कई पंक्तियां हैं तो यह काम नहीं करेगा। इसे आज़माएं, इस कोड को उदाहरण में जोड़ें: _Orders मानों में _insert (5,1, '20090703'); @ ऑर्डर मूल्यों (5,5, '20090703') में डालें _ आपको परिणाम सेट में उत्पाद 5 बार कई बार मिल जाएगा। –

0

क्या IX_Orders उत्पाद आईडी द्वारा क्रमबद्ध किया गया है, फिर CutomerId, फिर दिनांक या यह उत्पाद आईडी है, फिर दिनांक, फिर ग्राहक आईडी? यदि यह बाद में यह पूर्व परिवर्तन है।

create index IX_Orders on Orders (ProductId, CustomerId, Date) 

इस बजाय का उपयोग करें::

दूसरे शब्दों में इस का उपयोग नहीं करते

create index IX_Orders on Orders (ProductId, Date, CustomerId) 

तो अगर आप कार्य करें:

SELECT o1.* 
FROM [Order] o1 
JOIN 
    (
     SELECT ProductID, Min(Date) as Date 
     FROM [Order] 
     GROUP BY ProductID 
    ) o2 
    ON o1.ProductID = o2.ProductID AND o1.Date = o2.Date 
ORDER BY ProductID 

आप सिर्फ एक सूचकांक के साथ खत्म IX_Orders पर स्कैन करें, हालांकि यदि दो ग्राहक एक ही उत्पाद को उसी समय ऑर्डर कर सकते हैं, तो आप प्रत्येक उत्पाद के लिए कई पंक्तियां प्राप्त कर सकते हैं।आप इस अतीत निम्न क्वेरी का उपयोग करके प्राप्त कर सकते हैं, लेकिन यह पहले की तुलना में कम कुशल है:

WITH cte AS 
(
    SELECT ProductID, CustomerID, Date, 
     ROW_NUMBER() OVER(PARTITION BY ProductID ORDER BY Date ASC) AS row 
    FROM [Order] 
) 
SELECT ProductID, CustomerId, Date 
FROM cte 
WHERE row = 1 
ORDER BY ProductID 
3

इस उत्पादों डुप्लिकेट तिथि होती है कि संभाल लेंगे:

DECLARE @Orders table (ProductId int 
         ,CustomerId int 
         ,Date datetime 
        ) 

INSERT INTO @Orders VALUES (1,1,'20090701') 
INSERT INTO @Orders VALUES (2,1,'20090703') 
INSERT INTO @Orders VALUES (3,1,'20090702') 
INSERT INTO @Orders VALUES (1,2,'20090704') 
INSERT INTO @Orders VALUES (4,2,'20090701') 
INSERT INTO @Orders VALUES (1,3,'20090706') 
INSERT INTO @Orders VALUES (2,3,'20090704') 
INSERT INTO @Orders VALUES (4,3,'20090702') 
INSERT INTO @Orders VALUES (5,5,'20090703') --duplicate dates for product #5 
INSERT INTO @Orders VALUES (5,1,'20090703') --duplicate dates for product #5 
INSERT INTO @Orders VALUES (5,5,'20090703') --duplicate dates for product #5 

;WITH MinOrders AS 
(SELECT 
    o.ProductId, o.CustomerId, o.Date 
     ,row_number() over(partition by o.ProductId order by o.ProductId,o.CustomerId) AS RankValue 
    FROM @Orders o 
    INNER JOIN (SELECT 
        ProductId 
         ,MIN(Date) MinDate 
        FROM @Orders 
        GROUP BY ProductId 
       ) dt ON o.ProductId=dt.ProductId AND o.Date=dt.MinDate 
) 
SELECT 
    m.ProductId, m.CustomerId, m.Date 
    FROM MinOrders m 
    WHERE m.RankValue=1 
    ORDER BY m.ProductId, m.CustomerId 

इस समान परिणाम देगी, सिर्फ एक ही घोषणा और इसके बाद के संस्करण कोड के रूप में आवेषण का उपयोग करें:

;WITH MinOrders AS 
(SELECT 
    o.ProductId, o.CustomerId, o.Date 
     ,row_number() over(partition by o.ProductId order by o.ProductId,o.CustomerId) AS RankValue 
    FROM @Orders o 
) 
SELECT 
    m.ProductId, m.CustomerId, m.Date 
    FROM MinOrders m 
    WHERE m.RankValue=1 
    ORDER BY m.ProductId, m.CustomerId 

आप जो तेजी से चलेंगे देखने के लिए प्रत्येक संस्करण की कोशिश कर सकते हैं ...

+0

अच्छा, केवल एक इंडेक्स स्कैन है, लेकिन यह क्वेरी निष्पादन योजना में एक प्रकार देता है। –

0

मुझे सबकुछ या विंडोिंग फ़ंक्शन (जैसे row_number, रैंक) के बिना यह अच्छी तरह से करने का कोई तरीका नहीं दिखता है क्योंकि अधिकतम केवल एक कॉलम में दिखता है।

हालांकि आप इसे अच्छी तरह से नहीं कर सकते हैं।

SELECT 
    productid, 
    min(date), 
cast(
    substring( 
     min(convert(varchar(23),date,21) + cast(customerid as varchar(20))) 
       , 24, 44) 
    as int) customerid 
from 
    orders 
group by 
    productid 

यह केवल तभी काम करता है जब आपके ग्राहक आईडी के पास 20 अंक कम हों।

संपादित करें: समूह खंड द्वारा

+2

संदेश 8120, स्तर 16, राज्य 1, रेखा 51 कॉलम 'ऑर्डर.प्रोडक्ट आईडी' चयनित सूची में अमान्य है क्योंकि यह या तो कुल कार्य या ग्रुप बाय क्लॉज में निहित नहीं है। –

+1

ओह, क्लॉज –

1

जोड़ा SQL Server 2005+ में:

SELECT oo.* 
FROM (
     SELECT DISTINCT ProductId 
     FROM Orders 
     ) od 
CROSS APPLY 
     (
     SELECT TOP 1 ProductID, Date, CustomerID 
     FROM Orders oi 
     WHERE oi.ProductID = od.ProductID 
     ORDER BY 
       Date DESC 
     ) oo 

नाममात्र, क्वेरी के लिए योजना में शामिल है Nested Loops

हालांकि, बाहरी पाश Stream Aggregate के साथ एक Index Scan उपयोग करेगा, और भीतरी पाश एक Top साथ ProductID के लिए एक Index Seek शामिल होंगे।

वास्तव में, दूसरा ऑपरेशन लगभग मुफ़्त है, क्योंकि आंतरिक लूप में प्रयुक्त इंडेक्स पेज शायद कैश में रहता है क्योंकि इसका उपयोग बाहरी लूप के लिए किया गया था। ,

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 1 ms. 

(строк обработано: 100) 
Table 'Orders'. Scan count 103, logical reads 6020, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 234 ms, elapsed time = 125 ms. 

जबकि इस एक मात्र SELECT DISTINCT क्वेरी का परिणाम है:

यहाँ (100DISTINCTProductID के साथ) 1,000,000 पंक्तियों पर परीक्षा परिणाम है

SELECT od.* 
FROM (
     SELECT DISTINCT ProductId 
     FROM Orders 
     ) od 

और आँकड़े:

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 1 ms. 

(строк обработано: 100) 
Table 'Orders'. Scan count 3, logical reads 5648, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 250 ms, elapsed time = 125 ms. 

जैसा कि हम देख सकते हैं, प्रदर्शन समान है, और CROSS APPLY केवल 400 अतिरिक्त logical reads (जो शायद physical कभी नहीं होगा) लेता है।

यह नहीं देखें कि इस क्वेरी को और बेहतर कैसे करना संभव है।

इसके अलावा इस प्रश्न का लाभ यह है कि यह अच्छी तरह से समानांतर है। आप देख सकते हैं कि CPU समय elapsed time से दोगुना है: यह मेरे पुराने Core Duo पर समांतरता के कारण है।

4-coreCPU इस क्वेरी को उस समय के आधे में पूरा कर देगा।

खिड़की कार्यों का उपयोग कर समाधान parallelize नहीं:

SELECT od.* 
FROM (
     SELECT ProductId, Date, CustomerID, ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY Date DESC) AS rn 
     FROM Orders 
     ) od 
WHERE rn = 1 

, और यहाँ आँकड़े हैं:

SQL Server Execution Times: 
    CPU time = 0 ms, elapsed time = 1 ms. 

(строк обработано: 100) 
Table 'Orders'. Scan count 1, logical reads 5123, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 406 ms, elapsed time = 415 ms. 
+1

द्वारा समूह को जोड़ने के लिए भूल गए कहां से बदलना है: जहां oi.ProductID = od.ProductID। मेरी क्वेरी योजना, एक प्रकार और नेस्टेड पाश दिखाया। –

+0

मुझे _Msg 4104, स्तर 16, राज्य 1, रेखा 2 मिल गया है बहु-भाग पहचानकर्ता "oo.ProductID" को बाध्य नहीं किया जा सकता है। परिवर्तन को ठीक करने के लिए जहां @ शैनन सेवरेंस सुझाव देता है। –

+0

ठीक है, इसे ठीक करने के लिए भूल गए। '@ शैनन ': क्या आपने इंडेक्स बनाया है क्योंकि' @ op 'ने कहा था? 'आदेशों पर INDEX IX_orders_pdc बनाएं (उत्पाद आईडी, दिनांक, ग्राहक आईडी)' – Quassnoi