【问题标题】:Default value for order field in mysqlmysql中订单字段的默认值
【发布时间】:2017-12-05 00:36:39
【问题描述】:

在给定的表格中,我有一个字段 (field_order),它将用作定义用于显示表格行的自定义顺序的方式。插入新记录时 我想将该特定字段设置为该表中的行数加一

所以如果表有 3 行,在插入新行时,field_order 的默认值应该是 4。

设置该值的最佳方法是什么?

插入语句中的简单选择计数?

对于返回该值的TIMESTAMP 数据类型,是否有类似CURRENT_TIMESTAMP 的常量?

编辑:这背后的原因是能够按特定字段对表格进行排序;并且该字段将由客户端的用户使用 jQuery 的 sortable 进行操作

【问题讨论】:

  • 我想你想要一个 auto_increment 主键。 Try taking a look at this.
  • @McAdam331 我可以有两个 auto_increment 列吗?我已经有一个 auto_increment 主键,我们称之为 field_id
  • 很遗憾,您不能有两个 auto_increment。根据您的用途,可能需要在应用程序级别进行更改。假设在您的应用程序中,您需要访问此field_order 列。相反,您可以访问 field_id 并增加它。这可能是也可能不是一个好的解决方案,具体取决于问题。
  • 根据您的要求,计数选项是最简单的。你应该考虑一下如果你删除一行会发生什么。

标签: mysql


【解决方案1】:

好的,围绕这个问题的解决方案实际上涉及到一些细微差别。继续并决定回答,但也想解决一些 cmets 尚未解决的细微差别/细节。

首先,我强烈建议您不要在主键上使用 auto_increment,如果没有其他原因,这些自动增量 ID 很容易被丢弃(例如,回滚事务会干扰它们) MySQL AUTO_INCREMENT does not ROLLBACK。正如@Sebas 提到的那样,将删除)。

其次,您必须考虑您的存储引擎。如果您使用的是 MyISAM,您可以非常快速地获得表的 COUNT(*)(因为 MyISAM 总是知道每个表中有多少行)。如果您使用的是 INNODB,则情况并非如此。根据您需要此表的用途,您可能能够摆脱 MyISAM。它不是默认引擎,但您肯定有可能遇到 MyISAM 是更好选择的需求。

你应该问自己的第三件事是,“为什么?”为什么您需要以这种方式存储数据?这实际上给了你什么?您实际上是否需要 SQL 中的这些信息?在同一个SQL表的同一个表中?

如果“为什么”有一个可以证明使用它的答案,那么我要问的最后一件事是“如何?”特别是,您将如何处理并发插入?你将如何处理删除或回滚?

鉴于您的要求,基本上有必要对表进行计数...但即便如此,仍涉及一些细微差别(删除、回滚、并发)以及需要做出的一些决定(使用哪个存储引擎您使用;您可以使用 MyISAM,它会更快计数星星吗?)。

不过,最重要的是,我会质疑为什么我首先需要这个。也许你真的这样做了……但这是一个非常奇怪的要求。

根据您的编辑:

编辑:这背后的原因是能够对表格进行排序 该特定领域;并且该字段将由用户操作 在客户端使用 jQuery 的 sortable

本质上,您要求的是有关您的表的元数据。我建议将这些元数据存储在单独的表中,或者完全存储在单独的服务中(Elastic Search、Redis 等)。您需要定期更新该单独的表(或键值存储)。如果您在 SQL 中执行此操作,则可以使用触发器。或者你使用 Elastic Search 之类的东西,你可以同时将数据插入到 SQL 和 ES 中。无论哪种方式,您都需要解决一些棘手的问题(例如,最终一致性、并发性以及在 MySQL 中使用触发器时可能适得其反的所有美好事物)。

如果是我,我会注意两点。一,即使是谷歌也没有提供始终最新的COUNT(*)。 “显示大约 XYZ 的第 1-10 行。”他们这样做的部分原因是他们拥有比我想象的更多的数据,部分原因是实际上计算一个表格的精确COUNT(*) 并使其保持最新是不切实际的(并且很快变得不可行和令人望而却步)一直。

