我有2个具有以下结构的MySQL表:
**tblLocations**
ID [primary key]
CITY [non-unique varchar]
NAME [non-unique varchar]
----------------------------------
**tblPopularNames**
ID [primary key]
CITY [unique varchar]
POPULARNAME [non-unique varchar]
我通过网络表单收到用户的意见,然后PHP代码将数据插入 tblLocations 。这部分很简单。现在,每次插入 tblLocations 时,我都需要触发以下操作:
这可以在不使用任何查询嵌套的情况下完成吗?在内存使用方面执行此操作的最便宜的方法是什么?
我可以看到相关的帖子here,但那里的答案只提供了所寻求的值的最大数量,而这并不是我想要做的事情。我需要最简单的方法来完成这两项任务。此外,我并不确切地知道查询将如何处理关系,即两个名称对于所输入的城市具有相同的频率。老实说,我不介意在这种情况下返回任何值的查询,只要它不会抛出错误。
我希望我已经根据需要清楚地解释了它,但如果您有任何疑问,请随时发表评论。
P.S。不确定问题是属于此处还是属于DBA。我选择使用SO,因为我在本网站上看到了与查询有关的其他问题(例如this one)。如果其中一位主持人觉得DBA更合适,请他们按照他们认为合适的方式移动它。
答案 0 :(得分:2)
第一个表接受来自用户的两个值:他们的名字和城市 他们住在那里。该表中受影响的领域是CITY和NAME。 然后,每次对该表进行新的输入时,都会进行另一个输入 具有该城市的tblPopularNames以及最常出现的名称 经常在tblLocations对抗那个城市。例如,如果约翰是 纽约最受欢迎的名字,tblPopularNames随NY更新, 约翰。 -
好的,让我们把它分解成一个触发器。 每次创建新条目都会转换为AFTER INSERT ON tblLocations FOR EACH ROW
; 在tblLocations 中针对该城市最常出现的名称意味着我们运行SELECT NEW.insertedCity, old.insertedName FROM tblLocations AS old WHERE insertedCity = NEW.insertedCity GROUP BY insertedName ORDER BY COUNT(*) DESC LIMIT 1
;我们可能希望在ORDER BY中添加一些内容,以避免在相同频率下随机提取多个名称。
还有一项要求,即如果城市已存在于tblPopularNames中,则条目将被更新。我们需要在tblPopularNames.popularCity上有一个独特的键;它将允许我们使用ON DUPLICATE KEY UPDATE
。
最后:
DELIMITER //
CREATE TRIGGER setPopularName
AFTER INSERT ON tblLocations
FOR EACH ROW BEGIN
INSERT INTO tblPopularNames
SELECT NEW.insertedCity, insertedName
FROM tblLocations
WHERE insertedCity = NEW.insertedCity
GROUP BY insertedName
ORDER BY COUNT(*) DESC, insertedName
LIMIT 1
ON DUPLICATE KEY
UPDATE popularName = VALUES(popularName)
;
END;//
DELIMITER ;
mysql> INSERT INTO tblLocations VALUES ('Paris', 'Jean'), ('Paris', 'Pierre'), ('Paris', 'Jacques'), ('Paris', 'Jean'), ('Paris', 'Etienne');
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM tblPopularNames;
+-------------+-------------+
| popularCity | popularName |
+-------------+-------------+
| Paris | Jean |
+-------------+-------------+
1 row in set (0.00 sec)
mysql> INSERT INTO tblLocations VALUES ('Paris', 'Jacques'), ('Paris', 'Jacques'), ('Paris', 'Etienne'); Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM tblPopularNames;
+-------------+-------------+
| popularCity | popularName |
+-------------+-------------+
| Paris | Jacques |
+-------------+-------------+
1 row in set (0.00 sec)
不可否认@ Phil_1984的答案有很多很多,而且很多很多的优点。触发器有它们的用途,但它们不是银弹。
此外,在这个阶段,设计在其生命周期中可能还为时过早,因此值得将艰苦工作外包给触发器的麻烦。例如,如果你决定采用上面暗示的“反制”解决方案怎么办?或者,如果您决定使popularName的选择复杂化怎么办?
毫无疑问,维护(包括彻底的现场测试)触发器比在代码中执行的操作要昂贵得多。
所以我真正要做的是首先设计一个函数或方法,目的是接收insertedValues并做一些魔术。
然后我用PHP中的几个查询模拟触发器代码,包装在一个事务中。它们与上面的触发器中显示的查询相同。
然后我继续完成剩下的工作,安全地知道这个解决方案正在工作,如果可能的话,可以提高性能。
如果很久以后,设计具有说服力并得到提交,那么将该函数修改为仅运行一个 INSERT查询并利用其自身的触发器非常容易 - < / em>一个,或者在此期间进化过的略微修改过的。
如果略微修改已由creeping featurism接管并且不容易向后移植到触发器,则您无需执行任何操作,也不会丢失任何内容。否则你已经失去了初始实施的时间(很少),现在已经准备好了。
所以我的答案是:两者: - )
问题是,PHP执行的第一个查询是 无限大的,可能有数百个条目 立即插入。我确实需要每次更新第二个表 第一个是新的条目,因为它本质上是最多的 每个新条目都可能会改变城市的流行名称, 对?这就是为什么我考虑触发因为否则PHP 必须同时发出数百个查询。你是什么 认为?
事情是:在那个大批量的第一个和最后一个INSERT之间应该发生什么?
您是否在该周期中使用流行名称?
如果是,那么你别无选择:你需要在每次插入后检查流行度表(不是真的;如果你有兴趣,有一个解决方法...... )。
如果否,那么您可以在最后完成所有计算。
即,你有一长串的
NY John
Berlin Gottfried
Roma Mario
Paris Jean
Berlin Lukas
NY Peter
Berlin Eckhart
您可以检索所有常用名称(或您插入的列表中包含城市的所有常用名称)及其频率,并将它们放在数组数组中:
[
[ NY, John, 115 ],
[ NY, Alfred, 112 ],
...
]
然后从列表中“提取”频率:
NY John 1
NY Peter 1
Berlin Gottfried 1
Roma Mario 1
Paris Jean 1
Berlin Lukas 1
Berlin Eckhart 1
然后你将(你还在PHP中)的频率添加到你检索的频率。在这种情况下,例如纽约,约翰将从115到116。
您可以同时执行这两项操作,首先获取新插入的“提取”频率,然后运行查询:
while ($tuple = $exec->fetch()) {
// $tuple is [ NY, John, 115 ]
// Is there a [ NY, John ] in our distilled array?
$found = array_filter($distilled, function($item) use ($tuple) {
return (($item[0] === $tuple[0]) && ($item[1] === $tuple[1]));
}
if (empty($found)) {
// This is probably an error: the outer search returned Rome,
// yet there is no Rome in the distilled values. So how comes
// we included Rome in the outer search?
continue;
// But if the outer search had no WHERE, it's OK; just continue
}
$datum = array_pop($found);
// if (!empty($found)) { another error. Should be only one. }
// So we have New York with popular name John and frequency 115
$tuple[2] += $datum[2];
$newFrequency[] = $tuple;
}
然后,您可以使用例如城市和频率降序对数组进行排序。 uasort
。
uasort($newFrequency, function($f1, $f2) {
if ($f1[0] < $f2[0]) return -1;
if ($f1[0] > $f2[0]) return 1;
return $f2[2] - $f1[2];
});
然后循环遍历数组
$popularName = array();
$oldCity = null;
foreach ($newFrequency as $row) {
// $row = [ 'New York', 'John', 115 ]
if ($oldCity != $row[0]) {
// Given the sorting, this is the new maximum.
$popularNames[] = array( $row[0], $row[1] );
$oldCity = $row[0];
}
}
// Now popularNames[] holds the new cities with the new popular name.
// We can build a single query such as
INSERT INTO tblPopularNames VALUES
( city1, name1 ),
( city2, name2 ),
...
( city3, name3 )
ON DUPLICATE KEY
UPDATE popularName = VALUES(popularName);
这将插入那些没有条目的城市,或更新那些城市的热门名称。
答案 1 :(得分:1)
我认为这是应用程序逻辑优于数据库逻辑的问题。例如。代码与触发器。
由于你真正在做的是一种索引形式,专门用于你的应用程序,我建议这个逻辑位于你的应用程序级别(例如php)。它应该是:
您如何处理该解决方案是棘手的部分。例如。您可能认为最好只对每个插入进行计算,但如果您为同一个城市执行一批插入操作,则在每个插入上执行此操作效率很低。
我有一个非常糟糕的经验,即使用触发器来解决所有问题并让数据库变慢。当然,它是在postgre(15年前,在mysql触发器存在之前)和一个相当大的数据库中,大约有500个表。它很好,因为它可以捕获100%的插入物,但有时这不是你想要做的。通过使用触发器,您从应用程序的角度失去了一个控制元素。您可以使用太多这些触发器来减慢整个数据库的速度。所以这是一个反触发的视角。这就是失去控制权,这对我来说是一个交易障碍。