CakePHP:如何有效地强制执行唯一的联接表记录


CakePHP: How to efficiently enforce unique join table records

我使用CakePHP 2.x,经常遇到这样的情况:我想向联接表中添加记录,但我想确保同一个"联接"记录不存在。

关于具体性,这里有一个基本的例子。。。

模式:学生拥有并属于许多课程
模式:课程拥有并属于许多学生
Join Table=courses_students,具有字段"student_id"answers"course_id"

假设我有一张表格,上面有一群学生,我想把他们和一门课程联系起来。表单将一堆student_id提交给控制器。在添加新记录之前,我需要确保这些记录不存在(因为同一门课程不能注册两次)。这就是事情变得一团糟的地方。。。

选项1:我有时会为联接表中的每个(可能)预先存在的记录构建一个deleteAll(),该记录具有当前course_id和任何提交的user_id。这是有效的,但缺点是它会擦除任何可能存储在其他字段中的预先存在的数据,如日期等。

选项2:我有时会遍历每个提交的记录,并对每个(可能)预先存在的记录执行find()操作。这允许我保留现有的记录,但会对数据库进行大量额外的查询。

这是我通常处理这种情况的两种方法,但我怀疑还有更好的方法。它是什么?

您可以使用REPLACE INTO。例如:

REPLACE INTO courses_students (course_id, student_id, when)
  VALUES (1, 2, 12345678)

这将用新值覆盖列when。不过,我相信在纯SQL中有一种方法不能做到这一点:http://dev.mysql.com/doc/refman/5.0/en/replace.html

也不确定这将如何在CakePHP中工作。他们的DBAL可能有一个数据库合并的助手。

(MySQL不支持MERGE INTO .. WHEN MATCHED,所以无论如何都必须先执行SELECT,然后再执行INSERT。)

(顺便说一句:选项2并不低效,因为你已经为course_id+student_id创建了一个复合的、唯一的密钥。SELECT将非常快。)

编辑
如果您对列进行了正确的索引,SELECT是非常、非常快速和便宜的。表courses_students应具有(course_id, student_id)上的PK。如果你担心查询的数量,你可以在一个查询中完成所有的SELECT:

:ids from input
SELECT course_id, student_id FROM courses_students WHERE course_id IN (:ids) AND student_id IN (:ids)

然后将它们映射到一个assoc可访问的数组:

array(course_id => array(student_id => TRUE))

然后循环输入(POST?)并过滤现有记录:

if ( !isset($map[$course_id][$student_id]) ) {
  // INSERT HERE
}

对于总共count(INPUT) + 1个查询。

但所有这些都是大量的代码。您只能循环并查询每个记录。我保证它很便宜。