fungsi hang dengan operasi kasus nol

9

Saya membuat fungsi yang menerima tanggal mulai dan berakhir, dengan tanggal akhir menjadi opsional. Saya kemudian menulis CASEdi filter untuk menggunakan tanggal mulai jika tidak ada tanggal akhir yang dilewati.

CASE WHEN @dateEnd IS NULL
    THEN @dateStart
    ELSE @dateEnd
END

Ketika saya memanggil fungsi untuk bulan terakhir dari data:

SELECT * FROM theFunction ('2013-06-01', NULL)

... permintaannya hang. Jika saya menentukan tanggal akhir:

SELECT * FROM theFunction ('2013-06-01', '2013-06-01')

... hasilnya dikembalikan secara normal. Saya mengambil kode dari fungsi dan menjalankannya dengan baik di dalam jendela permintaan. Saya tidak bisa menduplikasi masalah biola juga. Kueri seperti:

SELECT * FROM theFunction ('2013-04-01', '2013-06-01')

... juga berfungsi dengan baik.

Apakah ada sesuatu dalam kueri (di bawah) yang dapat menyebabkan fungsi hang ketika a NULLdisahkan untuk tanggal akhir?

SQL Fiddle

Kermit
sumber
Bisakah Anda memposting lebih banyak logika? Apa yang Anda miliki di sana seharusnya tidak menyebabkan masalah.
Kenneth Fisher
3
Jika Anda mengganti CASEdengan COALESCE(@dateEnd,@dateStart), apakah masalah masih muncul?
ypercubeᵀᴹ
2
Dan dengan ISNULL()?
ypercubeᵀᴹ
3
Apakah sedang sibuk atau menunggu sesuatu? Sementara itu "digantung" apa yang SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x ditampilkan? Jika menghabiskan banyak waktu tidak di RUNNINGnegara apa jenis menunggu sesi yang masuk sys.dm_os_waiting_tasks?
Martin Smith
1
@ ypercube Tidak ada peningkatan dengan COALESCE. ISNULLmemperbaikinya.
Kermit

Jawaban:

7

Bagian dari permintaan awal Anda adalah sebagai berikut.

  FROM   [dbo].[calendar] a
          LEFT JOIN [dbo].[colleagueList] b
            ON b.[Date] = a.d
   WHERE  DAY(a.[d]) = 1
          AND a.[d] BETWEEN @dateStart AND COALESCE(@dateEnd,@dateStart) 

Bagian rencana itu ditunjukkan di bawah ini

masukkan deskripsi gambar di sini

Permintaan Anda yang direvisi BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)memiliki ini untuk gabungan yang sama

masukkan deskripsi gambar di sini

Perbedaannya tampaknya yang ISNULLmenyederhanakan lebih lanjut dan sebagai hasilnya Anda mendapatkan statistik kardinalitas yang lebih akurat masuk ke gabung berikutnya. Ini adalah fungsi bernilai tabel inline dan Anda memanggilnya dengan nilai literal sehingga dapat melakukan sesuatu seperti.

 a.[d] BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart) 
 a.[d] BETWEEN '2013-06-01' AND ISNULL(NULL,'2013-06-01') 
 a.[d] BETWEEN '2013-06-01' AND '2013-06-01'
 a.[d] = '2013-06-01'

Dan karena ada equi join predikat b.[Date] = a.d, rencana itu juga menunjukkan predikat kesetaraan b.[Date] = '2013-06-01'. Akibatnya, perkiraan kardinalitas dari 28,393baris cenderung cukup akurat.

Untuk versi CASE/ COALESCEkapan @dateStartdan @dateEndmemiliki nilai yang sama maka itu menyederhanakan OK untuk persamaan kesetaraan yang sama dan memberikan rencana yang sama tetapi kapan @dateStart = '2013-06-01'dan @dateEnd IS NULLhanya berjalan sejauh

