【问题标题】:MySQL: I need to return rows according to certain conditionsMySQL:我需要根据某些条件返回行
【发布时间】:2020-04-29 18:36:27
【问题描述】:

我有3张桌子:工作、录音、发布

1 个作品可以有多个录音,1 个录音只出现在 1 个版本中

表格:工作

+---------+-----------+
| work_id | name      |
+---------+-----------+
| 1       | Hello     | 
| 3       | Luna      | 
| 4       | Feel good | 
| 5       | My self   | 
+---------+-----------+

表格:录音

+---------------------------------------------------------------------+
| recording_id | work_id | release_id | name        | is_art | is_vid |
+---------------------------------------------------------------------+
| 45           | 1       | 45         | Hello4      | 1      | 0      |
| 78           | 3       | 67         | Luna5       | 1      | 0      |
| 23           | 5       | 128        | My self (r) | 1      | 0      |
| 95           | 5       | 156        | My self II  | 1      | 0      |
| 17           | 4       | 67         | Luna67      | 1      | 0      |
+---------------------------------------------------------------------+

表格:发布

+--------------------------------------------+
| release_id | name    | year | month | day  |
+--------------------------------------------+
| 45         | Yo      | 1998 | 12    | NULL |
| 67         | Testing | 1967 | 3     | 3    |
| 128        | Maybe   | 2018 | 10    | 21   |
| 156        | Again   | 2018 | 10    | NULL |
+--------------------------------------------+

基本上,对于每个work,我想返回recording,其中is_art = 1is_vid = 0 AND 是release 是最旧的(最旧的年、月和日)。我可能是recording release 可以具有相同的yearmonthday。在那种情况下,我想我需要找到一个唯一标识符,所以我会选择最新的release_id

结果集应如下所示:

+---------+---------------------------------------+
| work_id | name      | recording_id | name       |
+---------+---------------------------------------+
| 1       | Hello     | 45           | Hello4     |
| 3       | Luna      | 78           | Luna5      |
| 4       | Feel good | 17           | Luna67     |
| 5       | My self   | 23           | My self (r)|
+---------+---------------------------------------+

到目前为止,我创建了这个查询,但老实说,作为一个新手,我知道这一切都错了。它返回重复的行。我觉得我需要使用group by 和子查询,但是经过 2 天的搜索和测试,我无法创建解决方案……我快疯了

样本数据 1

