如何在一个 sql 查询中生成多个时间序列?
Posted
技术标签:
【中文标题】如何在一个 sql 查询中生成多个时间序列?【英文标题】:How to generate multiple time series in one sql query? 【发布时间】:2015-10-24 18:56:07 【问题描述】:这是数据库布局。我有一张桌子,随着时间的推移,销售额很少,每天汇总。如果我在 2015 年 1 月 1 日有 10 次销售,我将有一个条目,但如果我有 0,那么我没有条目。像这样。
|--------------------------------------|
| day_of_year | year | sales | item_id |
|--------------------------------------|
| 01 | 2015 | 20 | A1 |
| 01 | 2015 | 11 | A2 |
| 07 | 2015 | 09 | A1 |
| ... | ... | ... | ... |
|--------------------------------------|
这就是我获得 1 个项目的时间序列的方式。
SELECT doy, max(sales) FROM (
SELECT day_of_year AS doy,
sales AS sales
FROM myschema.entry_daily
WHERE item_id = theNameOfmyItem
AND year = 2015
AND day_of_year < 150
UNION
SELECT doy AS doy,
0 AS sales
FROM generate_series(1, 149) AS doy) as t
GROUP BY doy
ORDER BY doy;
我目前循环使用 R 对每个项目进行 1 次查询。然后我将结果汇总到一个数据框中。但这非常慢。实际上,我希望只有一个查询可以聚合以下形式的所有数据。
|----------------------------------------------|
| item_id | 01 | 02 | 03 | 04 | 05 | ... | 149 |
|----------------------------------------------|
| A1 | 10 | 00 | 00 | 05 | 12 | ... | 11 |
| A2 | 11 | 00 | 30 | 01 | 15 | ... | 09 |
| A3 | 20 | 00 | 00 | 05 | 17 | ... | 20 |
| ... |
|----------------------------------------------|
这可能吗?顺便说一句,我使用的是 Postgres 数据库。
【问题讨论】:
【参考方案1】:解决方案 1. 使用聚合的简单查询。
获得预期结果的最简单、最快的方法。在客户端程序中解析sales
列很容易。
select item, string_agg(coalesce(sales, 0)::text, ',') sales
from (
select distinct item_id item, doy
from generate_series (1, 10) doy -- change 10 to given n
cross join entry_daily
) sub
left join entry_daily on item_id = item and day_of_year = doy
group by 1
order by 1;
item | sales
------+----------------------
A1 | 20,0,0,0,0,0,9,0,0,0
A2 | 11,0,0,0,0,0,0,0,0,0
(2 rows)
解决方案 2. 动态创建的视图。
基于使用array_agg()
而不是string_agg()
的解决方案1。该函数创建一个具有给定列数的视图。
create or replace function create_items_view(view_name text, days int)
returns void language plpgsql as $$
declare
list text;
begin
select string_agg(format('s[%s] "%s"', i::text, i::text), ',')
into list
from generate_series(1, days) i;
execute(format($f$
drop view if exists %s;
create view %s as select item, %s
from (
select item, array_agg(coalesce(sales, 0)) s
from (
select distinct item_id item, doy
from generate_series (1, %s) doy
cross join entry_daily
) sub
left join entry_daily on item_id = item and day_of_year = doy
group by 1
order by 1
) q
$f$, view_name, view_name, list, days)
);
end $$;
用法:
select create_items_view('items_view_10', 10);
select * from items_view_10;
item | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
------+----+---+---+---+---+---+---+---+---+----
A1 | 20 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 | 0
A2 | 11 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
(2 rows)
解决方案 3. 交叉表。
易于使用,但由于需要定义行格式,因此对更多的列非常不舒服。
create extension if not exists tablefunc;
select * from crosstab (
'select item_id, day_of_year, sales
from entry_daily
order by 1',
'select i from generate_series (1, 10) i'
) as ct
(item_id text, "1" int, "2" int, "3" int, "4" int, "5" int, "6" int, "7" int, "8" int, "9" int, "10" int);
item_id | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
---------+----+---+---+---+---+---+---+---+---+----
A1 | 20 | | | | | | 9 | | |
A2 | 11 | | | | | | | | |
(2 rows)
【讨论】:
【参考方案2】:首先,您需要一个带有all dates 的表格来填充空白日期。 100 年的日期意味着 36,000 行,所以不是很大。而不是每次都计算。
所有日期:
date_id
s_date
或创建计算字段
date_id
s_date
doy = EXTRACT(DOY FROM s_date)
year = EXTRACT(YEAR FROM s_date)
您的基本查询将是 SQL FIDDLE DEMO:
SELECT
AD.year,
AD.doy,
allitems.item_id,
COALESCE(SUM(ED.sales), 0) as max_sales
FROM
(SELECT DISTINCT item_id
FROM entry_daily
) as allitems
CROSS JOIN alldates AD
LEFT JOIN entry_daily ED
ON ED.day_of_year = AD.doy
AND ED.year = AD.year
AND ED.item_id = allitems.item_id
WHERE AD.year = 2015
GROUP BY
AD.year, AD.doy, allitems.item_id
ORDER BY
AD.year, AD.doy, allitems.item_id
你会得到这个输出
| year | doy | item_id | max_sales |
|------|-----|---------|-----------|
| 2015 | 1 | A1 | 20 |
| 2015 | 1 | A2 | 11 |
| 2015 | 2 | A1 | 0 |
| 2015 | 2 | A2 | 0 |
| 2015 | 3 | A1 | 0 |
| 2015 | 3 | A2 | 0 |
| 2015 | 4 | A1 | 0 |
| 2015 | 4 | A2 | 0 |
| 2015 | 5 | A1 | 0 |
| 2015 | 5 | A2 | 0 |
| 2015 | 6 | A1 | 0 |
| 2015 | 6 | A2 | 0 |
| 2015 | 7 | A1 | 39 |
| 2015 | 7 | A2 | 0 |
| 2015 | 8 | A1 | 0 |
| 2015 | 8 | A2 | 0 |
| 2015 | 9 | A1 | 0 |
| 2015 | 9 | A2 | 0 |
| 2015 | 10 | A1 | 0 |
| 2015 | 10 | A2 | 0 |
那么你需要安装tablefunc
并使用交叉表来透视此表SAMPLE
【讨论】:
谢谢,我明天试试这个,然后告诉你。【参考方案3】:试试这个独立的代码,我们使用 5 而不是 149 来保持输出简短。
在 (1) 中,我们根据需要使用单个 SQL 语句来生成所有生成长格式结果的系列。通常在关系数据库中使用长格式而不是宽格式,这种格式可能更可取,但如果不是这样,我们会使用 reshape2 包转换为宽格式。
在 (2) 中,我们展示了如何将 SQL 语句替换为使用 dplyr 包的 R 代码。
1) PostgreSQL 关于下面的 SQL 语句,最里面的 select 生成一个表 1, 2, ..., 5,其列是 day_of_year
与 entry_daily
交叉连接,给出每个day_of_year 与 year 和 item 的组合,并且只保留不同的行。然后将其与entry_daily
连接起来以获取我们汇总的销售数字。
假设您已设置 postgreSQL 以使用 SQL,如 sqldf 主页 (https://github.com/ggrothendieck/sqldf) 上的 FAQ#12 中所示,以下应该说明它并且是自包含代码,您可以复制并粘贴到您的会话中。
library(sqldf)
library(RPostgreSQL)
# input data
entry_daily <-
structure(list(day_of_year = c(1L, 1L, 7L), year = c(2015L, 2015L,
2015L), sales = c(20L, 11L, 9L), item_id = structure(c(1L, 2L,
1L), .Label = c("A1", "A2"), class = "factor")), .Names = c("day_of_year",
"year", "sales", "item_id"), class = "data.frame", row.names = c(NA,
-3L))
s <- sqldf("select A.item_id, A.year, A.day_of_year, sum(coalesce(B.sales, 0)) sales
from (select distinct x.day_of_year, y.year, y.item_id
from (select * from generate_series(1, 5) as day_of_year) as x
cross join entry_daily as y) as A
left join entry_daily as B
on A.year = B.year and A.day_of_year = B.day_of_year and
A.item_id = B.item_id
where A.year = 2015
group by A.item_id, A.year, A.day_of_year
order by A.item_id, A.year, A.day_of_year")
上述查询的输出是这个data.frame:
> s
item_id year day_of_year sales
1 A1 2015 1 20
2 A1 2015 2 0
3 A1 2015 3 0
4 A1 2015 4 0
5 A1 2015 5 0
6 A2 2015 1 11
7 A2 2015 2 0
8 A2 2015 3 0
9 A2 2015 4 0
10 A2 2015 5 0
如果你真的需要它,那么我们可以在 R 中使用 reshape2 包中的dcast
来实现:
library(reshape2)
dcast(s, item_id + year ~ day_of_year, value.var = "sales")
给予:
item_id year 1 2 3 4 5
1 A1 2015 20 0 0 0 0
2 A2 2015 11 0 0 0 0
2) dplyr 请注意,作为 SQL 语句的替代方案,此 R 代码将计算 s
:
library(dplyr)
s2 <- expand.grid(item_id = unique(entry_daily$item_id),
year = 2015,
day_of_year = 1:5) %>%
left_join(entry_daily) %>%
group_by(item_id, year, day_of_year) %>%
summarize(sales = sum(sales, na.rm = TRUE)) %>%
ungroup() %>%
arrange(item_id, year, day_of_year)
给予:
> s2
Joining by: c("item_id", "year", "day_of_year")
Source: local data frame [10 x 4]
Groups: item_id, year [?]
item_id year day_of_year sales
(fctr) (dbl) (int) (int)
1 A1 2015 1 20
2 A1 2015 2 0
3 A1 2015 3 0
4 A1 2015 4 0
5 A1 2015 5 0
6 A2 2015 1 11
7 A2 2015 2 0
8 A2 2015 3 0
9 A2 2015 4 0
10 A2 2015 5 0
现在可以选择使用与 (1) 中相同的 dcast
。
【讨论】:
以上是关于如何在一个 sql 查询中生成多个时间序列?的主要内容,如果未能解决你的问题,请参考以下文章
在 ClearQuest 中,我如何在 SQL 编辑器中生成一个允许我提示用户输入值的查询?
如何在 Oracle SQL Developer 中生成带有子查询的 INSERT 语句?
如何有效地查询多个表以在 dataGrid 中生成 excel STYLE 报告