用户定义的函数在查询中是时间消费者,现在是什么?

时间:2011-04-30 11:34:50

标签: sql-server sql-server-2005

我有一个将Gregorian datetime转换为Jalali datetime的sql函数。 我有一张桌子

Id     StartDate       FinishDate        AlarmDate

当我执行一个简单的查询,例如这个

select Id,dbo.Jalali(StartDate) , dbo.Jalali(FinishDate),dbo.Jalali(AlarmDate) from MyTable

此查询将浪费超过2分钟的时间。该表只有2000条记录。

解决方案是什么?

功能

create FUNCTION dbo.MiladiToShamsi
(@dd datetime) 
RETURNS char(10)
AS 
BEGIN
DECLARE @mahs as char(2)
DECLARE @rozs as char(2)
DECLARE @diff As int
DECLARE @i As int
DECLARE @leap As int
DECLARE @roz AS int
DECLARE @mah As int
DECLARE @sal As int


SELECT @roz = 11
SELECT @mah = 10
SELECT @sal = 1358

SELECT @diff = DateDiff("d", cast('1980/01/01' as datetime), @dd) -- leap year

SELECT @i = 1

while @i <= @diff
BEGIN
    SELECT @roz = @roz + 1


    If @mah = 12 And  ((@sal+1) - ((@sal+1)/4)*4) <> 0
            If @roz > 29 BEGIN
                SELECT @roz = 1
                SELECT @mah = @mah + 1
            End




    If @mah > 12 BEGIN
      SELECT @sal = @sal + 1
      SELECT @mah = 1
   End

    If @mah > 6
            If @roz > 30 BEGIN
                SELECT @roz = 1
                SELECT @mah = @mah + 1
            End
    if @mah <= 6
            If @roz > 31 BEGIN
                   SELECT @roz = 1
                   SELECT @mah = @mah + 1
            End 
   SELECT @i = @i + 1

END

if @mah < 10
    SELECT @mahs = '0' + LTRIM(RTRIM(str(@mah)))
else
    SELECT @mahs = LTRIM(RTRIM(str(@mah)))

if @roz < 10
    SELECT @rozs = '0' + LTRIM(RTRIM(str(@roz)))
else
    SELECT @rozs = LTRIM(RTRIM(str(@roz)))

RETURN LTRIM(RTRIM(str(@sal))) + '/' + LTRIM(RTRIM(@mahs)) + '/' + LTRIM(RTRIM(@rozs))
END

1 个答案:

答案 0 :(得分:4)

您的功能问题取决于它循环的次数。您将于1980年1月1日开始参考日。因此,要获得最新信息,您需要循环大约30 * 365(11,000次)。我对Jalali日历一无所知,但通过查看代码,似乎每个公历日期在Jalali日历系统中只有一个表示。因此,您可以使用简单的查找表替换您的函数(执行大量循环)。

构建查找表:

CREATE TABLE [dbo].[Calendar](
    [Gregorian] [datetime] NOT NULL,
    [Jalali] [char](10) NOT NULL,
 CONSTRAINT [PK_Calendar] PRIMARY KEY CLUSTERED 
(
    [Gregorian] ASC,
    [Jalali] ASC
) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX [idx_Calendar_Jalali_Gregorian] ON [dbo].[Calendar] 
(
    [Jalali] ASC,
    [Gregorian] ASC
) ON [PRIMARY]

GO

使用值填充查找表:

Declare @dd datetime

DECLARE @mahs as char(2)
DECLARE @rozs as char(2)
DECLARE @diff As int
DECLARE @i As int
DECLARE @leap As int
DECLARE @roz AS int
DECLARE @mah As int
DECLARE @sal As int


SELECT @roz = 11
SELECT @mah = 10
SELECT @sal = 1358

SELECT @diff = DateDiff("d", cast('1980/01/01' as datetime), @dd) -- leap year

SELECT @i = 1

Set @dd = '19800101'
while @dd <= '22000101'
BEGIN
    SELECT @roz = @roz + 1


    If @mah = 12 And  ((@sal+1) - ((@sal+1)/4)*4) <> 0
            If @roz > 29 BEGIN
                SELECT @roz = 1
                SELECT @mah = @mah + 1
            End




    If @mah > 12 BEGIN
      SELECT @sal = @sal + 1
      SELECT @mah = 1
   End

    If @mah > 6
            If @roz > 30 BEGIN
                SELECT @roz = 1
                SELECT @mah = @mah + 1
            End
    if @mah <= 6
            If @roz > 31 BEGIN
                   SELECT @roz = 1
                   SELECT @mah = @mah + 1
            End 

    if @mah < 10
        SELECT @mahs = '0' + LTRIM(RTRIM(str(@mah)))
    else
        SELECT @mahs = LTRIM(RTRIM(str(@mah)))

    if @roz < 10
        SELECT @rozs = '0' + LTRIM(RTRIM(str(@roz)))
    else
        SELECT @rozs = LTRIM(RTRIM(str(@roz)))

    Insert Into Calendar(Gregorian, Jalali)
    Select @dd, LTRIM(RTRIM(str(@sal))) + '/' + LTRIM(RTRIM(@mahs)) + '/' + LTRIM(RTRIM(@rozs))

   SELECT @dd = DATEADD(day, 1, @dd)

END

现在您可以将功能简化为:

CREATE FUNCTION dbo.MiladiToShamsi
(@dd datetime) 
RETURNS char(10)
AS 
BEGIN
    Return (Select Jalali 
            From   dbo.Calendar 
            Where  Gregorian = DateAdd(Day, DateDiff(Day, 0, @dd), 0)
            )
END

现在,当您运行查询时,它应该表现得更好。但是,当您具有执行像此访问权限的用户定义函数时,性能可能仍会受到影响,因为SQL Server将为每个函数调用访问一次表。相反,最好不要使用该功能。现在有一个查找表,你可以简单地加入它(3次)来获得所有的转换,如下所示:

select Id,
       StartDate.Jalali As StartDate,
       FinishDate.Jalali As FinishDate,
       AlarmDate.Jalali As AlarmDate
From   MyTable
       Inner Join Calendar As StartDate
          On MyTable.StartDate = StartDate.Gregorian
       Inner Join Calendar As FinishDate
          On MyTable.FinishDate = FinishDate.Greogorian
       Inner Join Calendar As AlarmDate
          On MyTable.AlarmDate = AlarmDate.Gregorian

你说,在原帖中,你花了2分多钟才得到你的结果。如果您决定在这里遵循我的建议,我很想知道我描述的方法需要多长时间。我绝对相信它会比你现在的方法更快。