| work_id | work_name           | recording_id | release_id | rec_name                                            | year | month | day |
|---------|---------------------|--------------|------------|-----------------------------------------------------|------|-------|-----|
|     201 | Me ha dicho la luna |          253 |          5 | Me ha dicho la luna                                 | 1998 |     4 |  22 |
|     201 | Me ha dicho la luna |          579 |        528 | Me ha dicho la luna (Moonlight Radio Edit)          | 1998 |       |     |
|     201 | Me ha dicho la luna |          580 |        528 | Me ha dicho la luna (Luna llena Ambience Mix)       | 1998 |       |     |
|     201 | Me ha dicho la luna |          581 |        528 | Me ha dicho la luna (Extended Callejuela's Version) | 1998 |       |     |
|     201 | Me ha dicho la luna |          582 |        528 | Me ha dicho la luna (Stoned Baby Free Version)      | 1998 |       |     |
|     201 | Me ha dicho la luna |          252 |          1 | Me ha dicho la luna (con Chayanne)                  | 2006 |       |     |

样本数据 2

| work_id | work_name  | recording_id | release_id | rec_name                                                | year | month | day |
|---------|------------|--------------|------------|---------------------------------------------------------|------|-------|-----|
|     401 | Si amanece |          397 |         26 | Si amanece                                              | 1978 |     7 |   1 |
|     401 | Si amanece |          634 |        309 | Si amanece                                              | 1978 |     7 |   1 |
|     401 | Si amanece |          396 |        257 | Si amanece (con el Mariachi Oro y Plata de Pepe Chávez) | 1979 |       |     |
|     401 | Si amanece |          564 |        188 | Si amanece                                              | 2001 |       |     |
|     401 | Si amanece |          394 |        213 | Si amanece                                              | 2001 |       |     |
|     401 | Si amanece |          395 |          1 | Si amanece                                              | 2006 |       |     |
|     401 | Si amanece |          638 |        295 | Si amanece                                              |      |       |     |

【问题讨论】:

  • 在您的预期结果中,第 3 行如何获得recording_id = 128name= My self II
  • @tcadidot0 你问得对,因为我在我的例子中犯了一个错误。现在修好了它应该返回 recording_id = 23name=My self (r)
  • @Marco,我对 release.day 字段中的 NULL 值有些困惑。如何用 NULL day 猜出最旧的值?
  • @SlavaRozhnev 这是一个很好的观点。有时我没有release 专辑的日期、月份甚至年份。
  • 因此,您需要确定 NULL 值是旧值还是新值,然后是其他带有数字的值

标签: mysql sql join sql-order-by mysql-5.7


【解决方案1】:

这是一个为您的示例数据生成预期结果的查询:

select
    w.work_id,
    w.name work_name,
    r.recording_id,
    r.name recording_name
from work w
inner join recording r 
    on r.recording_id = (
        select r1.recording_id 
        from recording r1 
        inner join releases l1 on l1.release_id = r1.release_id
        where r1.work_id = w.work_id and r1.is_art = 1 and r1.is_vid = 0
        order by -l1.year desc, -l1.month desc, -l1.day desc, r1.release_id desc
        limit 1
    )

这可以通过将work 表与recording 连接起来,使用相关子查询来选择正确的行。从您的示例数据和结果来看,您似乎希望在对行进行排序时将nulls 放在首位:这不是 MySQL 中的默认行为,因此我们使用了一个技巧,即按 - <column_name> desc 排序(这使得nulls 优先考虑升序)。

注意:releasereserved word in MySQL,所以我将该表命名为 releases(否则,您需要用反引号括起来)。

Demo on DB Fiddle

工作编号 |工作名称 |录音ID |录音名称 ------: | :-------- | ------------: | :------------- 1 |你好 | 45 |你好4 3 |露娜 | 78 |月神5 5 |我的自我 | 23 |我的自己(r)

或者,如果您运行的是 MySQL 8.0,则使用 row_number() 来识别正确的记录。根据您的数据集,这可能会或可能不会表现更好:

select work_id, work_name, recording_id, recording_name
from (
    select
        w.work_id,
        w.name work_name,
        r.recording_id,
        r.name recording_name,
        row_number() over(
            partition by r.work_id 
            order by -l.year desc, -l.month desc, -l.day desc, r.release_id desc
        ) rn
    from work w
    inner join recording r 
        on r.work_id = w.work_id
        and r.is_art = 1
        and r.is_vid = 0
    inner join releases l 
        on l.release_id = r.release_id
) t
where rn = 1

Demo on DB Fiddle(结果同上)

【讨论】:

  • 如果每个表中的行数超过几行,性能可能会很糟糕。 (不,我没有看到更好的解决方案。)
  • @RickJames:是的,可能...我刚刚添加了另一个使用row_number() 选择正确版本的查询。这可能会,也可能不会,提供更好的性能...
  • 这样就行了!我必须同意,它有点慢但不是太多,因为我的数据库很小。运行查询平均需要 116 毫秒(0.11 秒)。没关系,因为没有更好的解决方案。
  • ROW_NUMBER() 效率不高。反正 5.7 就没有了。
  • 谢谢@GMB 这工作得很好,但有点慢。我非常感谢您的所有努力。
【解决方案2】:

这似乎得到了“正确”的答案:

-- Query 1
CREATE TEMPORARY TABLE t (
    new_id INT AUTO_INCREMENT PRIMARY KEY
)
SELECT  w.work_id,
        w.name AS work_name,
        rec.recording_id,
        rec.release_id,
        rec.name AS rec_name,
        year, month, day
    FROM work AS w
    JOIN recording AS rec ON rec.work_id = w.work_id
    JOIN releaset AS rel ON rel.release_id = rec.release_id
    WHERE is_art = 1
      AND is_vid = 0
    ORDER BY work_id, year, month, day, release_id;

-- Query 2
SELECT work_id, work_name, recording_id, rec_name
    FROM ( SELECT MIN(new_id) AS first_id FROM t
               GROUP BY work_id, year, month, day, release_id ) AS x
    JOIN t ON t.new_id = x.first_id;

不幸的是,它在某些版本上会失败。

  • MariaDB 10.2+ 不会抱怨Can't reopen table: 't'。有两种解决方法:将t 改为TEMPORARY 或将临时表复制到另一个临时表中。

  • MySQL 8.0 和 MariaDB 10.2+ 可以使用WITH,以有效地重复使用临时表。不过,一个潜在的问题是需要在临时表中添加 AUTO_INCREMENT 列。

好的,下面是绕过“重新打开”问题的方法:

-- Query 3
CREATE TEMPORARY TABLE x
    SELECT MIN(new_id) AS first_id FROM t
        GROUP BY work_id;

-- Query 4
SELECT work_id, work_name, recording_id, rec_name
    FROM x
    JOIN t ON t.new_id = x.first_id;

然后使用查询 1,3,4。

【讨论】:

  • 做了一些测试,速度真的很快。但我注意到在日期为空的情况下它返回错误的release。我放了一些真实数据(示例 1、2),以便您查看。例如,在 SAMPLE DATA 1 中,它应该返回 253,但它返回 579。在 SAMPLE DATA 2 中,它应该返回 397,但它返回 638。希望这些真实数据有所帮助。我应该早点放真实数据的。
  • 我可能会再次处理它,但首先我需要知道你关于空日期部分的规则。也就是说,[1998, null, null] 是比 [1998, 4, 28] 旧还是新?
  • [1998, 4, 28] 早于 [1998, null, null] 因为至少日期更完整。 [2001, null, null] 早于 [null, null, null]...
  • @Marco - 这些不一致。只是 day 不见了怎么办?
  • 我想我想说的是我优先考虑最完整的日期。如果对于相同的work_id 我有 [1998, 4 ,28] 和 [1998, null, null] 那么我将优先考虑 [1998, 4, 28] 因为至少我有一年、一个月和一天。如果对于相同的 work_id 我有 [2001, null, null] 和 [null, null, null] 那么我优先考虑 [2001,null,null] 因为至少我有一年
【解决方案3】:

根据work_id 获取最新的recording,您可以使用聚合函数max(),后跟group by 子句。

select w.work_id, w.name, r.recording_Id, r.name, 
     max(cast(concat(coalesce(year, '1000'), coalesce(month, '01'), coalesce(day, '01')) as date))
from work w
join recording r on w.work_id = r.work_id
join release rl on rl.release_id = r.release_id
where r.is_art = 1 and r.is_vid = 0
group by w.work_id, w.name, r.recording_Id, r.name
order by w.work_id

【讨论】:

  • 在 1 月到 2 月之间对 10 月进行排序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-11
  • 2015-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-31
相关资源
最近更新 更多