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

时间:2015-05-05 21:20:29

标签: mysql database insert-update

我有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 时,我都需要触发以下操作:

  1. 查看 tblPopularNames 是否包含插入的 CITY 值的条目
  2. 如果条目存在,请使用tblLocations中 CITY 字段的最高频率 NAME 值更新相应的 POPULARNAME 字段。
  3. 如果条目不存在,请使用刚输入的值创建一个。
  4. 这可以在不使用任何查询嵌套的情况下完成吗?在内存使用方面执行此操作的最便宜的方法是什么?

    我可以看到相关的帖子here,但那里的答案只提供了所寻求的值的最大数量,而这并不是我想要做的事情。我需要最简单的方法来完成这两项任务。此外,我并不确切地知道查询将如何处理关系,即两个名称对于所输入的城市具有相同的频率。老实说,我不介意在这种情况下返回任何值的查询,只要它不会抛出错误。

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

    P.S。不确定问题是属于此处还是属于DBA。我选择使用SO,因为我在本网站上看到了与查询有关的其他问题(例如this one)。如果其中一位主持人觉得DBA更合适,请他们按照他们认为合适的方式移动它。

2 个答案:

答案 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)。它应该是:

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

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

我有一个非常糟糕的经验,即使用触发器来解决所有问题并让数据库变慢。当然,它是在postgre(15年前,在mysql触发器存在之前)和一个相当大的数据库中,大约有500个表。它很好,因为它可以捕获100%的插入物,但有时这不是你想要做的。通过使用触发器,您从应用程序的角度失去了一个控制元素。您可以使用太多这些触发器来减慢整个数据库的速度。所以这是一个反触发的视角。这就是失去控制权,这对我来说是一个交易障碍。