2013-04-26 317 views
0

我正在创建函数以返回两个日期之间工作的分钟数。性能函数SQL Server

这会返回minuts的确切数量,但是当我在很多记录上使用它时,治疗时间很长。

我有3个功能:

CREATE FUNCTION FN_FERIES_SELON_ANNEE (@YEAR INT) 
RETURNS @FERIES TABLE (JourId INT NOT NULL, 
         JourDate DATETIME NOT NULL, 
         JoURLabel VARCHAR(50) NULL) 
AS 
BEGIN 
    DECLARE @JoursFeries TABLE (  
     [JourId] [INT] IDENTITY(1,1) NOT NULL,  
     [JourDate] [DATETIME] NOT NULL,  
     [JoURLabel] [VARCHAR](50) NULL 
    )  

    DECLARE @an INT 
    DECLARE @G INT  
    DECLARE @I INT  
    DECLARE @J INT  
    DECLARE @C INT  
    DECLARE @H INT  
    DECLARE @L INT  
    DECLARE @JourPaque INT  
    DECLARE @MoisPaque INT  
    DECLARE @DimPaque DATETIME  
    DECLARE @LunPaque DATETIME  
    DECLARE @JeuAscension DATETIME  
    DECLARE @LunPentecote DATETIME  
    DECLARE @NouvelAn DATETIME  
    DECLARE @FeteTravail DATETIME  
    DECLARE @Armistice3945 DATETIME  
    DECLARE @Assomption DATETIME  
    DECLARE @Armistice1418 DATETIME  
    DECLARE @FeteNationale DATETIME  
    DECLARE @ToussaINT DATETIME  
    DECLARE @Noel DATETIME 

    SET @an = @YEAR 

    SET @G = @an % 19  
    SET @C = @an/100  
    SET @H = (@C - @C/4 - (8 * @C + 13)/25 + 19 * @G + 15) % 30  
    SET @I = @H - (@H/28) * (1 - (@H/28) * (29/(@H + 1)) * ((21 - @G)/11))  
    SET @J = (@an + @an/4 + @I + 2 - @C + @C/4) % 7  

    SET @L = @I - @J  
    SET @MoisPaque = 3 + (@L + 40)/44  
    SET @JourPaque = @L + 28 - 31 * (@MoisPaque/4)  

    -- Jours fériés mobiles  

    SET @DimPaque = cast(cast(@an AS VARCHAR(4)) + '-'  
        + cast(@MoisPaque AS VARCHAR(2)) + '-'  
        + cast(@JourPaque AS VARCHAR(2)) AS DATETIME)  
    SET @LunPaque = DATEADD(DAY, 1, @DimPaque)  
    SET @JeuAscension = DATEADD(DAY, 39, @DimPaque)  
    SET @LunPentecote = DATEADD(DAY, 50, @DimPaque)  

    -- Jours fériés fixes  

    SET @NouvelAn = cast(cast(@an AS VARCHAR(4))+'-01-01 00:00:00' AS DATETIME)  
    SET @FeteTravail = cast(cast(@an AS VARCHAR(4))+'-05-01 00:00:00' AS DATETIME)  
    SET @Armistice3945 = cast(cast(@an AS VARCHAR(4))+'-05-08 00:00:00' AS DATETIME)  
    SET @Assomption = cast(cast(@an AS VARCHAR(4))+'-08-15 00:00:00' AS DATETIME)  
    SET @Armistice1418 = cast(cast(@an AS VARCHAR(4))+'-11-11 00:00:00' AS DATETIME)  
    SET @FeteNationale = cast(cast(@an AS VARCHAR(4))+'-07-14 00:00:00' AS DATETIME)  
    SET @ToussaINT = cast(cast(@an AS VARCHAR(4))+'-11-01 00:00:00' AS DATETIME)  
    SET @Noel = cast(cast(@an AS VARCHAR(4))+'-12-25 00:00:00' AS DATETIME)  

    INSERT INTO @JoursFeries (JourDate, JoURLabel) 
    SELECT @LunPaque, 'Lundi de Pâques' 
    UNION 
    SELECT @JeuAscension, 'Jeudi de l''Ascension' 
    UNION 
    SELECT @LunPentecote, 'Lundi de Pentecôte' 
    UNION 
    SELECT @NouvelAn, 'Nouvel an' 
    UNION 
    SELECT @FeteTravail, 'Fête du travail' 
    UNION 
    SELECT @Armistice3945, 'Armistice 39-45' 
    UNION 
    SELECT @Assomption, 'Assomption' 
    UNION 
    SELECT @FeteNationale, 'Fête Nationale' 
    UNION 
    SELECT @ToussaINT, 'Toussaint' 
    UNION 
    SELECT @Armistice1418, 'Armistice 14-18' 
    UNION 
    SELECT @Noel, 'Noël' 

    INSERT INTO @FERIES 
    SELECT * FROM @JoursFeries 
    RETURN 
