How To Select Records in a Status Between Timestam

2019-07-18 01:57发布

问题:

I have a T-SQL Quotes table and need to be able to count how many quotes were in an open status during past months.

The dates I have to work with are an 'Add_Date' timestamp and an 'Update_Date' timestamp. Once a quote is put into a 'Closed_Status' of '1' it can no longer be updated. Therefore, the 'Update_Date' effectively becomes the Closed_Status timestamp.

I'm stuck because I can't figure out how to select all open quotes that were open in a particular month.

Here's a few example records:

Quote_No   Add_Date   Update_Date  Open_Status  Closed_Status
001        01-01-2016  NULL         1            0
002        01-01-2016  3-1-2016     0            1
003        01-01-2016  4-1-2016     0            1

The desired result would be:

Year  Month  Open_Quote_Count
2016  01     3
2016  02     3
2016  03     2
2016  04     1

I've hit a mental wall on this one, I've tried to do some case when filtering but I just can't seem to figure this puzzle out. Ideally I wouldn't be hard-coding in dates because this spans years and I don't want to maintain this once written.

Thank you in advance for your help.

回答1:

You are doing this by month. So, three options come to mind:

  • A list of all months using left join.
  • A recursive CTE.
  • A number table.

Let me show the last:

with n as (
      select row_number() over (order by (select null)) - 1 as n
      from master..spt_values
     )
select format(dateadd(month, n.n, q.add_date), 'yyyy-MM') as yyyymm,
       count(*) as Open_Quote_Count
from quotes q join
     n
     on (closed_status = 1 and dateadd(month, n.n, q.add_date) <= q.update_date) or
        (closed_status = 0 and dateadd(month, n.n, q.add_date) <= getdate()) 
group by format(dateadd(month, n.n, q.add_date), 'yyyy-MM')
order by yyyymm;

This does assume that each month has at least one open record. That seems reasonable for this purpose.



回答2:

You can use datepart to extract parts of a date, so something like:

select datepart(year, add_date) as 'year',
       datepart(month, date_date) as 'month',
       count(1)
from theTable
where open_status = 1
group by datepart(year, add_date), datepart(month, date_date)

Note: this counts for the starting month and primarily to show the use of datepart.



回答3:

Updated as misunderstood the initial request.

Consider following test data:

DECLARE @test TABLE 
(
    Quote_No VARCHAR(3),
    Add_Date DATE,
    Update_Date DATE,
    Open_Status INT,
    Closed_Status INT
)

INSERT INTO @test (Quote_No, Add_Date, Update_Date, Open_Status, Closed_Status)
VALUES ('001', '20160101', NULL, 1, 0)
     , ('002', '20160101', '20160301', 0, 1)
     , ('003', '20160101', '20160401', 0, 1)

Here is a recursive solution, that doesn't rely on system tables BUT also performs poorer. As we are talking about months and year combinations, the number of recursions will not get overhand.

;WITH YearMonths AS
(
    SELECT YEAR(MIN(Add_Date)) AS [Year]
        , MONTH(MIN(Add_Date)) AS [Month]
        , MIN(Add_Date) AS YMDate
    FROM @test
    UNION ALL
    SELECT YEAR(DATEADD(MONTH,1,YMDate))
        , MONTH(DATEADD(MONTH,1,YMDate))
        , DATEADD(MONTH,1,YMDate)
    FROM YearMonths
    WHERE YMDate <= SYSDATETIME()
)
SELECT [Year]
    , [Month]
    , COUNT(*) AS Open_Quote_Count
FROM YearMonths ym
INNER JOIN @test t
ON (
    [Year] * 100 + [Month] <= CAST(FORMAT(t.Update_Date, 'yyyyMM') AS INT)
    AND t.Closed_Status = 1
    )
OR (
    [Year] * 100 + [Month] <= CAST(FORMAT(SYSDATETIME(), 'yyyyMM') AS INT)
    AND t.Closed_Status = 0
    )
GROUP BY [Year], [Month]
ORDER BY [Year], [Month]

Statement is longer, also more readable and lists all year/month combinations to date.

Take a look at Date and Time Data Types and Functions for SQL-Server 2008+

and Recursive Queries Using Common Table Expressions