php - 包含特殊数字格式的字符串列的 AUTO_INCRMENT 实现

标签 php mysql sql

背景和目标

在表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 of NULL (recommended) or 0 into an indexed AUTO_INCREMENT column, the column is set to the next sequence value. Typically this is value+1, where value is the largest value for the column currently in the table. AUTO_INCREMENT sequences begin with 1.

所以我想找到下一个可用的数字并将其用作新插入的客户行的 clientNum 值。下一个可用的数字是当前最大的 clientNum 增量。

我正在 PHP 中使用 PDO 进行编码访问 MySQL 数据库(请参阅 PDO Tutorial for MySQL Developers)。

客户号码格式

如上所述,客户号码的格式为 xxx-xxx-xxx,其中 x 是十进制数字。每个段的范围是000999。它基本上是一个 9 位整数,带有前导零和破折号作为千位分隔符。它不能高于 999-999-999

目前我们希望它受到更多限制,特别是格式 000-1xx-xxx(在 000-100-000000-199-999 之间) )。但数据库中已经存在一些可以从 000-000-001500-000-000 开始的数字。

不幸的是,它必须以这种格式存储,我无法更改它。

寻找最大值

我需要获取 000-100-000000-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-000999-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-000000-199-999。为了找到最大值,应忽略其他值。必须将 WHERE 子句添加到上面编写的 INSERTSELECT 部分。

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 the SET 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 use INSERT INTO t ... SELECT ... FROM t when t is a TEMPORARY table, because TEMPORARY tables cannot be referred to twice in the same statement (see Section C.5.7.2, “TEMPORARY Table Problems”).

对于我来说,这明确表明我使用 INSERT … SELECT 的原始方法应该可行。

<小时/>

为了提供完整的答案,我将使用数据库轮询来满足您对 PHP 解决方案的原始请求。我必须再次补充一点,这当然不是一个好的解决方案。

您的 clientNum 列必须是唯一键。您需要重复以下步骤直至更新成功:

  1. 获取当前最大clientNum
  2. 增加获得的值。
  3. 尝试插入行。
  4. 如果成功,则完成,否则丢弃 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/

相关文章:

php - 按行号选择值

php - 连接多个表失败时更新

php - 尝试使用 VFSStream 测试文件系统操作

php - 在 flex 和 php 之间传递变量

MySQL 将逗号分隔值从一列移动到另一列

.net - System.Data.SQLConnection 的跟踪和诊断

java - 从多列中选择最小值

php - 像php一样在sql中爆炸

php - 在 PHP 中运行超时的 sql 查询或如何在 PHP 中停止已经开始的 sql 查询?

sql - 查询返回太多结果?