END 
GO 

CREATE FUNCTION FN_JOUR_TRAVAILLE (@Date1 DATETIME) 
RETURNS INT AS 
BEGIN 
    DECLARE @FLAG INT 
    SET @FLAG = 1 
    DECLARE @YEAR INT 
    SET @YEAR = DATEPART(YEAR, @DATE1) 

    IF EXISTS(SELECT * FROM FN_FERIES_SELON_ANNEE(@YEAR) WHERE JourDate = @Date1) BEGIN 
     SET @FLAG = 0 
    END 
    ELSE 
     IF DatePart(weekday, @DATE1) = 7 OR DatePart(weekday, @DATE1) = 1 BEGIN 
      SET @FLAG = 0 
     END 

    RETURN @FLAG 
END 
GO 

CREATE FUNCTION FN_DATEDIFF_SELON_HORAIRES_ENTREPRISE2 (@Date1 DATETIME, @Date2 DATETIME) 
    RETURNS INT AS 
    BEGIN 
     DECLARE @NB_Jours INT  
     DECLARE @Cpt INT 
     DECLARE @Jours_Travailles INT 
     DECLARE @Date1_at_8_am DATETIME 
     DECLARE @Date2_at_6_pm DATETIME 
     DECLARE @Excedent INT 

     SET @NB_Jours = DATEDIFF(day, @Date1, @Date2) + 1 
     SET @Cpt = 0 
     SET @Jours_Travailles = 0 
     SET @Excedent = 0 

     SET @Date1_at_8_am = @Date1 
     SET @Date1_at_8_am = DATEADD(hour, - (DATEPART(hour, @Date1) - 8), @Date1_at_8_am) 
     SET @Date1_at_8_am = DATEADD(minute, - DATEPART(minute, @Date1_at_8_am), @Date1_at_8_am) 
     SET @Date1_at_8_am = DATEADD(second, - DATEPART(second, @Date1_at_8_am), @Date1_at_8_am) 

     SET @Date2_at_6_pm = @Date2 
     SET @Date2_at_6_pm = DATEADD(hour, 18 - DATEPART(hour, @Date2), @Date2_at_6_pm) 
     SET @Date2_at_6_pm = DATEADD(minute, - DATEPART(minute, @Date2_at_6_pm), @Date2_at_6_pm) 
     SET @Date2_at_6_pm = DATEADD(second, - DATEPART(second, @Date2_at_6_pm), @Date2_at_6_pm) 

     IF dbo.FN_JOUR_TRAVAILLE(@Date1) = 1 AND @Date1 > @Date1_at_8_am BEGIN 
      SET @Excedent = @Excedent + DATEDIFF(minute, @Date1_at_8_am, @Date1) 
     END 
     IF dbo.FN_JOUR_TRAVAILLE(@Date2) = 1 AND @Date2 < @Date2_at_6_pm BEGIN 
      SET @Excedent = @Excedent + DATEDIFF(minute, @Date2, @Date2_at_6_pm) 
     END 

     WHILE @Cpt < @NB_Jours 
     BEGIN 
      IF dbo.FN_JOUR_TRAVAILLE(DATEADD(day, @Cpt, @Date1)) = 1 
      BEGIN 
       SET @Jours_Travailles = @Jours_Travailles + 1 
      END 
      SET @Cpt = @Cpt + 1 
     END 

     RETURN @Jours_Travailles*600 - @Excedent 
END 
GO 

----------------------------------------------------------------------- 
----------------------------------------------------------------------- 
----------------------------------------------------------------------- 

的问题是在这条线的功能FN_JOUR_TRAVAILLE:

IF EXISTS(SELECT * FROM FN_FERIES_SELON_ANNEE(@YEAR) WHERE JourDate = @Date1) BEGIN 
     SET @FLAG = 0 
END 

对于每个周期I计算的每一天我生成公共假日的为表那一年。

最好的解决方案是检查我是否已经创建了这个表,是否在其中搜索,如果没有,我创建它并存储它。

但我不知道如何做到这一点。

我需要在上级范围中创建一个表,并将其作为参数传递给我的函数。

+4

为什么不建立公共假期表作为永久表? – 2013-04-26 10:58:15

+0

这是我最终决定要做的,谢谢。 – 2013-05-02 14:51:16

回答

0

怎么样的表型参数:

create type FERIES as table (
    JourId INT NOT NULL, 
    JourDate DATETIME NOT NULL, 
    JoURLabel VARCHAR(50) NULL 
) 
GO 

在你的外部范围使用FN_FERIES_SELON_ANNEE功能来填充表值:

declare @var FERIES; 

insert into @var 
    select * from FN_FERIES_SELON_ANNEE(@YEAR) 

,然后将它传递给你的其他功能使用。

你的其他功能将成为:

CREATE FUNCTION FN_JOUR_TRAVAILLE (@Date1 DATETIME, @table FERIES readonly) 
RETURNS INT AS 
BEGIN 
    DECLARE @FLAG INT 
    SET @FLAG = 1 
    DECLARE @YEAR INT 
    SET @YEAR = DATEPART(YEAR, @DATE1) 

    IF EXISTS(SELECT * FROM @table WHERE JourDate = @Date1) BEGIN 
     SET @FLAG = 0 
    END 
    ELSE 
     IF DatePart(weekday, @DATE1) = 7 OR DatePart(weekday, @DATE1) = 1 BEGIN 
      SET @FLAG = 0 
     END 

    RETURN @FLAG 
END 
GO 
0

尝试使用XML -

DECLARE @XML XML 

SELECT @XML = (
    SELECT JourId, JourDate, JoURLabel 
    FROM @JoursFeries t 
    FOR XML AUTO 
) 

RETURN @XML 

CREATE FUNCTION FN_JOUR_TRAVAILLE (@Date1 DATETIME, @XML XML) 
RETURNS INT 
AS BEGIN 

    DECLARE @YEAR INT 

    SELECT @YEAR = DATEPART(YEAR, @DATE1) 

    IF EXISTS(
     SELECT 1 
     FROM @XML.nodes('/t') t(p) 
     WHERE t.p.value('@JourDate', 'DATETIME') = @Date1 
    ) OR DATEPART(weekday, @DATE1) IN (1, 7)  
    BEGIN 

     RETURN 0 

    END 

    RETURN 1 

END 

UPDATE(2005或更高版本):

或尝试这种解决方案 -

ALTER FUNCTION FN_FERIES_SELON_ANNEE (@YEAR INT) 
RETURNS XML      
AS BEGIN 

    DECLARE 
      @an VARCHAR(4) 
     , @G INT, @I INT  
     , @J INT, @C INT  
     , @H INT, @L INT  
     , @JourPaque INT  
     , @MoisPaque INT  
     , @DimPaque DATETIME  

    SELECT @an = CAST(@YEAR AS VARCHAR(4)) 

    SELECT 
      @G = @YEAR % 19  
     , @C = @YEAR/100  
     , @H = (@C - @C/4 - (8 * @C + 13)/25 + 19 * @G + 15) % 30  
     , @I = @H - (@H/28) * (1 - (@H/28) * (29/(@H + 1)) * ((21 - @G)/11))  
     , @J = (@YEAR + @YEAR/4 + @I + 2 - @C + @C/4) % 7  
     , @L = @I - @J  
     , @MoisPaque = 3 + (@L + 40)/44  
     , @JourPaque = @L + 28 - 31 * (@MoisPaque/4)  
     , @DimPaque = CAST(@an + '-' + CAST(@MoisPaque AS VARCHAR(2)) + '-' + CAST(@JourPaque AS VARCHAR(2)) AS DATETIME)    

    DECLARE @XML XML 

    SELECT @XML = (
     SELECT JourId, JourDate, JoURLabel 
     FROM (
      SELECT JourId = 1, JourDate = DATEADD(DAY, 1, @DimPaque), JoURLabel = 'Lundi de Pâques' 
       UNION ALL 
      SELECT 2, DATEADD(DAY, 39, @DimPaque), 'Jeudi de l''Ascension' 
       UNION ALL 
      SELECT 3, DATEADD(DAY, 50, @DimPaque), 'Lundi de Pentecôte' 
       UNION ALL 
      SELECT 4, CAST(@an + '0101' AS DATETIME), 'Nouvel an' 
       UNION ALL 
      SELECT 5, CAST(@an + '0501' AS DATETIME), 'Fête du travail' 
       UNION ALL 
      SELECT 6, CAST(@an + '0508' AS DATETIME), 'Armistice 39-45' 
       UNION ALL 
      SELECT 7, CAST(@an + '0815' AS DATETIME), 'Assomption' 
       UNION ALL 
      SELECT 8, CAST(@an + '0714' AS DATETIME), 'Fête Nationale' 
       UNION ALL 
      SELECT 9, CAST(@an + '1101' AS DATETIME), 'Toussaint' 
       UNION ALL 
      SELECT 10, CAST(@an + '1111' AS DATETIME), 'Armistice 14-18' 
       UNION ALL 
      SELECT 11, CAST(@an + '1101' AS DATETIME), 'Noël' 
     ) t 
     FOR XML AUTO 
    ) 

    RETURN @XML 