所以,要么我完全改变我的要求并利用我可以快速获得的统计数据(如果您使用 MyISAM 进行存储,请继续使用 count( * )... 它会非常快)或者我会考虑维护我的表的计数星的索引,该索引每隔几个小时或每天通过某个进程(cron 作业、触发器等)定期更新,或者类似的东西。

在这个问题上的赏金......这个问题永远不会有一个单一的、规范的答案。无论您决定如何管理它,都需要进行权衡。它们可能在一致性、延迟、可扩展性、精确与近似解决方案、失去 INNODB 以换取 MyISAM 方面进行权衡……但会有权衡。最终决定归结为您愿意交易什么以获得您的要求。

如果是我,我可能会调整我的要求。如果我这样做了,我可能最终会在 Elastic Search 中对其进行索引,并确保它每隔几个小时左右更新一次。那是你应该做的吗?那要看。如果我可以忍受我的count(*) 有点过时,它肯定不是一个“正确答案”,而是一个(在众多答案中)可行的答案。

您应该为此使用 Elastic Search 吗?那要看。但是无论您走哪条路,您都将面临权衡取舍。那不取决于。你需要决定你愿意放弃什么,才能得到你想要的。如果它不重要,请调整要求。

【讨论】:

  • 我应该明确引用与@McAddam331 的协议......只是没有看到他的评论。但基本上,是的,我认为您应该真正考虑应用程序本身以及您是否真的需要它。就个人而言,我想不出一个好的理由。但谁知道呢,这是可能的。
【解决方案2】:

可能有更好的方法,但我现在能想到的就是创建第二个表来保存您需要的值,并使用触发器进行适当的插入/删除:

这是一个例子:

-- Let's say this is your table
create table tbl_test(
    id int unsigned not null auto_increment primary key,
    text varchar(50)
);

