比较SQL Server中两个日期范围之间的数据

时间:2018-05-25 11:38:08

标签: sql sql-server tsql date join

鉴于我有2个日期范围(之前的日期和当前日期),我想比较这2个日期范围的值。

比较方法:

  • 一个日期范围的第n天值与另一个日期范围的第n天值相比较(例如,上一个日期范围的第1天的值将与当前日期范围的第1天的值进行比较,依此类推)
  • 如果表中不存在日期,则显示值为零。
  • 如果两个日期范围的长度不同,则显示NULL和零以填补空白。

示例

数据库表"数据":

date         | value
-------------|------
2018-01-01   | 3
2018-01-02   | 5
2018-01-03   | 8
2018-01-04   | 6
2018-02-04   | 4
2018-02-05   | 2
2018-02-06   | 7
2018-02-07   | 0

鉴于日期范围:(当前)2018-02-04至2018-02-07,(上一页)2018-01-01至2018-01-03

期望的输出:

curDate      | curValue | preDate     | preValue
-------------|----------|-------------|---------
2018-02-04   | 4        | 2018-01-01  | 3
2018-02-05   | 2        | 2018-01-02  | 5
2018-02-06   | 7        | 2018-01-03  | 8
2018-02-07   | 0        | NULL        | 0

我现在陷入了连接状态,我当前的SQL就像:

DECLARE @currentStartDateTime  datetime   = '2018-02-04 00:00:00'
DECLARE @currentEndDateTime    datetime   = '2018-02-07 23:59:59'
DECLARE @previousStartDateTime datetime   = '2018-01-01 00:00:00'
DECLARE @previousEndDateTime   datetime   = '2018-01-03 23:59:59'

SELECT   cur.[date]             AS [curDate]
        ,ISNULL(cur.[total], 0) AS [curTotal]
        ,pre.[date]             AS [preDate]
        ,ISNULL(pre.[total], 0) AS [preTotal]
    FROM (
        SELECT * FROM [data] 
            WHERE [date] BETWEEN @currentStartDateTime AND @currentEndDateTime
    ) cur
    FULL OUTER JOIN (       
        SELECT * FROM [data] 
            WHERE [date] BETWEEN @previousStartDateTime AND @previousEndDateTime
    ) pre
        ON cur.[date] = DATEADD(day, 1, pre.[date]) -- <<< Stuck in this part

2 个答案:

答案 0 :(得分:1)

当您提出要求时,最好将所有DDL和插入包含在一起。

DROP TABLE data;
CREATE TABLE data
(
    date DATE,
    value INT
);
GO
INSERT INTO data
VALUES
('2018-01-01', 3),
('2018-01-02', 5),
('2018-01-03', 8),
('2018-01-04', 6),
('2018-02-04', 4),
('2018-02-05', 2),
('2018-02-06', 7),
('2018-02-07', 0);

DECLARE @currentStartDateTime DATETIME = '2018-02-04 00:00:00';
DECLARE @currentEndDateTime DATETIME = '2018-02-07 23:59:59';
DECLARE @previousStartDateTime DATETIME = '2018-01-01 00:00:00';
DECLARE @previousEndDateTime DATETIME = '2018-01-03 23:59:59';

SELECT a.date curDate,
       a.value curValue,
       b.date preDate,
       COALESCE(b.value, 0) preValue
FROM
(
    SELECT *,
           ROW_NUMBER() OVER (ORDER BY date) rn
    FROM data
    WHERE date
    BETWEEN @currentStartDateTime AND @currentEndDateTime
) a
    LEFT JOIN
    (
        SELECT *,
               ROW_NUMBER() OVER (ORDER BY date) rn
        FROM data
        WHERE date
        BETWEEN @previousStartDateTime AND @previousEndDateTime
    ) b
        ON a.rn = b.rn;

答案 1 :(得分:1)

我要做的第一件事是为您的日期范围创建公用表表达式,每个表达式都有两列:一列包含数据值,另一列包含行号值。这样,按第n个值加入就很容易了。

所以这是我建议的解决方案:

首先,创建并填充样本表(在将来的问题中将此步骤保存起来)

DECLARE @data As Table
(
    [date] DATE,
    [value] INT
);

INSERT INTO @data
VALUES
('2018-01-01', 3),
('2018-01-02', 5),
('2018-01-03', 8),
('2018-01-04', 6),
('2018-02-04', 4),
('2018-02-05', 2),
('2018-02-06', 7),
('2018-02-07', 0);

