V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
zzzain46
V2EX  ›  MySQL

如何高效地取连续三个月同一时间有交易的商户

  •  
  •   zzzain46 · 2022-05-19 13:09:55 +08:00 via iPhone · 2360 次点击
    这是一个创建于 701 天前的主题,其中的信息可能已经有所发展或是发生改变。
    流水表结构


    cust_id,trx_dte,trx_amt


    示例数据


    '10086','2022-01-05',23.56

    '10086','2022-02-05',23.56

    '10086','2022-03-05',23.56

    取 2021-05-01 到 2022-04-30 期间,连续三个月在同一天有同样金额交易的客户,例如上面这个 cust_id 为 10086 的客户就是我需要的客户。
    19 条回复    2022-05-27 11:21:26 +08:00
    encro
        1
    encro  
       2022-05-19 13:35:09 +08:00
    select count(*),cust_id,hour(trx_amt) from table group by cust_id,hour/day/minute(trx_amt) order by count(*) ?
    wolfie
        2
    wolfie  
       2022-05-19 13:53:30 +08:00
    where
    exists ( 下个月同一天 )
    and
    exists ( 下下个月同一天 )

    同一商户可能重复显示多次。
    hay313955795
        3
    hay313955795  
       2022-05-19 14:07:14 +08:00
    连续三次在同一天消费的.那三个月内不是同一天消费的算吗?
    VensonEEE
        4
    VensonEEE  
       2022-05-19 14:43:44 +08:00
    这个不是 sql 能解决的吧____
    zzzain46
        5
    zzzain46  
    OP
       2022-05-19 14:43:57 +08:00 via iPhone
    @hay313955795 不算
    liprais
        6
    liprais  
       2022-05-19 14:46:17 +08:00
    三个集合 join 是最简单的
    lookStupiToForce
        7
    lookStupiToForce  
       2022-05-19 15:21:17 +08:00   ❤️ 1
    最快速的方法需要窗口函数,理论上扫一次表即可,不会回表二次查询,除非内存不够要写临时表,否则只剩内存计算

    partition by 的条件根据需要的时间粒度去取
    下面以需要{日-小时}一致为例

    with cte_1 as
    (select distinct user_id, 月, 日, 时 from table where ...), -- 在这里扫表一次
    cte_2 as
    (select user_id, 月, 日, 时,
    lag(月, 1, null) over (partition by user_id, 日, 时 order by 月 asc) as last_月, -- 这里演示怎么抓到上一个相同 [日-小时] 记录的月份
    case when lag(月, 1, null) over (partition by user_id, 日, 时 order by 月 asc) = 月 - 1 then true else false end as flag_1
    case when lag(月, 2, null) over (partition by user_id, 日, 时 order by 月 asc) = 月 - 2 then true else false end as flag_2
    from cte_1)
    select * from cte_2 where flag_1 and flag_2;

    如果需要{日-小时-分-秒}一致,
    只需要把上面 distinct 后的和 partition by 后的 [日, 时] 替换成 [日, 时, 分, 秒] 即可
    lookStupiToForce
        8
    lookStupiToForce  
       2022-05-19 15:27:01 +08:00
    @lookStupiToForce
    补充一下,这里面的 [月] 得是 年月,就是类似这种 '2021-01' 包含年份形式的月份,否则会出问题
    oyasumi
        9
    oyasumi  
       2022-05-19 15:27:14 +08:00 via Android
    先把时间重复的过滤出来,再把日期重复(不包括月)的过滤出来,是不是数据量就少了
    zzzain46
        10
    zzzain46  
    OP
       2022-05-19 15:43:46 +08:00 via iPhone
    @liprais 流水量比较大,我想要的也是这个
    LemonK
        11
    LemonK  
       2022-05-19 20:43:18 +08:00
    select a.cust_id, a.m, a.d from (select cust_id,month(trx_dte) as m, day(trx_dte) as d from table where trx_dte between '2022-01-01' and '2022-03-31' group by cust_id, m, d, trx_amt) as a group by a.cust_id, a.d HAVING count(a.m) = 3

    外边再加层循环,每轮 between 窗口固定三个月,起始结束各增加一个月,把需要查的时段跑一轮。
    asmile1993
        12
    asmile1993  
       2022-05-20 11:27:16 +08:00   ❤️ 2
    -- 测试数据
    drop table t;
    create table t(
    id int auto_increment primary key,
    cust_id int,
    trx_dte date,
    trx_amt decimal(10, 2)
    );

    insert into t (cust_id, trx_dte, trx_amt) value(10086, '2022-01-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10086, '2022-02-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10086, '2022-03-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10086, '2022-04-05', 23.56);

    insert into t (cust_id, trx_dte, trx_amt) value(10085, '2022-01-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10085, '2022-02-05', 23.56);

    insert into t (cust_id, trx_dte, trx_amt) value(10010, '2021-01-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10010, '2021-02-05', 13.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10010, '2021-03-05', 33.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10010, '2021-04-05', 53.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10010, '2021-05-05', 23.56);

    insert into t (cust_id, trx_dte, trx_amt) value(10020, '2021-01-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10020, '2021-02-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10020, '2021-04-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10020, '2021-05-05', 23.56);
    insert into t (cust_id, trx_dte, trx_amt) value(10020, '2021-07-05', 23.56);



    -- 递归调用生成 '2021-05-01' 到 '2022-05-30' 的日期表
    with recursive Date_Ranges AS
    (
    select '2021-05-01' as Date
    union all
    select Date + interval 1 day
    from Date_Ranges
    where Date < '2022-05-30'
    ),
    -- 生成月份相连,日期相同并且连续三个月的日期数组,例如:["2021-01-05", "2021-02-05", "2021-03-05"]
    -- 注意这里限制了日期,因为过了 '2022-03-30' 后,就不再满足数据的过滤条件
    date_array_ranges as (
    select JSON_ARRAY(date, date_add(date, interval 1 month), date_add(date, interval 2 month)) json_array_trx_dte
    from date_ranges
    where date <= '2022-03-30'
    ),
    -- 根据 cust_id ,trx_amt 进行聚合,并生成用户相同,金额相同的交易日期 json 数组
    cust_trx_amt_dte_array as
    (
    select cust_id, trx_amt, JSON_ARRAYAGG(trx_dte) json_array_trx_dte
    from t
    group by cust_id, trx_amt
    )
    -- 由于是连续三个月,那么交易日期的 json 数组的数量肯定是大于等于 3
    -- 在满足上述条件后,进一步判断交易日期 json 数组是否包含连续三个月日期相同的元素
    -- 这里用 exists 来判断是为了避免数据重复
    select *
    from cust_trx_amt_dte_array a
    where json_length(a.json_array_trx_dte) >= 3
    and exists (select 1
    from date_array_ranges b
    where json_contains(a.json_array_trx_dte->'$', b.json_array_trx_dte)
    );

    -- 返回结果
    +-----------+---------+----------------------------------------------------------+
    | cust_id | trx_amt | json_array_trx_dte |
    +-----------+---------+----------------------------------------------------------+
    | 10086 | 23.56 | ["2022-01-05", "2022-02-05", "2022-03-05", "2022-04-05"] |
    +---------------------+----------------------------------------------------------+
    asmile1993
        13
    asmile1993  
       2022-05-20 13:45:26 +08:00
    @lookStupiToForce
    一天进行多次交易或一个月多次交易还能生效吗? lag/lead 只是取前一条记录和后一条记录的值而已,并不能确保取得是上一个月和下一个月
    lookStupiToForce
        14
    lookStupiToForce  
       2022-05-20 13:51:44 +08:00
    @asmile1993
    你注意看,我那段 sql 最开始的 cte 里有 distinct (或者你用 group by 也可以),已经按照所需的时间粒度去重了,所以肯定是拿的是排序后之前月份的数,已去重了不可能拿到当前月份
    asmile1993
        15
    asmile1993  
       2022-05-20 15:19:17 +08:00
    @lookStupiToForce 为什么不可以,1 个 cust_id 又没限定一天只能交易一次,一天交易多次,你 lag order by 取的就不是下个月了吧,而是下一次交易的值
    lookStupiToForce
        16
    lookStupiToForce  
       2022-05-20 15:25:15 +08:00
    @asmile1993 亲,你看懂了先好不?
    cte_1 里 distinct 已经按照 [user_id ,月,日,时] 去重了,cte_2 里的 partition by 用的 [user_id ,日,时] ,两者只相差 [月] ,所以 lag 只能取到不同的月,懂了不?
    还不懂也不用回我了,你自个儿测试去
    thinkingbullet
        17
    thinkingbullet  
       2022-05-26 10:15:34 +08:00
    @lookStupiToForce 老哥你的 sql 咋运行,小弟愚钝,还请指教 https://pic.imgdb.cn/item/628ee2a809475431298985c2.png
    lookStupiToForce
        18
    lookStupiToForce  
       2022-05-26 10:22:50 +08:00
    @thinkingbullet #17 我咋感觉你回错人了,我没有用到 recursive cte ,你是不是要找 @asmile1993
    你如果要用我那方法,回头有时间我用 pgsql 版本的测试一下,把通过的 sql 发出来,你再看着改算了
    asmile1993
        19
    asmile1993  
       2022-05-27 11:21:26 +08:00
    @thinkingbullet MySQL 版本得是 8.0 的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2707 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 15:00 · PVG 23:00 · LAX 08:00 · JFK 11:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.