MySQL 合并查询 join 查询出的不同列合并到一个表中

Posted 酸菜鱼土豆大侠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 合并查询 join 查询出的不同列合并到一个表中相关的知识,希望对你有一定的参考价值。

为了求解问题时思路清晰,建议先分列查询,再将列合并到一个表中,这样相当于将复杂问题拆解为简单问题一一解决。优点是避免所有问题混在一起,代码逻辑清晰,可迁移性强,下次遇到类似的查询问题能快速求解,缺点是代码看起来不够简洁,存在代码冗余的问题。

一、适用场景和方法

(1)适用场景

考虑查询过程中是否存在以下情况:

  • 查询某些列时需要分组才能得到,某些列不需要分组就能得到;
  • 查询某些列时需要where条件,某些列不需要where条件;
  • 查询这些列时需要多次用到不同的表;
  • 某一个列或几个列的查询过程很复杂。

存在上述情况时候,为了求解问题时思路清晰,建议先分列查询,再将列合并到一个表中,这样相当于将复杂问题拆解为简单问题,一一解决。

(2)方法

mysql多表查询,将查询到的列合并到一个表中使用join函数

具体包括:

连接类型(四者选一)连接条件(三者选一)
left joinnatural
right joinon <连接条件>
inner joinusing(col1,col2,…,coln)
full outer join

根据查询需要使用不同的连接类型和条件。其中col指列名(注意两个表的该列名必须相同)。

二、案例分析

案例来自:SQL135 每个6/7级用户活跃情况

现有用户信息表user_info(uid用户ID,nick_name昵称, achievement成就值, level等级, job职业方向, register_time注册时间):

iduidnick_nameachievementleveljobregister_time
11001牛客1号31007算法2020-01-01 10:00:00
21002牛客2号23007算法2020-01-01 10:00:00
31003牛客3号25007算法2020-01-01 10:00:00
41004牛客4号12005算法2020-01-01 10:00:00
51005牛客5号16006C++2020-01-01 10:00:00
61006牛客6号26007C++2020-01-01 10:00:00

试卷信息表examination_info(exam_id试卷ID, tag试卷类别, difficulty试卷难度, duration考试时长, release_time发布时间):

idexam_idtagdifficultydurationrelease_time
19001SQLhard602021-09-01 06:00:00
29002C++easy602021-09-01 06:00:00
39003算法medium802021-09-01 10:00:00

试卷作答记录表exam_record(uid用户ID, exam_id试卷ID, start_time开始作答时间, submit_time交卷时间, score得分):

uidexam_idstart_timesubmit_timescore
100190012021-09-01 09:01:012021-09-01 09:31:0078
100190012021-09-01 09:01:012021-09-01 09:31:0081
100590012021-09-01 19:01:012021-09-01 19:30:0185
100590022021-09-01 12:01:012021-09-01 12:31:0285
100690032021-09-07 10:01:012021-09-07 10:21:5984
100690012021-09-07 10:01:012021-09-07 10:21:0181
100290012020-09-01 13:01:012020-09-01 13:41:0181
100590012021-09-01 14:01:01(NULL)(NULL)

题目练习记录表practice_record(uid用户ID, question_id题目ID, submit_time提交时间, score得分):

uidquestion_idsubmit_timescore
100180012021-08-02 11:41:0160
100480012021-08-02 19:38:0170
100480022021-08-02 19:48:0190
100180022021-08-02 19:38:0170
100480022021-08-02 19:48:0190
100680022021-08-04 19:58:0194
100680032021-08-03 19:38:0170
100680032021-08-02 19:48:0190
100680032020-08-01 19:38:0180

请统计每个6/7级用户总活跃月份数、2021年活跃天数、2021年试卷作答活跃天数、2021年答题活跃天数,按照总活跃月份数、2021年活跃天数降序排序。由示例数据结果输出如下:

uidact_month_totalact_days_2021act_days_2021_examact_days_2021_question
10063413
10012211
10051110
10021000
10030000

解释:6/7级用户共有5个,其中1006在202109、202108、202008共3个月活跃过,2021年活跃的日期有20210907、20210804、20210803、20210802共4天,2021年在试卷作答区20210907活跃1天,在题目练习区活跃了3天。

分析是否适用‘列拼接成表’的方法:

根据题目要求可知,总活跃月份数、2021年活跃天数和 2021年试卷作答活跃天数、2021年答题活跃天数,查询所用的表不一样,并且每一个列的查询过程都很复杂,所以采取分列查询再合并列的方法。

分析思路

难点:

1.建立合并列的思想

(1)统计用户总活跃月份数 如果日期重复算一个月份

​ [使用]:[年月]date_format(exrp,'%y%m') ; 去重distinct

