【问题标题】:create mysql row with not really unique keys based on some other rows基于其他一些行创建具有不是真正唯一键的mysql行
【发布时间】:2015-12-25 19:22:26
【问题描述】:

数据库示例:

| country | animal | size   | x_id* |
|---------+--------+--------+-------|
|  777    | 1001   | small  |   1   |
|  777    | 2002   | medium |   2   |
|  777    | 7007   | medium |   3   |
|  777    | 7007   | large  |   4   |
|  42     | 1001   | small  |   1   |
|  42     | 2002   | medium |   2   |
|  42     | 7007   | large  |   4   |

我需要根据(动物,大小)中的条目连续生成x_id,如果x_id 组合x_id 存在,请再次使用它。

目前我使用以下 PHP 脚本执行此操作,但在大型 db 表上它非常慢。

query("UPDATE myTable SET x_id = -1");

$i = $j;
$c = array();
$q = query("
    SELECT animal, size
      FROM myTable
     WHERE x_id = -1
  GROUP BY animal, size");

while($r = fetch_array($q)) {

  $hk = $r['animal'] . '-' . $r['size'];

  if( !isset( $c[$hk] ) ) $c[$hk] = $i++;

  query("
      UPDATE myTable
       SET x_id = {$c[$hk]}
     WHERE animal = '".$r['animal']."'
       AND size = '".$r['size']."'
       AND x_id = -1");

}

有没有办法将PHP脚本转换成一两个mysql命令?

编辑:

CREATE TABLE `myTable` (
`country` int(10) unsigned NOT NULL DEFAULT '1', -- country
`animal` int(3) NOT NULL,
`size` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`lang_id` tinyint(4) NOT NULL DEFAULT '1',
`x_id` int(10) NOT NULL,
KEY `country` (`country`),
KEY `x_id` (`x_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

【问题讨论】:

  • 创建一个容纳动物和大小的表格。使用它来支持有问题的表格。这是一个提示
  • @Drew 我知道这是正确的方法,但是我无法更改此数据库表,复杂性远高于这个小示例
  • 我可以帮助你,但它会令人难以置信的混乱,你说你不能改变架构。所以我。为什么吹20分钟就可以了。这对任何人都没有用:P
  • 我无法更改行或拆分表格,但我可以添加索引或其他内容
  • 感谢 Drew、Sebas 和 Mihai - 干得好!

标签: php mysql sql


【解决方案1】:
UPDATE myTable m
    JOIN (
        SELECT animal, size, @newid := @newid + 1 AS x_id
        FROM myTable a
            CROSS JOIN (SELECT @newid := 0) b
        WHERE x_id = -1
        GROUP BY animal, size
    ) t ON m.animal = t.animal AND m.size = t.size
SET m.x_id = t.x_id
;

http://sqlfiddle.com/#!9/5525ba/1

不需要子查询中的 group by。它会产生无用的开销。如果足够快,就这样吧,否则我们可以使用 distinct+another 子查询来代替。

【讨论】:

  • 这个命令运行良好,这正是我想要的,但它运行了 30-40 分钟,而我的 php 脚本运行了一个多小时
  • 受影响的行:18k(查询耗时 5.9308 秒),索引为@Mihai - 谢谢!!
  • 根据您的评论,我没有建议任何与 ddl 相关的内容,以粗体 i can't change this db table。但我很高兴你把它解决了。干杯
【解决方案2】:

用户变量很尴尬,但应该可以解决问题,在我的机器上测试过

CREATE TABLE t
( animal VARCHAR(20),
 size VARCHAR(20),
x_id INT);

 INSERT INTO T(animal,size) VALUES('crocodile','small'),
   ('elephant','medium'),
 ('giraffe','medium'),
 ('giraffe','large'),
 ('crocodile','small'),
 ('elephant','medium'),
 ('giraffe','large');




 UPDATE t  RIGHT JOIN
 (SELECT animal,size,
MIN(CASE WHEN @var:=CONCAT(animal,size) THEN @id ELSE @id:=@id+1  END)id
FROM t,
(SELECT  @var:=CONCAT(animal,size) FROM t)x ,
                      (SELECT @id:=0)y
                     GROUP BY animal,size)q
                     ON t.animal=q.animal AND t.size=q.size
                     SET x_id=q.id

结果

"animal"    "size"     "x_id"
"crocodile" "small"     "1"
"elephant"  "medium"    "2"
"giraffe"   "medium"    "3"
"giraffe"   "large"     "4"
"crocodile" "small"     "1"
"elephant"  "medium"    "2"
"giraffe"   "large"     "4"

您希望添加这些索引以(很多)更快地访问

ALTER TABLE `yourtable` ADD INDEX `as_idx` (`animal`,`size`);
ALTER TABLE `yourtable` ADD INDEX `id_idx` (`x_id`);

【讨论】:

  • 它在我杀死这个进程之前运行了 40 多分钟,这并不比我的 php-script 超过 18k 条目好,感谢您的时间
  • @HQ5 在你的问题中显示表定义,这个查询在 18 k 行上肯定不会超过一秒 SHOW CREATE TABLE yourtable
  • 表定义是什么意思?
  • @HQ5 运行我在评论中发布的查询,使用该查询的结果编辑您的问题
  • 那很复杂,这里不是确切的例子
【解决方案3】:

这是一个概念。如果有用的话,把它带入你的世界。

架构

create table AnimalSize
(   id int auto_increment primary key,
    animal varchar(100) not null,
    size varchar(100) not null,
    unique key(animal,size) -- this is critical, no dupes
);

create table CountryAnimalSize
(   id int auto_increment primary key,
    country varchar(100) not null,
    animal varchar(100) not null,
    size varchar(100) not null,
    xid int not null -- USE THE id achieved thru use of AnimalSize table
);

一些查询

-- truncate table animalsize; -- clobber and reset auto_increment back to 1
insert ignore AnimalSize(animal,size) values ('snake','small'); -- id=1
select last_insert_id(); -- 1
insert ignore AnimalSize(animal,size) values ('snake','small'); -- no real insert but creates id GAP (ie blows slot 2)
select last_insert_id(); -- 1
insert ignore AnimalSize(animal,size) values ('snake','small'); -- no real insert but creates id GAP (ie blows slot 3)
select last_insert_id(); -- 1
insert ignore AnimalSize(animal,size) values ('frog','medium'); -- id=4
select last_insert_id(); -- 4
insert ignore AnimalSize(animal,size) values ('snake','small'); -- no real insert but creates id GAP (ie blows slot 3)
select last_insert_id(); -- 4

注意:insert ignore 说做,忽略它可能会死的事实。在我们的例子中,它会因为唯一键而失败(这很好)。一般来说,除非您知道自己在做什么,否则不要使用insert ignore

通常认为它与insert on duplicate key update (IODKU) 调用有关。或者我应该说考虑过,例如,我该如何解决当前的困境。但是,在这种情况下,(IODKU)将是一个延伸。但是,请将两者都放在您的工具箱中以寻求解决方案。

insert ignore 触发后,您知道,无论哪种方式,该行都在那里。

忘记INNODB GAP方面,上面的建议是,如果在插入忽略之前该行已经存在,那么

对于id,您不能依赖last_insert_id()

所以在触发插入忽略后,去获取你知道必须在那里的 id。在随后针对CountryAnimalSize的调用中使用它

继续沿着这条推理路线,将 CountryAnimalSize 表插入该行可能已经存在或不存在的位置。

没有理由在这里正式确定解决方案,因为正如您所说,这些甚至都不是您在问题中的表格。

另外,返回INNODB GAP。谷歌那个。弄清楚你是否可以忍受创造的差距。

大多数人都有更大的鱼要炸,以保持 id 的紧密和无缝。

其他人(阅读:强迫症)被感知到的差距问题如此消耗,以至于他们为此付出了很多天。

因此,这些是一般性的 cmets,旨在帮助更广泛的受众,而不是回答您的问题,正如您所说,这甚至不是您的架构。

【讨论】:

  • 现在是圣诞节。我想我会给你一些东西。希望不是一块煤。
  • 圣诞快乐,你的工作得到了 +1,但这不是我问题的解决方案
【解决方案4】:

您可以像这样使用x_id

CONCAT(`animal`, '_', `size`) AS `x_id`

然后将其与x_id 进行比较,这样你会得到类似的结果:

+---------+-----------+--------+------------------+
| country | animal    | size   | x_id*            |
+---------+-----------+--------+------------------+
| africa  | crocodile | small  | crocodile_small  |
| africa  | elephant  | medium | elephant_medium  |
| africa  | giraffe   | medium | giraffe_medium   |
| africa  | giraffe   | large  | giraffe_large    |
| europe  | crocodile | small  | crocodile_small  |
| europe  | elephant  | medium | elephant_medium  |
| europe  | giraffe   | large  | giraffe_large    |
+---------+-----------+--------+------------------+

【讨论】:

  • 可以只使用视图,也可以只查询,不需要存储。
  • @user5717193 它需要一个关系表。你准备好了吗?
  • 我知道关系表是正确的方法,但现在为时已晚。 ://
  • 我认为像 UPDATE .. INNER JOIN .. SET ..IFNULL(b.x_id, MAX(c.x_id)) 这样的解决方案可以解决
【解决方案5】:

正如我所见,您已经在使用MyISAM 引擎类型,您可以将countryx_id 字段定义为PRIMARY KEY(联合),您可以将AUTO_INCREMENT 设置为x_id 字段.现在MySQL 将为您完成剩下的工作!宾果游戏!

这是给你的SQL Fiddle

CREATE TABLE `myTable` (
    `country` int(10) unsigned NOT NULL DEFAULT '1', -- country
    `animal` int(4) NOT NULL,
    `size` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    `lang_id` tinyint(4) NOT NULL DEFAULT '1',
    `x_id` int(10) NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (country,x_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


INSERT INTO `myTable` (`country`, `animal`, `size`) VALUES 
    (777, 1001, 'small'),
    (777, 2002, 'medium'),
    (777, 7007, 'medium'),
    (777, 7007, 'large'),
    (42, 1001, 'small'),
    (42, 2002, 'medium'),
    (42, 7007, 'large')

结果会是这样的:

| country | animal | size   |lang_id | x_id  | 
|---------+--------+--------+--------+-------|
|  777    | 1001   | small  |   1    |   1   |
|  777    | 2002   | medium |   1    |   2   |
|  777    | 7007   | medium |   1    |   3   |
|  777    | 7007   | large  |   1    |   4   |
|  42     | 1001   | small  |   1    |   1   |
|  42     | 2002   | medium |   1    |   2   |
|  42     | 7007   | large  |   1    |   4   |

注意:这仅适用于 MyISAMBDB 表,对于其他引擎类型,您将收到错误提示 “表定义不正确;只能有一个自动列,并且必须定义为关键!”请参阅此答案以了解更多信息:https://stackoverflow.com/a/5416667/5645769

【讨论】:

  • ALTER TABLE myTable 添加主键 (country, animal);返回 #1062 - 键 'PRIMARY' 的重复条目 '###'
  • @HQ5,您的错误已经告诉您问题所在,您在同一国家/地区有重复条目,无论如何都应该删除。如果可能,清理表并从头开始。
  • @HQ5,好吧,您真的尝试过使用ADD PRIMARY KEY ( country, animal ); 而不是ADD PRIMARY KEY ( country, x_id ); 吗?为什么animal 代替x_id
猜你喜欢
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
相关资源
最近更新 更多