Расчет в SQL первого рабочего дня данного месяца - PullRequest
2 голосов
/ 11 мая 2009

Я должен рассчитать все счета, которые были оплачены в первые 'N' дней месяца. У меня есть две таблицы

. СЧЕТ: в нем есть информация о счете. Единственное поле, которое имеет значение, называется datePayment

. ПРАЗДНИКИ: Это таблица с одним столбцом. Записи в этой таблице имеют форму "2009-01-01", 2009-05-01 "и так далее.

Я должен рассмотреть также субботы и воскресенья (это может не быть проблемой, потому что я мог бы вставить эти дни в таблицу Hollidays, чтобы рассматривать их как праздники, если это необходимо)

Задача состоит в том, чтобы рассчитать, какой будет «предел оплаты».

select count(*) from invoice 
where datePayment  < PAYMENTLIMIT

Мой вопрос заключается в том, как рассчитать эту ОПЛАТУ. Где PAYMENTLIMIT - «пятый рабочий день каждого месяца».

Запрос должен выполняться под Mysql и Oracle, поэтому следует использовать стандартный SQL.

Есть подсказка?

EDIT Чтобы соответствовать названию вопроса, псевдопрос должен выглядеть следующим образом:

select count(*) from invoice 
where datePayment  < FIRST_WORKING_DAY + N

тогда вопрос можно уменьшить, чтобы рассчитать FIRST_WORKING_DAY каждого месяца.

Ответы [ 9 ]

4 голосов
/ 11 мая 2009

Вы можете найти первую дату в месяце, где дата не указана в праздничной таблице, а дата не является выходным:

select min(datePayment), datepart(mm, datePayment)
from invoices
where datepart(dw, datePayment) not in (1,7) --day of week
and not exists (select holiday from holidays where holiday = datePayment)
group by datepart(mm, datePayment) --monthnr
2 голосов
/ 12 мая 2009

Вместо таблицы праздничных дней, которую мы исключаем, мы используем подход календарной таблицы: одна строка на каждый день, в котором когда-либо понадобится приложение (тридцать лет охватывают скромные 11К строк). Так что у него есть не только столбец is_weekday, но и другие важные для предприятия вещи, например, julianized_date. Таким образом, каждая возможная дата будет иметь готовое значение для first_working_day_this_month, и для ее обнаружения требуется простой поиск (для которого продукты SQL, как правило, оптимизируются!), А не «вычисление» его каждый раз на лету.

2 голосов
/ 11 мая 2009

Примерно так может работать:

create function dbo.GetFirstWorkdayOfMonth(@Year INT, @Month INT)
returns DATETIME
as begin
    declare @firstOfMonth VARCHAR(20)
    SET @firstOfMonth = CAST(@Year AS VARCHAR(4)) + '-' + CAST(@Month AS VARCHAR) + '-01'

    declare @currDate DATETIME 
    set @currDate = CAST(@firstOfMonth as DATETIME)

    declare @weekday INT
    set @weekday = DATEPART(weekday, @currdate)

    -- 7 = saturday, 1 = sunday
    while @weekday = 1 OR @weekday = 7
    begin
        set @currDate = DATEADD(DAY, 1, @currDate)
        set @weekday = DATEPART(weekday, @currdate)
    end

    return @currdate
end

Я не уверен на 100% в том, являются ли числа «будний день» фиксированными или могут зависеть от вашей локали на вашем SQL Server. Проверьте это!

Марк

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

Возвращает первый понедельник текущего месяца

SELECT DATEADD(
    WEEK,
    DATEDIFF( --x weeks between 1900-01-01 (Monday) and inner result
        WEEK,
        0, --1900-01-01
        DATEADD( --inner result
            DAY,
            6 - DATEPART(DAY, GETDATE()),
            GETDATE()
        )
    ),
    0 --1900-01-01 (Monday)
)
1 голос
/ 12 мая 2009

Получает первые N рабочих дней каждого месяца 2009 года:

select * from invoices as x
where 
    datePayment between '2009-01-01' and '2009-12-31'


    and exists
    ( 
        select             
                 1
        from invoices
        where
            -- exclude holidays and sunday saturday...
            (
                datepart(dw, datePayment) not in (1,7) -- day of week


                /*
                -- Postgresql and Oracle have programmer-friendly IN clause
                and 
                (datepart(yyyy,datePayment), datepart(mm,datePayment))
                not in (select hyear, hday from holidays) 
                */


                -- this is the MSSQL equivalent of programmer-friendly IN
                and 
                not exists
                (
                    select * from holidays
                    where 
                        hyear = datepart(yyyy,datePayment)
                        and hmonth = datepart(mm, datePayment)
                )                                
            )
            -- ...exclude holidays and sunday saturday



            -- get the month of x datePayment
            and                 
            (datepart(yyyy, datePayment) = datepart(yyyy, x.datePayment)
             and datepart(mm, datePayment) = datepart(mm, x.datePayment)) 


        group by 
            datepart(yyyy, datePayment), datepart(mm, datePayment)    

        having 
            x.datePayment < MIN(datePayment) + @N -- up to N working days
    )
1 голос
/ 11 мая 2009

Хорошо, при первом ударе вы можете поместить следующий код в UDF и передать в качестве переменных Год и Месяц. Затем он может вернуть TestDate, который является первым рабочим днем ​​месяца.

DECLARE @Month INT
DECLARE @Year INT

SELECT @Month = 5
SELECT @Year = 2009

DECLARE @FirstDate DATETIME
SELECT @FirstDate = CONVERT(varchar(4), @Year) + '-' + CONVERT(varchar(2), @Month) + '-' + '01 00:00:00.000'

DROP TABLE #HOLIDAYS
CREATE TABLE #HOLIDAYS (HOLIDAY DateTime)

INSERT INTO #HOLIDAYS VALUES('2009-01-01 00:00:00.000')
INSERT INTO #HOLIDAYS VALUES('2009-05-01 00:00:00.000')

DECLARE @DateFound BIT
SELECT @DateFound = 0
WHILE(@DateFound = 0)
BEGIN
    IF(
        DATEPART(dw, @FirstDate) = 1
        OR
        DATEPART(dw, @FirstDate) = 1
        OR
        EXISTS(SELECT * FROM #HOLIDAYS WHERE HOLIDAY = @FirstDate)
    )
    BEGIN
        SET @FirstDate = DATEADD(dd, 1, @FirstDate)
    END
    ELSE
    BEGIN
        SET @DateFound = 1
    END
END

SELECT @FirstDate

Что мне не нравится в этом решении, так это то, что, если ваша таблица праздников содержит все дни месяца, будет бесконечный цикл. (Вы можете проверить, что цикл все еще смотрит на нужный месяц) Он основан на одинаковых датах, например, все время 00:00:00. Наконец, способ, которым я вычислял 1-е число прошлого месяца при использовании конкатенации строк, был коротким путем. Есть гораздо лучшие способы найти фактический первый день месяца.

1 голос
/ 11 мая 2009

В нашем приложении есть таблица дат (заполненная всеми датами и частями даты за несколько десятков лет), что позволяет выполнять различные «недостающие» манипуляции с датами, например (в псевдо-sql):

select min(ourdates.datevalue)
from ourdates
where ourdates.year=<given year> and ourdates.month=<given month>
    and ourdates.isworkday
    and not exists (
        select * from holidays
        where holidays.datevalue=ourdates.datevalue
    )
0 голосов
/ 24 марта 2015
select if(weekday('yyyy-mm-01') < 5,'yyyy-mm-01',if(weekday('yyyy-mm-02') < 5,'yyyy-mm-02','yyyy-mm-03'))

Субботам и воскресеньям 5, 6, поэтому вам нужно только два чека, чтобы получить первый рабочий день

0 голосов
/ 24 ноября 2014
SELECT DATEADD(day, DATEDIFF (day, 0, DATEADD (month, DATEDIFF (month, 0, GETDATE()), 0)  -1)/7*7 + 7, 0);
...