CSV文件上传以处理状态更新&插入新记录

时间:2017-08-31 09:37:48

标签: php mysql csv file-upload load-data-infile

在处理本地托管的项目时,我一直坚持管理CSV上传。其中一项任务要求我每天上传具有新条目或现有条目更新状态的数据。某些条目(存在于数据库中)也有可能没有更新状态。

问题陈述;

我创建了一个CSV上传功能,可将CSV文件上传到特定位置,并导入指定TABLE中的信息。 我想知道在进行CSV上传时验证数据库记录的最佳方法是什么。

理想情况下应该如下工作;

  1. 如果条目不存在( INSERT 来自CSV文件的新条目基础数据)
  2. 如果条目存在并且状态为SAME作为新上传的CSV文件( IGNORE &什么都不做)
  3. 如果条目存在且状态与新上传的CSV文件中的状态不同(更新状态为CSV文件中提及的内容)
  4. 数据库/ CSV文件结构

    • tracking_id(自动增量)
    • odanumber(通过CSV上传并可以有重复的条目)
    • airwaybill(通过CSV& UNIQUE上传)
    • courierful(通过CSV上传,可以有重复的条目)
    • delstatus(通过CSV上传,主要是更新)
    • deliverydate(通过CSV上传,每次发送时都会更新)

    从上面可以看出,delstatus几乎每次都会更新(对于现有条目)新的CSV上传,因此需要进行检查。

      

    我认为我们可以选择' airwaybill '检查它是否存在,和   如果是,请检查 delstatus 是否与CSV文件相同或   更新。如果' airwaybill'如果不存在,则必须添加新记录   到数据库。因为那将使我无法输入所有记录   数据库不必要。或者可以做得更好(那个   我还没去探索过。

    现在发生了什么;

    我可以上传完整的CSV文件集,通过以下代码在数据库中创建新条目。

    <?php 
    
    if(isset($_POST['csv']))
    {
    $sqlname= 'localhost';
    $username= 'root';
    $table= 'tracking';
    $password= '';
    $db='aatrack';
    $file=$_POST['csv'];
    $cons= mysqli_connect("$sqlname", "$username","$password","$db") or die(mysql_error());
    
    $result1=mysqli_query($cons,"select count(*) count from $table");
    $r1=mysqli_fetch_array($result1);
    $count1=(int)$r1['count'];
    
    
    mysqli_query($cons, '
        LOAD DATA LOCAL INFILE "'.$file.'"
            INTO TABLE '.$table.'
            FIELDS TERMINATED by \',\'
            LINES TERMINATED BY \'\n\'
            IGNORE 1 LINES
    ')or die(mysql_error());
    
    $result2=mysqli_query($cons,"select count(*) count from $table");
    $r2=mysqli_fetch_array($result2);
    $count2=(int)$r2['count'];
    
    $count=$count2-$count1;
    if($count>0)
    {
        header("location:success.php?id=$count");
    }
    
    }
    
    ?>
    

    请您帮助指导实现同样目标的最佳方式。我知道可以通过首先将信息上传到temp_table并在LIVE表中更新条目之前比较它来完成。

    请建议实现结果的最佳方式。

    感谢您阅读此内容。

    致以最诚挚的问候,

    Amit Agnihotri

2 个答案:

答案 0 :(得分:0)

这里有两种情况:

  1. 表格的列与csv列完全匹配。在这种情况下,REPLACE就是答案 - 它是LOAD DATA INFILE的关键字,请参阅doc entry

  2. 表格的列与csv列不匹配:REPLACE会导致冲突的记录被删除并重新插入,从而有效地删除了其他数据。在这种情况下,LOAD DATA INFILE本身无效,您需要另一种方法,既可以过滤您的文件,也可以通过php或其他方法进行更新。

  3. 在任何情况下,如果你想添加更多&#34;逻辑&#34;对于导入过程,也许LOAD DATA INFIlE并不是真正正确的方法,但使用临时表很可能会从数据库提供的所有优点中受益。

答案 1 :(得分:0)

LOAD DATA INFILE的工作原理

基于UNIQUE索引,LOAD DATA INFILE会插入新记录或更新现有记录(仅当REPLACE选项处于活动状态时)。

(1)关于插入

如果在db表中找不到UNIQUE索引列的csv输入值,则添加一条新记录,其中包含来自csv文件的(已定义)输入值。

(2)关于更新

如果在db表中找到UNIQUE索引列的csv输入值,那么LOAD DATA INIFILE查询将执行以下操作(按此顺序!):

  • 将新的csv值插入作为具有新PRIMARY KEY ID的新记录;
  • 从db中删除旧记录。

注意:在我的其余部分中,我将仅谈及更新部分(2)。

BEFORE INSERT-TRIGGER作为条件更新的解决方案

由于LOAD DATA INFILE在删除操作之前运行插入操作,因此您可以利用旧数据库记录在插入带有csv值的新记录时仍然存在的事实。因此,您可以根据旧记录中包含的值自定义新的输入值。其中非常酷的部分是:您甚至可以维护PRIMARY KEY字段的旧值

关键是要定义一个 BEFORE INSERT-TRIGGER ,其中包含所有需要的自定义,验证和分配:

  • 通过运行SELECT sql语句获取旧记录的值;
  • 将获取的值存储到先前定义的用户变量中;
  • 使用用户变量比较旧值和csv输入值;
  • 根据此比较:指定 PRIMARY KEY字段的旧值作为新值,将新csv值更改为旧值或其他值,如果也需要。

然后从PHP执行LOAD DATA INFILE查询。

代码

创建表语法:

CREATE TABLE `tracking` (
  `tracking_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `odanumber` int(11) DEFAULT NULL,
  `airwaybill` int(11) DEFAULT NULL,
  `courierful` varchar(100) DEFAULT NULL,
  `delstatus` tinyint(1) DEFAULT NULL,
  `deliverydate` varchar(19) DEFAULT NULL,
  PRIMARY KEY (`tracking_id`),
  UNIQUE KEY `uni_airwaybill` (`airwaybill`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

在INSERT-TRIGGER之前:

USE `tests`;

DELIMITER $$

DROP TRIGGER IF EXISTS tests.tracking_BEFORE_INSERT$$
USE `tests`$$
CREATE DEFINER = CURRENT_USER TRIGGER `tests`.`tracking_BEFORE_INSERT` BEFORE INSERT ON `tracking` FOR EACH ROW
BEGIN

    /* Define vars to store old record values. */
    SET @old_tracking_id = NULL;
    SET @old_odanumber = NULL;
    SET @old_courierful = NULL;
    SET @old_delstatus = NULL;
    SET @old_deliverydate = NULL;

    /* 
        Fetch the existing record if exists and pass 
        its values into the correspnding vars.
    */
    SELECT 
        tracking_id,
        odanumber,
        courierful,
        delstatus,
        deliverydate 
    INTO 
        @old_tracking_id,
        @old_odanumber,
        @old_courierful,
        @old_delstatus,
        @old_deliverydate 
    FROM tracking 
    WHERE airwaybill = NEW.airwaybill
    LIMIT 1;

    /* If an old record was found... */
    IF @old_tracking_id IS NOT NULL THEN

        /* ...set the new record's tracking_id to it. */
        SET NEW.tracking_id = @old_tracking_id;

        /* ...and if delstatus are the same... */
        IF NEW.delstatus = @old_delstatus THEN

            /* ...maintain the old record values. */
            SET NEW.odanumber = @old_odanumber;
            SET NEW.courierful = @old_courierful;
            SET NEW.deliverydate = @old_deliverydate;

        END IF;

    END IF;

END$$
DELIMITER ;

CSV文件(tracking.csv)

odanumber,airwaybill,"courierful",delstatus,"deliverydate"
19,1,abc,0,2017-04-31
25,2,def,1,2017-05-31
103,3,ghi,1,2017-06-31
324,4,jkl,1,2017-07-31
564,5,mno,0,2017-08-31

LOAD DATA INFILE函数(从PHP调用)

LOAD DATA INFILE "<PATH-TO>/tracking.csv"
REPLACE
INTO TABLE tests.tracking
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
IGNORE 1 LINES
(odanumber, airwaybill, courierful, delstatus, deliverydate);

说明:

*)关于LOAD DATA INFILE,可能会遇到错误:

  

ERROR 1290(HY000):MySQL服务器正在运行   --secure-file-priv选项,因此无法执行此语句

这意味着:LOAD DATA INFILE无权读取csv文件。因此,您必须自己在数据库的配置文件(my.cnf或my.ini)中设置 secure-file-priv 。像这样:

[mysqld]
secure-file-priv = "<PATH-TO-FOLDER-CONTAINING-THE-CSV-FILES>/"

*)您无法定义运行LOAD DATA INFILE的存储过程。

最后,还有其他涉及临时表的解决方案,毫无疑问,它们可以完美地工作。其中一个在this great article中呈现。因此,触发解决方案只是另一种方法。

祝你好运!