Gerçek hayat örneği, SQL'de OUTER / CROSS APPLY ne zaman kullanılır?


124

CROSS / OUTER APPLYBir meslektaşımla bakıyordum ve onları nerede kullanacağımıza dair gerçek hayattan örnekler bulmakta zorlanıyoruz.

Inner Join üzerinden Cross Apply'ı ne zaman kullanmalıyım? ve googling, ancak ana (yalnızca) örnek oldukça tuhaf görünüyor (başka bir tablodan kaç satır seçileceğini belirlemek için bir tablodaki satır sayısını kullanarak).

Bu senaryonun şunlardan yararlanabileceğini düşündüm OUTER APPLY:

İletişim Tablosu (her kişi için 1 kayıt içerir) İletişim Girişleri Tablosu (her kişi için n telefon, faks, e-posta içerebilir)

Ama, alt sorgular, ortak tablo ifadeler kullanarak OUTER JOINbirlikte RANK()ve OUTER APPLYhepsi eşit gerçekleştirmek gibi görünüyor. Sanırım bu, senaryonun geçerli olmadığı anlamına geliyor APPLY.

Lütfen bazı gerçek hayat örneklerini paylaşın ve özelliği açıklamaya yardımcı olun!


5
"grup başına n" veya ayrıştırma XML yaygındır. Cevaplarımdan bazılarını görün stackoverflow.com/…
gbn




Yanıtlar:


174

Bazı kullanım APPLYalanları ...

1) Grup başına en iyi N sorgu (bazı kardinaliteler için daha verimli olabilir)

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) Dış sorgudaki her satır için Tablo Değerli İşlevi çağırma

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

3) Bir sütun takma adını yeniden kullanma

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) Birden fazla sütun grubunun özetini açma

1NF'nin tablo yapısını ihlal ettiğini varsayar ....