END 
GO 

ALTER FUNCTION FN_JOUR_TRAVAILLE (@Date1 DATETIME, @XML XML) 
RETURNS INT 
AS BEGIN 

    DECLARE @YEAR INT 
    SELECT @YEAR = DATEPART(YEAR, @DATE1) 

    IF EXISTS(
     SELECT 1 
     FROM @XML.nodes('/t') t(p) 
     WHERE t.p.value('@JourDate', 'DATETIME') = @Date1 
    ) OR DATEPART(weekday, @DATE1) IN (1, 7)  
    BEGIN 

     RETURN 0 

    END 

    RETURN 1 

END 
GO 

ALTER FUNCTION FN_DATEDIFF_SELON_HORAIRES_ENTREPRISE2 
(
     @Date1 DATETIME 
    , @Date2 DATETIME 
) 
RETURNS INT AS 
BEGIN 

    DECLARE 
      @NB_Jours INT  
     , @Cpt INT 
     , @Jours_Travailles INT 
     , @Date1_at_8_am DATETIME 
     , @Date2_at_6_pm DATETIME 
     , @Excedent INT 

    SELECT 
      @NB_Jours = DATEDIFF(DAY, @Date1, @Date2) + 1 
     , @Cpt = 0 
     , @Jours_Travailles = 0 
     , @Excedent = 0 
     , @Date1_at_8_am = DATEADD(HOUR, 8, CAST(FLOOR(CAST(@Date1 AS FLOAT)) AS DATETIME)) 
     , @Date2_at_6_pm = DATEADD(HOUR, 18, CAST(FLOOR(CAST(@Date1 AS FLOAT)) AS DATETIME)) 

    SELECT @Excedent = @Excedent + 
      CASE WHEN @Date1 > @Date1_at_8_am AND dbo.FN_JOUR_TRAVAILLE(@Date1, dbo.FN_FERIES_SELON_ANNEE(YEAR(@Date1))) = 1 
       THEN DATEDIFF(MINUTE, @Date1_at_8_am, @Date1) 
       ELSE 0 
      END + 
      CASE WHEN @Date2 < @Date2_at_6_pm AND dbo.FN_JOUR_TRAVAILLE(@Date2, dbo.FN_FERIES_SELON_ANNEE(YEAR(@Date2))) = 1 
       THEN DATEDIFF(MINUTE, @Date2, @Date2_at_6_pm) 
       ELSE 0 
      END 

    ;WITH years AS 
    (
     SELECT cont = 1, dt = DATEADD(DAY, @Cpt, @Date1), years = YEAR(DATEADD(DAY, @Cpt, @Date1)) 

     UNION ALL 

     SELECT cont + 1, DATEADD(DAY, 1, dt), YEAR(DATEADD(DAY, 1, dt)) 
     FROM years 
     WHERE cont < @NB_Jours 
    ) 
    SELECT @Jours_Travailles = SUM(dbo.FN_JOUR_TRAVAILLE(dt, tt.xmls)) 
    FROM years y 
    JOIN (
     SELECT y3.years, xmls = dbo.FN_FERIES_SELON_ANNEE(y3.years) 
     FROM (
      SELECT DISTINCT y2.years 
      FROM years y2 
     ) y3 
    ) tt ON tt.years = y.years 
    OPTION (MAXRECURSION 0) 

    RETURN @Jours_Travailles * 600 - @Excedent 

