Perbedaan antara tampilan inline dan klausa DENGAN?

9

Tampilan sebaris memungkinkan Anda untuk memilih dari subquery seolah-olah itu adalah tabel yang berbeda:

SELECT
    *
FROM /* Selecting from a query instead of table */
    (
        SELECT
            c1
        FROM
            t1
        WHERE
            c1 > 0
    ) a
WHERE
    a.c1 < 50;

Saya telah melihat ini disebut menggunakan istilah yang berbeda: pandangan inline, DENGAN klausa, CTE dan tabel turunan. Bagi saya tampaknya mereka adalah sintaks khusus vendor yang berbeda untuk hal yang sama.

Apakah ini asumsi yang salah? Apakah ada perbedaan teknis / kinerja di antara ini?

Kshitiz Sharma
sumber
5
Nama "resmi" dari SQL Standar adalah Derived Table (yang Oracle namakan Inline View ) dan Common Table Expression (= WITH...). Anda dapat menulis ulang setiap Derived Table sebagai CTE, tetapi mungkin bukan sebaliknya (misalnya CTE Rekursif atau menggunakan CTE beberapa kali)
dnoeth

Jawaban:

8

Ada beberapa perbedaan penting antara tampilan inline (tabel turunan) dan WITH clause (CTE) di Oracle. Beberapa dari mereka cukup universal, yaitu berlaku untuk RDBMS lainnya.

  1. WITH dapat digunakan untuk membangun subqueries rekursif, tampilan inline -tidak (sejauh yang saya tahu sama untuk semua RDBMS yang mendukung CTE)
  2. Subquery dalam WITHklausa lebih mungkin dieksekusi secara fisik terlebih dahulu; dalam banyak kasus, memilih antara WITHtampilan dan inline membuat pengoptimal untuk memilih rencana eksekusi yang berbeda (saya kira itu vendor khusus, mungkin bahkan versi spesifik).
  3. Subquery in WITHdapat terwujud sebagai tabel sementara (saya tidak tahu apakah ada vendor lain tetapi Oracle mendukung fitur ini).
  4. Subquery in WITHdapat direferensikan beberapa kali, di subqueries lain, dan di query utama (berlaku untuk sebagian besar RDBMS).
a1ex07
sumber
MySQL (setidaknya versi terbaru MariaDB) dapat mematerialisasi tabel turunan (dan bahkan menambahkan indeks).
ypercubeᵀᴹ
3
Saya ingin menambahkan bahwa, sebagai manfaat sampingan, menggunakan CTE umumnya lebih mudah dibaca untuk manusia juga.
Joishi Bodio
@ JoishiBodio: Secara pribadi, saya setuju dengan Anda, tetapi keterbacaan adalah masalah yang sangat subjektif. Saya lebih suka tidak menyebutkannya
a1ex07
Selain itu, CTE dapat merujuk CTE yang dinyatakan sebelumnya. Tabel turunan tidak dapat mereferensikan tabel turunan yang dideklarasikan sebelumnya pada tingkat yang sama kecuali LATERALdigunakan
Lennart
8

Jawaban lain mencakup perbedaan sintaks dengan cukup baik sehingga saya tidak akan membahasnya. Sebaliknya jawaban ini hanya akan mencakup kinerja di Oracle.

Pengoptimal Oracle dapat memilih untuk mewujudkan hasil CTE ke dalam tabel sementara internal. Ia menggunakan heuristik untuk melakukan ini alih-alih optimasi berbasis biaya. Heuristik adalah sesuatu seperti "Mewujudkan CTE jika itu bukan ekspresi sepele dan CTE direferensikan lebih dari sekali dalam kueri". Ada beberapa pertanyaan dimana materialisasi akan meningkatkan kinerja. Ada beberapa pertanyaan yang materialisasi akan secara dramatis menurunkan kinerja. Contoh berikut ini sedikit dibuat-buat tetapi menggambarkan poin dengan baik:

Pertama-tama buat tabel dengan kunci utama yang berisi bilangan bulat dari 1 hingga 10.000:

CREATE TABLE N_10000 (NUM_ID INTEGER NOT NULL, PRIMARY KEY (NUM_ID));

INSERT /*+APPEND */ INTO N_10000
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= 10000
ORDER BY LEVEL;

COMMIT;

Pertimbangkan kueri berikut yang menggunakan dua tabel turunan:

SELECT t1.NUM_ID
FROM 
(
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
) t1
LEFT OUTER JOIN 
(
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
) t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;

Kami dapat melihat permintaan ini dan dengan cepat menentukan bahwa itu tidak akan mengembalikan baris apa pun. Oracle harus dapat menggunakan indeks untuk menentukan itu juga. Di mesin saya, kueri hampir selesai dengan rencana berikut:

rencana yang bagus

Saya tidak suka mengulang sendiri, jadi mari kita coba pertanyaan yang sama dengan CTE:

