Bir seçim eklerken kendi kendine referans skalar işlevi yuvalama düzeyi aşıldı


24

amaç

Kendi kendine başvuru işlevinin bir test örneği oluşturmaya çalışırken, bir sürüm başarılı iken bir sürüm başarısız oluyor.

Tek fark, SELECTher ikisi için de farklı bir yürütme planına yol açan işlev gövdesine eklenmektir .


Çalışan fonksiyon

CREATE FUNCTION dbo.test5(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN  dbo.test5(1) + dbo.test5(2)
END
)
END;

İşlev çağrısı

SELECT dbo.test5(3);

İade

(No column name)
3

Çalışmayan fonksiyon

CREATE FUNCTION dbo.test6(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.test6(1) + dbo.test6(2))
END
)END;

İşlev çağrısı

SELECT dbo.test6(3);

veya

SELECT dbo.test6(2);

Hata ile sonuçlanır

Maksimum saklı yordam, işlev, tetikleme veya yuvalama düzeyi görünümü aşıldı (sınır 32).

Nedenini tahmin etmek

Arızalı işlevin tahmini planında ek bir hesaplama skalası vardır.

<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END">

Ve expr1000 olmak

<ColumnReference Column="Expr1000" />
<ScalarOperator ScalarString="[dbo].[test6]((1))+[dbo].[test6]((2))">

Hangi 32'yi aşan özyinelemeli referansları açıklayabilir.

Gerçek soru

Eklenen SELECT, fonksiyonun kendisini tekrar tekrar çağırmasını sağlar ve sonsuz bir döngüye neden olur, ancak neden SELECTbu sonucu veriyor?


ilave bilgi

Tahmini yürütme planları

DB <> keman

Build version:
14.0.3045.24

Uyumluluk seviyesi 100 ve 140'ta test edildi

Yanıtlar:


26

Bu, proje normalleştirmesinde , deterministik olmayan bir işlevi olan bir vaka ifadesinde bir alt sorgu kullanarak ortaya çıkan bir hatadır .

Açıklamak için iki şeyi önceden not almamız gerekiyor:

  1. SQL Server, alt sorguları doğrudan çalıştıramaz, bu nedenle her zaman denetlenmez veya bir uygulamaya dönüştürülür .
  2. 'In semantiği, CASEbir THENifadenin sadece WHENmadde doğru döndüğünde değerlendirilmesinin gerektiği şekildedir .

Bu nedenle, sorunlu durumda ortaya çıkan (önemsiz) alt sorgu, geçerli bir işleçle sonuçlanır (iç içe döngüler birleşir). İkinci gereksinimi karşılamak için, SQL Server ilk önce ifadeyi dbo.test6(1) + dbo.test6(2)uygulamanın iç tarafına yerleştirir:

vurgulanan hesaplama skaler

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

... birleşimde başarılıCASE bir belirleyici tarafından onurlandırılan anlambilim ile :

[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)

Döngünün iç tarafı ancak doğrudan geçiş koşulu false (anlam @i = 3) olarak değerlendirilirse değerlendirilir . Şimdiye kadar hepsi doğru. Hesapla Scalar iç içe döngüler de onurlandırıyor katılmak aşağıdaki CASEdoğru anlambilim:

[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

Sorun, sorgu derlemesinin proje normalleştirme aşamasının Expr1000, ilişkisiz olduğunu görmesi ve onu döngünün dışına taşımanın güvenli olacağını ( anlatıcı: değil ) belirlemesidir:

taşınan proje

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

Bu sonları uyguladığı anlambilim * geçişkenliğindeki olması gerektiği halde işlev değerlendirilir, böylece yüklem ve sonsuz döngü sonuçları.

Bu hatayı bildirmelisin. Bir geçici çözüm, ifadenin ilişkili hale getirilmesiyle (yani @iifadeye dahil olmak üzere ) başvurunun dışına taşınmasını önlemektir, ancak bu bir kesintidir. Proje normalleştirmesini devre dışı bırakmanın bir yolu var, ancak daha önce halka açık olarak paylaşmamam istendi, bu yüzden yapmayacağım.

SQL Server 2019'da skaler işlevi satır içine alındığında bu sorun ortaya çıkmaz , çünkü satır içi mantık doğrudan ayrıştırılan ağaç üzerinde çalışır (proje normalizasyonundan hemen önce). Sorudaki basit mantık özyinelemeli olmayanlara yönelik mantıkla basitleştirilebilir:

[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))

... 3 döndürür.

Çekirdek sorunu göstermek için başka bir yol:

-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error() 
RETURNS integer 
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
    RETURN 1/0;
END;
GO
DECLARE @i integer = 1;

SELECT
    CASE 
        WHEN @i = 1 THEN 1
        WHEN @i = 2 THEN 2
        WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
        ELSE NULL
    END;

2008 R2 ile 2019 CTP 3.0 arasındaki tüm sürümlerin en son sürümlerinde çoğaltılmıştır.

Martin Smith tarafından sağlanan bir başka örnek (skaler fonksiyon olmadan) :

SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))

Bu gerekli tüm temel unsurlara sahiptir:

  • CASE(dahili olarak uygulandı ScaOp_IIF)
  • Deterministik olmayan bir işlev ( CRYPT_GEN_RANDOM)
  • Dalda çalıştırılmaması gereken bir alt sorgu ( (SELECT ...))

* Kesinlikle, değerlendirmenin Expr1000doğru bir şekilde ertelenmesi durumunda, yalnızca güvenli yapı tarafından referans verildiği için yukarıdaki dönüşüm hala doğru olabilir :

[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

... ancak bu, ayarlanmamış bir iç ForceOrder bayrağı (sorgu ipucu değil) gerektirir . Her durumda, proje normalizasyonu tarafından uygulanan mantığın uygulanması yanlış veya eksiktir.

SQL Server için Azure Feedback sitesinde hata raporu .

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.