SQL中通过QUALIFY语法过滤窗口函数简化代码

MaxCompute和hive都支持使用QUALIFY语法对窗口函数的数据进行过滤,该语法类似于HAVING对聚合和GROUP BY之后的结果的处理。这个语法在很多场景中都可以用到,可以用于简化代码,少写一个子查询,如统计排名,分组内部排序等。

QUALIFY语法后至少要跟一个窗口函数,并且允许使用窗口列的别名来过滤数据。

如:

SELECT column1, column2, ..., columnN
,Row_Number() OVER (PARTITION BY partition_columns ORDER BY sort_columns) as rn
FROM table_name
QUALIFY rn = 1;

该代码效果等同于:(区别仅是上面代码多一个rn列,下面代码没有rn列)

SELECT column1, column2, ..., columnN
FROM table_name
QUALIFY Row_Number() OVER (PARTITION BY partition_columns ORDER BY sort_columns) = 1;

如果不使用QUALIFY语法则需要:

SELECT * FROM (
SELECT column1, column2, ..., columnN
,Row_Number() OVER (PARTITION BY partition_columns ORDER BY sort_columns) as rn
FROM table_name
) t 
WHERE rn = 1;

很明显可以看出,相比于直接使用row_number排序后通过子查询过滤数据来说,通过

QUALIFY语法可以至少帮助我们少写2行代码和一个子查询。