WITH N_10000_CTE AS (
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
)
SELECT t1.NUM_ID
FROM N_10000_CTE t1
LEFT JOIN N_10000_CTE t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;

Ini rencananya:

rencana yang buruk

Itu rencana yang sangat buruk. Alih-alih menggunakan indeks, Oracle mematerialisasi 10000 X 10000 = 100000000 baris ke tabel temp hanya untuk akhirnya mengembalikan 0 baris. Biaya paket ini sekitar 6 M yang jauh lebih tinggi daripada permintaan lainnya. Kueri membutuhkan waktu 68 detik untuk selesai di mesin saya.

Perhatikan bahwa kueri bisa gagal jika tidak ada cukup memori atau ruang kosong di temp tablespace.

Saya dapat menggunakan INLINEpetunjuk tidak berdokumen untuk melarang pengoptimal dari mewujudkan CTE:

WITH N_10000_CTE AS (
  SELECT /*+ INLINE */ n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
)
SELECT t1.NUM_ID
FROM N_10000_CTE t1
LEFT JOIN N_10000_CTE t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;

Permintaan itu dapat menggunakan indeks dan selesai hampir secara instan. Biaya permintaan sama dengan sebelumnya, 11. Jadi untuk permintaan kedua, heuristik yang digunakan oleh Oracle menghasilkannya dengan memilih permintaan dengan perkiraan biaya 6 M, bukan permintaan dengan perkiraan biaya 11.

Joe Obbish
sumber
1

Untuk SQL Server, WITH CTEtentukan himpunan sementara bernama hasil, tetapi hanya diperlukan untuk yang pertama CTE. yaitu

WITH CTE AS (SELECT .... FROM), 
CTE2 AS (SELECT .... FROM)

SELECT CTE.Column, CTE2.Column
FROM CTE
INNER JOIN CTE2 on CTE.Column = CTE2.Column

Tapi ini bukan subquery, atau subquery yang berhubungan. Ada hal-hal yang dapat Anda lakukan dengan CTE yang tidak dapat Anda lakukan dengan sub-kueri di SQL Server, seperti memperbarui Tabel yang dirujuk dalam CTE. Berikut adalah contoh memperbarui tabel dengan CTE.

Subquery akan seperti itu

SELECT
   C1,
   (SELECT C2 FROM SomeTable) as C2
FROM Table

Atau sub-kueri yang berkorelasi adalah apa yang Anda berikan dalam OP Anda jika Anda ingin referensi / bergabung / batasi hasil Anda berdasarkan a.c1.

Jadi, mereka jelas bukan hal yang sama, meskipun dalam banyak kasus Anda dapat menggunakan satu atau lebih dari metode ini untuk mencapai hasil yang sama. Itu tergantung apa hasil akhirnya.

scsimon
sumber
1

Perbedaan utama antara withklausa dan subquery di Oracle adalah bahwa Anda bisa mereferensikan kueri dalam klausa beberapa kali. Anda kemudian dapat melakukan beberapa optimasi dengan itu seperti mengubahnya menjadi tabel temp menggunakan materializepetunjuk. Anda juga bisa melakukan kueri rekursif dengan merujuknya sendiri di dalam withklausa. Anda tidak dapat melakukannya dengan tampilan inline.

Informasi lebih lanjut dapat ditemukan di sini dan di sini .

Marko Vodopija
sumber
Secara umum petunjuk terwujud tidak diperlukan. Secara default, Oracle optimizer memutuskan apakah masuk akal untuk mewujudkan CTE atau tidak - tetapi Anda dapat menimpa evaluasi optimizer dengan beberapa petunjuk MATERIALIZE. INLINEuntuk kebalikannya.
Wernfried Domscheit
@WernfriedDomscheit itu benar. Tetapi kadang-kadang optimizer tidak memilih untuk mewujudkan CTE dan dalam hal itu, menggunakan materializepetunjuk adalah pilihan yang valid. Saya kadang-kadang perlu menentukannya ketika mengoptimalkan pertanyaan yang sangat kompleks di mana saya tahu mewujudkan CTE akan menguntungkan rencana eksekusi.
Marko Vodopija
0

Anda perlu berhati-hati dengan CTE di SQL server bukan hanya oracle, ada kasus di mana kueri berkinerja lebih buruk ketika menggunakan CTE dibandingkan dengan subqueries, cross apply, dll.

Seperti biasa, penting untuk menguji permintaan apa pun di bawah berbagai kondisi pemuatan untuk menentukan mana yang terbaik.

Mirip dengan @scsimon dengan oracle, terkadang MS SQL server tidak melakukan apa yang Anda harapkan terkait penggunaan indeks.

Jika Anda akan menggunakan data yang sama lebih dari sekali, CTE dapat lebih bermanfaat, jika Anda hanya menggunakannya sekali, seringkali sebuah subquery lebih cepat dalam kumpulan data besar.

mis. pilih * dari (subquery saya) bergabung dengan yang lain ...

Justin
sumber