【问题标题】:How to generate multiple time series in one sql query?如何在一个 sql 查询中生成多个时间序列?
【发布时间】: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 数据库。

【问题讨论】:

    标签: sql r postgresql


    【解决方案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_yearentry_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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-09-02
        • 1970-01-01
        • 1970-01-01
        • 2018-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多