-- Now, here's the table I propose.
-- It will be related to your original table using 'Id'
-- (If you're using InnoDB you can add the appropriate constraint
create table tbl_incremental_values(
    id int unsigned not null primary key,
    incremental_value int unsigned not null default 0
);

-- The triggers that make this work:
delimiter $$
create trigger trig_add_one after insert on tbl_test for each row
begin
    declare n int unsigned default 0;
    set n = (select count(*) from tbl_test);
    insert into tbl_incremental_values
        values (NEW.id, (n));
end $$

-- If you're using InnoDB tables and you've created a constraint that cascades
-- delete operations, skip this trigger
create trigger trig_remove before delete on tbl_test for each row
begin
    delete from tbl_incremental_values where id = OLD.id;
end $$
delimiter ;

现在,让我们测试一下:

insert into tbl_test(text) values ('a'), ('b');

select a.*, b.incremental_value 
from tbl_test as a inner join tbl_incremental_values as b using (id);
-- Result:
--    id | text | incremental_value
--    ---+------+------------------
--    1  | a    | 1
--    2  | b    | 2

delete from tbl_test where text = 'b';
select a.*, b.incremental_value 
from tbl_test as a inner join tbl_incremental_values as b using (id);
-- Result:
--    id | text | incremental_value
--    ---+------+------------------
--    1  | a    | 1

insert into tbl_test(text) values ('c'), ('d');
select a.*, b.incremental_value 
from tbl_test as a inner join tbl_incremental_values as b using (id);
-- Result:
--    id | text | incremental_value
--    ---+------+------------------
--    1  | a    | 1
--    3  | c    | 2
--    4  | d    | 3

这适用于小型数据集,但作为evanv says in his answer

为什么?“为什么您需要以这种方式存储数据?这实际上给了您什么?您实际上是否需要 SQL 中的信息?在同一个 SQL 表的同一个表中?

如果您只需要输出该结果,还有一种更简单的方法可以完成这项工作:用户变量。

现在假设您的表是这样的:

create table tbl_test(
    id int unsigned not null auto_increment primary key,
    ts timestamp,
    text varchar(50)
);

insert into tbl_test(text) values('a');
insert into tbl_test(text) values('b');
insert into tbl_test(text) values('c');
insert into tbl_test(text) values('d');
delete from tbl_test where text = 'b';
insert into tbl_test(text) values('e');

ts 列将采用插入每一行的日期和时间的值,因此如果您按该列对其进行排序,您将按插入顺序获得行。但是现在:如何添加“增量值”?使用用户变量的小技巧是可能的:

select a.*
     , @n := @n + 1 as incremental_value
--     ^^^^^^^^^^^^ This will update the value of @n on each row
from (select @n := 0) as init -- <-- you need to initialize @n to zero
   , tbl_test as a
order by a.ts;
-- Result:
--   id | ts                  | text | incremental_value
--   ---+---------------------+------+----------------------
--   1  | xxxx-xx-xx xx:xx:xx | a    | 1
--   3  | xxxx-xx-xx xx:xx:xx | c    | 2
--   4  | xxxx-xx-xx xx:xx:xx | d    | 3
--   5  | xxxx-xx-xx xx-xx-xx | e    | 4

但是现在...如何处理大型数据集,您可能会使用LIMIT?只需将@n初始化为limit的起始值:

-- A dull example:

prepare stmt from 
    "select a.*, @n := @n + 1 as incremental_value
    from (select @n := ?) as init, tbl_test as a
    order by a.ts
    limit ?, ?";
-- The question marks work as "place holders" for values. If you're working
-- directly on MySQL CLI or MySQL workbench, you'll need to create user variables
-- to hold the values you want to use.
set @first_row = 2, @nrows = 2;
execute stmt using @first_row,   @first_row,   @nrows;
--                 ^^^^^^^^^^    ^^^^^^^^^^    ^^^^^^
--                 Initalizes    The "floor"   The number
--                 the @n        of the        of rows
--                 value         LIMIT         you want
--
-- Set @first_row to zero if you want to get the first @nrows rows
--
-- Result:
--   id | ts                  | text | incremental_value
--   ---+---------------------+------+----------------------
--   4  | xxxx-xx-xx xx:xx:xx | d    | 3
--   5  | xxxx-xx-xx xx-xx-xx | e    | 4
deallocate prepare stmt;

【讨论】:

  • 是的,如果“为什么”确实有一个很好的答案,那么我也很好奇 SQL 是否适合这样做。弹性搜索实际上可能是一个不错的选择,有点像给定记录在特定时间的 count(*) 索引。但这个“为什么”问题首先需要一个答案。
【解决方案3】:

似乎最初的问题是要求一种简单的方法来为新记录设置默认排序顺序。稍后,用户可以调整该“订单字段”值。似乎 DELETES 和 ROLLBACKS 与此无关。

这是一个简单的解决方案。对于排序顺序字段,将默认值设置为 0,并使用主键作为辅助排序。只需将查询中的排序顺序更改为 DESC。如果您希望默认功能是“首先显示最近添加的”,请使用:

SELECT * from my_table
WHERE user_id = :uid
ORDER BY field_order, primary_id DESC

如果您想“显示最近添加的最后一个”,请使用:

SELECT * from my_table
WHERE user_id = :uid
ORDER BY field_order DESC, primary_id

【讨论】:

    【解决方案4】:

    为了避免插入查询中的SELECT COUNT(*) ...,我所做的是使field_order 列的未排序 状态,假设默认值为0

    选择查询看起来像:

    SELECT * FROM my_table ... ORDER BY id_primary, field_order
    

    只要您不应用自定义顺序,您的查询就会产生时间顺序。

    当您想应用自定义排序时,field_order 应通过将它们从 -X 计数到 0 来重新设置:

    id | sort
    ---+-----
    1  | -2   
    2  | -1   
    3  |  0   
    

    当发生更改时,自定义排序仍然存在,并且新行将始终在自定义排序结束时按时间顺序排序:

    id | sort
    ---+-----
    1  | -2   
    3  |  0   
    4  |  0   
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-16
      • 1970-01-01
      • 2018-09-03
      相关资源
      最近更新 更多