背景和目标
在表clients
中,我有一列clientNum CHAR(11) NOT NULL
,带有UNIQUE KEY
约束。它包含格式为 xxx-xxx-xxx
的客户编号,其中 x
是十进制数字。有关格式的更多详细信息,请参阅下文。
我想为此列实现类似 AUTO_INCRMENT
的功能,以便每个客户端都能自动计算其数量。来自 MySQL CREATE TABLE
docs :
An integer or floating-point column can have the additional attribute
AUTO_INCREMENT
. When you insert a value ofNULL
(recommended) or0
into an indexedAUTO_INCREMENT
column, the column is set to the next sequence value. Typically this isvalue+1
, wherevalue
is the largest value for the column currently in the table.AUTO_INCREMENT
sequences begin with1
.
所以我想找到下一个可用的数字并将其用作新插入的客户行的 clientNum
值。下一个可用的数字是当前最大的 clientNum
增量。
我正在 PHP 中使用 PDO 进行编码访问 MySQL 数据库(请参阅 PDO Tutorial for MySQL Developers)。
客户号码格式
如上所述,客户号码的格式为 xxx-xxx-xxx
,其中 x
是十进制数字。每个段的范围是000
到999
。它基本上是一个 9 位整数,带有前导零和破折号作为千位分隔符。它不能高于 999-999-999
。
目前我们希望它受到更多限制,特别是格式 000-1xx-xxx
(在 000-100-000
和 000-199-999 之间)
)。但数据库中已经存在一些可以从 000-000-001
到 500-000-000
开始的数字。
不幸的是,它必须以这种格式存储,我无法更改它。
寻找最大值
我需要获取 000-100-000
到 000-199-999
范围内的最大数字,超出此范围的值必须被忽略。这就是我的问题出现的地方,因为正如之前所说,一些数字已经存在于上面。
最大值绝不是000-199-999
。否则将导致添加 000-200-000
并且下次调用最大值将再次为 000-199-999
,从而导致尝试插入 000-再次输入 200-000
。
增量如何工作
在 PHP 中可以这样完成:
$clientNum = "000-100-000";
$clientNum = str_replace("-", "", $clientNum);
$clientNum++;
$clientNum = implode("-", str_split(str_pad($clientNum, 9, "0", STR_PAD_LEFT), 3));
最终$clientNum
值为000-100-001
。
当初始编号为 000-120-015
时,上面的代码将生成 000-120-016
。溢出传播到下一个段,即 000-100-999
变为 000-101-000
。 999-999-999
无法递增。
开始的想法
在一个循环中,我想获取下一个可用的数字,检查该数字是否存在于数据库中,如果存在,则重做该循环,直到找到未使用的数字。我知道如何第一次检查它是否在数据库中,但我不知道如何进行循环。
有人知道如何做到这一点吗?
最佳答案
你可能想用 SQL 来解决这个问题,因为否则你需要两个事务(一个用于读取,一个用于写入),同时该数字可以被并发访问使用。
在 MySQL 中,您可以使用此 SQL 重新实现 PHP 代码:
INSERT(INSERT(LPAD(CAST(CAST(REPLACE(clientNum, '-', '') as UNSIGNED) + 1 as CHAR), 9, '0'), 7, 0, '-'), 4, 0, '-')
这会将 000-000-999
递增到 000-001-000
,将 999-999-999
递增到 100-000-000
(由 LPAD()
从 100-000-0000
截断)。我警告过你。
例如要预览下一个值是什么,请使用
SELECT INSERT(INSERT(LPAD(CAST(CAST(REPLACE(clientNum, '-', '') as UNSIGNED) + 1 as CHAR), 9, '0'), 7, 0, '-'), 4, 0, '-') FROM clients
如果你想在插入新行时使用它,则使用如下:
INSERT
INTO clients(clientNum, name)
SELECT
INSERT(INSERT(LPAD(CAST(
COALESCE(MAX(CAST(REPLACE(clientNum, '-', '') AS UNSIGNED)), 0) + 1
AS CHAR), 9, '0'), 7, 0, '-'), 4, 0, '-'),
'John Doe'
FROM clients
无论您使用什么 API 访问数据库,只要它是 MySQL 数据库,这都有效。数据库进行计算。但是,如果 clients
是临时表,则它不起作用,而我希望它不是。更多内容请参见下文。
另请参阅string functions , CAST() , COALESCE()和 INSERT … SELECT在 MySQL 手册中。
<小时/>后来您添加了允许的值范围为 000-100-000
到 000-199-999
。为了找到最大值,应忽略其他值。必须将 WHERE
子句添加到上面编写的 INSERT
的 SELECT
部分。
INSERT
INTO clients(clientNum, name)
SELECT
INSERT(INSERT(LPAD(CAST(
COALESCE(MAX(CAST(REPLACE(clientNum, '-', '') AS UNSIGNED)), 0) + 1
AS CHAR), 9, '0'), 7, 0, '-'), 4, 0, '-'),
'John Doe'
FROM clients
WHERE clientNum BETWEEN '000-100-000' AND '000-199-999'
<小时/>
然后你说我的解决方案不适合你和 proposed a supposed fix :
INSERT
INTO clients(clientNum, name)
VALUES
(SELECT
INSERT(INSERT(LPAD(CAST(
COALESCE(MAX(CAST(REPLACE(clientNum, '-', '') AS UNSIGNED)), 0) + 1
AS CHAR), 9, '0'), 7, 0, '-'), 4, 0, '-')
FROM clients AS tmptable
WHERE clientNum BETWEEN '000-100-000' AND '000-199-999'),
'John Doe'
这使用 subquery而不是 INSERT … SELECT
语法。
在 MySQL 中,表不能同时被子查询修改(在本例中为 INSERT)和读取。引用子查询手册:
In MySQL, you cannot modify a table and select from the same table in a subquery. This applies to statements such as
DELETE
,INSERT
,REPLACE
,UPDATE
, and (because subqueries can be used in theSET
clause)LOAD DATA INFILE
.
但是,您发现了 workaround using a temporary table 。定义别名(在本例中为 clients AS tmptable
)时会使用临时表,这样可以避免同时读取和写入同一个表。您使用临时表来存储原始表,描述解决方法的文章使用它来存储子查询的结果(我猜这更有效)。两种方法都有效。
此时我想指出,我的解决方案也应该有效(并且 works for me!),除了 clients
是临时表的不太可能的情况。我想我可以预期它不会是一个。引用 INSERT … SELECT
手册页:
When selecting from and inserting into a table at the same time, MySQL creates a temporary table to hold the rows from the
SELECT
and then inserts those rows into the target table. However, it remains true that you cannot useINSERT INTO t ... SELECT ... FROM t
whent
is aTEMPORARY
table, becauseTEMPORARY
tables cannot be referred to twice in the same statement (see Section C.5.7.2, “TEMPORARY
Table Problems”).
对于我来说,这明确表明我使用 INSERT … SELECT
的原始方法应该可行。
为了提供完整的答案,我将使用数据库轮询来满足您对 PHP 解决方案的原始请求。我必须再次补充一点,这当然不是一个好的解决方案。
您的 clientNum
列必须是唯一键。您需要重复以下步骤直至更新成功:
- 获取当前最大
clientNum
。 - 增加获得的值。
- 尝试插入行。
- 如果成功,则完成,否则丢弃
clientNum
最大值并循环。
由于违反上述唯一键约束,插入将失败。当与数据库的另一个连接在步骤 1. 和 3..
之间成功执行插入时,就会发生这种情况您应该使用 PDO::prepare()
在循环外准备语句然后execute它在循环中。 execute
方法的返回值表示成功 (true
) 或失败 (false
)。
这足以实现步骤 3。。步骤1.和2.包括获取结果
SELECT MAX(clientNum) FROM clients
并通过 Stephanus Yanaputra 提供的代码运行它。步骤4.是一个简单的循环条件,使用步骤3.中执行INSERT
查询的返回值。
关于php - 包含特殊数字格式的字符串列的 AUTO_INCRMENT 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20603197/