现在,我已将@currentStartDateTime2018-02-04更改为2018-02-03, 确保我还返回表格中没有的行(确保您的样本数据涵盖所有要求)

DECLARE @currentStartDateTime  datetime   = '2018-02-03 00:00:00',
        @currentEndDateTime    datetime   = '2018-02-07 23:59:59',
        @previousStartDateTime datetime   = '2018-01-01 00:00:00',
        @previousEndDateTime   datetime   = '2018-01-03 23:59:59'

现在,我的解决方案似乎非常麻烦,因为我想详细显示所有步骤。 您可以简化它。

首先,计算天数的最大日期差异 然后,创建一个从1到该差值+ 1的数字cte 然后,为每个范围创建日历cte,
然后是最后的cte,在范围之间进行完全连接,
并从最后一个cte中选择左边两次加入数据表。

-- This allows us to use the smallest possible tally cte.
DECLARE @MaxDateDiff int;
SELECT @MaxDateDiff = MAX(d)
FROM (
    VALUES  (DATEDIFF(DAY, @currentStartDateTime, @currentEndDateTime)), 
            (DATEDIFF(DAY, @previousStartDateTime, @previousEndDateTime))
    ) v(d) -- I like table value constructors :-)

;WITH Tally AS 
(
    SELECT TOP (@MaxDateDiff + 1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) As Number
    FROM sys.objects a
    -- if your database is very small (number of tables, procedures ect'), 
    -- you might want to unremark the next row
    -- CROSS JOIN sys.objects b
),
CurrentRange AS  
(
    SELECT DATEADD(DAY, Number-1, @currentStartDateTime) As [Date], Number
    FROM Tally
    -- we need the where clause in case the other range is bigger...
    WHERE DATEADD(DAY, Number-1, @currentStartDateTime) <= @currentEndDateTime
), 
PreviousRange AS 
(
    SELECT DATEADD(DAY, Number-1, @previousStartDateTime) As [Date], Number
    FROM Tally
    WHERE DATEADD(DAY, Number-1, @previousStartDateTime) <= @previousEndDateTime
), 
BothRanges AS 
(        
    SELECT C.Date As CurDate, P.Date As PreDate
    FROM CurrentRange As C
    FULL JOIN PreviousRange As P ON C.Number =  P.Number
)

SELECT CurDate, ISNULL(c.Value, 0) as CurValue, PreDate, ISNULL(p.Value, 0) as PreValue
FROM BothRanges
LEFT JOIN @data AS c ON CurDate = c.[Date]
LEFT JOIN @data AS p ON PreDate = p.[Date]

结果:(请记住,@currentStartDateTime与问题上的CurDate CurValue PreDate PreValue 03.02.2018 00:00:00 0 01.01.2018 00:00:00 3 04.02.2018 00:00:00 4 02.01.2018 00:00:00 5 05.02.2018 00:00:00 2 03.01.2018 00:00:00 8 06.02.2018 00:00:00 7 NULL 0 07.02.2018 00:00:00 0 NULL 0 不同)

include("conn.php");
$ne_lat = $_POST['a'];
$ne_lon = $_POST['b'];
$sw_lat = $_POST['c'];
$sw_lon = $_POST['d'];

$result = mysqli_query($con,"SELECT schoolname, latitude, longitude, total_students, act_composite_25, schoolid, (sat_critread_25+sat_math_25) as sat, (100 * admissions_total/applicants_total) as admrate, degree_bachelor, degree_masters, url, city, usstate, tuition_outstate, tuition_instate FROM usschools WHERE
(CASE WHEN $ne_lat < $sw_lat
        THEN latitude BETWEEN $ne_lat AND $sw_lat
        ELSE latitude BETWEEN $sw_lat AND $ne_lat
END)
AND
(CASE WHEN $ne_lon < $sw_lon
        THEN longitude BETWEEN $ne_lon AND $sw_lon
        ELSE longitude BETWEEN $sw_lon AND $ne_lon
END) and degree_bachelor = 1 order by act_composite_25 desc limit 20");
  if (!$result) {
    printf("Error: %s\n", mysqli_error($con));
    exit();
}

while ($row = $result->fetch_assoc()) {
  $data[] = $row;
  }

You can see a live demo on rextester.