it-swarm.asia

Apakah SQL Server Hanya Melakukan Perhitungan Dalam Daftar SELECT Sekali?

Ambil contoh berikut:

SELECT <CalculationA> As ColA,
       <CalculationB> As ColB,
       <CalculationA> + <CalculationB> As ColC
FROM TableA

Apakah KalkulasiA dan KalkulasiB, masing-masing dihitung dua kali?
Atau apakah optimizer akan cukup pintar untuk menghitungnya sekali dan menggunakan hasilnya dua kali?

Saya ingin melakukan tes untuk melihat hasilnya sendiri, namun, saya tidak yakin bagaimana saya bisa memeriksa sesuatu seperti ini.

Asumsi saya adalah akan melakukan perhitungan dua kali.
Dalam kasus apa, tergantung pada perhitungan yang terlibat, mungkinkah lebih baik menggunakan tabel turunan, atau tampilan bersarang? Pertimbangkan yang berikut ini:

SELECT TableB.ColA,
       TableB.ColB,
       TableB.ColA + TableB.ColB AS ColC,
FROM(    
      SELECT <CalculationA> As ColA,
             <CalculationB> As ColB
      FROM TableA
    ) As TableB

Dalam hal ini, saya berharap penghitungan hanya akan dilakukan sekali?

Tolong bisakah seseorang mengonfirmasi atau membantah asumsi saya? Atau ajari aku cara menguji sesuatu seperti ini untuk diriku sendiri?

Terima kasih.

29
Gravitate

Sebagian besar informasi yang Anda perlukan akan ada dalam rencana eksekusi (dan rencana XML).

Terima kueri ini:

SELECT COUNT(val) As ColA,
       COUNT(val2) As ColB,
       COUNT(val) +  COUNT(val2) As ColC
FROM dbo.TableA;

Paket eksekusi (dibuka dengan sentryone plan Explorer ) menunjukkan langkah-langkah apa yang dilaluinya:

enter image description here

Dengan agregat aliran menggabungkan nilai untuk EXPR1005 & EXPR1006

enter image description here

Jika kita ingin tahu apa ini, kita bisa mendapatkan info yang tepat tentang ekspresi ini dari rencana kueri XML:

<ColumnReference Column="Expr1005" />
<ScalarOperator ScalarString="COUNT([Database].[dbo].[TableA].[val])">
<Aggregate AggType="COUNT_BIG" Distinct="false">

Dengan komputasi skalar komputasi pertama ColA & ColB:

enter image description here

Dan perhitungan skalar terakhir menjadi tambahan sederhana:

enter image description here

Ini membacanya saat data mengalir, secara teori Anda harus membacanya dari kiri ke kanan jika melewati eksekusi logis.

Dalam hal ini, EXPR1004 Memanggil ekspresi lain, EXPR1002 & EXPR1003. Pada gilirannya ini memanggil EXPR1005 & EXPR1006.

Apakah KalkulasiA dan KalkulasiB, masing-masing dihitung dua kali? Atau apakah optimizer cukup pintar untuk menghitungnya sekali dan menggunakan hasilnya dua kali?

Tes sebelumnya menunjukkan bahwa dalam kasus ini ColC disederhanakan sebagai tambahan dari perhitungan yang didefinisikan sebagai ColA & ColB.

Akibatnya, ColA & ColB hanya dihitung sekali.


Pengelompokan berdasarkan 200 nilai berbeda

Jika kita mengelompokkan berdasarkan 200 nilai berbeda (val3), hal yang sama ditunjukkan:

SET STATISTICS IO, TIME ON;
SELECT SUM(val) As ColA,
       SUM(val2) As ColB,
       SUM(val) +  SUM(val2) As ColC
FROM dbo.TableA
GROUP BY val3;

Menggabungkan hingga 200 nilai berbeda ini dalam val3

enter image description here

melakukan penjumlahan pada val & val2 dan kemudian menambahkannya bersama untuk ColC:

enter image description here

Bahkan jika kita mengelompokkan semua kecuali satu nilai non-unik, penambahan yang sama harus dilihat untuk skalar komputasi.


Menambahkan fungsi ke ColA & ColB

Bahkan jika kami mengubah kueri ke ini:

SET STATISTICS IO, TIME ON;
SELECT ABS(SUM(val)) As ColA,
       ABS(SUM(val2)) As ColB,
       SUM(val) +  SUM(val2) As ColC
FROM dbo.TableA

Agregasi masih tidak akan dihitung dua kali, kami hanya menambahkan fungsi ABS() ke resultset agregasi, yang merupakan satu baris:

enter image description here

enter image description here

