仅从CSV导入不存在的数据到数据库

时间:2017-08-18 14:31:13

标签: php csv doctrine-orm symfony-2.8

我创建了一个从CSV文件读取数据的脚本,检查数据中是否已存在数据,如果数据不存在则导入数据。如果数据确实存在(特定产品的代码),则需要从CSV文件更新其余信息。

例如; 我的代码为WTW-2LT,在我的CSV文件中名为Alex和姓Johnson。该脚本检查代码为WTW-2LT且名为Alex和姓氏Johnson的成员是否已存在,如果有,则需要从脚本更新联系人详细信息和额外详细信息(还需要检查主题和讲师等其他详细信息,所有细节都在CSV中的一行中,如果它不存在,则只需要创建新成员。

我的脚本到目前为止我用最少的其他检查来防止分心现在;

while ($row = fgetcsv($fp, null, ";")) {
    if ($header === null) {
        $header = $row;
        continue;
    }

    $record = array_combine($header, $row);

    $member = $this->em->getRepository(Member::class)->findOneBy([
        'code' =>$record['member_code'],
        'name' =>$record['name'],
        'surname' =>$record['surname'],
    ]);

    if (!$member) {
        $member = new Member();
        $member->setCode($record['member_code']);
        $member->setName($record['name']);
        $member->setName($record['surname']);
    }    
    $member->setContactNumber($record['phone']);
    $member->setAddress($record['address']);
    $member->setEmail($record['email']);

    $subject = $this->em->getRepository(Subject::class)->findOneBy([
        'subject_code' => $record['subj_code']
    ]);

    if (!$subject) {
        $subject = new Subject();
        $subject->setCode($record['subj_code']);
    }
    $subject->setTitle($record['subj_title']);
    $subject->setDescription($record['subj_desc']);
    $subject->setLocation($record['subj_loc']);

    $lecturer = $this->em->getRepository(Lecturer::class)->findOneBy([
        'subject' => $subject,
        'name' => $record['lec_name'],
        'code' => $record['lec_code'],
    ]);

    if (!$lecturer) {
        $lecturer = new Lecturer();
        $lecturer->setSubject($subject);
        $lecturer->setName($record['lec_name']);
        $lecturer->setCode($record['lec_code']);
    }
    $lecturer->setEmail($record['lec_email']);
    $lecturer->setContactNumber($record['lec_phone']);

    $member->setLecturer($lecturer);

    $validationErrors = $this->validator->validate($member);
    if (!count($validationErrors)) {
        $this->em->persist($member);
        $this->em->flush();
    } else {
        // ...
    }
}

您可以注意到此脚本必须查询数据库3次以检查是否存在一个CSV行。在我的情况下,我有多达2000多行的文件,因此每行执行3次查询以检查该行是否存在非常耗时。

不幸的是,我也无法批量导入行,因为如果一个主题不存在,它将创建它很多次,直到批次被刷新到数据库,然后我坐在一起没有任何意义的重复记录。

如何才能最大限度地提高性能和速度?就像首先从数据库中获取所有记录并将其存储在数组中(内存消耗?)然后进行检查并将该行添加到数组并从那里检查......

有人可以帮我找到改进方法(请提供示例代码吗?)

3 个答案:

答案 0 :(得分:6)

老实说,我确实找到了2000多行,其中3x的查询数量并不多。但是,既然你要求表现,这是我的两分钱:

使用框架总是会产生开销。这意味着如果您在本机PHP中创建此代码,它将更快地运行。我不熟悉symfony,但我假设你将数据存储在数据库中。在MySQL中,您可以使用命令INSERT ... ON DUPLICATE KEY update。如果已将3个字段(代码,名称,姓氏)设置为主键(我假设),则可以使用它来:插入数据,但如果密钥已存在,则更新数据库中的值。 MySQL将为你做checsk,看看数据是否已经改变:如果没有,不会发生diskwrite。

我非常肯定您可以将本机SQL写入symfony,允许您使用框架提供的安全性,同时加快插入速度。

答案 1 :(得分:2)

通常,如果您想要性能,我最好的经验是将所有数据转储到数据库中,然后使用SQL语句将其转换为数据库。 DBS将能够以这种方式优化您的所有步骤。

您可以使用SQL命令

将CSV文件直接导入MySQL数据库
LOAD DATA INFILE 'data.csv'
INTO TABLE tmp_import

该命令有很多选项,您可以在其中指定CSV文件的格式,例如:

如果你的data.csv是一个包含所有旧行和新行的完整转储,那么你可以在修改它之后将当前表替换为导入的表。

例如,您的csv文件(和导入表)看起来有点像

WTW-2LT, Alex, Johnson, subj_code1, ..., lec_name1, ...
WTW-2LT, Alex, Johnson, subj_code1, ..., lec_name2, ...
WTW-2LT, Alex, Johnson, subj_code2, ..., lec_name3, ...
WTW-2LU, John, Doe,     subj_code3, ..., lec_name4, ...

然后,您可以通过分组获取不同的行:

SELECT member_code, name, surname
FROM tmp_import
GROUP BY member_code, name, surname

如果member_code是密钥,则可以在MySQL中GROUP BY member_code。星展银行不会抱怨,即使我相信它在技术上against the standard

要获取其余数据,请执行相同的操作:

SELECT subj_code, subj_title, member_code
FROM tmp_import
GROUP BY subj_code

SELECT lec_code, lec_name, subj_code
FROM tmp_import
GROUP BY lec_code

假设subj_codelec_code都是主题和讲座的关键。

要将此结果实际保存为表格,您可以使用MySQL CREATE TABLE ... SELECT - 语法,例如

CREATE TABLE tmp_import_members
SELECT member_code, name, surname
FROM tmp_import
GROUP BY member_code, name, surname

然后,您可以在两个查询中执行insertsupdates

INSERT INTO members (member_code, name, surname)
SELECT member_code, name, surname
FROM tmp_import_members
WHERE tmp_import_members.member_code NOT IN (
  SELECT member_code FROM members WHERE member_code IS NOT NULL
);

UPDATE members 
JOIN tmp_import_members ON 
  members.member_code = tmp_import_members.members_code
SET 
  members.name = tmp_import_members.name,
  members.surname = tmp_import_members.surname;

和你喜欢的科目和讲座相同。

这一切都等于

  • 您的CSV文件的一次批量导入,应该非常快,
  • 为您的会员,主题和讲座提供3张临时表格,
  • 3个插入和3个更新语句(每个表一个)
  • 完成后临时表上的一个删除表

再次:如果您的CSV文件包含所有行,您只需替换现有表并保存3个插入和3个更新。

确保在临时表的相关列上创建索引,以便MySQL可以加快上述查询中的NOT INJOIN

答案 2 :(得分:1)

您可以先运行自定义sql来获取所有三个表的计数

SELECT
  (SELECT COUNT(*) FROM member WHERE someCondition) as memberCount, 
  (SELECT COUNT(*) FROM subject WHERE someCondition) as subjectCount,
  (SELECT COUNT(*) FROM lecturer WHERE someCondition) as lecturerCount

然后,根据计数,您可以查找表中是否存在数据。如果使用本机SQL

,则不必多次运行查询以获得唯一性

查看此链接以了解如何在Doctrine中运行自定义SQL

Symfony2 & Doctrine: Create custom SQL-Query