以下是标记的当前表格结构:
// tags
+----+------------+----------------------------------+----------+------------+
| id | name | description | used_num | date_time |
+----+------------+----------------------------------+----------+------------+
| 1 | PHP | some explanations for PHP | 4234 | 1475028896 |
| 2 | SQL | some explanations for SQL | 734 | 1475048601 |
| 3 | jQuery | some explanations for jQuery | 434 | 1475068321 |
| 4 | MySQL | some explanations for MySQL | 535 | 1475068332 |
| 5 | JavaScript | some explanations for JavaScript | 3325 | 1475071430 |
| 6 | HTML | some explanations for HTML | 2133 | 1475077842 |
| 7 | postgresql | some explanations for postgresql | 43 | 1475077851 |
+----+------------+----------------------------------+----------+------------+
如您所知,某些标签彼此相关。例如:
SQL
,MySQL
,postgresql
JavaScript
,jQuery
是上表中的相关内容。我怎样才能在他们之间建立这种关系?我应该再添加一列吗?它应该包含什么东西? (因为有时会有超过2个相关标签)
答案 0 :(得分:2)
至少对于选项1,请参阅底部的Edit 1
,了解相交的实际INSERT
策略。
选项1
create table tags
( id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
UNIQUE KEY `key_tags_name` (name)
-- All the other columns
)ENGINE=InnoDB;
create table tagIntersects
( id1 INT NOT NULL,
id2 INT NOT NULL,
PRIMARY KEY(id1,id2),
KEY `ti_flipped` (id2,id1), -- flipped left-mode (thin size)
FOREIGN KEY `fk_ti_id1` (id1) REFERENCES tags(id),
FOREIGN KEY `fk_ti_id2` (id2) REFERENCES tags(id)
)ENGINE=InnoDB;
选项2
create table tags
( id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
UNIQUE KEY `key_tags_name` (name)
-- All the other columns
)ENGINE=InnoDB;
create table tagIntersects
( id INT AUTO_INCREMENT PRIMARY KEY,
name1 VARCHAR(60) NOT NULL,
name2 VARCHAR(60) NOT NULL,
KEY `ti_Flipped` (name2,name1), -- these get costly (wide)
FOREIGN KEY `fk_ti_id1` (name1) REFERENCES tags(name),
FOREIGN KEY `fk_ti_id2` (name2) REFERENCES tags(name)
)ENGINE=InnoDB;
选项3
create table tags
( id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
UNIQUE KEY `key_tags_name` (name)
-- All the other columns
)ENGINE=InnoDB;
create table tagIntersects
( name1 VARCHAR(60) NOT NULL,
name2 VARCHAR(60) NOT NULL,
PRIMARY KEY (name1,name2),
KEY `ti_Flipped` (name2,name1), -- these get costly (wide)
FOREIGN KEY `fk_ti_id1` (name1) REFERENCES tags(name),
FOREIGN KEY `fk_ti_id2` (name2) REFERENCES tags(name)
)ENGINE=InnoDB;
INSERT tags (name) VALUES ('PHP'),('PDO'),('MYSQLI'),('PHPMyAdmin');
伪造200个随机名称的标签:
DROP PROCEDURE IF EXISTS tagDataLoad;
DELIMITER $$
CREATE PROCEDURE tagDataLoad()
BEGIN
-- warning this is horribly slow
DECLARE i INT DEFAULT 0;
WHILE i<200 DO
INSERT IGNORE tags(name)
select concat(substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1)
);
-- poach the above from Gordon, link: https://stackoverflow.com/a/16738136
SET i=i+1;
END WHILE;
END$$
DELIMITER ;
称之为:
CALL tagDataLoad(); -- load 200 fake tags
select * from tags; -- eyeball it
CALL tagDataLoad(); -- load more
CALL tagDataLoad(); -- load more
SELECT MIN(id),MAX(id),count(*) FROM tags;
-- 1 604 604
伪造负载数量iCount
假标签相交数量:
DROP PROCEDURE IF EXISTS tagIntersectDataLoad;
DELIMITER $$
CREATE PROCEDURE tagIntersectDataLoad(iCount INT)
BEGIN
-- warning this is horribly slow
-- don't pass a number greater than 100 until you time it
DECLARE i INT DEFAULT 0;
WHILE i<=iCount DO
INSERT IGNORE tagIntersects(id1,id2)
SELECT FLOOR(RAND()*600)+1,FLOOR(RAND()*600)+1;
SET i=i+1;
END WHILE;
END$$
DELIMITER ;
CALL tagIntersectDataLoad(100);
-- slow, I don't recommend a number above 100 until you time it
将100的更改为更大的计数后,我最终获得了10k行
select count(*) from tag Intersects;
-- 9900
由于超时,我不建议您这样做。但最后我有了上面的
上面假加载存储过程的一半原因是为了使表大小足够高,甚至使用索引。它们不用于小桌子。他们还为您提供了一种方法来chg到其他模式并加载特殊的数据。然后使用100k行或数千万的查询(根据您的需要)分析性能。
请参阅EXPLAIN
计划:
explain
select * from tagIntersects where id1=111 or id2=111;
+----+-------------+---------------+-------------+--------------------+--------------------+---------+------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------+-------------+--------------------+--------------------+---------+------+------+----------------------------------------------+
| 1 | SIMPLE | tagIntersects | index_merge | PRIMARY,ti_flipped | PRIMARY,ti_flipped | 4,4 | NULL | 27 | Using union(PRIMARY,ti_flipped); Using where |
+----+-------------+---------------+-------------+--------------------+--------------------+---------+------+------+----------------------------------------------+
解释
select * from tagIntersects where (id1=111 or id2=111) and (id1=500 or id2=500);
+----+-------------+---------------+-------------+--------------------+--------------------+---------+------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------+-------------+--------------------+--------------------+---------+------+------+----------------------------------------------+
| 1 | SIMPLE | tagIntersects | index_merge | PRIMARY,ti_flipped | PRIMARY,ti_flipped | 4,4 | NULL | 21 | Using union(PRIMARY,ti_flipped); Using where |
+----+-------------+---------------+-------------+--------------------+--------------------+---------+------+------+----------------------------------------------+
对于典型的查询,EXPLAIN
计划看起来很好。请注意瘦键大小(总共8个字节)。该计划显示了最左侧两个关键用法的index_merge
/ UNION
:一个使用PK,另一个使用翻转的二级索引。这就是ti_flipped
。
另请注意,FK按键很薄。
请注意,tags.name
可以很容易地从'node.js'更新为'nodejs',而不会影响标签主键。该更新对tagsInserted
列或密钥没有任何影响。
关于使用选项2或3:键很宽。对tags.name
的更改将具有PK和FK更改,因为交叉表中的选项1不会承受影响。根据您的数据大小(例如,与SO标签不同的东西),一个name
具有数千万行和数千个交叉点,可以在UX中感受到这种变化。对于中小型,不用担心,但要说明影响。
因此,由于我的数据集非常庞大并且试图保持关系密钥的存在,我选择了一种选项1方法。
最近在Spencer7593中提到的comment大型varchars在连接期间对内部内存产生负面影响,并且在临时表中显示子查询/派生的影响。薄FK的另一个原因。
所以这与一般读者有很多关系,他们有不同的模式,并认为对他们的表现没有影响。
因此,在最终确定模式(尤其是大型表)之前,请对查询的性能进行概要分析。
create table tags
( id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
UNIQUE KEY `key_tags_name` (name) --
-- All the other columns
)ENGINE=InnoDB;
create table tagIntersects
( id1 INT NOT NULL,
id2 INT NOT NULL,
PRIMARY KEY(id1,id2),
KEY `ti_flipped` (id2,id1),
FOREIGN KEY `fk_ti_id1` (id1) REFERENCES tags(id),
FOREIGN KEY `fk_ti_id2` (id2) REFERENCES tags(id)
)ENGINE=InnoDB;
负载:
insert tags(name) values ('tag1'),('tag2'); -- ids 1 and 2
现在我在某些编程语言中有两个id,我希望与标签相交。
使用MySQL作为编程语言,我们只需将它们称为以下变量:
set @myId1=2; -- actually run this so it is not NULL
set @myId2=1; -- actually run this so it is not NULL
请注意,它们可以颠倒,没关系。现在假设你 并没有搞砸编程语言,以至于@ myId1 = @ myId2 (注意下面的内容仍然有效,但只是说')
insert tagIntersects(id1,id2) select LEAST(@myId1,@myId2),GREATEST(@myId1,@myId2); -- ok
insert tagIntersects(id1,id2) select LEAST(@myId1,@myId2),GREATEST(@myId1,@myId2); -- GOOD it failed
- 翻转em:
set @myId1=1; -- actually run this so it is not NULL
set @myId2=2; -- actually run this so it is not NULL
insert tagIntersects(id1,id2) select LEAST(@myId1,@myId2),GREATEST(@myId1,@myId2); -- GOOD it failed
您的数据保持清洁。清洁意味着你不会有两行欺骗和污染你的数据,例如MYSQL
/ SQL-SERVER
的交叉行...... SQL-SERVER
/ {{1的另一行尊敬地{}} MYSQL
,id1
。
来自用户id2
的问题:好的,你有三个标签,tag1,tag2,tag3 ..它们是相关的。因此tagIntersects表中有三行,如:tag1,tag2,tag1,tag3,tag2,tag3。对?现在我想用tag1选择所有相关的标签。写下查询... :-)看起来像个噩梦,是吗?
答案:
Shafizadeh
我的问题是,使用您的CSV,您的解释计划是什么?看起来很糟糕,桌面扫描。
答案 1 :(得分:0)
用于在关系数据库中存储M:N关系您通常需要另一个表。表有两列id1,id2,相关表的外键。 (id1,id2)应该是唯一的。
如果您将表与自身联系起来,则有更多可能性。如果行A与B相关,则B与A有关吗?如果是这种情况,则添加约束id1&lt;关系表上的id2所以只有一种方法来存储两行之间的关系。即使某些插入不符合规则并试图插入不同的顺序,数据也不会重复。
最有趣的问题是及物性。如果A与B相关,而B与C相关,则A与C有关吗?我认为应该是这种情况。您不必将关系A存储到C,因为它可以被推测,但是每次要查询它时计算它都是不切实际的。我认为最好的方法是在更新时存储所有感染关系,但将它们标记为不感知,因此可以在将来的更改中重新计算它们。它使关系的添加和删除可逆。如果您不需要(关系不会被删除),您可以省略该标志或仅使用替代方法:
我们所拥有的实际上是等价(反身,对称,传递)和等价产生等价类。您可以在标记等价类的每一行中存储数字。具有相同数字的行将是相等的。这就是Shafizadeh提出的,我不同意@Drew,这是最后一件事。如果在正确的情况下使用它可能会很好。