वास्तविक जीवन उदाहरण, जब SQL में OUTER / CROSS APPLY का उपयोग करना है


124

मैं CROSS / OUTER APPLYएक सहकर्मी के साथ देख रहा हूं और हम वास्तविक जीवन के उदाहरणों को खोजने के लिए संघर्ष कर रहे हैं जहां उनका उपयोग करना है।

मैंने काफी समय बिताया है जब मुझे क्रॉस अप्लाई इनर जॉइन का उपयोग करना चाहिए? और गुग्लिंग लेकिन मुख्य (केवल) उदाहरण बहुत विचित्र लगता है (किसी तालिका से चयन करने के लिए कि कितनी पंक्तियों का चयन करने के लिए एक तालिका से पंक्ति-पंक्ति का उपयोग करके)।

मैंने सोचा कि इस परिदृश्य से लाभ हो सकता है OUTER APPLY:

संपर्क तालिका (प्रत्येक संपर्क के लिए 1 रिकॉर्ड होता है) संचार प्रविष्टियां तालिका (n फोन, फैक्स, ईमेल प्रत्येक संपर्क में शामिल कर सकते हैं)

लेकिन उपश्रेणियों का उपयोग करते हुए, सामान्य टेबल एक्सप्रेशन, OUTER JOINसाथ RANK()और OUTER APPLYसभी समान रूप से प्रदर्शन करते हैं। मैं यह अनुमान लगा रहा हूं कि इसका मतलब यह नहीं है कि यह परिदृश्य लागू नहीं है APPLY

कृपया कुछ वास्तविक जीवन के उदाहरणों को साझा करें और फीचर को समझाने में मदद करें!


5
"शीर्ष n प्रति समूह" या XML को पार्स करना आम है। मेरे कुछ जवाब देखें stackoverflow.com/-
gbn




जवाबों:


174

कुछ उपयोग APPLYहैं ...

1) प्रति समूह प्रश्नों में शीर्ष एन (कुछ कार्डिनैलिटी के लिए अधिक कुशल हो सकता है)

SELECT pr.name,
       pa.name
FROM   sys.procedures pr
       OUTER APPLY (SELECT TOP 2 *
                    FROM   sys.parameters pa
                    WHERE  pa.object_id = pr.object_id
                    ORDER  BY pr.name) pa
ORDER  BY pr.name,
          pa.name 

2) बाहरी क्वेरी में प्रत्येक पंक्ति के लिए एक टेबल वैल्यूड फंक्शन को कॉल करना

SELECT *
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)

3) एक स्तंभ उपनाम का पुन: उपयोग

SELECT number,
       doubled_number,
       doubled_number_plus_one
FROM master..spt_values
CROSS APPLY (SELECT 2 * CAST(number AS BIGINT)) CA1(doubled_number)  
CROSS APPLY (SELECT doubled_number + 1) CA2(doubled_number_plus_one)  

4) स्तंभों के एक से अधिक समूहों को खोलना

मान लेता है 1NF तालिका संरचना का उल्लंघन ...।

CREATE TABLE T
  (
     Id   INT PRIMARY KEY,

     Foo1 INT, Foo2 INT, Foo3 INT,
     Bar1 INT, Bar2 INT, Bar3 INT
  ); 

2008+ VALUESसिंटैक्स का उपयोग करके उदाहरण ।

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (VALUES(Foo1, Bar1),
                          (Foo2, Bar2),
                          (Foo3, Bar3)) V(Foo, Bar); 

2005 में इसके UNION ALLबजाय इस्तेमाल किया जा सकता है।

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (SELECT Foo1, Bar1 
                    UNION ALL
                    SELECT Foo2, Bar2 
                    UNION ALL
                    SELECT Foo3, Bar3) V(Foo, Bar);

1
वहाँ का उपयोग करता है की एक अच्छी सूची है, लेकिन कुंजी वास्तविक जीवन उदाहरण है- मैं प्रत्येक के लिए एक देखना पसंद करेंगे।
ली टिकेट

# 1 के लिए यह रैंक, सबक्वेरी या कॉमन टेबल एक्सप्रेशन का उपयोग करके समान रूप से प्राप्त किया जा सकता है? क्या यह सच है जब आप एक उदाहरण दे सकते हैं?
बजे ली टिकेट

@LeeTickett - कृपया लिंक पढ़ें। इसमें 4 पेज की चर्चा है, जब आप एक से दूसरे को पसंद करेंगे।
मार्टिन स्मिथ

