ฟังก์ชั่นแฮงค์กับการดำเนินการกรณีที่ไม่มี


9

ฉันสร้างฟังก์ชั่นที่ยอมรับวันที่เริ่มต้นและสิ้นสุดโดยมีวันที่สิ้นสุดเป็นตัวเลือก จากนั้นฉันเขียนCASEตัวกรองเพื่อใช้วันที่เริ่มต้นหากไม่มีวันที่สิ้นสุด

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

เมื่อฉันเรียกใช้ฟังก์ชันสำหรับเดือนล่าสุดของข้อมูล:

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

... แบบสอบถามหยุดทำงาน ถ้าฉันระบุวันที่สิ้นสุด:

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

... ผลลัพธ์จะถูกส่งคืนตามปกติ ฉันเอารหัสออกจากฟังก์ชั่นและวิ่งได้ดีในหน้าต่างแบบสอบถาม ฉันไม่สามารถทำซ้ำปัญหาซอทั้ง แบบสอบถามที่ชอบ:

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

... ยังใช้งานได้ดี

มีอะไรในแบบสอบถาม (ด้านล่าง) ที่อาจทำให้ฟังก์ชันหยุดทำงานเมื่อมีการNULLส่งผ่านสำหรับวันที่สิ้นสุดหรือไม่

ซอ Fiddle


คุณสามารถโพสต์ตรรกะเพิ่มเติมได้หรือไม่? สิ่งที่คุณมีไม่ควรทำให้เกิดปัญหา
Kenneth Fisher

3
หากคุณแทนที่CASEด้วยCOALESCE(@dateEnd,@dateStart)ปัญหาจะยังคงปรากฏขึ้นหรือไม่
ypercubeᵀᴹ

2
และด้วยISNULL()?
ypercubeᵀᴹ

3
มันยุ่งหรือรออะไรอยู่เหรอ? ในขณะที่มัน "แขวน" สิ่งที่จะSELECT task_state FROM sys.dm_os_tasks WHERE session_id = x แสดง? ถ้ามันใช้เวลามากเวลาที่ไม่ได้อยู่ในRUNNINGรัฐสิ่งที่รออยู่ในประเภทเซสชั่นที่ได้รับในsys.dm_os_waiting_tasks?
Martin Smith

1
ปรับปรุง @ypercube COALESCEไม่มี ISNULLซ่อมมัน.
Kermit

คำตอบ:


7

ส่วนหนึ่งของแบบสอบถามเริ่มต้นของคุณมีดังนี้

  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) 

ส่วนของแผนนั้นแสดงไว้ด้านล่าง

ป้อนคำอธิบายรูปภาพที่นี่

ข้อความค้นหาที่แก้ไขของคุณ BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)มีไว้สำหรับการเข้าร่วมเดียวกัน

ป้อนคำอธิบายรูปภาพที่นี่

ความแตกต่างนั้นเป็นสิ่งที่ISNULLทำให้ง่ายขึ้นอีกและด้วยเหตุนี้คุณจึงได้สถิติเชิงสถิติที่แม่นยำยิ่งขึ้นในการเข้าร่วมครั้งต่อไป นี่คือฟังก์ชันที่มีค่าในตารางแบบอินไลน์และคุณกำลังเรียกมันด้วยค่าที่แท้จริงเพื่อให้สามารถทำสิ่งที่ต้องการ

 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'

และในขณะที่มีการเข้าร่วม equi สรุปแผนนอกจากนี้ยังแสดงให้เห็นถึงความเท่าเทียมกันกริยาb.[Date] = a.d b.[Date] = '2013-06-01'ผลที่ตามมาคือการประมาณความ28,393น่าจะเป็นของแถวที่ค่อนข้างแม่นยำ

สำหรับCASE/ COALESCEรุ่นเมื่อใด@dateStartและ@dateEndมีค่าเท่ากันมันจะทำให้ตกลงไปยังนิพจน์ความเสมอภาคเดียวกันได้ง่ายขึ้นและให้แผนเดียวกัน แต่เมื่อใด@dateStart = '2013-06-01'และ@dateEnd IS NULLมันไปเท่าที่

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

ColleagueListที่มันยังใช้เป็นคำกริยาโดยนัย จำนวนแถวโดยประมาณในครั้งนี้คือ79.8แถว

เข้าร่วมต่อไปคือ

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

colleagueTimeเป็น3,249,590ตารางแถวที่เห็นได้ชัดคือฮีปที่ไม่มีดัชนีที่มีประโยชน์

ความคลาดเคลื่อนในการประมาณนี้ส่งผลต่อตัวเลือกการเข้าร่วมที่ใช้ ISNULLแผนเลือกกัญชาเข้าร่วมว่าเพียงแค่สแกนตารางครั้ง COALESCEแผนเลือกลูปซ้อนกันเข้าร่วมและประมาณการว่ามันจะยังคงเป็นเพียงต้องสแกนตารางครั้งและสามารถที่จะ spool ผลและเล่นมัน 78 ครั้ง นั่นคือมันประมาณว่าพารามิเตอร์ที่สัมพันธ์กันจะไม่เปลี่ยนแปลง

จากความจริงที่ว่าแผนการลูปซ้อนกันยังคงดำเนินต่อไปหลังจากสองชั่วโมงข้อสันนิษฐานของการสแกนครั้งเดียวต่อcolleagueTimeดูเหมือนว่าจะไม่ถูกต้องสูง

