【问题标题】:SQL join table to itself to get data for previous yearSQL将表连接到自身以获取上一年的数据
【发布时间】:2019-04-21 16:41:58
【问题描述】:

SQL。如何将表连接到自身以获得所需的结果,如下表所示。逻辑是我想拥有相同产品和上一年相应月份的单位。

源表上的简单左连接到键 a.[year]=b.[year]+1 上的自身(当然还有逐月和产品到产品)会导致我们在前一年有值但现在没有的数据丢失。

【问题讨论】:

  • 为什么2017、4、1有2933?
  • @SalmanA 已更正。谢谢。
  • 你真的需要 2017 年的 rows 吗?您可以简单地输出 5 行,例如 product, month, 2018_units, 2017_units
  • @SalmanA 是的,我确实每年都需要该结构中的数据——比 2018 年和 2017 年还要多。

标签: sql sql-server tsql join self-join


【解决方案1】:

完全连接就足够了

  select distinct
    coalesce(a.year, b.year+1) as year
    , coalesce(a.month, b.month) as month
    , coalesce(a.product, b.product) as product
    , a.units as units
    , b.units as units_prev
  from yourtable a
  full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product

您的预期结果与 2018 年第 2 个月的描述略有不同,产品 2 不存在,先前值为 2933。

DB 小提琴:https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4

结果:

year    month   product units   units_prev
2017    1       1       1721    
2017    2       1       4915    
2017    4       2       2933    
2017    5       1       5230    
2018    1       1               1721
2018    1       2       7672    
2018    2       1       5216    4915
2018    3       1       8911    
2018    4       2               2933
2018    5       1               5230
2019    1       2               7672
2019    2       1               5216
2019    3       1               8911

如果您需要过滤掉这样的期货,那么您可以添加一个额外的 where 谓词,例如:

where coalesce(a.year, b.year+1) <= year(getdate())

【讨论】:

  • 令人惊讶!您没有在 [月] 加入吗?是这样还是你忘了?
  • 好的,让我印象深刻的是当年的合并,它应该有 b,year+1 - 从技术上讲,在 2019 年的第 3 个月,产品 1 的 units_prev 为 8911 - 但是你没有在预期的结果中给出它
【解决方案2】:

年月

使用cross join 生成行,left join 引入数据,然后使用lag() 获取“先前”值:

select y.year, m.month, p.product, t.units,
       lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
     (select distinct month from t) m cross join
     (select distinct product from t) p left join
     t
     on t.year = y.year and t.month = m.month and t.product = p.producct;

【讨论】:

  • 我喜欢它的简单性,但应该改进。它在我的数据的产品列中产生三个空值。在我的预期结果中,产品 2 在 2017 年和 2018 年都没有销售的月份应该没有行。我认为你的笛卡尔会为此产生行。你能在你的代码的第二行加上别名吗?
  • @PrzemyslawRemin 。 . .是的,我看到您还希望 product 也可以交叉连接。查询已修复。
  • 从显示的预期结果来看,我猜他们宁愿寻找(select distinct year from t) y cross join (select distinct month, product from t) mp
  • @GordonLinoff 几乎完成了。额外的where coalesce(units,prev_units) is not null 是让您的查询达到预期结果的最佳方式吗?我不想要今年和上一年没有销售的行。这是一些仅在一年中的特定月份有销售的产品。由于源表是几百万行,如果可能的话,我不希望一次杂草。
  • @PrzemyslawRemin 。 . .是的,我认为可以做到。
【解决方案3】:

我会选择 LAGcalendar table

SELECT C.[Year],
       C.[Month],
       YPT.product,
       YST.units,
       YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
     CROSS JOIN YourProductTable YPT
     LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
                                  AND C.[Month] = YST.[Month]
                                  AND YPT.Product = YST.Product
WHERE C.[day] = 1
  AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];

这对您的设计有点猜测(假设您有一个产品表)。