1
उदाहरण # 1 में शामिल लिंक पर जाना सुनिश्चित करें। मैंने इन दोनों दृष्टिकोणों (ROW OVER और CROSS APPLY) का उपयोग किया है, दोनों ही विभिन्न परिदृश्यों में अच्छा प्रदर्शन कर रहे हैं, लेकिन मैंने कभी नहीं समझा कि वे अलग-अलग प्रदर्शन क्यों करते हैं। वह लेख स्वर्ग से भेजा गया था !! निर्देशों द्वारा ऑर्डर को मिलाते हुए उचित अनुक्रमण पर ध्यान केंद्रित करने से उन प्रश्नों के लिए बड़े पैमाने पर मदद मिली जिनके पास "उचित" संरचना है, लेकिन जब समस्या होती है तो प्रदर्शन के मुद्दे। इसे शामिल करने के लिए धन्यवाद !!
क्रिस पोर्टर

1
@mr_eclair ऐसा लगता है कि अब itprotoday.com/software-development/… पर है
मार्टिन स्मिथ

87

ऐसी कई स्थितियाँ हैं जहाँ आप नहीं बच सकते हैं CROSS APPLYया OUTER APPLY

विचार करें कि आपके पास दो टेबल हैं।

मास्टर टेबल

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x

विवरण

x------x--------------------x-------x
| Id   |      PERIOD        |   QTY |
x------x--------------------x-------x
|  1   |   2014-01-13       |   10  |
|  1   |   2014-01-11       |   15  |
|  1   |   2014-01-12       |   20  |
|  2   |   2014-01-06       |   30  |
|  2   |   2014-01-08       |   40  |
x------x--------------------x-------x                                       



                                                            क्रॉस आवेदन

ऐसी कई परिस्थितियां हैं, जहां हमें INNER JOINसाथ बदलने की जरूरत है CROSS APPLY

1. यदि हम कार्यक्षमता वाले TOP nपरिणामों पर 2 तालिकाओं में शामिल होना चाहते हैंINNER JOIN

अगर हम चयन करने की आवश्यकता पर विचार करें Idऔर Nameसे Masterप्रत्येक के लिए और पिछले दो तारीखों Idसे Details table

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
INNER JOIN
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D      
    ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID

उपरोक्त क्वेरी निम्न परिणाम उत्पन्न करती है।

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
x------x---------x--------------x-------x

देखें, इसने अंतिम दो तारीखों के साथ अंतिम दो तारीखों के लिए परिणाम उत्पन्न किए Idऔर फिर इन रिकॉर्ड्स को केवल बाहरी क्वेरी में शामिल किया Id, जो कि गलत है। इसे पूरा करने के लिए, हमें उपयोग करने की आवश्यकता है CROSS APPLY

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
CROSS APPLY
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    WHERE M.ID=D.ID
    ORDER BY CAST(PERIOD AS DATE)DESC
)D

और परिणाम के बाद वह रूपों।

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
x------x---------x--------------x-------x

यहाँ काम कर रहा है। अंदर की क्वेरी CROSS APPLYबाहरी तालिका को संदर्भित कर सकती है, जहां INNER JOINऐसा नहीं किया जा सकता (त्रुटि संकलन को फेंकता है)। अंतिम दो तिथियों का पता लगाने पर, जॉइनिंग CROSS APPLYयानी अंदर किया जाता है WHERE M.ID=D.ID

2. जब हमें INNER JOINफ़ंक्शन का उपयोग करके कार्यक्षमता की आवश्यकता होती है।

CROSS APPLYएक प्रतिस्थापन के रूप में इस्तेमाल किया जा सकता है INNER JOINजब हमें Masterतालिका और ए से परिणाम प्राप्त करने की आवश्यकता होती है function

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C

और यहाँ फ़ंक्शन है

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

जो निम्नलिखित परिणाम उत्पन्न करता है

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-11   |  15   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-06   |  30   |
|   2  |   B     | 2014-01-08   |  40   |
x------x---------x--------------x-------x



                                                            OUTER APPLY

1. यदि हम कार्यक्षमता वाले TOP nपरिणामों पर 2 तालिकाओं में शामिल होना चाहते हैंLEFT JOIN

विचार करें कि क्या हमें तालिका Masterसे प्रत्येक आईडी के लिए अंतिम और दो तारीखों से आईडी और नाम का चयन करना है Details

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
LEFT JOIN
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID

जो निम्न परिणाम बनाता है

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     |   NULL       |  NULL |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

यह गलत परिणाम लाएगा अर्थात्, भले ही हम साथ जुड़ें Details, Idभले ही यह तालिका से केवल नवीनतम दो तिथियों का डेटा लाएगा Id। तो उचित समाधान का उपयोग कर रहा है OUTER APPLY

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
OUTER APPLY
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    WHERE M.ID=D.ID
    ORDER BY CAST(PERIOD AS DATE)DESC
)D

