从一个 table 中的字段中检索频率最高的值并将其更新到另一个中

Retrieving the highest-frequency value from a field in one table and updating it into another

我有 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]

我通过 Web 表单接收用户的输入,然后 PHP 代码将数据插入 tblLocations。这部分很简单。现在,每次对 tblLocations 进行插入时,我都需要触发以下操作:

  1. 查看 tblPopularNames 是否包含插入的 CITY
  2. 的条目
  3. 如果该条目存在,则根据 CITY 使用频率最高的 NAME 值更新对应的 POPULARNAME 字段 tblLocations 中的字段。
  4. 如果该条目不存在,请使用刚输入的值创建一个。

是否可以在不使用任何查询嵌套的情况下完成此操作?就内存使用而言,执行此操作的成本最低的方法是什么?

我可以看到一个相关的 post here 但那里的答案只提供了所寻求的最大价值数,这不是我想要做的。我需要完成这两项任务的最不人为的方式。另外,我不确切知道查询将如何处理关系,即两个名字在输入的城市中享有相同的频率。老实说,我不介意查询在这种情况下返回任何一个值,只要它不抛出错误即可。

希望我已经解释清楚了,但如果您有任何疑问,请随时发表评论。

P.S. 不确定这个问题属于这里还是属于 DBA。我选择使用 SO 是因为我看到了与此站点上的查询有关的其他问题(例如,this one)。如果其中一位版主认为 DBA 更合适,请他们在他们认为合适的情况下移动它。

The first table accepts two values from users: their name and the city they live in. The fields affected in that table are CITY and NAME. Then each time a new entry is made to this table, another is made to tblPopularNames with that city and the name that occurs most frequently against that city in tblLocations. For example, if John is the most popular name in NY, tblPopularNames gets updated with NY, John. –

好的,让我们把它分解成一个触发器。 每次创建新条目时 转换为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 上的 UNIQUE KEY;它将允许我们使用 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 的回答有很多很多 很多 的优点。触发器有其用途,但它们不是灵丹妙药。

此外,在这个阶段,设计可能还处于其生命周期的早期,不值得将繁重的工作外包给触发器。例如,如果您决定采用上面暗示的 "counter" 解决方案怎么办?或者,如果您决定使 popularName 的选择复杂化怎么办?

毫无疑问,维护(包括彻底的现场测试)触发器比用代码完成同样的事情要昂贵得多。

所以我真正要做的是首先设计一个函数或方法,目的是接收 insertedValues 并做一些魔术。

然后我会 模拟 触发器代码与 PHP 中的多个查询,并封装在一个事务中。它们将与上面的触发器中出现的查询相同。

然后我会继续进行其余的工作,确信这个解决方案是 有效的,如果可能会改进性能的话。

如果很久以后,设计令人信服并得到落实,那么将函数修改为仅 运行 一个 INSERT 查询并利用自身将非常容易触发器的 - 那个一个,或者同时进化的略微修改的一个。

如果 稍作修改 已被 creeping featurism 接管并且不容易向后移植到触发器,则您无需执行任何操作,也不会丢失任何内容。否则,您已经浪费了初始实施的时间(非常少),现在可以获利了。

所以我的答案是:两者 :-)

用例略有不同(根据评论)

The thing is, the first query being performed by PHP is an indefinitely large one with potentially hundreds of entries being inserted at once. And I do need to update the second table every time a new entry is made to the first because by its very nature, the most popular name for a city can potentially change with every new entry, right? That's why I was considering a trigger since otherwise PHP would have to fire hundreds of queries simultaneously. What do you think?

问题是:在那个大批量的第一个和最后一个 INSERT 之间应该发生什么

你用的是那个周期的流行名字吗?

如果,那么您别无选择:您需要在每次插入后检查受欢迎程度table(不是真的;有一个解决方法, 如果你有兴趣...).

如果没有,那么你可以在最后做所有的计算。

也就是说,你有一长串

 NY        John
 Berlin    Gottfried
 Roma      Mario
 Paris     Jean
 Berlin    Lukas
 NY        Peter
 Berlin    Eckhart

您可以检索所有流行名称(或您要插入的列表中包含城市的所有流行名称)及其出现频率,并将它们放在一个数组中数组:

 [
     [ NY,        John,    115 ],
     [ NY,        Alfred,  112 ],
     ...
 ]

然后从你的列表中你 "distill" 频率:

 NY        John       1
 NY        Peter      1
 Berlin    Gottfried  1
 Roma      Mario      1
 Paris     Jean       1
 Berlin    Lukas      1
 Berlin    Eckhart    1

然后您将(您仍在 PHP 中)的频率添加到您检索到的频率。在这种情况下,例如纽约,约翰将从 115 变为 116。

您可以同时执行这两项操作,方法是首先获取新插入的 "distilled" 频率,然后 运行 查询:

 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);

这会插入那些没有条目的城市,或者更新那些有条目的城市的 popularNames。

我认为这是 应用程序逻辑 优于 数据库逻辑 的问题。例如。代码与触发器。

由于您真正在做的是一种专门用于您的应用程序的索引形式,我建议该逻辑位于您的应用程序级别的某个位置(例如 php)。应该是:

  • 简单(我只做 2 个查询。select 计数和更新。)
  • 易于维护(使用良好的数据库接口抽象,例如 1 个函数)
  • 仅在需要时 运行(在该函数中使用逻辑)

您如何处理该解决方案是棘手的部分。例如。您可能认为最好只对每个插入进行计算,但是如果您要对同一个城市进行一批插入,则对每个插入都进行计算效率会很低。

我曾有过非常糟糕的经历,对所有事情都使用触发器并使数据库变慢。当然它是在 postgre 中(15 年前 mysql 触发器存在之前)和一个相当大的大约 500 个表的数据库。这很好,因为它捕获了 100% 的插入,但有时这不是你想要做的。从应用程序的角度来看,使用触发器会失去控制元素。如果这些触发器过多,您最终可能会减慢整个数据库的速度。所以这是一个反触发的观点。失去控制对我来说是个大问题。