基于CodeIgniter 3实现Excel/CSV导入PostgreSQL并按program_id去重
解决CodeIgniter 3 + PostgreSQL 13下Excel/CSV导入去重(按program_id过滤)的方案
针对你的场景,核心思路是先获取数据库中已存在的program_id集合,过滤上传数据中的重复项后批量插入,同时结合PostgreSQL的特性做双重保障,适配千级数据量的高效处理。
一、数据库层面的基础保障
先给program_id字段添加唯一约束,从根源上防止重复数据:
ALTER TABLE programs ADD CONSTRAINT unique_program_id UNIQUE (program_id);
这样即使代码过滤出现遗漏,PostgreSQL会直接拦截重复插入操作;如果希望静默忽略重复项,可以用ON CONFLICT DO NOTHING语法(后面代码会提到)。
二、CodeIgniter实现步骤
1. 模型层:获取已有ID + 批量插入
在你的数据模型(比如Program_model.php)中添加两个方法:
class Program_model extends CI_Model { // 获取所有已存在的program_id,转成键值对提升查找效率 public function get_existing_program_ids() { $query = $this->db->select('program_id')->get('programs'); $id_list = array_column($query->result_array(), 'program_id'); return array_flip($id_list); // 转成键为program_id的数组,O(1)查找 } // 批量插入过滤后的记录,支持PostgreSQL的冲突忽略 public function batch_insert_programs($data) { if (empty($data)) return 0; // 方式1:用CI原生insert_batch(配合数据库唯一约束,重复会报错) $this->db->insert_batch('programs', $data); // 方式2:原生SQL+ON CONFLICT,静默忽略重复项(推荐高并发场景) // $columns = implode(', ', array_keys($data[0])); // $value_strings = []; // foreach ($data as $row) { // $escaped_vals = array_map(fn($val) => $this->db->escape($val), $row); // $value_strings[] = '(' . implode(', ', $escaped_vals) . ')'; // } // $sql = "INSERT INTO programs ($columns) VALUES " . implode(', ', $value_strings) . " ON CONFLICT (program_id) DO NOTHING"; // $this->db->query($sql); return $this->db->affected_rows(); } }
2. 控制器层:处理上传 + 过滤数据
以PhpSpreadsheet处理Excel为例(CSV可以用更轻量的fgetcsv),在控制器中实现导入逻辑:
class Import_controller extends CI_Controller { public function __construct() { parent::__construct(); $this->load->model('Program_model'); // 加载PhpSpreadsheet(需提前通过Composer或手动放入third_party) require_once APPPATH . 'third_party/PhpSpreadsheet/src/PhpSpreadsheet/IOFactory.php'; } public function import() { // 校验上传文件 if ($_FILES['import_file']['error'] !== UPLOAD_ERR_OK) { echo "文件上传失败,请检查后重试"; return; } // 读取Excel文件 $file_path = $_FILES['import_file']['tmp_name']; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_path); $worksheet = $spreadsheet->getActiveSheet(); $highest_row = $worksheet->getHighestRow(); // 获取已存在的program_id集合 $existing_ids = $this->Program_model->get_existing_program_ids(); $insert_data = []; // 遍历数据行(跳过表头,假设第一行是表头) for ($row = 2; $row <= $highest_row; $row++) { $program_id = trim($worksheet->getCell('A' . $row)->getValue()); // 读取其他字段(根据你的表结构调整列位置) $name = trim($worksheet->getCell('B' . $row)->getValue()); $description = trim($worksheet->getCell('C' . $row)->getValue()); // 过滤已存在的记录 if (!isset($existing_ids[$program_id])) { $insert_data[] = [ 'program_id' => $program_id, 'name' => $name, 'description' => $description // 注意:不要包含自增id字段,数据库会自动生成 ]; } } // 执行批量插入 if (!empty($insert_data)) { $count = $this->Program_model->batch_insert_programs($insert_data); echo "成功插入 {$count} 条新记录"; } else { echo "没有新记录需要插入"; } } }
3. CSV文件处理优化(可选)
如果是CSV文件,用fgetcsv比PhpSpreadsheet更高效,适合大文件:
// 替换控制器中的文件读取部分 $file = fopen($file_path, 'r'); fgetcsv($file); // 跳过表头 while (($row = fgetcsv($file)) !== false) { $program_id = trim($row[0]); $name = trim($row[1]); $description = trim($row[2]); if (!isset($existing_ids[$program_id])) { $insert_data[] = [ 'program_id' => $program_id, 'name' => $name, 'description' => $description ]; } } fclose($file);
三、千级数据量适配要点
- 查找效率:用
array_flip将已存在的program_id转成键值对,查找时间复杂度从O(n)降到O(1),处理千级数据毫无压力。 - 批量插入:
insert_batch或原生批量SQL减少数据库交互次数,比单条插入效率提升数倍。 - 内存控制:如果是超大文件,建议分块读取处理,避免一次性加载所有数据到内存。
内容的提问来源于stack exchange,提问作者MoshiDev
相关产品推荐
相关产品推荐

