如何有条件地插入或替换 SQLite 中的一行?

Posted

技术标签:

【中文标题】如何有条件地插入或替换 SQLite 中的一行?【英文标题】:How to conditionally INSERT OR REPLACE a row in SQLite? 【发布时间】:2018-03-03 02:49:03 【问题描述】:

我想插入或替换_on_condition。如果不满足条件,请勿插入或更换。这可能吗?

对于我的项目,我目前有两个数据收集过程。一个速度很快,但不能捕获所有内容。另一个很慢,但能抓住一切。通过快速处理,我几乎可以实时获取数据。使用慢速,我在一天结束时使用批处理获取数据。

我的问题是:有时快速过程会在慢速过程之前“完成”记录(意味着不再需要更新),而在夜间批处理过程中,“完成”记录会被缓慢进程的批量数据中的过时“待处理”记录所取代。

我想要的是类似于以下伪代码的条件检查:

If(record_is_not_complete or does_not_exist) 
 INSERT or REPLACE; 
Else 
 do_nothing and move_to_the_next; 

如果我从一个标准的INSERT OR REPLACE 示例开始:

INSERT OR REPLACE INTO UserProgress (id, status, level) 
  VALUES (1, 'COMPLETE', 5);

这应该会在 UserProgress 表中产生一行,其中包含条目 [1,COMPLETE,5]。

如果出现以下情况:

INSERT OR REPLACE INTO UserProgress (id, status, level) 
  VALUES (1, 'PENDING', 4);

我希望它跳过这个,因为已经有一条 COMPLETE 记录。

我确定这是一个重复的问题。但真的是这样吗?这个问题有很多答案,我不确定哪个是最好的方法。看看我发现的所有这些例子:

我可以尝试添加CASE 语句,有人告诉我它等同于IF-THEN-ELSE 语句。 As done in this example.

我可以尝试在VALUES 中使用SELECTCOALESCE 语句。 As done in this example.

我什至可以尝试使用SELECT WHERE 语句。 As done in this example.

我可以尝试使用LEFT JOIN 语句。 As done in this example.

这对 SQLite 来说很棒。似乎有多种方法可以给同一只猫剥皮。我是一个新手,我现在很困惑。目前尚不清楚我应该使用哪种方法。

我正在寻找一种可以在一条 sql 语句中完成的解决方案。

* 更新 *

我找到了两个事务的解决方案。我仍在寻找单一交易解决方案。

这可行,但使用两个事务:

 public void Create(IEnumerable<UserProgress> items)
        
            var sbFields = new StringBuilder();
            sbFields.Append("ID,");
            sbFields.Append("STATUS,");
            sbFields.Append("LEVEL,");

            int numAppended = 3;

            var sbParams = new StringBuilder();
            for (int i = 1; i <= numAppended; i++)
            
                sbParams.Append("@param");
                sbParams.Append(i);

                if (i < numAppended)
                
                    sbParams.Append(", ");
                
            

            // attempting this solution: https://***.com/questions/2251699/sqlite-insert-or-replace-into-vs-update-where

            // first insert the new stuff.
            using (var command = new SQLiteCommand(Db))
                           

                command.CommandText = "INSERT OR IGNORE INTO USERPROGRESS (" + sbFields + ") VALUES(" + sbParams + ")";

                command.CommandType = CommandType.Text;

                using (var transaction = Db.BeginTransaction())
                
                    foreach (var user in items)
                    
                        command.Parameters.Add(new SQLiteParameter("@param1", user.Id));
                        command.Parameters.Add(new SQLiteParameter("@param2", user.Status));
                        command.Parameters.Add(new SQLiteParameter("@param3", user.Level));

                        command.ExecuteNonQuery();
                    

                    transaction.Commit();
                
            

            using (var command = new SQLiteCommand(Db))
            
                string parameterized = "";

                for (int i = 1; i <= 3; i++)
                
                    parameterized += _columnNames[i - 1] + "=" + "@param" + i;

                    if (i != 3)
                        parameterized += ",";
                

                command.CommandText = "UPDATE USERPROGRESS SET " + parameterized + " WHERE ID=@param1 AND STATUS !='COMPLETE'";

                command.CommandType = CommandType.Text;

                using (var transaction = Db.BeginTransaction())
                
                    foreach (var user in items)
                    
                        command.Parameters.Add(new SQLiteParameter("@param1", user.Id));
                        command.Parameters.Add(new SQLiteParameter("@param2", user.Status));
                        command.Parameters.Add(new SQLiteParameter("@param3", user.Level));

                        command.ExecuteNonQuery();
                    

                    transaction.Commit();
                
            
        

【问题讨论】:

你应该在 for-each 循环之前调用command.Parameters.Clear(); 【参考方案1】:

SQL

INSERT OR REPLACE INTO UserProgress (id, status, level) SELECT 'id 值', '状态值', '等级值' WHERE NOT EXISTS (SELECT * FROM UserProgress WHERE id = 'id 值' AND status = 'COMPLETE');

(其中id值状态值级别值 酌情插入)

演示

http://www.sqlfiddle.com/#!5/a9b82d/1

说明

EXISTS部分用于查找表中是否存在具有相同id且状态值为'COMPLETE'的行。如果匹配此条件,则不执行任何操作(由于WHERE NOT)。否则,具有指定 id 的行要么在不存在时插入,要么在存在时使用指定的值更新(由于 INSERT OR REPLACE)。

【讨论】:

这对我的十行左右的小测试非常有用。你知道我是否会遇到大量行的任何性能问题? 我看不出如何真正提高性能,因为在 WHERE 子句中所做的所有事情都是通过其主键查找行,无论如何都需要执行插入/更新。 (不是 SQLite 专家,但希望数据库能够优化,因此每次查询只进行一次行查找)。一定要保持赏金开放,以防有人建议您可以比较其他方法。【参考方案2】:

IF 语句有三种形式:IF-THEN、IF-THEN-ELSE 和 IF-THEN-ELSIF。

在 SQLite 中使用 If 语句的语法是:

IF expr THEN statement-list
[ELSIF expr THEN statement-list ]*
[ELSE statement-list]
END IF

链接:https://www.sqliteconcepts.org/pl_if.html

【讨论】:

投反对票,因为链接似乎是希望的语法而不是实际语言。 AFAIK,SQLite 使用 CASE 语句而不是此处显示的语法。 sqlite.org/lang_expr.html#case

以上是关于如何有条件地插入或替换 SQLite 中的一行?的主要内容,如果未能解决你的问题,请参考以下文章

sed如何在文件的最后 插入一行文字

如何有条件地替换Collection中的值,例如replaceIf(Predicate<T>)?

Spark:有条件地将 col1 值替换为 col2 [重复]

Android SQLite:插入/替换方法中的 nullColumnHack 参数

使用 dplyr [重复] 有条件地将一列中的值替换为另一列中的值

将数据插入SQLite表后如何获取最后一行ID [重复]