(2)统计用户2021年活跃天数 如果日期重复算一天

​ [使用]:[2021年]: year(exrp) = 2021; [年月日]date(exrp) ; 去重distinct

注意: 判断是否是2021年应该放在select里面而不是where中

(3)统计2021年试卷作答活跃天数

​ [使用]: [2021年]: year(exrp) = 2021; [年月日]date(exrp) ;

(4)统计2021年答题活跃天数

​ [使用]:多表连接使用 join using( )

(5)合并列

​ [使用]: [2021年]: year(exrp) = 2021; [年月日]date(exrp) ;

最终结果

select 查询结果 [总活跃月份数; 2021年活跃天数; 2021年试卷作答活跃天数; 2021年答题活跃天数]
from 从哪张表中查询数据[多个join连接的表]
where 查询条件 [level等级是6/7]
order by 对查询结果排序 [按照总活跃月份数、2021年活跃天数降序];

实现过程

(1)需要一个临时表:

with 
    main as(
		#试卷作答记录和题目练习记录
        select distinct
            a.uid,
            date(start_time) as days,
            'exam' as tag
        from user_info a
        left join exam_record b
        using(uid)
        union
        select distinct
            a.uid,
            date(submit_time) as days,
            'question' as tag
        from user_info a
        left join practice_record c
        using(uid)
    ) 

注意:mysql版本在8.0之前不支持with。如需配置mysql的8.0版本参考

(2)求select列

  • 总活跃月份数
#总活跃月份数 attr
select 
    uid,
    count(distinct date_format(days,'%y%m')) as act_month_total
from main
group by uid
  • 2021年活跃天数
#2021年试卷作答活跃天数 attr1
select
	uid,
	count(distinct(if(year(start_time) = 2021,start_time,null))) as act_days_2021_exam
from main
group by uid
  • 2021年试卷作答活跃天数

  • count(distinct(if(year(date(act_date)) = 2021 and tag = 'exam',act_date,null)))

    利用tag标记是试卷作答记录还是答题作答记录。

#2021年试卷作答活跃天数 attr2
select
    uid,
    count(distinct(if(year(days) = 2021 and tag = 'exam',days,null))) as act_days_2021_exam
from main1
group by uid
  • 2021年答题活跃天数
#2021年答题活跃天数 attr3
select
    uid,
    count(distinct(if(year(days) = 2021 and tag = 'question', days, null))) as act_days_2021_question
from main1
group by uid

(3)合并列

select
    a.uid,
    act_month_total,
    act_days_2021,
    act_days_2021_exam,
    act_days_2021_question
from user_info a
left join attr using(uid) 
left join attr1 using(uid)
left join attr2 using(uid)
left join attr3 using(uid)
where level between 6 and 7
order by act_month_total desc,act_days_2021 desc

求解代码

方法一:

使用 with

with 
    main as(
		#试卷作答记录和题目练习记录
        select distinct
            a.uid,
            date(start_time) as days,
            'exam' as tag
        from user_info a
        left join exam_record b
        using(uid)
        union
        select distinct
            a.uid,
            date(submit_time) as days,
            'question' as tag
        from user_info a
        left join practice_record c
        using(uid)
    ) 
#合并列
select
    a.uid,
    act_month_total,
    act_days_2021,
    act_days_2021_exam,
    act_days_2021_question
from user_info a
left join(
    #总活跃月份数指的是所有年
    select 
        uid,
        count(distinct date_format(days,'%y%m')) as act_month_total
    from main
    group by uid
) attr using(uid)
left join(
    #2021年活跃天数
    select 
        uid,
        count(distinct if(year(days) = 2021,days,null)) as act_days_2021	
    from main
    group by uid
) attr1 using(uid)

left join(
    #2021年试卷作答活跃天数
    select
        uid,
        count(distinct(if(year(days) = 2021 and tag = 'exam',days,null))) as act_days_2021_exam
    from main
    group by uid
) attr2 using(uid)

left join(
    #2021年答题活跃天数
    select
        uid,
        count(distinct(if(year(days) = 2021 and tag = 'question',days,null))) as act_days_2021_question
    from main
    group by uid
) attr3 using(uid)
where level between 6 and 7
order by  act_month_total desc,act_days_2021 desc#按照总活跃月份数、2021年活跃天数降序排序

方法二:

不使用 with

select
    uid,
    count(distinct date_format(days,'%y%m')) as act_month_total,#总活跃月份数指的是所有年
    count(distinct if(year(days) = 2021,days,null)) as act_days_2021,#2021年活跃天数
    count(distinct(if(year(days) = 2021 and tag = 'exam',days,null))) as act_days_2021_exam,#2021年试卷作答活跃天数
    count(distinct(if(year(days) = 2021 and tag = 'question',days,null))) as act_days_2021_question#试卷作答记录和题目练习记录
