MySQL - 更新一列中具有不同(非唯一)值的行

时间:2014-01-20 16:12:26

标签: mysql sql-update greatest-n-per-group

我有桌子和物品。每个项目都有id,item_type(比方说 - 书籍,音频,视频,电子书),插入时间和标志:

id|  item_type | created (timestamp) | sign
1 |    book    | 2014-01-14 15:40:24 | NULL
2 |    book    | 2014-01-15 15:40:24 | NULL
3 |   audio    | 2014-01-16 15:40:24 | NULL
4 |   audio    | 2014-01-17 15:40:24 | NULL
5 |   ebook    | 2014-01-18 15:40:24 | NULL
6 |   video    | 2014-01-19 15:40:24 | NULL

我想更新具有不同item_type的3个最旧行的列标志,对于上面的数据,这些行的ID为1,3,5。

但我不知道如何表达item_type对于更新的行必须不同的条件。谢谢你的建议。我感谢您的帮助。理查德

编辑:如果描述令人困惑,请道歉: 我想更新总是3行,每行不同 item_type。不是3本书,3个视频,......


广告排序 - 这是真的我可以按ID排序,而不是时间戳。这个演示是简化的,在实际应用中,我将使用修改时间戳进行排序。

4 个答案:

答案 0 :(得分:1)

这是满足指定要求的一种方法。

(请注意,我们没有保证每个item_type的created列的唯一性,并且要求查询更新AT MOST三行,每个item_type一行。此查询假定“id”列是主键,或至少是唯一键。)

UPDATE thetable t
  JOIN ( SELECT r.item_type
              , MIN(r.id) AS id
           FROM thetable r
           JOIN ( SELECT p.item_type
                       , MIN(p.created) AS oldest
                    FROM thetable p
                   GROUP BY p.item_type 
                   ORDER BY oldest
                   LIMIT 3
                ) q
             ON q.item_type = r.item_type
            AND q.oldest = r.created
          GROUP BY r.item_type
       ) s
    ON s.id = t.id
   SET t.sign = 0
是的,这看起来有点讨厌。我们稍微打开它。

别名为“q”的内嵌视图最多可获得三行,其中包含三个不同的item_type,以及每个item_type的最早“created”值。这就是那里的一半战斗。

别名为“s”的内联视图获得与“id”中的行匹配的单行的“q”值。 (我们需要这样做,因为我们没有得到保证,不会有两行或更多行匹配“item_type”和“created”,这样就有可能更新更多内容超过三行。)


-- test case
CREATE TABLE thetable (id INT, item_type VARCHAR(9), created TIMESTAMP, `sign` INT);
INSERT INTO thetable VALUES
('1','book','2014-01-14 15:40:24',NULL)
,('2','book','2014-01-15 15:40:24',NULL)
,('3','audio','2014-01-16 15:40:24',NULL)
,('4','audio','2014-01-17 15:40:24',NULL)
,('5','ebook','2014-01-18 15:40:24',NULL)
,('6','video','2014-01-19 15:40:24',NULL);

SELECT * FROM thetable;

后续

是否很难修改此UPDATE查询,以便更新最旧的行的符号并且其符号为空?

如果您的意思是您希望语句像它一样工作,但只对表中符号列中包含NULL的行进行操作,就像它假装符号中带有非NULL值的行一样列不存在,这是一个非常简单的更改。

我们可以在内联视图中添加WHERE子句,如下所示:

UPDATE thetable t
  JOIN ( SELECT r.item_type
              , MIN(r.id) AS id
           FROM thetable r
           JOIN ( SELECT p.item_type
                       , MIN(p.created) AS oldest
                    FROM thetable p
                   WHERE p.sign IS NULL        -- only consider rows with NULLs
                   GROUP BY p.item_type 
                   ORDER BY oldest
                   LIMIT 3
                ) q
             ON q.item_type = r.item_type
            AND q.oldest = r.created
          WHERE r.sign IS NULL                 -- only consider rows with NULLs
          GROUP BY r.item_type
       ) s
    ON s.id = t.id
   SET t.sign = 0

如果您的意思是其他内容,则修改会有所不同。

答案 1 :(得分:0)

此查询将为您提供每种项目类型的最早记录:

select item_type, min(created) as MinCreated
from MyTable
group by item_type

然后您可以执行以下操作:

update MyTable a
inner join (
    select item_type, min(created) as MinCreated
    from MyTable
    group by item_type
    ) b on a.item_type = b.item_type
    and a.created = b.MinCreated
set sign = 1

答案 2 :(得分:0)

您可以使用以下内容:

   SELECT * FROM MyTable GROUP BY item_type ORDER BY created DESC LIMIT 3

使用group by为您提供唯一的item_type,以及order by created的后代顺序以及limit 3,将为您提供最早的三个

答案 3 :(得分:0)

我测试了这个解决方案:

SET @t := NULL;
SET @c := 0;

UPDATE n 
SET sign = IF(@t = item_type, sign, IF(@c < 3, SIGN(@c:=@c+1), sign)),
    item_type = IF(@t:=item_type, item_type, item_type)
ORDER BY item_type, created;

以下是对数据的最终更改:

+----+-----------+---------------------+------+
| id | item_type | created             | sign |
+----+-----------+---------------------+------+
|  1 | book      | 2014-01-20 08:54:00 |    1 |
|  2 | book      | 2014-01-15 15:40:24 | NULL |
|  3 | audio     | 2014-01-20 08:54:00 |    1 |
|  4 | audio     | 2014-01-17 15:40:24 | NULL |
|  5 | ebook     | 2014-01-20 08:54:00 |    1 |
|  6 | video     | 2014-01-19 15:40:24 | NULL |
+----+-----------+---------------------+------+

在进一步思考并查看其他人的解决方案之后,如果我们不限制在单个SQL语句中执行此操作,我认为这会容易得多。

选择具有不同item_types的三个最早的条目,首先是创建的时间戳,然后只是在有条件的情况下,匹配该时间戳的条目,选择ID最低的项目。

SELECT item_type, MIN(id) AS id
FROM n
JOIN (
    SELECT item_type, MIN(created) AS created
    FROM n 
    GROUP BY item_type 
    ORDER BY created ASC LIMIT 3
) AS t USING (item_type, created)
GROUP BY item_type;

这为您提供了三个最早条目的ID,每个item_type只选择一个。

+-----------+------+
| item_type | id   |
+-----------+------+
| audio     |    3 |
| book      |    1 |
| ebook     |    5 |
+-----------+------+

然后你必须获取id并形成一个新的查询:

UPDATE n SET sign = 1 WHERE id IN (3,1,5);

我尝试将两者合并为一个UPDATE语句,但是我收到了以下错误:

ERROR 1093 (HY000): You can't specify target table 'n' for update in FROM clause

以为我还测试了其他用户提供的其他一些解决方案,但它们似乎也有效。因此,当UPDATE允许子查询时,必须有一个非常微妙的规则。

我建议更简单的解决方案,因为它更容易理解,编码,调试和维护。所以在两个声明中这样做并称之为胜利。