在表格clients
中,我有一个clientNum CHAR(11) NOT NULL
列UNIQUE KEY
约束。它包含xxx-xxx-xxx
格式的客户编号,其中x
是十进制数字。有关格式的更多详细信息,请参阅下文。
我想为此列实现AUTO_INCREMENT
之类的内容,以便每个客户端自动计算其编号。来自MySQL CREATE TABLE
docs:
整数或浮点列可以具有附加属性
AUTO_INCREMENT
。将值NULL
(推荐)或0
插入索引AUTO_INCREMENT
列时,该列将设置为下一个序列值。通常,这是value+1
,其中value
是当前表中列的最大值。AUTO_INCREMENT
序列以1
开头。
所以我想找到下一个可用的数字,并将其用作新插入的客户行的clientNum
值。可用的下一个数字是当前最大值clientNum
递增。
我使用PDO编写PHP代码来访问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
无法递增。
在循环中我想获得下一个数字,检查数据库中是否存在该数字,如果是,则重做该循环直到找到未使用的数字。我知道如何第一次检查它是否在数据库中,但我不知道如何进行循环。
有没有人知道这样做的方法?
答案 0 :(得分:1)
您可能希望在SQL中解决此问题,因为否则您需要两个事务(一个用于读取,一个用于写入),同时该数字可以由并发访问使用。
在MySQL中,您可以使用PHP代码的SQL重新实现:
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
(从100-000-0000
LPAD()
截断。我警告过你。
E.g。要预览下一个值是什么,请使用
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
是一个临时表,它不起作用,我希望它不是。更多关于以下内容。
另请参阅MySQL手册中的string functions,CAST(),COALESCE()和INSERT … SELECT。
稍后您添加了允许的值范围为000-100-000
到000-199-999
。为了找到最大值,应忽略其他值。必须在WHERE
上面写的SELECT
部分添加INSERT
子句。
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
)并且同时由子查询读取。引用子查询手册:
在MySQL中,您无法修改表并从子查询中的同一个表中进行选择。这适用于
DELETE
,INSERT
,REPLACE
,UPDATE
等语句,以及(因为子查询可以在SET
子句中使用){{3} }。
但是,您找到了LOAD DATA INFILE
。当定义别名(在这种情况下为clients AS tmptable
)时,将使用临时表,该别名同时避免读取和写入同一个表。您使用临时表来存储原始表,描述变通方法的文章使用它来存储子查询的结果(我猜这更有效)。这两种方法都有效。
此时我想指出我的解决方案应该有效(和workaround using a temporary table!),除了clients
是临时表的不可能的情况。我想我可以期待它不是一个。引用INSERT … SELECT
手册页:
当同时从表中选择并插入表时,MySQL会创建一个临时表来保存works for me中的行,然后将这些行插入到目标表中。但是,当
INSERT INTO t ... SELECT ... FROM t
是t
表时,您仍然无法使用TEMPORARY
,因为TEMPORARY
表在同一语句中不能被引用两次(请参阅{{3 }})。
至于我,这明确地说我使用INSERT … SELECT
的原始方法应该有效。
为了提供完整的答案,我将使用数据库轮询解决您对PHP解决方案的原始请求。我必须补充一点,这肯定不是一个好的解决方案。
您的clientNum
列必须是唯一键。您需要重复以下步骤,直到成功更新:
clientNum
。clientNum
最大值抛出并循环。由于违反上述唯一键约束,插入将失败。当数据库的另一个连接在步骤 1。和 3 之间同时成功执行插入时,会发生这种情况。
您应该使用SELECT
在循环外准备语句,然后在循环中使用Section C.5.7.2, “TEMPORARY
Table Problems”。 execute
方法的返回值表示成功(true
)或失败(false
)。
这足以实现步骤 3。。步骤 1。和 2。包括获取
的结果SELECT MAX(clientNum) FROM clients
并通过PDO::prepare()
提供的代码运行它。步骤 4。是一个简单的循环条件,使用步骤 3中执行INSERT
查询的返回值。。
答案 1 :(得分:1)
<?php
mysql_connect(....);
mysql_select_db($db_name);
$res=mysql_query("select ClientNum from ClientTable");
$name_arr=array();
while($row=mysql_fetch_array($res))
{
foreach($row as $name)
$name_arr[]=$name;
}
$clientNum="000-000-000";
while(true){
$clientNum = str_replace("-", "", $clientNum);
$clientNum++;
if($clientNum>999999999)
{
echo("No mismatch found");
break;
}
$clientNum = implode("-", str_split($clientNum, 3));
if(!in_array($clientNum, $name_arr))
{
echo "The first unmatched clientNum is:".$clientNum;//This is what you want.
break;
}
}
?>
while
循环之外,使得时间复杂化程度降低。由于使用数组而不是多次执行查询本身,时间复杂度降低了,因为在数组中搜索比在数据库中搜索的时间复杂得多。答案 2 :(得分:0)
基于您提供的功能的简单解决方案。 将函数名称 rawr()更改为您喜欢的任何命名。 (我找不到最好的名字,最后使用了一些乱码名称lol)。
function rawr($in)
{
$num = str_replace("-", "", $in);
$num++;
// Convert back
$str = (string) $num;
// Add Leading 0
while(strlen($str) < 9)
{
$str = "0" . $str;
}
echo $str . "<br />";
$final = substr($str,0,3) . "-" . substr($str,3,3) . "-" . substr($str,6,3);
return $final;
}
要测试它,请尝试以下代码:
echo rawr(0);
echo "<br />";
echo rawr("000-000-000");
echo "<br />";
echo rawr("012-345-678");
echo "<br />";
echo rawr("123-456-789");
echo "<br />";
这将提供您想要的输出。但是,您必须自己编写代码才能测试数据库。在我看来,这不是解决问题的最佳方法,但它应该有效:)