from user_info
left join(
    select distinct
        uid,
        date(start_time) as days,
        'exam' as tag
    from user_info
    left join exam_record using(uid)
    union
    select distinct
        uid,
        date(submit_time) as days,
        'question' as tag
    from user_info
    left join practice_record using(uid)
) main using(uid)
where level between 6 and 7
group by uid
order by  act_month_total desc,act_days_2021 desc#按照总活跃月份数、2021年活跃天数降序排序

扩展:

前往查看MySQL 嵌套子查询 with子句 from子查询 in子查询 join子查询


非常荣幸您能阅读到最后,希望文章中的内容能够帮助到您。
本博客内容均为博主原创,未经授权,请勿转载,谢谢!

MySQL:在没有 JOIN 或 UNION 的情况下合并两个不同的表

【中文标题】MySQL:在没有 JOIN 或 UNION 的情况下合并两个不同的表【英文标题】:MySQL: Merge two different tables without JOIN or UNION 【发布时间】:2011-12-21 23:11:49 【问题描述】:

我需要合并两个表:

两者都有一个主键列日期,但具有不同的值(不同的时间间隔)。 两者都有不同的(未知)列:我不知道列的名称(两个表中可能出现相同的列名),我不知道有多少列,但都是相同的类型。

一个例子:

table1
date       | colA | colB | colC
2011-02-02 | 1.09 | 1.03 | 1.04

table2
date       | col1 | col2 | col3 | col4
2011-02-03 | 1.03 | 1.02 | 1.07 | 1.03

查询结果应如下所示:

tableResult
date       | colA | colB | colC | col1 | col2 | col3 | col4
2011-02-02 | 1.09 | 1.03 | 1.04 | null | null | null | null
2011-02-03 | null | null | null | 1.03 | 1.02 | 1.07 | 1.03

这不起作用:

INNER JOIN 因为它只会返回table1table2 之间的交集, OUTER JOIN 仅从左表返回交集 + 值(如果使用右连接,则返回右表) UNION 因为列数可能不同。

有什么想法吗?

克里斯托夫

【问题讨论】:

我有一个解决方案,但包含多余的主键。你可以吗? 如果字段“colA”在同一日期有不同的值怎么办?其中一张表应该是主表。 列名是简单传感器网络中传感器的地址。因此,两个表中的相同列名表示相同的传感器。两个条目中的相似值将只是相同的值。 DISTINCT 可以帮助解决这个问题,如果我没记错的话。另一方面,两个表的日期间隔不同,它们永远不应该发生冲突(偶数值与奇数值) @Christoph,您找到任何更好的解决方案请建议我...我有同样的情况... @samirprogrammer:不,我自己没有找到更好的解决方案,请参见下面给出的答案。 【参考方案1】:

您可以使用仅日期列的并集创建一个临时表,然后使用该临时表与其他 2 列进行左外连接。

例子:

DROP TABLE temptbl IF EXISTS;
CREATE TEMPORARY TABLE temptbl (myDate DATETIME PRIMARY KEY)
    AS (SELECT MyDate FROM table1)
    UNION (SELECT MyDate FROM table2)
    ORDER BY MyDate;
SELECT * FROM temptbl
    LEFT OUTER JOIN table1 USING (MyDate)
    LEFT OUTER JOIN table2 USING (MyDate);

【讨论】:

是的,没错。这是上面代码的示例:DROP TABLE temptbl IF EXISTS; CREATE TEMPORARY TABLE temptbl (myDate DATETIME PRIMARY KEY) AS (SELECT MyDate FROM table1) UNION (SELECT MyDate FROM table2) ORDER BY MyDate; SELECT * FROM temptbl LEFT OUTER JOIN table1 USING (MyDate) LEFT OUTER JOIN table2 USING (MyDate);【参考方案2】:
select coalesce(t2.data,'')+coalesce(t1.data,'') as data,
    t2.col1, t2.col2, t2.col3 ,t2.col4 ,t1.cola ,t1.colb, t1.colc
from table2 as t2
full outer join 
table1 t1
on t2.data = 2011-02-03
or t1.data = 2011-02-02

【讨论】:

以上是关于MySQL 合并查询 join 查询出的不同列合并到一个表中的主要内容,如果未能解决你的问题,请参考以下文章

如何根据不同的列合并行(可以查询吗?)

mysql多表查询合并到一个临时表,怎么再加一列并把各自的表名加上?

SQL合并两个具有不同列号的选择查询,同时删除重复项?

SQL查询结果查询列合并

MySQL GROUP_CONCAT 和 ORDER 以及合并列请帮助

怎样一个表中的2个查询结果合并到一个表中的两列