SQL заполняет общее количество рабочих дней в месяце минус праздничные дни в текущем финансовом году - PullRequest
0 голосов
/ 07 февраля 2012

Мне нужен вид, который будет выглядеть как мое первое прикрепленное изображение, но с заполненным правым столбцом, а не пустым.Логика следующая:

Данные должны быть за текущий финансовый период.Поэтому апрель будет 2011, а март будет 2012 и так далее.

Расчет дней, доступных для отдельных месяцев, составит:

Общее количество рабочих дней (понедельник-пятница) за вычетом любых банковских праздников, приходящихся на этот конкретный месяц, для данного финансового года.(Который мы сохранили в таблице - см. Второе изображение).

Имена столбцов для таблицы праздников слева направо: holidaytypeid , name , holstart , Холенд .Имя таблицы: праздничный стол

Чтобы рассчитать совокупные месяцы «Доступные дни» , это будет случай суммирования уже заполненных данных за отдельные месяцы.Например, апрель-май будет суммой данных за апрель и май и т. Д. И т. Д.

Мне нужен SQL-запрос в идеальном формате, чтобы его можно было вставить прямо и работать (т. Е. С правильными именами столбцов).и имена таблиц)

Спасибо за поиск.

enter image description here

enter image description here

Ответы [ 2 ]

6 голосов
/ 07 февраля 2012
DECLARE @StartDate DATETIME, @EndDate DATETIME

SELECT  @StartDate = '01/04/2011',
        @EndDate = '31/03/2012'

CREATE TABLE #Data (FirstDay DATETIME NOT NULL PRIMARY KEY, WorkingDays INT NOT NULL)

;WITH DaysCTE ([Date]) AS
(   SELECT  @StartDate
    UNION ALL
    SELECT  DATEADD(DAY, 1, [Date])
    FROM    DaysCTE
    WHERE   [Date] <= @Enddate
)

INSERT INTO #Data
SELECT  MIN([Date]),
        COUNT(*) [Day]
FROM    DaysCTE
        LEFT JOIN HolidayTable
            ON [Date] BETWEEN HolStart AND HolEnd
WHERE   HolidayTypeID IS NULL
AND     DATENAME(WEEKDAY, [Date]) NOT IN ('Saturday', 'Sunday')
GROUP BY DATEPART(MONTH, [Date]), DATEPART(YEAR, [Date])
OPTION (MAXRECURSION 366)

DECLARE @Date DATETIME
SET @Date = (SELECT MIN(FirstDay) FROM #Data)

SELECT  Period,
        WorkingDays [Days Available (Minus the Holidays)]
FROM    (   SELECT  DATENAME(MONTH, Firstday) [Period],
                    WorkingDays,
                    0 [SortField],
                    FirstDay
            FROM    #Data
            UNION
            SELECT  DATENAME(MONTH, @Date) + ' - ' + DATENAME(MONTH, Firstday),
                    (   SELECT  SUM(WorkingDays)
                        FROM    #Data b
                        WHERE   b.FirstDay <= a.FirstDay
                    ) [WorkingDays],
                    1 [SortField],
                    FirstDay 
            FROM    #Data a
            WHERE   FirstDay > @Date
        ) data
ORDER BY SortField, FirstDay

DROP TABLE #Data

Если вы делаете это более 1 года, вам нужно изменить строку:

OPTION (MAXRECURSION 366)

В противном случае вы получите ошибку - число должно быть больше количества дней, которое вы запрашиваете.


EDIT

Я только что натолкнулся на этот мой старый ответ, и он мне действительно не нравится, есть так много вещей, которые я сейчас считаю плохой практикой, поэтому собираюсь исправить все проблемы:

  1. Я неправильно завершил операторы точкой с запятой
  2. Использовал рекурсивный CTE для генерации списка дат
  3. Не включает список столбцов для вставки
  4. Используется DATENAME для исключения выходных, что зависит от языка, гораздо лучше явно установить DATEFIRST и использовать DATEPART
  5. Используется LEFT JOIN/IS NULL вместо NOT EXISTS для исключения записей из таблицы выходных. В SQL Server LEFT JOIN / IS NULL менее эффективен, чем NOT EXISTS

Это все второстепенные вещи, но это те вещи, которые я бы критиковал (по крайней мере, в моей голове, если не громко) при рассмотрении чужого запроса, так что не могу не исправить мою собственную работу! Переписав запрос, вы получите.

SET DATEFIRST 1;

DECLARE @StartDate DATETIME = '20110401',
        @EndDate DATETIME = '20120331';

CREATE TABLE #Data (FirstDay DATETIME NOT NULL PRIMARY KEY, WorkingDays INT NOT NULL);

WITH DaysCTE ([Date]) AS
(   SELECT  TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
            DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @StartDate)
    FROM    sys.all_objects a
)
INSERT INTO #Data (FirstDay, WorkingDays)
SELECT  FirstDay =  MIN([Date]),
        WorkingDays = COUNT(*) 