Tentu saja, menjalankan SUM(ABS(ColA) & SUM(ABS(ColB)) akan membuat optimizer tidak dapat menggunakan ekspresi yang sama untuk menghitung ColC.


Jika Anda ingin masuk lebih dalam ketika ini terjadi, saya akan mendorong Anda ke arah Query Optimizer Deep Dive - Bagian 1 (hingga Bagian 4) oleh Paul White.

Cara lain untuk masuk lebih dalam ke fase eksekusi kueri adalah dengan menambahkan petunjuk ini:

OPTION 
(
    RECOMPILE, 
    QUERYTRACEON 3604,
    QUERYTRACEON 8605
);

Ini akan mengekspos pohon input seperti yang dibuat oleh pengoptimal.

Penambahan dua nilai yang dihitung sebelumnya untuk mendapatkan ColC kemudian diterjemahkan ke:

AncOp_PrjEl COL: Expr1004 

ScaOp_Arithmetic x_aopAdd

    ScaOp_Identifier COL: Expr1002 

    ScaOp_Identifier COL: Expr1003 

Informasi ini sudah ada di Pohon Input , bahkan sebelum fase penyederhanaan telah terjadi, menunjukkan bahwa pengoptimal segera mengetahui bahwa ia tidak harus melakukan perhitungan yang sama dua kali.

38
Randi Vertongen

Jika bagian pertama dari perhitungan Anda adalah perhitungan aktual (Col1 + Col2) Dan bukan fungsi, maka perhitungan individual dilakukan untuk setiap langkah "perhitungan".

SELECT <CalculationA> As ColA,
       <CalculationB> As ColB,
       <CalculationA> + <CalculationB> As ColC
FROM TableA

Jika kami mengganti <CalculationA> Dari pernyataan Anda dengan perhitungan yang valid menggunakan ColA dan ColB dari tabel, dan ulangi ini untuk setiap langkah <CalculationB>,... Selanjutnya, maka langkah tugas aktual untuk menghitung hasil akan dilakukan untuk setiap langkah secara individual.

Untuk mereproduksi pernyataan saya tempelkan potongan kode berikut ke dalam SQL Server Management Studio dan jalankan. Pastikan Anda telah mengaktifkan opsi Sertakan Rencana Eksekusi Aktual .

Include Actual Execution Plan

Itu membuat database, tabel, mengisi tabel dan melakukan perhitungan yang menghasilkan rencana eksekusi.

 BUAT DATABASE Q252661 
 GO 
 GUNAKAN Q252661 
 GO 
 
 CREATE TABLE dbo.Q252661_TableA (
 ColA INT , 
 ColB INT, 
 ColC INT, 
 ColD INT) 
 GO 
 
 INSERT INTO Q252661_TableA 
 (
 ColA, 
 ColB, 
 ColC, 
 ColD 
) 
 NILAI 
 ( 
 1, 
 2, 
 3, 
 4 
)), (
 2, 
 4, 
 8, 
 16 
) 
 
 GO 
 SELECT ColA + ColB AS ColA, 
 ColC + ColD AS ColB, 
 ColA + ColB + ColC + ColD AS ColC 
 DARI Q252661_TableA 
 
 GO 

Kueri akan berjalan dan menghasilkan rencana eksekusi grafis yang mirip dengan yang berikut:

Graphical Execution Plan of ADDing valuesRencana Eksekusi Grafis dari nilai ADDING

Seperti dalam jawaban Randi kita akan fokus pada Compute Scalar operator.

Jika Anda mengklik Rencana Eksekusi Kueri di SSMS dan klik kanan untuk menampilkan rencana sebenarnya:

Show Execution Plan XML...

.. Anda akan menemukan XML berikut (berfokus pada bagian Hitung Skalar ):

          <ComputeScalar>
            <DefinedValues>
              <DefinedValue>
                <ColumnReference Column="Expr1003" />
                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]">
                  <Arithmetic Operation="ADD">
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                      </Identifier>
                    </ScalarOperator>
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                      </Identifier>
                    </ScalarOperator>
                  </Arithmetic>
                </ScalarOperator>
              </DefinedValue>
              <DefinedValue>
                <ColumnReference Column="Expr1004" />
                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColC]+[Q252661].[dbo].[Q252661_TableA].[ColD]">
                  <Arithmetic Operation="ADD">
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                      </Identifier>
                    </ScalarOperator>
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
                      </Identifier>
                    </ScalarOperator>
                  </Arithmetic>
                </ScalarOperator>
              </DefinedValue>
              <DefinedValue>
                <ColumnReference Column="Expr1005" />
                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]+[Q252661].[dbo].[Q252661_TableA].[ColC]+[Q252661].[dbo].[Q252661_TableA].[ColD]">
                  <Arithmetic Operation="ADD">
                    <ScalarOperator>
                      <Arithmetic Operation="ADD">
                        <ScalarOperator>
                          <Arithmetic Operation="ADD">
                            <ScalarOperator>
                              <Identifier>
                                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                              </Identifier>
                            </ScalarOperator>
                            <ScalarOperator>
                              <Identifier>
                                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                              </Identifier>
                            </ScalarOperator>
                          </Arithmetic>
                        </ScalarOperator>
                        <ScalarOperator>
                          <Identifier>
                            <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                          </Identifier>
                        </ScalarOperator>
                      </Arithmetic>
                    </ScalarOperator>
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
                      </Identifier>
                    </ScalarOperator>
                  </Arithmetic>
                </ScalarOperator>
              </DefinedValue>
            </DefinedValues>
            <RelOp AvgRowSize="23" EstimateCPU="8.07E-05" EstimateIO="0.0032035" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="2" LogicalOp="Table Scan" NodeId="1" Parallel="false" PhysicalOp="Table Scan" EstimatedTotalSubtreeCost="0.0032842" TableCardinality="2">
              <OutputList>
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
              </OutputList>
              <TableScan Ordered="false" ForcedIndex="false" ForceScan="false" NoExpandHint="false" Storage="RowStore">
                <DefinedValues>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                  </DefinedValue>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                  </DefinedValue>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                  </DefinedValue>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
                  </DefinedValue>
                </DefinedValues>
                <Object Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" IndexKind="Heap" Storage="RowStore" />
              </TableScan>
            </RelOp>
          </ComputeScalar>