जो निम्नलिखित वांछित परिणाम बनाता है

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

2. जब हमें LEFT JOINकार्यक्षमता का उपयोग करना चाहिए functions

OUTER APPLYएक प्रतिस्थापन के रूप में इस्तेमाल किया जा सकता है LEFT JOINजब हमें Masterतालिका और ए से परिणाम प्राप्त करने की आवश्यकता होती है function

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
OUTER APPLY dbo.FnGetQty(M.ID) C

और फंक्शन यहाँ हो जाता है।

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

जो निम्नलिखित परिणाम उत्पन्न करता है

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-11   |  15   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-06   |  30   |
|   2  |   B     | 2014-01-08   |  40   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x



                             की सामान्य सुविधा CROSS APPLYऔरOUTER APPLY

CROSS APPLYया unpivoting, जो विनिमेय हैं, जब मान OUTER APPLYबनाए रखने के लिए उपयोग किया जा सकता NULLहै।

नीचे दी गई तालिका पर विचार करें

x------x-------------x--------------x
|  Id  |   FROMDATE  |   TODATE     |
x------x-------------x--------------x
|   1  |  2014-01-11 | 2014-01-13   | 
|   1  |  2014-02-23 | 2014-02-27   | 
|   2  |  2014-05-06 | 2014-05-30   |    
|   3  |   NULL      |   NULL       | 
x------x-------------x--------------x

जब आप AND को एक कॉलम में UNPIVOTलाने के लिए उपयोग करते हैं , तो यह डिफ़ॉल्ट रूप से मानों को समाप्त कर देगा ।FROMDATETODATENULL

SELECT ID,DATES
FROM MYTABLE
UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P

जो नीचे परिणाम उत्पन्न करता है। ध्यान दें कि हमने Idसंख्या का रिकॉर्ड याद किया है3

  x------x-------------x
  | Id   |    DATES    |
  x------x-------------x
  |  1   |  2014-01-11 |
  |  1   |  2014-01-13 |
  |  1   |  2014-02-23 |
  |  1   |  2014-02-27 |
  |  2   |  2014-05-06 |
  |  2   |  2014-05-30 |
  x------x-------------x

ऐसे मामलों में CROSS APPLYया एक OUTER APPLYउपयोगी होगा

SELECT DISTINCT ID,DATES
FROM MYTABLE 
OUTER APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)

जो निम्न परिणाम बनाता है और Idजहां उसका मूल्य है, उसे बनाए रखता है3

  x------x-------------x
  | Id   |    DATES    |
  x------x-------------x
  |  1   |  2014-01-11 |
  |  1   |  2014-01-13 |
  |  1   |  2014-02-23 |
  |  1   |  2014-02-27 |
  |  2   |  2014-05-06 |
  |  2   |  2014-05-30 |
  |  3   |     NULL    |
  x------x-------------x

दो सवालों पर एक ही जवाब पोस्ट करने के बजाय, एक डुप्लिकेट के रूप में ध्वज क्यों नहीं?
ताब अललेमन

2
मुझे मूल प्रश्न का उत्तर देने के लिए यह उत्तर अधिक लागू होता है। इसके उदाहरण 'वास्तविक जीवन' परिदृश्यों को दर्शाते हैं।
फ्रेंको

तो स्पष्ट करना। "शीर्ष एन" परिदृश्य; जो बाईं / आंतरिक जुड़ाव के साथ किया जा सकता है, लेकिन "id द्वारा विभाजन पर एक पंक्ति_नंबर" का उपयोग करके और फिर "WHERE M.RowNumber <3" या कुछ ऐसा ही चुनना?
चैतन्य

1
कुल मिलाकर शानदार जवाब! यह सुनिश्चित करने के लिए कि यह स्वीकार किए गए से बेहतर उत्तर है, क्योंकि यह है: सरल, आसान दृश्य उदाहरण और स्पष्टीकरण के साथ।
आर्सेन खाचरौतन

9

एक वास्तविक जीवन उदाहरण होगा यदि आपके पास एक अनुसूचक था और यह देखना चाहता था कि प्रत्येक निर्धारित कार्य के लिए सबसे हाल ही में लॉग प्रविष्टि क्या थी।

select t.taskName, lg.logResult, lg.lastUpdateDate
from task t
cross apply (select top 1 taskID, logResult, lastUpdateDate
             from taskLog l
             where l.taskID = t.taskID
             order by lastUpdateDate desc) lg