<think>嗯,用户想从两个表里获取每天有收发消息的用户最近登录时间和区服。首先,我需要确定这两个表的结构。消息表t_chat_all有发送者和接收者的user_id,日期Ftime,消息时间send_time。登录表t_login_all有用户登录的日期Ftime,user_id,登录id,区服login_loc,登录时间login_time。 首先,用户需要的是每天有收发消息的用户,也就是说,无论是发送还是接收消息的用户,只要当天有参与过消息,就应该被包括进来。所以第一步可能需要从t_chat_all中提取出所有发送者和接收者的user_id,并去重,然后按日期分组。这样就能得到每天有活动的用户列表。 接下来,需要关联登录表t_login_all,找到这些用户最近的登录时间和区服。这里的关键是“最近登录时间”,也就是在消息发送当天或之前最近的登录记录。需要注意的是,登录可能发生在同一天,也可能更早。所以可能需要用窗口函数,按用户和日期分区,按登录时间降序排列,取第一条记录。 不过,这里有个问题,登录表的Ftime是否和消息表的Ftime对应?比如,登录的Ftime是指登录日期,而消息的Ftime也是消息日期。如果用户在同一天登录多次,可能需要取最大的login_time。但如果用户是在消息日期之前的某天登录,那需要找到最近的那次登录。 可能的步骤是: 1. 从t_chat_all中获取每个Ftime下的所有send_user_id和receive_user_id,合并并去重,得到每天有活动的用户列表。 2. 将这些用户与t_login_all进行关联,条件是用户相同,且登录日期小于等于当天的Ftime。 3. 对每个用户在每个消息日期,找到最大的login_time对应的login_loc和login_time。 4. 最后输出ftime(消息日期)、user_id、login_loc、login_time。 不过这里可能需要使用子查询或者窗口函数来处理。比如,在关联后,按用户和消息日期分组,然后取最大的登录时间。或者使用ROW_NUMBER()按用户分区,按登录时间降序排序,取第一条记录。 可能的SQL结构: 先获取每天的活动用户: WITH daily_users AS ( SELECT Ftime, send_user_id AS user_id FROM t_chat_all UNION SELECT Ftime, receive_user_id AS user_id FROM t_chat_all ), distinct_daily_users AS ( SELECT DISTINCT Ftime, user_id FROM daily_users ) 然后关联登录表,找到每个用户在每个消息日期之前的最近登录: SELECT du.Ftime, du.user_id, l.login_loc, l.login_time FROM distinct_daily_users du LEFT JOIN t_login_all l ON du.user_id = l.user_id AND l.Ftime <= du.Ftime QUALIFY ROW_NUMBER() OVER (PARTITION BY du.Ftime, du.user_id ORDER BY l.login_time DESC) = 1 不过这里要注意,可能需要处理不同日期的登录记录,比如用户可能在多个不同的日期登录,但我们需要找到在消息日期之前最近的。使用LEFT JOIN并加上l.Ftime <= du.Ftime的条件,然后按用户和消息日期分区,按登录时间降序排列,取第一个。这样就能得到最近的登录信息。 但这里是否要考虑登录表中的Ftime和login_time的关系?比如,Ftime是登录日期,而login_time是具体时间,可能Ftime和login_time是同一天,也可能不同?比如,假设Ftime是DATE类型,而login_time是DATETIME,那么正确的条件应该是l.login_time <= du.Ftime的结束时间(比如当天的23:59:59)。但如果没有具体时间,可能只能按Ftime来比较。 如果Ftime和login_time都是日期,没有时间部分,那么只需要比较l.Ftime <= du.Ftime。如果有时间部分,可能需要转换格式,比如将du.Ftime视为当天的开始,然后比较l.login_time <= du.Ftime的结束时间。 不过在实际操作中,可能需要根据具体的字段类型来处理。假设Ftime在登录表中是登录日期,而login_time是具体时间,那么正确的条件应该是l.Ftime <= du.Ftime,并且当天的所有登录时间都可能被考虑进去。或者,如果login_time是精确到时间戳,那么可能需要将du.Ftime转换为日期,然后找到所有login_time在该日期或之前的最大的那个。 但不管怎样,使用ROW_NUMBER()来按用户分区,按登录时间降序排列,应该是可行的。然后通过QUALIFY子句筛选出每个用户在每个消息日期下的最近登录记录。 不过需要注意,不同的SQL方言可能支持不同的语法,比如在Hive中可以使用QUALIFY,而其他数据库可能需要使用子查询或者公共表表达式。 最终的SQL可能如下: WITH daily_users AS ( SELECT Ftime, send_user_id AS user_id FROM t_chat_all UNION SELECT Ftime, receive_user_id AS user_id FROM t_chat_all ), distinct_users AS ( SELECT DISTINCT Ftime, user_id FROM daily_users ), joined_data AS ( SELECT du.Ftime, du.user_id, l.login_loc, l.login_time, ROW_NUMBER() OVER (PARTITION BY du.Ftime, du.user_id ORDER BY l.login_time DESC) AS rn FROM distinct_users du LEFT JOIN t_login_all l ON du.user_id = l.user_id AND l.Ftime <= du.Ftime ) SELECT Ftime, user_id, login_loc, login_time FROM joined_data WHERE rn = 1; 这个查询首先获取每天的所有发送和接收用户,去重后关联登录表,找到每个用户在当天或之前的所有登录记录,然后按登录时间排序,取最近的一条。这样就能得到每个用户每天最近的登录信息。 不过需要确认登录表的Ftime和login_time是否一致。如果login_time是时间戳,而Ftime是其日期部分,那么条件可能需要调整为l.login_time <= CONCAT(du.Ftime, ' 23:59:59'),或者直接比较日期部分。但为了简化,这里假设Ftime是登录的日期,并且使用Ftime <= du.Ftime来筛选。 另外,如果用户没有登录记录,那么LEFT JOIN会导致login_loc和login_time为NULL。是否需要处理这种情况?用户需求是“有收发消息的用户最近登录时间”,所以可能这些用户必须有登录记录,否则可能不需要出现在结果中。这时应该使用INNER JOIN而不是LEFT JOIN。 但用户的问题描述中没有说明是否必须存在登录记录,所以可能需要根据实际情况调整。如果用户必须存在登录记录,则使用INNER JOIN。否则,LEFT JOIN保留可能为NULL的情况。 综上,最终的SQL应该包括上述结构,并根据需求选择JOIN类型。</think>### 问题拆解 需要从消息流水表和登录流水表中,提取每天有收发行为的用户,并关联其最近一次登录的区服和时间。具体步骤如下: --- ### 1. **提取当天有收发消息的用户** 无论用户是发送方还是接收方,只要当天有消息行为,即视为有效用户。需合并发送和接收用户,并去重。 ```sql -- 消息用户合并与去重 WITH daily_users AS ( SELECT Ftime, send_user_id AS user_id FROM t_chat_all UNION SELECT Ftime, receive_user_id AS user_id FROM t_chat_all ), distinct_users AS ( SELECT DISTINCT Ftime, user_id FROM daily_users ) ``` --- ### 2. **关联登录表获取最近登录记录** 将消息用户与登录表关联,按用户和日期分区,取登录时间最晚的一条记录。 ```sql -- 关联登录表并筛选最近登录 joined_data AS ( SELECT du.Ftime, du.user_id, l.login_loc, l.login_time, ROW_NUMBER() OVER ( PARTITION BY du.Ftime, du.user_id ORDER BY l.login_time DESC ) AS rn FROM distinct_users du LEFT JOIN t_login_all l ON du.user_id = l.user_id AND l.Ftime <= du.Ftime -- 登录日期不晚于消息日期 ) ``` --- ### 3. **过滤并输出结果** 取每个用户当天最近一次登录的区服和时间。 ```sql -- 最终输出 SELECT Ftime, user_id, login_loc, login_time FROM joined_data WHERE rn = 1; ``` --- ### 完整 SQL 代码 ```sql WITH daily_users AS ( SELECT Ftime, send_user_id AS user_id FROM t_chat_all UNION SELECT Ftime, receive_user_id AS user_id FROM t_chat_all ), distinct_users AS ( SELECT DISTINCT Ftime, user_id FROM daily_users ), joined_data AS ( SELECT du.Ftime, du.user_id, l.login_loc, l.login_time, ROW_NUMBER() OVER ( PARTITION BY du.Ftime, du.user_id ORDER BY l.login_time DESC ) AS rn FROM distinct_users du LEFT JOIN t_login_all l ON du.user_id = l.user_id AND l.Ftime <= du.Ftime ) SELECT Ftime, user_id, login_loc, login_time FROM joined_data WHERE rn = 1; ``` --- ### 关键逻辑说明 1. **合并收发用户**:通过 `UNION` 合并发送方和接收方用户,确保覆盖所有活跃用户。 2. **去重处理**:使用 `DISTINCT` 避免同一用户在同一天被多次统计。 3. **时间范围关联**:登录日期需早于或等于消息日期(`l.Ftime <= du.Ftime`)。 4. **窗口函数排序**:按登录时间降序排列,取最近一次登录记录(`ROW_NUMBER() = 1`)。 --- ### 注意事项 - **空值处理**:若用户无登录记录,`login_loc` 和 `login_time` 会为 `NULL`。如需排除,可改用 `INNER JOIN`。 - **性能优化**:若数据量较大,建议对 `t_login_all` 按 `user_id` 和 `Ftime` 建立索引。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王义凯_Rick

遇见即是缘,路过就给个评论吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值