【讨论】:

  • 我没有[Day]。
  • @PrzemyslawRemin C.[Day] = 1 不是 YST.[Day]
  • 但是,如果您的意思是您的日历表没有day 列,@PrzemyslawRemin 我的问题是为什么?这是日历表中非常重要的一列,我建议您对其进行更改以包含它。
  • 我没有 [Day] 列。仅仅因为我处理月度数据。我的 [Units] 列汇总为整月。
  • 那么,@PrzemyslawRemin,在您的日历表中,您是否只存储该月的第一天?如果是这样,那么只需从ON 子句中删除AND C.[Day] = 1。如果没有,那么如果您的日历表中包含该月的每一天,那么为什么您的日历表中没有 day。但是,根据您的上述陈述,为什么您的 calendar 表中没有每个 calendar 日?这就是它被称为日历表的原因。
【解决方案4】:

您可以使用CROSS JOIN 在您的数据中生成所有可能的年、月和产品组合。如果存在特定组合的数据,一个简单的LEFT JOIN 将为您提供值或 NULL。

DECLARE @t TABLE (year int, month int, product int, unit int);
INSERT INTO @t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);

SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM @t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM @t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM @t) AS allm
LEFT JOIN @t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN @t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month

结果:

| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1     | 1       | 1721  | NULL       |
| 2017 | 2     | 1       | 4915  | NULL       |
| 2017 | 3     | 1       | NULL  | NULL       |
| 2017 | 4     | 1       | NULL  | NULL       |
| 2017 | 5     | 1       | 5230  | NULL       |
| 2017 | 1     | 2       | NULL  | NULL       |
| 2017 | 2     | 2       | NULL  | NULL       |
| 2017 | 3     | 2       | NULL  | NULL       |
| 2017 | 4     | 2       | 2933  | NULL       |
| 2017 | 5     | 2       | NULL  | NULL       |
| 2018 | 1     | 1       | NULL  | 1721       |
| 2018 | 2     | 1       | 5216  | 4915       |
| 2018 | 3     | 1       | 8911  | NULL       |
| 2018 | 4     | 1       | NULL  | NULL       |
| 2018 | 5     | 1       | NULL  | 5230       |
| 2018 | 1     | 2       | 7672  | NULL       |
| 2018 | 2     | 2       | NULL  | NULL       |
| 2018 | 3     | 2       | NULL  | NULL       |
| 2018 | 4     | 2       | NULL  | 2933       |
| 2018 | 5     | 2       | NULL  | NULL       |

【讨论】:

  • 嗨,Salman,(1) 您的最终结果中没有 [year] 列。 (2)我不理解子句WHERE year = 2018 OR year = 2018 - 1(它的意思是选择*)。 (3) [Year] 列值是源表中的示例。有[Year] 2016, 2015....请不要参考数值。 (4) 您能否解释一下您的笛卡尔积如何只产生 10 行而不是 20 行(参见 Gordon Linoff 的回答)?
  • 抱歉,我以为我们只讨论了两年。它可以轻松使用 n 年,但 LAG 比两个 LEFT JOINS 好。
  • 为什么你认为LAG 比两个LEFT JOINS 更好?表现似乎有利于self-join:dba.stackexchange.com/questions/158374/…
  • 视情况而定。但是我仍然修改了我的答案。它现在与另一个完全相同,只是它使用 LEFT JOIN 查找上一行。计划看起来稍微好一些。
【解决方案5】:

如果您希望 2017 年和 2018 年均未售出任何商品的行与 2017 年 3 月的预期结果一样,您需要生成月份、年份并加入产品以获取空值。

此查询针对月份和年份进行,希望您也可以在需要时添加产品

DECLARE @startMonth INT=1
DECLARE @endMonth INT=12
DECLARE @startYear INT=2017
DECLARE @endYear INT=2018
;
WITH months AS (
    SELECT @startMonth AS m
    UNION ALL
    SELECT m+1 FROM months WHERE m+1<=@endMonth
),
years AS (
    SELECT @startYear AS y
    UNION ALL
    SELECT y+1 FROM years WHERE y+1<=@endYear
),
monthYears AS (
    SELECT m, y
    FROM months, years
)
SELECT  thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM 
    (SELECT [Product], my.y as [Year], my.m as [Month], [Units]
    FROM monthYears my
    LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN     
    (SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
    FROM monthYears my
    LEFT JOIN sales on my.m = [Month] and my.y = [Year])  as prevYear 
    on thisYear.Product = prevYear.Product
        and (thisYEAR.[Year]) = prevYear.[NextYear]
        and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product] 
option (maxrecursion 12);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多