FROM    DaysCTE d
WHERE   DATEPART(WEEKDAY, [Date]) NOT IN (6, 7)
AND     NOT EXISTS
        (   SELECT  1
            FROM    dbo.HolidayTable ht
            WHERE   d.[Date] BETWEEN ht.HolStart AND ht.HolEnd
        )
GROUP BY DATEPART(MONTH, [Date]), DATEPART(YEAR, [Date]);

DECLARE @Date DATETIME = (SELECT MIN(FirstDay) FROM #Data);

SELECT  Period,
        [Days Available (Minus the Holidays)] = WorkingDays 
FROM    (   SELECT  DATENAME(MONTH, Firstday) [Period],
                    WorkingDays,
                    0 [SortField],
                    FirstDay
            FROM    #Data
            UNION
            SELECT  DATENAME(MONTH, @Date) + ' - ' + DATENAME(MONTH, Firstday),
                    (   SELECT  SUM(WorkingDays)
                        FROM    #Data b
                        WHERE   b.FirstDay <= a.FirstDay
                    ) [WorkingDays],
                    1 [SortField],
                    FirstDay 
            FROM    #Data a
            WHERE   FirstDay > @Date
        ) data
ORDER BY SortField, FirstDay;

DROP TABLE #Data;

В заключение, этот запрос становится намного проще с календарной таблицей , которая хранит все даты и имеет флаги для рабочих дней, праздников и т. Д., Вместо использования таблицы праздников, которая просто хранит праздники.

1 голос
/ 21 августа 2013

Позвольте мне добавить несколько центов к этому сообщению. Только что получил задание для расчета разницы между запланированными и фактическими часами Код ниже был преобразован в функцию. Пока что нет проблем с логикой:

declare @date datetime = '11/07/2012'
declare @t table (HolidayID int IDENTITY(1,1) primary key,
    HolidayYear int,
    HolidayName varchar(50),
    HolidayDate datetime)

INSERT @t
VALUES(2012, 'New Years Day', '01/02/2012'),
(2012,'Martin Luther King Day', '01/16/2012'),
(2012,'Presidents Day', '02/20/2012'),
(2012,'Memorial Day', '05/28/2012'),
(2012,'Independence Day', '07/04/2012'),
(2012,'Labor Day', '09/03/2012'),
(2012,'Thanksgiving Day', '11/22/2012'),
(2012,'Day After Thanksgiving', '11/23/2012'),
(2012,'Christmas Eve', '12/24/2012'),
(2012,'Christmas Day', '12/25/2012'),
(2013, 'New Years Day', '01/01/2013'),
(2013,'Martin Luther King Day', '01/21/2013'),
(2013,'Presidents Day', '02/18/2013'),
(2013,'Good Friday', '03/29/2013'),
(2013,'Memorial Day', '05/27/2013'),
(2013,'Independence Day', '07/04/2013'),
(2013,'Day After Independence Day', '07/05/2013'),
(2013,'Labor Day', '09/02/2013'),
(2013,'Thanksgiving Day', '11/28/2013'),
(2013,'Day After Thanksgiving', '11/29/2013'),
(2013,'Christmas Eve', NULL),
(2013,'Christmas Day', '12/25/2013')


DECLARE @START_DATE DATETIME, 
    @END_DATE DATETIME, 
    @Days int

SELECT  @START_DATE = DATEADD(MONTH, DATEDIFF(MONTH, 0, @date), 0) 

SELECT  @END_DATE = DATEADD(month, 1,@START_DATE)

;WITH CTE AS
(
    SELECT DATEADD(DAY, number, (DATEADD(MONTH, DATEDIFF(MONTH, 0, @date), 0) )) CDate
    FROM master.dbo.spt_values where type = 'p' and number between 0 and 365
        EXCEPT
    SELECT HolidayDate FROM @t WHERE HolidayYear = YEAR(@START_DATE) 
)
SELECT @Days = COUNT(CDate) --, datepart(dw, CDate) WDay
FROM CTE 
WHERE (CDate >=@START_DATE and CDate < @END_DATE) AND DATEPART(dw, CDate) NOT IN(1,7)

SELECT @Days 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...