如何检查一个值是不是已经存在以避免重复?
Posted
技术标签:
【中文标题】如何检查一个值是不是已经存在以避免重复?【英文标题】:How to check if a value already exists to avoid duplicates?如何检查一个值是否已经存在以避免重复? 【发布时间】:2010-09-08 19:54:36 【问题描述】:我有一个 URL 表,我不想要任何重复的 URL。如何使用 php/mysql 检查给定的 URL 是否已经在表中?
【问题讨论】:
许多答案建议将UNIQUE
约束添加到“url”列。要记住的一件事是 MySQL 限制了键的大小。根据您在 URL 中允许的最大字节数,这可能是个问题。 The 5.6 reference manual states: "[A] 前缀对于 MyISAM 表最长可达 1000 字节,对于 InnoDB 表最长可达 767 字节。"
【参考方案1】:
如果您不想重复,可以执行以下操作:
添加唯一性约束 使用“REPLACE”或“INSERT ... ON DUPLICATE KEY UPDATE”语法如果多个用户可以向数据库插入数据,@Jeremy Ruten 建议的方法可能导致错误:在您执行检查后,有人可以向表中插入类似的数据。
【讨论】:
如果您要插入副本,那么INSERT IGNORE
应该比REPLACE
快。作为额外的好处,您可以知道它是否是新的,因为 MySQL 返回受影响的行数(使用ROW_COUNT()
或 API)。它也适用于多行插入。【参考方案2】:
要回答您最初的问题,检查是否存在重复的最简单方法是针对您要添加的内容运行 SQL 查询!
例如,如果您想在表 links
中检查 url http://www.example.com/
,那么您的查询将类似于
SELECT * FROM links WHERE url = 'http://www.example.com/';
你的 PHP 代码看起来像
$conn = mysql_connect('localhost', 'username', 'password');
if (!$conn)
die('Could not connect to database');
if(!mysql_select_db('mydb', $conn))
die('Could not select database mydb');
$result = mysql_query("SELECT * FROM links WHERE url = 'http://www.example.com/'", $conn);
if (!$result)
die('There was a problem executing the query');
$number_of_rows = mysql_num_rows($result);
if ($number_of_rows > 0)
die('This URL already exists in the database');
我已经在这里写了这个,所有连接到数据库等等。很可能你已经有一个到数据库的连接,所以你应该使用它而不是开始一个新的连接(替换@ mysql_query
命令中的 987654325@ 并删除与 mysql_connect
和 mysql_select_db
相关的内容)
当然,还有其他方法可以连接到数据库,例如 PDO,或使用 ORM 或类似方法,所以如果您已经在使用这些方法,那么这个答案可能不相关(而且可能有点超出范围在这里给出与此相关的答案!)
然而,MySQL 提供了许多方法来防止这种情况发生。
首先,您可以将字段标记为“唯一”。
假设我有一个表,我想在其中存储从我的网站链接到的所有 URL,以及上次访问它们的时间。
我的定义可能是这样的:-
CREATE TABLE links
(
url VARCHAR(255) NOT NULL,
last_visited TIMESTAMP
)
这将允许我一遍又一遍地添加相同的 URL,除非我编写了一些类似于上面的 PHP 代码来阻止这种情况发生。
但是,我的定义是否要更改为
CREATE TABLE links
(
url VARCHAR(255) NOT NULL,
last_visited TIMESTAMP,
PRIMARY KEY (url)
)
那么当我尝试两次插入相同的值时,这会使mysql抛出错误。
PHP 中的一个例子是
$result = mysql_query("INSERT INTO links (url, last_visited) VALUES ('http://www.example.com/', NOW()", $conn);
if (!$result)
die('Could not Insert Row 1');
$result2 = mysql_query("INSERT INTO links (url, last_visited) VALUES ('http://www.example.com/', NOW()", $conn);
if (!$result2)
die('Could not Insert Row 2');
如果你运行它,你会发现在第一次尝试时,脚本会因为注释Could not Insert Row 2
而死掉。但是,在随后的运行中,它会以 Could not Insert Row 1
消失。
这是因为 MySQL 知道 url 是表的 Primary Key。主键是该行的唯一标识符。大多数情况下,将行的唯一标识符设置为数字很有用。这是因为 MySQL 查找数字比查找文本更快。在 MySQL 中,键(尤其是主键)用于定义两个表之间的关系。例如,如果我们有一个用户表,我们可以将其定义为
CREATE TABLE users (
username VARCHAR(255) NOT NULL,
password VARCHAR(40) NOT NULL,
PRIMARY KEY (username)
)
但是,当我们想要存储有关用户发布的帖子的信息时,我们必须将用户名与该帖子一起存储,以识别该帖子属于该用户。
我已经提到 MySQL 在查找数字方面比字符串更快,所以这意味着我们会花时间在不必要的时候查找字符串。
为了解决这个问题,我们可以添加一个额外的列 user_id,并将其作为主键(这样在根据帖子查找用户记录时,我们可以更快地找到它)
CREATE TABLE users (
user_id INT(10) NOT NULL AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
password VARCHAR(40) NOT NULL,
PRIMARY KEY (`user_id`)
)
您会注意到我在这里还添加了一些新内容 - AUTO_INCREMENT。这基本上允许我们让该领域自己照顾自己。每次插入新行时,它会将前一个数字加 1,并将其存储起来,因此我们不必担心编号,只需让它自己完成即可。
所以,有了上面的表格,我们可以做类似的事情
INSERT INTO users (username, password) VALUES('Mez', 'd3571ce95af4dc281f142add33384abc5e574671');
然后
INSERT INTO users (username, password) VALUES('User', '988881adc9fc3655077dc2d4d757d480b5ea0e11');
当我们从数据库中选择记录时,我们得到以下信息:-
mysql> SELECT * FROM users;
+---------+----------+------------------------------------------+
| user_id | username | password |
+---------+----------+------------------------------------------+
| 1 | Mez | d3571ce95af4dc281f142add33384abc5e574671 |
| 2 | User | 988881adc9fc3655077dc2d4d757d480b5ea0e11 |
+---------+----------+------------------------------------------+
2 rows in set (0.00 sec)
但是,在这里 - 我们有一个问题 - 我们仍然可以添加另一个具有相同用户名的用户!显然,这是我们不想做的事情!
mysql> SELECT * FROM users;
+---------+----------+------------------------------------------+
| user_id | username | password |
+---------+----------+------------------------------------------+
| 1 | Mez | d3571ce95af4dc281f142add33384abc5e574671 |
| 2 | User | 988881adc9fc3655077dc2d4d757d480b5ea0e11 |
| 3 | Mez | d3571ce95af4dc281f142add33384abc5e574671 |
+---------+----------+------------------------------------------+
3 rows in set (0.00 sec)
让我们改变我们的表定义!
CREATE TABLE users (
user_id INT(10) NOT NULL AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
password VARCHAR(40) NOT NULL,
PRIMARY KEY (user_id),
UNIQUE KEY (username)
)
让我们看看当我们现在尝试插入同一个用户两次时会发生什么。
mysql> INSERT INTO users (username, password) VALUES('Mez', 'd3571ce95af4dc281f142add33384abc5e574671');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO users (username, password) VALUES('Mez', 'd3571ce95af4dc281f142add33384abc5e574671');
ERROR 1062 (23000): Duplicate entry 'Mez' for key 'username'
嘘!!当我们第二次尝试插入用户名时,我们现在得到一个错误。使用类似上面的方法,我们可以在 PHP 中检测到这一点。
现在,让我们回到我们的链接表,但有一个新的定义。
CREATE TABLE links
(
link_id INT(10) NOT NULL AUTO_INCREMENT,
url VARCHAR(255) NOT NULL,
last_visited TIMESTAMP,
PRIMARY KEY (link_id),
UNIQUE KEY (url)
)
让我们将“http://www.example.com”插入数据库。
INSERT INTO links (url, last_visited) VALUES ('http://www.example.com/', NOW());
如果我们再次尝试插入它......
ERROR 1062 (23000): Duplicate entry 'http://www.example.com/' for key 'url'
但是如果我们想更新上次访问的时间会发生什么?
好吧,我们可以用 PHP 做一些复杂的事情,像这样:-
$result = mysql_query("SELECT * FROM links WHERE url = 'http://www.example.com/'", $conn);
if (!$result)
die('There was a problem executing the query');
$number_of_rows = mysql_num_rows($result);
if ($number_of_rows > 0)
$result = mysql_query("UPDATE links SET last_visited = NOW() WHERE url = 'http://www.example.com/'", $conn);
if (!$result)
die('There was a problem updating the links table');
或者,甚至获取数据库中行的 id 并使用它来更新它。
$result = mysql_query("SELECT * FROM links WHERE url = 'http://www.example.com/'", $conn);
if (!$result)
die('There was a problem executing the query');
$number_of_rows = mysql_num_rows($result);
if ($number_of_rows > 0)
$row = mysql_fetch_assoc($result);
$result = mysql_query('UPDATE links SET last_visited = NOW() WHERE link_id = ' . intval($row['link_id'], $conn);
if (!$result)
die('There was a problem updating the links table');
但是,MySQL 有一个很好的内置功能,称为 REPLACE INTO
让我们看看它是如何工作的。
mysql> SELECT * FROM links;
+---------+-------------------------+---------------------+
| link_id | url | last_visited |
+---------+-------------------------+---------------------+
| 1 | http://www.example.com/ | 2011-08-19 23:48:03 |
+---------+-------------------------+---------------------+
1 row in set (0.00 sec)
mysql> INSERT INTO links (url, last_visited) VALUES ('http://www.example.com/', NOW());
ERROR 1062 (23000): Duplicate entry 'http://www.example.com/' for key 'url'
mysql> REPLACE INTO links (url, last_visited) VALUES ('http://www.example.com/', NOW());
Query OK, 2 rows affected (0.00 sec)
mysql> SELECT * FROM links;
+---------+-------------------------+---------------------+
| link_id | url | last_visited |
+---------+-------------------------+---------------------+
| 2 | http://www.example.com/ | 2011-08-19 23:55:55 |
+---------+-------------------------+---------------------+
1 row in set (0.00 sec)
注意,使用REPLACE INTO
时,更新了last_visited时间,并没有抛出错误!
这是因为 MySQL 检测到您正在尝试替换行。它知道您想要的行,因为您已将 url 设置为唯一。 MySQL 通过使用您传入的应该是唯一的位(在本例中为 url)并为该行更新其他值来计算要替换的行。它还更新了 link_id - 这有点出乎意料! (事实上,直到我看到它发生时,我才意识到会发生这种情况!)
但是如果你想添加一个新的 URL 怎么办?好吧,REPLACE INTO
如果找不到匹配的唯一行,它会很高兴地插入一个新行!
mysql> REPLACE INTO links (url, last_visited) VALUES ('http://www.***.com/', NOW());
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM links;
+---------+-------------------------------+---------------------+
| link_id | url | last_visited |
+---------+-------------------------------+---------------------+
| 2 | http://www.example.com/ | 2011-08-20 00:00:07 |
| 3 | http://www.***.com/ | 2011-08-20 00:01:22 |
+---------+-------------------------------+---------------------+
2 rows in set (0.00 sec)
我希望这能回答您的问题,并为您提供有关 MySQL 工作原理的更多信息!
【讨论】:
Eep - 我没有意识到这篇文章有多长! 我认为它实际上是从上一个问题开始的。REPLACE INTO
更新 link_id 的原因是因为它实际上执行了 DELETE
和 INSERT
,而不是 UPDATE
- 这很可怕。考虑改用INSERT ON DUPLICATE KEY UPDATE
。
这个答案值得拥有自己的博文。
必须是 SO 上最长的答案之一。 :o【参考方案3】:
您是否只关心完全相同的字符串的 URL .. 如果是这样,其他答案中有很多好的建议。还是您还需要担心封圣?
例如:http://google.com 和 http://go%4fgle.com 是完全相同的 URL,但任何仅数据库技术都允许重复。如果这是一个问题,您应该预处理要解析的 URL 和字符转义序列。
根据 URL 的来源,您还必须担心参数以及它们在您的应用程序中是否重要。
【讨论】:
【参考方案4】:首先,准备数据库。
域名不区分大小写,但您必须假设 URL 的其余部分是。 (并非所有 Web 服务器都尊重 URL 中的大小写,但大多数都这样做,而且您不能通过查看轻易分辨。) 假设您需要存储多个域名,请使用区分大小写的排序规则。 如果您决定将 URL 存储在两列中(一列用于域名,另一列用于资源定位器),请考虑对域名使用不区分大小写的排序规则,对资源定位器使用区分大小写的排序规则.如果我是你,我会同时测试两种方式(一列中的 URL 与两列中的 URL)。 在 URL 列上设置一个 UNIQUE 约束。或者在这对列上,如果您将域名和资源定位器存储在单独的列中,如UNIQUE (url, resource_locator)
。
使用 CHECK() 约束将编码的 URL 保留在数据库之外。这个 CHECK() 约束对于防止坏数据通过大容量复制或通过 SQL shell 进入是必不可少的。
其次,准备网址。
域名不区分大小写。如果您将完整的 URL 存储在一列中,请将所有 URL 上的域名小写。但请注意,有些语言的大写字母没有对应的小写字母。考虑修剪尾随字符。例如,来自 amazon.com 的这两个 URL 指向同一个产品。您可能想要存储第二个版本,而不是第一个。
http://www.amazon.com/Systemantics-Systems-Work-Especially-They/dp/070450331X/ref=sr_1_1?ie=UTF8&qid=1313583998&sr=8-1
http://www.amazon.com/Systemantics-Systems-Work-Especially-They/dp/070450331X
解码编码的 URL。 (请参阅php's urldecode() function。仔细注意它的缺点,如该页的 cmets 中所述。)就个人而言,我宁愿在数据库中而不是在客户端代码中处理这些类型的转换。这将涉及撤销对表和视图的权限,并只允许通过存储过程进行插入和更新;存储过程处理将 URL 转换为规范形式的所有字符串操作。但是,当您尝试这样做时,请注意性能。 CHECK() 约束(见上文)是您的安全网。
第三,如果您只插入 URL,先不要测试它的存在。相反,如果值已经存在,请尝试插入并捕获您将得到的错误。对于每个新 URL,测试和插入都会命中数据库两次。插入和陷阱只命中数据库一次。请注意,插入并陷阱与插入并忽略错误不同。只有一个特定错误意味着您违反了唯一约束;其他错误意味着还有其他问题。
另一方面,如果您要在同一行中插入 URL 以及其他一些数据,则需要提前决定是否处理重复的 url
删除旧行并插入新行(参见 MySQL 的 REPLACE extension to SQL) 更新现有值(参见ON DUPLICATE KEY UPDATE) 忽略问题 要求用户采取进一步措施REPLACE 消除了捕获重复键错误的需要,但如果存在外键引用,它可能会产生不幸的副作用。
【讨论】:
如何将 urldecode() 添加到 URL 以解决 Rob Walker 回答中提出的问题?或者至少是它的域名部分 PHP 在 dbms 之外,这意味着可能插入 URL 的每个其他应用程序都必须记住要通过您的 PHP 应用程序或开发具有相同行为的代码。但是在 db 外部使用 urldecode() 并在 db 内部使用 CHECK() 约束是一种可防御的、依赖于应用程序的方法。 OP 确实说 PHP/MySQL,但是,这也可以使用存储过程来完成(例如 snippets.dzone.com/posts/show/7746) 是的,这就是我所说的“我宁愿在数据库中而不是在客户端代码中处理这些类型的更改”时的意思。【参考方案5】:为了保证唯一性,您需要添加唯一性约束。假设您的表名是“urls”并且列名是“url”,您可以使用这个 alter table 命令添加唯一约束:
alter table urls add constraint unique_url unique (url);
如果您的表中已经有重复的 url,alter 表可能会失败(谁知道 MySQL)。
【讨论】:
【参考方案6】:简单的 SQL 解决方案需要一个唯一的字段;逻辑解决方案没有。
您应该规范化您的网址以确保没有重复。 PHP 中的函数,例如 strtolower() 和 urldecode() 或 rawurldecode()。
假设:你的表名是'websites',你的url的列名是'url',和url关联的任意数据在'data'列中。
逻辑解决方案
SELECT COUNT(*) AS UrlResults FROM websites WHERE url='http://www.domain.com'
使用 SQL 或 PHP 中的 if 语句测试上一个查询,以确保在继续执行 INSERT 语句之前它为 0。
简单的 SQL 语句
场景 1:您的数据库是先到先得的表,您不希望将来有重复的条目。
ALTER TABLE websites ADD UNIQUE (url)
如果该列中已经存在 url 值,这将阻止任何条目进入数据库。
场景 2:您想要每个网址的最新信息,并且不想重复内容。这种情况有两种解决方案。 (这些解决方案还要求 'url' 是唯一的,因此场景 1 中的解决方案也需要执行。)
REPLACE INTO websites (url, data) VALUES ('http://www.domain.com', 'random data')
如果在所有情况下都存在一行后跟一个 INSERT,这将触发 DELETE 操作,因此请小心使用 ON DELETE 声明。
INSERT INTO websites (url, data) VALUES ('http://www.domain.com', 'random data')
ON DUPLICATE KEY UPDATE data='random data'
如果存在行,则触发 UPDATE 操作,如果不存在,则触发 INSERT。
【讨论】:
【参考方案7】:在考虑解决此问题时,您需要首先定义“重复 URL”对您的项目意味着什么。这将确定在将 URL 添加到数据库之前如何canonicalize。
至少有两种定义:
-
如果两个 URL 代表相同的资源,对生成相应内容的相应 Web 服务一无所知,则认为它们是重复的。一些考虑因素包括:
URL 的方案和域名部分不区分大小写,因此HTTP://WWW.***.COM/ 与http://www.***.com/ 相同。
如果一个 URL 指定了一个端口,但它是该方案的常规端口,并且它们在其他方面是等效的,那么它们是相同的(http://www.***.com/ 和 http://www.***.com:80/)。
如果查询字符串中的参数是简单的重新排列并且参数名称都不同,那么它们是相同的;例如http://authority/?a=test&b=test 和 http://authority/?b=test&a=test。请注意,根据第一个相同性定义,http://authority/?a%5B%5D=test1&a%5B%5D=test2 与 http://authority/?a%5B%5D=test2&a%5B%5D=test1 不同。
如果方案是 HTTP 或 HTTPS,则可以删除 URL 的哈希部分,因为这部分 URL 不会发送到 Web 服务器。
可以扩展缩短的 IPv6 地址。
如果缺少,则仅在授权后附加正斜杠。
Unicode 规范化改变了引用的资源;例如你不能断定http://google.com/?q=%C3%84(
%C3%84
代表UTF-8中的'Ä')与http://google.com/?q=A%CC%88相同(%CC%88
代表U+0308,COMBINING DIAERESIS)。
如果方案是 HTTP 或 HTTPS,如果两个 URL 相同,则不能简单地删除一个 URL 权限中的“www.
”,因为域名文本作为Host
的值发送HTTP 标头,一些 Web 服务器使用虚拟主机根据此标头发回不同的内容。更一般地说,即使域名解析到相同的 IP 地址,也不能断定引用的资源相同。
应用基本 URL 规范化(例如小写方案和域名,提供默认端口,按参数名称稳定排序查询参数,在 HTTP 和 HTTPS 的情况下删除哈希部分,...),并且考虑到网络服务的知识。也许您会假设所有 Web 服务都足够智能以规范化 Unicode 输入(例如 Wikipedia),因此您可以申请 Unicode Normalization Form Canonical Composition (NFC)。您将从所有 Stack Overflow URL 中删除“www.
”。您可以使用 PostRank 的 postrank-uri 代码(移植到 PHP)来删除各种不必要的 URL(例如 &utm_source=...
)。
定义 1 导致了一个稳定的解决方案(即没有可以执行的进一步规范化,并且 URL 的规范化不会改变)。定义 2,我认为是人类对 URL 规范化的定义,导致规范化例程可以在不同的时间产生不同的结果。
无论您选择哪种定义,我建议您为方案、登录名、主机、端口和路径部分使用单独的列。这将允许您智能地使用索引。 scheme 和 host 的列可以使用字符排序规则(所有字符排序规则在 MySQL 中都是不区分大小写的),但是 login 和 path 的列需要使用二进制的、不区分大小写的排序规则。此外,如果您使用定义 2,则需要保留原始方案、权限和路径部分,因为可能会不时添加或删除某些规范化规则。
编辑:以下是示例表定义:
CREATE TABLE `urls1` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`scheme` VARCHAR(20) NOT NULL,
`canonical_login` VARCHAR(100) DEFAULT NULL COLLATE 'utf8mb4_bin',
`canonical_host` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci', /* the "ci" stands for case-insensitive. Also, we want 'utf8mb4_unicode_ci'
rather than 'utf8mb4_general_ci' because 'utf8mb4_general_ci' treats accented characters as equivalent. */
`port` INT UNSIGNED,
`canonical_path` VARCHAR(4096) NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`),
INDEX (`canonical_host`(10), `scheme`)
) ENGINE = 'InnoDB';
CREATE TABLE `urls2` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`canonical_scheme` VARCHAR(20) NOT NULL,
`canonical_login` VARCHAR(100) DEFAULT NULL COLLATE 'utf8mb4_bin',
`canonical_host` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`port` INT UNSIGNED,
`canonical_path` VARCHAR(4096) NOT NULL COLLATE 'utf8mb4_bin',
`orig_scheme` VARCHAR(20) NOT NULL,
`orig_login` VARCHAR(100) DEFAULT NULL COLLATE 'utf8mb4_bin',
`orig_host` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`orig_path` VARCHAR(4096) NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`),
INDEX (`canonical_host`(10), `canonical_scheme`),
INDEX (`orig_host`(10), `orig_scheme`)
) ENGINE = 'InnoDB';
表 `urls1` 用于存储根据定义 1 的规范 URL。表 `urls2` 用于存储根据定义 2 的规范 URL。
不幸的是,您将无法在元组(`scheme`/`canonical_scheme`、`canonical_login`、`canonical_host`、`port`、`canonical_path`)上指定 UNIQUE
约束,因为 MySQL 限制了InnoDB 键为 767 字节。
【讨论】:
【参考方案8】:我不知道 MySQL 的语法,但您需要做的就是用 IF 语句包装您的 INSERT,该语句将查询表并查看具有给定 url 的记录是否存在,如果它存在 - 不要插入新纪录。
如果是 MSSQL,你可以这样做:
IF NOT EXISTS (SELECT 1 FROM YOURTABLE WHERE URL = 'URL')
INSERT INTO YOURTABLE (...) VALUES (...)
【讨论】:
【参考方案9】:如果您想将 url 插入表中,但只有那些不存在的,您可以在列上添加 UNIQUE 约束,并在 INSERT 查询中添加 IGNORE,这样您就不会收到错误。
示例:INSERT IGNORE INTO urls
SET url = 'url-to-insert'
【讨论】:
【参考方案10】:首先要做的事情。如果您还没有创建表,或者您创建了一个表但其中没有数据,那么您需要添加一个唯一约束或唯一索引。有关在索引或约束之间进行选择的更多信息,请参见文章末尾。但它们都完成了同样的事情,强制列只包含唯一值。
要在此列上创建具有唯一索引的表,您可以使用。
CREATE TABLE MyURLTable(
ID INTEGER NOT NULL AUTO_INCREMENT
,URL VARCHAR(512)
,PRIMARY KEY(ID)
,UNIQUE INDEX IDX_URL(URL)
);
如果您只想要一个唯一约束,并且该表上没有索引,则可以使用
CREATE TABLE MyURLTable(
ID INTEGER NOT NULL AUTO_INCREMENT
,URL VARCHAR(512)
,PRIMARY KEY(ID)
,CONSTRAINT UNIQUE UNIQUE_URL(URL)
);
现在,如果您已经有一个表,并且其中没有数据,那么您可以使用以下代码之一将索引或约束添加到表中。
ALTER TABLE MyURLTable
ADD UNIQUE INDEX IDX_URL(URL);
ALTER TABLE MyURLTable
ADD CONSTRAINT UNIQUE UNIQUE_URL(URL);
现在,您可能已经有了一个包含一些数据的表。在这种情况下,您可能已经有一些重复的数据。您可以尝试创建上面显示的约束或索引,如果您已经有重复数据,它将失败。如果您没有重复数据,很好,如果有,您将不得不删除重复数据。您可以使用以下查询查看大量带有重复的网址。
SELECT URL,COUNT(*),MIN(ID)
FROM MyURLTable
GROUP BY URL
HAVING COUNT(*) > 1;
要删除重复的行并保留一个,请执行以下操作:
DELETE RemoveRecords
FROM MyURLTable As RemoveRecords
LEFT JOIN
(
SELECT MIN(ID) AS ID
FROM MyURLTable
GROUP BY URL
HAVING COUNT(*) > 1
UNION
SELECT ID
FROM MyURLTable
GROUP BY URL
HAVING COUNT(*) = 1
) AS KeepRecords
ON RemoveRecords.ID = KeepRecords.ID
WHERE KeepRecords.ID IS NULL;
现在您已经删除了所有记录,您可以继续创建索引或约束。现在,如果你想在你的数据库中插入一个值,你应该使用类似的东西。
INSERT IGNORE INTO MyURLTable(URL)
VALUES('http://www.example.com');
这将尝试进行插入,如果找到重复项,则不会发生任何事情。现在,假设您有其他列,您可以这样做。
INSERT INTO MyURLTable(URL,Visits)
VALUES('http://www.example.com',1)
ON DUPLICATE KEY UPDATE Visits=Visits+1;
这看起来会尝试插入值,如果找到 URL,那么它将通过增加访问次数计数器来更新记录。当然,您总是可以做一个普通的旧插入,并在您的 PHP 代码中处理由此产生的错误。现在,至于是否应该使用约束或索引,这取决于很多因素。索引可以加快查找速度,因此随着表变大,您的性能会更好,但是存储索引会占用额外的空间。索引通常也会使插入和更新花费更长的时间,因为它必须更新索引。但是,由于必须以任何一种方式查找该值,以强制执行唯一性,在这种情况下,无论如何只有索引可能会更快。至于与性能相关的任何事情,答案是尝试这两个选项并分析结果,看看哪个最适合您的情况。
【讨论】:
【参考方案11】:如果您只想回答“是”或“否”,则此语法应该可以为您提供最佳性能。
select if(exists (select url from urls where url = 'http://asdf.com'), 1, 0) from dual
【讨论】:
【参考方案12】:如果你只是想确保没有重复,那么在 url 字段中添加一个唯一索引,这样就不需要显式检查 url 是否存在,只需正常插入,如果它已经存在则插入将因重复键错误而失败。
【讨论】:
【参考方案13】:答案取决于您是否想知道何时尝试输入具有重复字段的记录。如果您不在乎,请使用“INSERT... ON DUPLICATE KEY”语法,因为这将使您的尝试悄悄成功,而不会创建重复。
另一方面,如果您想知道此类事件何时发生并阻止它,那么您应该使用唯一键约束,这将导致尝试的插入/更新失败并出现有意义的错误。
【讨论】:
【参考方案14】:$url = "http://www.scroogle.com";
$query = "SELECT `id` FROM `urls` WHERE `url` = '$url' ";
$resultdb = mysql_query($query) or die(mysql_error());
list($idtemp) = mysql_fetch_array($resultdb) ;
if(empty($idtemp)) // if $idtemp is empty the url doesn't exist and we go ahead and insert it into the db.
mysql_query("INSERT INTO urls (`url` ) VALUES('$url') ") or die (mysql_error());
else
//do something else if the url already exists in the DB
【讨论】:
【参考方案15】:将列设为primary key
【讨论】:
【参考方案16】:您可以使用自联接来定位(和删除)。您的表格有一些 URL 和一些 PK(我们知道 PK 不是 URL,否则不允许您有重复项)
SELECT
*
FROM
yourTable a
JOIN
yourTable b -- Join the same table
ON b.[URL] = a.[URL] -- where the URL's match
AND b.[PK] <> b.[PK] -- but the PK's are different
这将返回所有具有重复 URL 的行。
不过,假设您只想选择 重复项 并排除原件.... 那么您需要决定什么是原件。出于这个答案的目的,我们假设最低 PK 是“原始”
您需要做的就是将以下子句添加到上述查询中:
WHERE
a.[PK] NOT IN (
SELECT
TOP 1 c.[PK] -- Only grabbing the original!
FROM
yourTable c
WHERE
c.[URL] = a.[URL] -- has the same URL
ORDER BY
c.[PK] ASC) -- sort it by whatever your criterion is for "original"
现在您有一组所有非原始重复行。您可以轻松地从该结果集中执行DELETE
或您喜欢的任何内容。
请注意,这种方法可能效率低下,部分原因是 mySQL 并不总是能很好地处理 IN
,但我从 OP 了解到这是对表的“清理”,并不总是检查。
如果你想在INSERT
时间检查一个值是否已经存在,你可以运行这样的东西
SELECT
1
WHERE
EXISTS (SELECT * FROM yourTable WHERE [URL] = 'testValue')
如果你得到一个结果,那么你可以断定该值已经存在于你的数据库中至少一次。
【讨论】:
【参考方案17】:你可以这样查询:
SELECT url FROM urls WHERE url = 'http://asdf.com' LIMIT 1
然后检查mysql_num_rows() == 1是否存在。
【讨论】:
从插入时开始检查时,如何防止另一个连接进入具有该值的行?以上是关于如何检查一个值是不是已经存在以避免重复?的主要内容,如果未能解决你的问题,请参考以下文章