CREATE TABLE T
  (
     Id   INT PRIMARY KEY,

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

2008+ VALUESsözdizimi kullanan örnek .

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

Bunun UNION ALLyerine 2005 yılında kullanılabilir.

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
Orada kullanımların güzel bir listesi ama anahtar gerçek hayat örnekleridir - her biri için bir tane görmek isterim.
Lee Tickett

# 1 için bu, sıralama, alt sorgular veya ortak tablo ifadeleri kullanılarak eşit şekilde elde edilebilir mi? Bunun doğru olmadığı bir örnek verebilir misiniz?
Lee Tickett

@LeeTickett - Lütfen bağlantıyı okuyun. Ne zaman birini diğerine tercih edeceğinizle ilgili 4 sayfalık bir tartışma var.
Martin Smith

1
Örnek 1'de bulunan bağlantıyı ziyaret ettiğinizden emin olun. Bu yaklaşımların ikisini de (ROW OVER ve CROSS APPLY) çeşitli senaryolarda iyi performans göstererek kullandım, ancak neden farklı performans gösterdiklerini hiç anlamadım. O makale göklerden gönderildi !! Yönlendirmelerle sırayla eşleşen uygun indekslemeye odaklanma, "uygun" yapıya sahip ancak sorgulandığında performans sorunları olan sorgular için büyük bir yol sağladı. Dahil ettiğiniz için teşekkür ederiz !!
Chris Porter


88

Kaçınamayacağınız CROSS APPLYveya kaçınamayacağınız çeşitli durumlar vardır OUTER APPLY.

İki masanız olduğunu düşünün.

MASTER TABLO

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

DETAY TABLOSU

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                                       



                                                            ÇAPRAZ UYGULAMA

Değiştirmemiz gereken birçok durum INNER JOINvar CROSS APPLY.

1. TOP nSonuçlarda INNER JOINişlevselliğe sahip 2 tabloyu birleştirmek istiyorsak

Biz belirlemeniz gerekirse düşünün Idve Namegelen Masterbiri için ve son iki tarihleri Idarasından 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

Yukarıdaki sorgu aşağıdaki sonucu verir.

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

Bakın, son iki tarih için son iki tarih için sonuçlar üretti Idve sonra bu kayıtları sadece dış sorguda birleştirdi Id, ki bu yanlış. Bunu başarmak için kullanmamız gerekiyor 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

ve takip eden sonucu oluşturur.

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

İşte çalışma. İçerideki sorgu CROSS APPLY, INNER JOINbunu yapamayan dış tabloya başvurabilir (derleme hatası verir). Son iki tarihi bulurken, birleştirme CROSS APPLYyani içinde yapılır WHERE M.ID=D.ID.

2. INNER JOINİşlevleri kullanarak işlevselliğe ihtiyaç duyduğumuzda .

CROSS APPLYtablo ve INNER JOINa'dan sonuç almamız gerektiğinde yerine kullanılabilir .Masterfunction

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

Ve işte fonksiyon

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

aşağıdaki sonucu oluşturan

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



                                                            DIŞ UYGULAMA

1. TOP nSonuçlarda LEFT JOINişlevselliğe sahip 2 tabloyu birleştirmek istiyorsak

Tablodaki Masterher bir Id için başlangıç ve son iki tarihi seçmemiz gerekip gerekmediğini düşünün 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

aşağıdaki sonucu oluşturan

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

Bu yanlış sonuçlar getirecektir, yani biz katılmış olsak da Detailstablodan en son iki tarih verisini getirecektir . Yani doğru çözüm kullanmaktır .IdIdOUTER 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

aşağıdaki istenen sonucu oluşturan

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 JOINKullanarak işlevselliğe ihtiyacımız olduğunda functions.

OUTER APPLYtablo ve LEFT JOINa'dan sonuç almamız gerektiğinde yerine kullanılabilir .Masterfunction

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

Ve fonksiyon buraya gelir.

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

aşağıdaki sonucu oluşturan

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



                             Ortak özelliği CROSS APPLYveOUTER APPLY

CROSS APPLYveya birbirinin yerine geçebilen değerleri döndürürken OUTER APPLYkorumak için kullanılabilir NULL.

Aşağıdaki tabloya sahip olduğunuzu düşünün

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

Kullandığınızda UNPIVOTgetirmek FROMDATEVE TODATEtek sütuna, bu ortadan kaldıracak NULLvarsayılan olarak değerleri.

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

aşağıdaki sonucu oluşturur. Rekor Idsayıları kaçırdığımızı unutmayın3

  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

Bu gibi durumlarda a CROSS APPLYveya OUTER APPLYyararlı olacaktır

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

aşağıdaki sonucu oluşturan Idve değerini olduğu yerde tutan3

  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

Aynı yanıtı iki soruya göndermek yerine, neden birini kopya olarak işaretlemiyorsunuz?
Tab Alleman

2
Bu cevabı orijinal soruyu cevaplamak için daha uygun buluyorum. Örnekleri 'gerçek hayat' senaryolarını gösterir.
FrankO

Yani açıklığa kavuşturmak için. "En üst n" senaryosu; bu, sol / iç birleştirme ile, ancak "id tarafından bölümleme üzerinde satır_sayısı" kullanılarak ve ardından "NEREDE M. Satır Sayısı <3" veya bunun gibi bir şey kullanılarak yapılabilir mi?
Chaitanya

1
Genel olarak harika cevap! Elbette bu, kabul edilenden daha iyi bir cevap çünkü basit, kullanışlı görsel örnekler ve açıklamalarla.
Arsen Khachaturyan

Detaylı ve anlaşılır cevap için çok teşekkür ederim!
Kushan Randima

9

Gerçek hayattan bir örnek, bir zamanlayıcınız varsa ve her zamanlanmış görev için en son günlük girişinin ne olduğunu görmek istemeniz olabilir.

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

Testlerimizde, her zaman pencere işleviyle birleşim işlevinin top n için en verimli olduğunu bulduk (bunun her zaman doğru olacağını düşündüm, çünkü hem uygula hem de alt sorgu hem el yazısı hem de iç içe döngüler gerektirir). Şimdi onu kırmış olabileceğimi düşünmeme rağmen ... Martin'in bağlantısı sayesinde, tüm tabloyu geri getirmiyorsanız ve tablodaki optimal indeksler yoksa, okuma sayısı cross apply kullanıldığında çok daha az olacaktır (veya üst n ise bir alt sorgu, burada n = 1)
Lee Tickett

Esasen bu sorguya sahibim ve kesinlikle iç içe döngülerle herhangi bir alt sorgu gerçekleştirmiyor. Günlük tablosunun görev kimliği ve lastUpdateDate için PK'ye sahip olduğu göz önüne alındığında, bu çok hızlı bir işlemdir. Bir pencere işlevini kullanmak için bu sorguyu nasıl yeniden biçimlendirirsiniz?
BJury

2
görev t iç birleşiminden * seçin (görev kimliği, logresult, lastupdatedate, rank () over (lastupdatedate desc ile görev kimliği sırasına göre bölüm) _rank) lg üzerinde lg.taskid = t.taskid ve lg._rank = 1
Lee Tickett

5

Yukarıdaki noktayı cevaplamak için bir örnek verelim:

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

Ve şimdi iki sorguyu bir yürütme planıyla çalıştırı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

Dış uygulama sorgusunun daha verimli olduğunu görebilirsiniz. (Yeni bir kullanıcı olduğum için plan eklenemedi ... Doh.)


yürütme planı ilgimi çekiyor - rank () çözümünün neden bir dizin taraması yaptığını ve bir dizin aradığı ve bir sıralama yapmıyor gibi göründüğü dış uygulamanın aksine pahalı bir sıralama yaptığını biliyor musunuz? sıralama olmadan top yapamaz mıyım?)
Lee Tickett

1
Temel tablodaki dizini kullanabildiğinden, dış uygulamanın sıralama yapması gerekmez. Muhtemelen rank () işlevine sahip sorgunun, sıralamasının doğru olduğundan emin olmak için tüm tabloyu işlemesi gerekir.
BJury

bir çeşit olmadan üst yapamazsınız. Tüm tabloyu işlemekle ilgili düşünceniz doğru OLABİLİR ancak beni şaşırtabilirdi (sql optimizer / derleyicinin zaman zaman hayal kırıklığına uğrayabileceğini biliyorum ama bu çılgınca bir davranış olurdu)
Lee Tickett

2
İyileştirici önceden sıralandığını bildiğinden, dizinden ilk (veya son) girişi çekmesi gerektiğinden, gruplandırdığınız veriler bir dizine karşıyken bir üst sırayı sıralayabilirsiniz.
BJury
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.