हमारे परीक्षणों में हमने हमेशा पाया कि विंडो फ़ंक्शन के साथ शीर्ष n के लिए सबसे अधिक कुशल है (मुझे लगता है कि यह हमेशा लागू होता है और सबक्वेरी दोनों शापित / नेस्टेड छोरों की आवश्यकता होती है)। हालांकि मुझे लगता है कि मैंने अब इसे फटा दिया हो सकता है ... मार्टिन के लिंक के लिए धन्यवाद, जो बताता है कि यदि आप पूरी तालिका नहीं लौटा रहे हैं और मेज पर इष्टतम सूचकांक नहीं हैं, तो क्रॉस अप्लाय (या) का उपयोग करके रीड की संख्या बहुत कम होगी एक उपशम यदि शीर्ष n जहां n = 1)
ली टिकेट

मुझे अनिवार्य रूप से वह क्वेरी यहीं मिली है और निश्चित रूप से नेस्टेड लूप्स के साथ किसी भी सबक्वेरी का प्रदर्शन नहीं कर रहा है। यह देखते हुए कि लॉग टेबल में टास्क का पीके और लास्ट ड्यूडेटडॉट है, यह एक बहुत तेज ऑपरेशन है। विंडो फ़ंक्शन का उपयोग करने के लिए आप उस क्वेरी को कैसे सुधारेंगे?
ब्यूरी

2
चयन * कार्य टी इनर जॉइन से (सेलेक्ट टास्किड, लॉगरस, लास्ट ड्यूडेटेड, रैंक () ओवर (डिविजन टू टास्किड ऑर्डर बाय लास्टडिकेटेड डेस्क) _rank) lg पर lg.taskid = t.twid और lg._rank = 1
ली टिकेट

5

ऊपर दिए गए बिंदु का उत्तर देने के लिए एक उदाहरण दें:

create table #task (taskID int identity primary key not null, taskName varchar(50) not null)
create table #log (taskID int not null, reportDate datetime not null, result varchar(50) not null, primary key(reportDate, taskId))

insert #task select 'Task 1'
insert #task select 'Task 2'
insert #task select 'Task 3'
insert #task select 'Task 4'
insert #task select 'Task 5'
insert #task select 'Task 6'

insert  #log
select  taskID, 39951 + number, 'Result text...'
from    #task
        cross join (
            select top 1000 row_number() over (order by a.id) as number from syscolumns a cross join syscolumns b cross join syscolumns c) n

और अब निष्पादन योजना के साथ दो प्रश्नों को चलाएं।

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        left join (select taskID, reportDate, result, rank() over (partition by taskID order by reportDate desc) rnk from #log) lg
            on lg.taskID = t.taskID and lg.rnk = 1

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        outer apply (   select  top 1 l.*
                        from    #log l
                        where   l.taskID = t.taskID
                        order   by reportDate desc) lg

आप देख सकते हैं कि बाहरी लागू क्वेरी अधिक कुशल है। (इस योजना को संलग्न नहीं कर सका क्योंकि मैं एक नया उपयोगकर्ता हूं ... दोह।)


निष्पादन योजना मुझे रुचिकर करती है- क्या आप जानते हैं कि रैंक () समाधान एक इंडेक्स स्कैन और एक महंगी तरह के बाहरी आवेदन के विपरीत क्यों होता है, जो एक इंडेक्स की तलाश करता है और एक तरह का प्रतीत नहीं होता है (हालाँकि यह अवश्य है क्योंकि आप कर सकते हैं ' t एक प्रकार के बिना एक शीर्ष करते हैं?)
ली टिकेट

1
बाहरी अनुप्रयोग को एक प्रकार का प्रदर्शन करने की आवश्यकता नहीं है, क्योंकि यह अंतर्निहित तालिका पर सूचकांक का उपयोग कर सकता है। संभवत: रैंक () फ़ंक्शन के साथ क्वेरी को इसकी रैंकिंग सुनिश्चित करने के लिए संपूर्ण तालिका को संसाधित करने की आवश्यकता है।
बीजूरी

आप एक प्रकार के बिना एक शीर्ष नहीं कर सकते। हालाँकि, पूरी तालिका को संसाधित करने के बारे में आपकी बात सच है, यह मुझे आश्चर्यचकित करेगा (मुझे पता है कि sql अनुकूलक / संकलक समय-समय पर निराश कर सकता है लेकिन यह पागल व्यवहार होगा)
ली टिकेट

2
जब कोई डेटा आपके द्वारा समूहीकृत किया जाता है, तो आप किसी सॉर्ट के बिना किसी शीर्ष को शीर्ष पर रख सकते हैं, क्योंकि ऑप्टिमाइज़र को यह पहले से ही पता होता है कि शाब्दिक रूप से केवल इंडेक्स से पहली (या अंतिम) प्रविष्टि को खींचने की आवश्यकता है।
बीजूरी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.