END 

UPDATE2(2000):

似乎有限制相当大的数字,如果使用2000年版本,请尝试这个例子。我不认为这是一个完美的决定,在不改变当前逻辑的情况下解决问题,但我认为它应该对你有所帮助。

ALTER FUNCTION FN_FERIES_SELON_ANNEE (@YEAR INT) 
RETURNS @FERIES TABLE 
(
     JourId INT NOT NULL 
    , JourDate DATETIME NOT NULL 
    , JoURLabel VARCHAR(50) NULL 
)     
AS BEGIN 

    DECLARE 
      @an VARCHAR(4) 
     , @G INT, @I INT  
     , @J INT, @C INT  
     , @H INT, @L INT  
     , @JourPaque INT  
     , @MoisPaque INT  
     , @DimPaque DATETIME  

    SELECT @an = CAST(@YEAR AS VARCHAR(4)) 

    SELECT 
      @G = @YEAR % 19  
     , @C = @YEAR/100  
     , @H = (@C - @C/4 - (8 * @C + 13)/25 + 19 * @G + 15) % 30  
     , @I = @H - (@H/28) * (1 - (@H/28) * (29/(@H + 1)) * ((21 - @G)/11))  
     , @J = (@YEAR + @YEAR/4 + @I + 2 - @C + @C/4) % 7  
     , @L = @I - @J  
     , @MoisPaque = 3 + (@L + 40)/44  
     , @JourPaque = @L + 28 - 31 * (@MoisPaque/4)  
     , @DimPaque = CAST(@an + '-' + CAST(@MoisPaque AS VARCHAR(2)) + '-' + CAST(@JourPaque AS VARCHAR(2)) AS DATETIME)    

    INSERT INTO @FERIES (JourId, JourDate, JoURLabel) 
    SELECT JourId, JourDate, JoURLabel 
    FROM (
     SELECT JourId = 1, JourDate = DATEADD(DAY, 1, @DimPaque), JoURLabel = 'Lundi de Pâques' 
      UNION ALL 
     SELECT 2, DATEADD(DAY, 39, @DimPaque), 'Jeudi de l''Ascension' 
      UNION ALL 
     SELECT 3, DATEADD(DAY, 50, @DimPaque), 'Lundi de Pentecôte' 
      UNION ALL 
     SELECT 4, CAST(@an + '0101' AS DATETIME), 'Nouvel an' 
      UNION ALL 
     SELECT 5, CAST(@an + '0501' AS DATETIME), 'Fête du travail' 
      UNION ALL 
     SELECT 6, CAST(@an + '0508' AS DATETIME), 'Armistice 39-45' 
      UNION ALL 
     SELECT 7, CAST(@an + '0815' AS DATETIME), 'Assomption' 
      UNION ALL 
     SELECT 8, CAST(@an + '0714' AS DATETIME), 'Fête Nationale' 
      UNION ALL 
     SELECT 9, CAST(@an + '1101' AS DATETIME), 'Toussaint' 
      UNION ALL 
     SELECT 10, CAST(@an + '1111' AS DATETIME), 'Armistice 14-18' 
      UNION ALL 
     SELECT 11, CAST(@an + '1101' AS DATETIME), 'Noël' 
    ) t 

    RETURN 

END 
GO 