Jadi setiap perhitungan dilakukan berulang kali dalam kasus nilai yang diambil dari tabel aktual. Fragmen XML berikut berasal dari ringkasan di atas:

                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]">
                  <Arithmetic Operation="ADD">

Ada lima langkah <Arithmetic Operation="ADD"> Dalam rencana eksekusi.


Menjawab Pertanyaan Anda

Apakah KalkulasiA dan KalkulasiB, masing-masing dihitung dua kali?

Ya, jika perhitungannya adalah jumlah kolom aktual sesuai contoh. Perhitungan terakhir adalah jumlah CalculationA + CalculationB.

Atau apakah optimizer cukup pintar untuk menghitungnya sekali dan menggunakan hasilnya dua kali?

Itu tergantung pada apa yang Anda hitung. - Dalam contoh ini: ya. - Dalam jawaban Randi: tidak.

Asumsi saya adalah akan melakukan perhitungan dua kali.

Anda tepat untuk perhitungan tertentu.

Dalam kasus apa, tergantung pada perhitungan yang terlibat, mungkin lebih baik menggunakan tabel turunan, atau tampilan bersarang?

Benar.


Setelah selesai, Anda dapat menjatuhkan basis data lagi:

USE [master]
GO
DROP DATABASE Q252661
4

Karena sudah ada jawaban yang baik untuk pertanyaan saya akan fokus pada aspek KERING (KERING = jangan ulangi sendiri).

Saya terbiasa menggunakan CROSS APPLY, jika saya harus melakukan perhitungan yang sama dalam permintaan yang sama beberapa kali (jangan lupa GROUP BY / WHERE / ORDER BY, di mana perhitungan yang sama cenderung terjadi berulang-ulang kali).

SELECT calc.ColA,
       calc.ColB,
       calc.ColA + calc.ColB AS ColC
  FROM TableA AS a
 CROSS APPLY (SELECT a.Org_A * 100 AS ColA
                   , a.Org_B / 100 AS ColB
             ) AS calc
 WHERE calc.ColB = @whatever
 ORDER BY calc.ColA

Ketika satu perhitungan tergantung pada yang lain, tidak ada alasan untuk tidak menggunakan banyak CROSS APPLY panggilan untuk menghitung hasil sementara (sama, ketika Anda harus menggunakan hasil akhir di WHERE/ORDER BY)

SELECT calc1.ColA,
       calc2.ColB,
       calc3.ColC
  FROM TableA AS a
 CROSS APPLY (SELECT a.Org_A    * 100        AS ColA) AS calc1
 CROSS APPLY (SELECT calc1.ColA * 100        AS ColB) AS calc2
 CROSS APPLY (SELECT calc1.ColA + calc2.ColB AS ColC) AS calc3
 WHERE calc.ColB = @whatever
 ORDER BY calc.ColA, calc3.ColC

Poin utama untuk melakukan ini adalah bahwa Anda harus memodifikasi/memperbaiki hanya satu baris kode ketika Anda menemukan bug atau harus mengubah sesuatu daripada beberapa kejadian dan tidak berisiko mengalami banyak (sedikit berbeda, karena Anda lupa mengubah satu ) versi dari perhitungan yang sama.

PS: tentang keterbacaan CROSS APPLY biasanya akan menang vs beberapa sub seleksi bersarang (atau CTE), terutama ketika perhitungan menggunakan kolom dari tabel sumber yang berbeda atau Anda memiliki hasil sementara.

0
Thomas Franz