สำหรับสาเหตุที่จำนวนแถวโดยประมาณระหว่างการรวมสองครั้งนั้นต่ำกว่านี้มากฉันไม่แน่ใจโดยที่ไม่สามารถดูสถิติในตารางได้ วิธีเดียวที่ฉันสามารถจัดการกับการนับแถวโดยประมาณที่มากในการทดสอบของฉันคือการเพิ่มจำนวนNULLแถว (นี่เป็นการลดจำนวนแถวโดยประมาณแม้ว่าจำนวนแถวที่แท้จริงจะกลับมาเหมือนเดิม)

จำนวนแถวโดยประมาณในCOALESCEแผนข้อมูลการทดสอบของฉันอยู่ในลำดับ

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

หรือใน SQL

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

แต่นี่ไม่ได้เป็นรูปสี่เหลี่ยมจัตุรัสกับความคิดเห็นของคุณว่าคอลัมน์ไม่มีNULLค่า


"คุณมีสัดส่วนค่า NULL สูงมากในคอลัมน์วันที่ในตารางนั้นหรือไม่" ฉันไม่มีNULLค่าสำหรับวันที่ในตารางเหล่านั้น
มิต

@FreshPrinceOfSO - น่าเสียดาย ฉันยังไม่รู้ว่าทำไมจึงมีความคลาดเคลื่อนขนาดใหญ่ในการประมาณการทั้งสองครั้ง ในการทดสอบฉันทำฟิลเตอร์บิตแมปและเพรดิเคตเพิ่มเติมดูเหมือนจะไม่เปลี่ยนแปลงค่าประมาณของ cardinality บางทีอาจเป็นเพราะที่นี่
Martin Smith

@FreshPrinceOfSO - แม้ว่าคุณจะรู้สึกอยากเขียนสคริปต์สถิติฉันสามารถลองและคิดออก
Martin Smith

ฉันวันที่ 2008R2; เมื่อฉันได้รับการเลือก Schemas , dboไม่อยู่ในรายการ เพียง schema อื่น ๆ ที่ฉันไม่ได้ใช้
Kermit

4

ดูเหมือนว่ามีปัญหากับชนิดข้อมูล ISNULLแก้ไขปัญหา (ขอบคุณypercube ) หลังจากการวิจัยบางอย่างCOALESCEเทียบเท่ากับCASEข้อความที่ฉันใช้:

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

Paul White อธิบายว่า:

COALESCE( expression [ ,...n ] ) ส่งคืนชนิดข้อมูลของนิพจน์ที่มีลำดับความสำคัญสูงสุดเป็นประเภทข้อมูล

ISNULL(check_expression, replacement_value) ส่งคืนชนิดเดียวกันกับ check_expression

เพื่อหลีกเลี่ยงปัญหาประเภทข้อมูลใด ๆ ดูเหมือนว่าISNULLเป็นฟังก์ชั่นที่เหมาะสมที่จะใช้สำหรับจัดการกับสองนิพจน์เท่านั้น

เนื้อหาแผน XML

แผนการใช้XMLCASEการแสดงออก 2 คือ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>

แผนการใช้XMLCASEการแสดงออก 2 เป็นวันที่:

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>

แผนการใช้XMLISNULLการแสดงออก 2 คือ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>

แผนการใช้XMLISNULLการแสดงออก 2 เป็นวันที่:

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>

แต่นั่นไม่ได้อธิบายว่าทำไมมันจึงใช้ได้SELECT * FROM theFunction ('2013-06-01', '2013-06-01')ดี ประเภทข้อมูลการแสดงออกยังคงเหมือนเดิม และพารามิเตอร์ทั้งสองเป็นdateประเภทข้อมูลต่อไป คุณสามารถดูแผนการดำเนินการได้หรือไม่
Martin Smith

@MartinSmith นี่คือแผนสำหรับการสืบค้นที่ส่งคืนผลลัพธ์ NULLฉันไม่ได้มีแผนเมื่อการแสดงออกที่สองคือ
Kermit

การส่งนิพจน์ภายในCASEยังไม่มีผลใด ๆ แบบสอบถามยังคงค้างอยู่
Kermit

2
ทำไมไม่มีแผนสำหรับคดีที่สอง? เป็นเพราะแบบสอบถามไม่เคยเสร็จสิ้น? ถ้าเป็นเช่นนั้นคุณจะได้รับแผนโดยประมาณ สงสัยว่าการแสดงออกที่แตกต่างกันเปลี่ยนการประมาณค่าของ cardinality หรือไม่และคุณจะจบลงด้วยแผนการที่แตกต่าง
Martin Smith

3
ISNULLแผนดูเหมือนว่ามันช่วยลดความยุ่งยากที่ดีกว่า แต่ก็มีคำกริยาที่เรียบง่ายความเท่าเทียมกันใน ColleagueList ของ[Date]='2013-06-01'ในขณะที่ คนมีกริยาบนCASE [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date])แถวโดยประมาณที่มาจากการเข้าร่วมนั้นคือ 28,393 สำหรับISNULLรุ่น แต่ต่ำกว่ามาก79.8สำหรับCASEรุ่นที่มีผลต่อตัวเลือกการเข้าร่วมในภายหลังในแผน ไม่แน่ใจว่าทำไมถึงมีความคลาดเคลื่อนดังกล่าว
Martin Smith
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.