【问题标题】:Aggregations in SQLiteSQLite 中的聚合
【发布时间】:2019-03-31 13:38:56
【问题描述】:

我希望构建一个能够按月汇总分期付款值的 SQL 查询。通常这不会太难,因为您只需将分期付款值和group by 月份相加即可。然而,问题并不那么容易,在本文的其余部分中,我将说明原因并寻求人们能够提供的任何帮助。

首先要注意installments 列。其中installments 为 1,这意味着在购买时支付了总价值。其中installments 大于 1,这意味着在当月以及接下来的月份支付总价值。例如,如果我们看到transaction_id 9 和 10,这是一笔 100 美元的交易,分两期付款,这意味着 50 美元将在 2 月支付,50 美元将在 3 月支付。

假设我们想查看credit_card_id = 11111111 的每月账单。如果我们查看installments 列,我们可以看到正确的输出应该如下:

  • 1 月:19.99 + 75.3
  • 2 月:1337 + 75.3
  • 三月:75.3

再次说明,3 月份的 75.3 发生是因为我们在一月份进行了 3 次分期付款交易,这意味着客户将在 1 月、2 月和 3 月被收取 75.3 的费用。问题是我不知道如何根据给定数据创建三月的类别。

首先,我在 SQL 中重新创建了表,并通过以下 SQLite 查询轻松地按月获取卡的所有交易

select strftime('%m', transaction_date) as Month, total_value, installment_value, installments 
from transactions 
WHERE credit_card_id = '11111111';

它输出一个看起来像这样的表

但是,如何将 3 个分期付款周期拆分为 01、02 和 03 并不明显,因此我创建了一个新表,其中包含 txn 列,该列旨在为唯一交易提供一个 ID,可以被认为是 1 组。

CREATE TABLE transactions (
    transaction_id int primary key,
    credit_card_id int,
    transaction_date timestamp,
    merchant_name varchar(256),
    total_value decimal(19,4),
    installment_value decimal(19,4),
    installments int,
    txn int
);

insert into transactions values(1,11111111,'2018-01-10T00:00:00','Colorful Soaps', 19.99, 19.99, 1, 1);
insert into transactions values(2,22222222,'2018-01-11T00:01:00','Cantina da Mamma',43.5,43.5,1,2);
insert into transactions values(3,33333333,'2018-01-12T01:02:00','Boulevard Hotel',129,129,1,3);
insert into transactions values(4,11111111,'2018-01-15T11:11:11','Micas Bar',225.9,75.3,3,4);
insert into transactions values(5,11111111,'2018-01-15T11:11:11','Micas Bar',225.9,75.3,3,4);
insert into transactions values(6,11111111,'2018-01-15T11:11:11','Micas Bar',225.9,75.3,3,4);
insert into transactions values(7,22222222,'2018-01-18T22:10:01','IPear Store',9999.99,9999.99,1,5);
insert into transactions values(8,11111111,'2018-02-20T21:08:32','Forrest Paintball',1337,1337,1,6);
insert into transactions values(9,44444444,'2018-02-22T00:05:30','Unicorn Costumes',100,50,2,7);
insert into transactions values(10,44444444,'2018-02-22T00:05:30','Unicorn Costumes',100,50,2,7);

我的问题是

  1. 是否可以在 SQLite 中获得我上面确定的格式的输出,如果可以,如何获得?
  2. 是否必须拥有txn 列才能获取此信息?

感谢您的帮助。