a.[d]>='2013-06-01' AND a.[Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END

yang juga berlaku sebagai predikat tersirat pada ColleagueList. Perkiraan jumlah baris kali ini adalah 79.8baris.

Bergabung berikutnya adalah

   LEFT JOIN colleagueTime
     ON colleagueTime.TC_DATE = colleagueList.Date
        AND colleagueTime.ASSOC_ID = CAST(colleagueList.ID AS VARCHAR(10)) 

colleagueTimeadalah 3,249,590tabel baris yang (sekali lagi) ternyata tumpukan tanpa indeks yang berguna.

Perbedaan dalam estimasi ini memengaruhi pilihan bergabung yang digunakan. The ISNULLrencana memilih hash join yang hanya memindai meja sekali. The COALESCErencana memilih sebuah loop bersarang bergabung dan perkiraan bahwa itu akan tetap hanya perlu memindai meja sekali dan dapat spool hasil dan memutar ulang 78 kali. yaitu memperkirakan bahwa parameter berkorelasi tidak akan berubah.

Dari kenyataan bahwa rencana loop bersarang masih berjalan setelah dua jam, asumsi pemindaian tunggal terhadap ini colleagueTimetampaknya sangat tidak akurat.

Adapun mengapa perkiraan jumlah baris antara dua gabungan jauh lebih rendah, saya tidak yakin tanpa bisa melihat statistik di tabel. Satu-satunya cara saya berhasil memiringkan jumlah baris yang diperkirakan dalam pengujian saya adalah menambahkan banyak NULLbaris (ini mengurangi jumlah baris yang diperkirakan meskipun jumlah sebenarnya baris yang dikembalikan tetap sama).

Perkiraan jumlah baris dalam COALESCEpaket dengan data pengujian saya adalah dalam urutan

number of rows matching >= condition * 30% * (proportion of rows in the table not null)

Atau dalam SQL

SELECT 1E0 * COUNT([Date]) / COUNT(*) * ( COUNT(CASE
                                                  WHEN [Date] >= '2013-06-01' THEN 1
                                                END) * 0.30 )
FROM   [dbo].[colleagueList] 

tetapi ini tidak sesuai dengan komentar Anda bahwa kolom tidak memiliki NULLnilai.

Martin Smith
sumber
"Apakah Anda memiliki proporsi nilai NULL yang sangat tinggi di kolom Tanggal di tabel itu?" Saya tidak memiliki NULLnilai untuk tanggal di tabel mana pun.
Kermit
@FreshPrinceOfSO - Sayang sekali. Saya masih tidak tahu mengapa ada perbedaan besar dalam dua perkiraan itu. Dalam tes yang saya lakukan, filter bitmap dan predikat tambahan tampaknya tidak mengubah perkiraan kardinalitas yang mungkin dilakukannya di sini.
Martin Smith
@FreshPrinceOfSO - Meskipun jika Anda merasa ingin menulis statistik saya dapat mencoba dan mencari tahu.
Martin Smith
Saya menggunakan 2008R2; ketika saya bisa memilih Skema , dbotidak terdaftar. Hanya skema lain yang tidak saya gunakan.
Kermit
4

Sepertinya ada masalah dengan tipe data. ISNULLmemperbaiki masalah (terima kasih ypercube ). Setelah beberapa penelitian, COALESCEadalah setara dengan CASEpernyataan yang saya gunakan:

CASE
   WHEN (expression1 IS NOT NULL) THEN expression1
   WHEN (expression2 IS NOT NULL) THEN expression2
   ...
   ELSE expressionN
END

Paul White menjelaskan bahwa:

COALESCE( expression [ ,...n ] ) mengembalikan tipe data dari ekspresi dengan diutamakan tipe data tertinggi.

ISNULL(check_expression, replacement_value) mengembalikan tipe yang sama dengan check_expression.

Untuk menghindari masalah tipe data apa pun, tampaknya ISNULLini adalah fungsi yang tepat untuk digunakan untuk menangani hanya dua ekspresi.

Kutipan Rencana XML

Menggunakan rencana XMLCASE , ekspresi 2 adalah NULL:

SELECT * FROM theFunction ('2013-06-01', NULL)
<ScalarOperator ScalarString="CASE WHEN (1) THEN '2013-06-01' ELSE NULL END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Const ConstValue="(1)"/>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="'2013-06-01'"/>
      </ScalarOperator>
    </Then>
    <Else>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>

Menggunakan rencana XMLCASE , ekspresi 2 adalah tanggal:

SELECT * FROM theFunction ('2013-06-01', '2013-06-01')
<ScalarOperator ScalarString="CASE WHEN [Expr1035]=(0) THEN NULL ELSE [Expr1036] END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Compare CompareOp="EQ">
          <ScalarOperator>
            <Identifier>
              <ColumnReference Column="Expr1035"/>
            </Identifier>
          </ScalarOperator>
          <ScalarOperator>
            <Const ConstValue="(0)"/>
          </ScalarOperator>
        </Compare>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
      </Then>
    <Else>
      <ScalarOperator>
        <Identifier>
          <ColumnReference Column="Expr1036"/>
        </Identifier>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>

Menggunakan rencana XMLISNULL , ekspresi 2 adalah NULL:

SELECT * FROM theFunction ('2013-06-01', NULL)
<ScalarOperator ScalarString="CASE WHEN [Expr1035]=(0) THEN NULL ELSE [Expr1036] END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Compare CompareOp="EQ">
          <ScalarOperator>
            <Identifier>
              <ColumnReference Column="Expr1035"/>
            </Identifier>
          </ScalarOperator>
          <ScalarOperator>
            <Const ConstValue="(0)"/>
          </ScalarOperator>
        </Compare>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
    </Then>
    <Else>
      <ScalarOperator>
        <Identifier>
          <ColumnReference Column="Expr1036"/>
        </Identifier>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>

Menggunakan rencana XMLISNULL , ekspresi 2 adalah tanggal:

SELECT * FROM theFunction ('2013-06-01', '2013-06-01')
<ScalarOperator ScalarString="CASE WHEN [Expr1035]=(0) THEN NULL ELSE [Expr1036] END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Compare CompareOp="EQ">
          <ScalarOperator>
            <Identifier>
              <ColumnReference Column="Expr1035"/>
            </Identifier>
          </ScalarOperator>
          <ScalarOperator>
            <Const ConstValue="(0)"/>
          </ScalarOperator>
        </Compare>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
    </Then>
    <Else>
      <ScalarOperator>
        <Identifier>
          <ColumnReference Column="Expr1036"/>
        </Identifier>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>
Kermit
sumber
Tetapi itu tidak menjelaskan mengapa itu berhasil SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). Tipe data ekspresi masih sama. Dan kedua parameter tersebut adalah datetipe data. Bisakah Anda melihat rencana eksekusi?
Martin Smith
@MartinSmith Inilah rencana untuk kueri yang mengembalikan hasil. Saya tidak punya rencana ketika ekspresi kedua NULL.
Kermit
Casting ekspresi di dalam CASEjuga tidak berpengaruh, permintaan masih hang.
Kermit
2
Kenapa tidak ada rencana untuk kasus kedua? Apakah hanya karena kueri tidak pernah selesai? Jika demikian, bisakah Anda mendapatkan perkiraan rencana? Ingin tahu apakah ekspresi yang berbeda mengubah perkiraan kardinalitas dan Anda berakhir dengan rencana yang berbeda.
Martin Smith
3
The ISNULLrencana tampak seperti menyederhanakan lebih baik. Ini memiliki predikat kesetaraan sederhana di ColleagueList, [Date]='2013-06-01'sedangkan CASEpredikatnya ada pada [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). Perkiraan baris yang keluar dari gabungan itu adalah 28.393 untuk ISNULLversi tetapi jauh lebih rendah di 79.8untuk CASEversi yang mempengaruhi pilihan bergabung nanti dalam rencana. Tidak yakin mengapa ada perbedaan seperti itu.
Martin Smith