ALTER FUNCTION FN_DATEDIFF_SELON_HORAIRES_ENTREPRISE2 
(
     @Date1 DATETIME 
    , @Date2 DATETIME 
) 
RETURNS INT AS 
BEGIN 

    DECLARE 
      @NB_Jours INT  
     , @Year INT 
     , @Jours_Travailles INT 
     , @Date1_at_8_am DATETIME 
     , @Date2_at_6_pm DATETIME 
     , @Excedent INT 

    SELECT 
      @NB_Jours = DATEDIFF(DAY, @Date1, @Date2) + 1 
     , @Jours_Travailles = 0 
     , @Excedent = 0 
     , @Date1_at_8_am = DATEADD(HOUR, 8, CAST(FLOOR(CAST(@Date1 AS FLOAT)) AS DATETIME)) 
     , @Date2_at_6_pm = DATEADD(HOUR, 18, CAST(FLOOR(CAST(@Date1 AS FLOAT)) AS DATETIME)) 

    DECLARE @emun TABLE (i BIGINT IDENTITY, blank BIT) 
    INSERT INTO @emun (blank) 
    SELECT NULL 
    FROM [master].dbo.spt_values n 
    CROSS JOIN (
     SELECT i = 1 
      UNION ALL 
     SELECT 2 
      UNION ALL 
     SELECT 3 
    ) b 

    DECLARE @temp TABLE (dt DATETIME) 
    INSERT INTO @temp (dt) 
    SELECT dt 
    FROM (
     SELECT dt = @Date1 

     UNION ALL 

     SELECT DATEADD(DAY, i, @Date1) 
     FROM @emun 
     WHERE i < @NB_Jours 
    ) d 

    DECLARE @temp2 TABLE 
    (
      JourId INT NOT NULL 
     , JourDate DATETIME NOT NULL 
     , JoURLabel VARCHAR(50) NULL 
    ) 

    DECLARE cur CURSOR FAST_FORWARD READ_ONLY LOCAL FOR 
     SELECT DISTINCT YEAR(y.dt) 
     FROM @temp y 

    OPEN cur 

    FETCH NEXT FROM cur INTO @Year 

    WHILE @@FETCH_STATUS = 0 BEGIN 

     INSERT INTO @temp2 (JourId, JourDate, JoURLabel) 
     SELECT JourId, JourDate, JoURLabel 
     FROM FN_FERIES_SELON_ANNEE(@Year) 

     FETCH NEXT FROM cur INTO @Year 

    END 

    CLOSE cur 
    DEALLOCATE cur 

    SELECT @Excedent = @Excedent + 
      CASE WHEN @Date1 > @Date1_at_8_am AND NOT EXISTS(SELECT 1 FROM @temp2 WHERE JourDate = @Date1 OR DATEPART(weekday, @Date1) IN (1,7)) 
       THEN DATEDIFF(MINUTE, @Date1_at_8_am, @Date1) 
       ELSE 0 
      END + 
      CASE WHEN @Date2 < @Date2_at_6_pm AND NOT EXISTS(SELECT 1 FROM @temp2 WHERE JourDate = @Date2 OR DATEPART(weekday, @Date2) IN (1,7)) 
       THEN DATEDIFF(MINUTE, @Date2, @Date2_at_6_pm) 
       ELSE 0 
      END 

    SELECT @Jours_Travailles = COUNT(1) 
    FROM @temp t 
    LEFT JOIN @temp2 t2 ON t.dt = t2.JourDate 
    WHERE NOT(t2.JourId IS NOT NULL OR DATEPART(weekday, t.dt) IN (1,7)) 

    RETURN @Jours_Travailles * 600 - @Excedent 

END 
+0

我不得不说,我真的不明白你所有的解决方案,我是一个初学者,所以我只是尝试粘贴它,我有这些错误: '170,Niveau 15,第1号,ProcédureFN_FERIES_SELON_ANNEE,Ligne 54 线54:'XML'附近的语法错误。 第170行,Niveau 15,第1行,ProcédureFN_JOUR_TRAVAILLE,Ligne 11 第11行:'。'附近语法不正确。 Msg 156,Niveau 15,État1,ProcédureFN_DATEDIFF_SELON_HORAIRES_ENTREPRISE2,Ligne 36 关键字'WITH'附近的语法不正确。 消息170,Niveau 15,第1部分,ProcédureFN_DATEDIFF_SELON_HORAIRES_ENTREPRISE2,Ligne 55 第55行:'MAXRECURSION'附近语法不正确。' – 2013-04-26 12:58:42

+0

什么是SQL Server版本?我在2005版本上测试过它。 – Devart 2013-04-26 13:00:22

+0

SQL Server 2000 – 2013-04-26 13:02:50