【问题讨论】:

    标签: sql database sqlite aggregate-functions data-analysis


    【解决方案1】:

    这是一个不需要行编号的解决方案/将在更旧的 SQLite 中工作(基本上支持 date() 的任何版本).. 它仅依赖于对日历表的连接(您可以使用多种技术生成该表,但在链接的示例中,我只是通过创建表并直接插入查询所需的数据来生成 N 行)每个月的第一天有一排。它使用笛卡尔连接条件导致例如3 行,每笔付款分 3 期付款:

    select 
      t.credit_card_id,
      date(cal.d, '-1 month') as month_of_installment,
      sum(t.installment_value)
    from
      cal inner join transactions t on
      t.transaction_date between date(cal.d, '-'||installments||' months') and cal.d
    group by
      t.credit_card_id,
      date(cal.d, '-1 month')
    

    您可以在https://www.db-fiddle.com/f/ogj2hK3cMwqp46MY6uVwX8/0查看设置

    顺便说一句,您的问题或示例数据中有问题。

    信用卡 ID 11111111 的每月付款为:

    2018-01   245.89
    2018-02   1562.9
    2018-03   225.9
    

    您的示例数据有 3 次同时支付给 Micas Bar。我们知道这些是不同的,因为尽管其他数据相同,但它们具有不同的事务 ID。因此,一月是 75.3 + 75.3 + 75.3 + 19.99,而不仅仅是问题所断言的 19.99 + 75.3。

    要查看查询的更多工作原理,请运行非分组表单:

    select 
      t.credit_card_id,
      date(cal.d, '-1 month') as month_of_installment,
      t.*
    from
      cal inner join transactions t on
      t.transaction_date between date(cal.d, '-'||installments||' months') and cal.d
    order by
      t.credit_card_id,
      date(cal.d, '-1 month')
    

    我遇到的大多数 DBA 都提倡在 db 中使用数字/日期表来生成这样的查询 - 这是一种生成行序列的快速方法,并且在有例如一个月没有交易(对于没有交易的月份,您可以将交易表连接到日历表并获得一个总和为 0 的单行)。为接下来的 100 年生成一堆月/日行是一个微不足道的一次性操作

    【讨论】:

    • 当 installment 大于 1 时,必须为每个 moth 传播 installment_value。答案已编辑以达到要求。
    【解决方案2】:

    SQLlite 有 ROW_NUMBER()(在此处查看 SQLlite)。

       SELECT 
            installment_month
            ,credit_card_id
            ,SUM(installment_value) 
        FROM (
            SELECT 
                CASE WHEN strftime('%m',transaction_date) + ROW_NUMBER () OVER(PARTITION BY credit_card_id, transaction_date ORDER BY transaction_date) - 1 > 12
                        THEN strftime('%Y',transaction_date)*100+strftime('%m',transaction_date) + ROW_NUMBER () OVER(PARTITION BY credit_card_id, transaction_date ORDER BY transaction_date) - 1 + 88
                        ELSE strftime('%Y',transaction_date)*100+strftime('%m',transaction_date) + ROW_NUMBER () OVER(PARTITION BY credit_card_id, transaction_date ORDER BY transaction_date) - 1
                END as installment_month
                ,* 
            from transactions
            ) AS a
        GROUP by installment_month, credit_card_id
    

    当分期付款超过两年时,就会出现问题。你将不得不工作那部分。我会在一个函数中编写这段代码(情况下......)它会使整个查询更加清晰。

    【讨论】:

    • SNR,您能否解释一下当分期付款超过两年时为什么/如何发生故障?
    • 修改表中的数据,12月分期大于1。接受的答案将失败。对 12 月大于 13 的分期付款日期执行相同操作,我的答案将失败。 @Caius Jard 回答只要您的日历足够大,它就不会失败。答案必须是因为现在每个 credit_card_id 生成的行太多,我们不会获得预期的结果。
    • @g.humpkins(编辑已发送)您可以通过使用inner join 的输出在from 子句中使用select distinct 来实现要求。你会得到预期的结果。但也许有更好的方法来做到这一点,让我们拭目以待......
    【解决方案3】:

    假设您运行 SQLite 3.25+ 版本,请考虑使用CTE and window function,它通过相同的 credit_card_idtransaction_date 创建一个运行计数,并使用此值将所需的月份添加到交易日期。从那里,根据新的计算日期聚合,install_date

    WITH cte AS
       (SELECT *,
           DATE(transaction_date, 
                '+' || (ROW_NUMBER() 
                          OVER(PARTITION BY transaction_date, credit_card_id 
                               ORDER BY transaction_date) - 1)
                    || ' month'
                ) AS install_date       
        FROM transactions)
    
    SELECT credit_card_id, 
           STRFTIME('%Y', install_date) AS install_year, 
           STRFTIME('%m', install_date) AS install_month, 
           SUM(installment_value) AS sum_installment_value
    FROM cte 
    GROUP BY credit_card_id, 
             STRFTIME('%Y', install_date), 
             STRFTIME('%m', install_date)
    ORDER BY credit_card_id, 
             STRFTIME('%Y', install_date), 
             STRFTIME('%m', install_date);
    

    Rextester Demo 使用 PostgreSQL,因为 AFAIK 没有在线小提琴(SQLFiddle、SQLiteonline、DBFiddle 等)支持带有窗口函数的 SQLite

    【讨论】:

    • 注意,当分期付款+交易日期超过一年时,会失败。我试图在我的回答中警告这一点(当情况下)。
    猜你喜欢
    • 1970-01-01
    • 2019-05-07
    • 2018-12-18
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-28
    • 2012-01-16
    相关资源
    最近更新 更多