我有一个看起来像这样的表:
MONTH | WIDGET | VALUE
------+--------+------
Dec | A | 3
Jan | B | 5
Feb | B | 6
Mar | B | 7
我希望编写一个查询,为每个MONTH
和WIDGET
生成当前月份和上个月之间VALUE
的差异。所以我想要一个像这样的输出表:
MONTH | WIDGET | VALUE
------+--------+------
Dec | A | 3
Jan | A | -3
Feb | A | 0
Mar | A | 0
Dec | B | 0
Jan | B | 5
Feb | B | 1
Mar | B | 1
如果给定窗口小部件的上个月没有记录值,我想假设上个月的值为零。相反,如果当前月份没有记录值,我想假设当前月份的值为零。
我相信在月份和小部件的所有组合上交叉加入可能会有效,给我一个“脊椎”,我可以将其加入我的数据然后使用coalesce
- 但是有更好的方法吗? / p>
编辑:我们可以假设MONTH
列实际上有一个数字表示,以便更容易识别前一个。
答案 0 :(得分:1)
我会使用滞后函数。 IBM Reference我刚刚默认为0,因为先前值不存在但您可以通过多种不同的方式处理这些值。
create temp table test (
mth date
,widget char(1)
,value integer
)
distribute on random;
insert into test values('2013-12-01','A',3);
insert into test values('2014-01-01','A',-3);
insert into test values('2014-02-01','A',0);
insert into test values('2014-03-01','A',0);
insert into test values('2013-12-01','B',0);
insert into test values('2014-01-01','B',5);
insert into test values('2014-02-01','B',1);
insert into test values('2014-03-01','B',1);
select *
,lag(value,1) over(partition by widget order by mth) as prior_row
,value - nvl(lag(value,1) over(partition by widget order by mth),0) as diff
from test
答案 1 :(得分:0)
行。我已经在MS SQL中解决了这个问题,但它应该可以转移到PostgresQL。我有SQLFiddled the answer:
CREATE TABLE WidgetMonths (Month tinyint, Widget varchar(1), Value int)
CREATE TABLE Months (Month tinyint, MonthOrder tinyint)
insert into WidgetMonths Values
(12, 'A', 3),
(1,'B', 5),
(2,'B', 6),
(3,'B', 7);
insert into Months Values
(12, 1), (1, 2), (2, 3), (3, 4)
Select
AllWidgetMonths.Widget,
AllWidgetMonths.Month,
IsNull(wm.Value,0) - IsNull(wmn.Value,0) as Value
from (
select Distinct Widget, Months.Month, Months.MonthOrder
from WidgetMonths
Cross Join months
) AllWidgetMonths
left join WidgetMonths wm on wm.Widget = AllWidgetMonths.Widget
AND wm.Month = AllWidgetMonths.Month
left join WidgetMonths wmn on wmn.Widget = AllWidgetMonths.Widget
AND Case When wmn.Month = 12 Then 1 Else wmn.Month + 1 End = AllWidgetMonths.Month
Order by AllWidgetMonths.Widget, AllWidgetMonths.MonthOrder
我从你的例子中开始使用一个WidgetMonths表,唯一的区别是我将月份转换为代表性整数。
然后我从你的例子中创建了我们感兴趣的所有月份的月份表。如果你想要全年的月份,你可以简单地添加到这个表或找到另一种生成1-12行结果集的方法。 MonthOrder是可选的,只是帮助我完成了你的答案。
正如你所提到的,AllwidgetMonths拥有Cross join,它为我们提供了Widgets和Months的所有组合。通过“小部件”之间的交叉连接可以更好地实现这一点。表和月表。但我不确定这是否存在,所以就这样做了。
我们将WidgetMonths加入到我们的所有小部件月份的主表中,以向我们展示我们有哪个月值。
套管上的技巧然后再次加入同一个表,但这次在连接中添加1到月份数。这会将行向下移动一行。注意我有一个Case语句(在PostgresSql中不确定)来处理第12个月到第1个月的翻转。这有效地为我提供了AllwidgetMonths每行的每个月及其前一个的值。
最后一位是从另一个取一个值。
嘿,先生。我可以尝试将此更新到PostgresSQL,但您可能有更多的知识,可以比我更快地解决它。答案 2 :(得分:0)
这是获取所需数据的另一种方法。使用两个CTE,包括一个包含月份数的CTE。
可以访问SQL小提琴here。
WITH month_order as
(
SELECT 'Jan' as month, 1 as month_no, 12 as prev_month_no
UNION ALL
SELECT 'Feb' as month, 2 as month_no, 1 as prev_month_no
UNION ALL
SELECT 'Mar' as month, 3 as month_no, 2 as prev_month_no
UNION ALL
SELECT 'Apr' as month, 4 as month_no, 3 as prev_month_no
UNION ALL
SELECT 'May' as month, 5 as month_no, 4 as prev_month_no
UNION ALL
SELECT 'Jun' as month, 6 as month_no, 5 as prev_month_no
UNION ALL
SELECT 'Jul' as month, 7 as month_no, 6 as prev_month_no
UNION ALL
SELECT 'Aug' as month, 8 as month_no, 7 as prev_month_no
UNION ALL
SELECT 'Sep' as month, 9 as month_no, 8 as prev_month_no
UNION ALL
SELECT 'Oct' as month, 10 as month_no, 9 as prev_month_no
UNION ALL
SELECT 'Nov' as month, 11 as month_no, 10 as prev_month_no
UNION ALL
SELECT 'Dec' as month, 12 as month_no, 11 as prev_month_no
)
, values_all_months as
(
SELECT
month_order.prev_month_no as prev_month_no
, month_order.month_no as month_no
, w4.month as month
, w4.widget as widget
, COALESCE(w3.value, 0) as value
FROM widgets w3
RIGHT OUTER JOIN
(
SELECT
w1.widget as widget
,w2.month as month
FROM
(SELECT
DISTINCT
widget
FROM widgets) w1,
(SELECT
DISTINCT
month
FROM widgets) w2
) w4
ON w3.month = w4.month and w3.widget = w4.widget
INNER JOIN month_order
ON w4.month = month_order.month
)
SELECT mo.month, vam1.widget, vam1.value - COALESCE(vam2.value, 0) VALUE
FROM values_all_months vam1
LEFT OUTER JOIN values_all_months vam2
ON vam1.widget = vam2.widget AND vam1.prev_month_no = vam2.month_no
INNER JOIN month_order mo
ON vam1.month_no = mo.month_no
ORDER BY vam1.widget, (SELECT CASE vam1.month_no WHEN 12 THEN 0 ELSE vam1.month_no END);