Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally INSERT OR REPLACE a row in SQLite?

I would like to insert or replace_on_condition. If the condition is not satisfied, do not insert or replace. Is this possible?

For my project, I currently have two data collection processes. One is fast but doesn't catch everything. The other is slow but catches everything. With the fast process, I get data almost in real time. With the slow one I get data using a batch process at the end of day.

My issue is this: sometimes the fast process will "Complete" a record (meaning it no longer needs to be updated) BEFORE the slow process, and later in the day during the nightly batch process, the "Complete" record will get replaced by an outdated "Pending" record found in the slow process's bulk data.

What I would like is a conditional check that goes something like this pseudocode:

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

If I begin with a standard INSERT OR REPLACE example:

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

Which should result in a row in UserProgress table with entry [1,COMPLETE,5].

If the following occurs:

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

I would like for it to skip this, because there is already a COMPLETE record.

I'm sure this is a duplicate question. But is it really? There are so many answers to this question I am not sure which is the best approach. Look at all these examples that I found:

I can attempt to add a CASE statement, I have been told it is equivalent to a IF-THEN-ELSE statement. As done in this example.

I can attempt to use SELECT or COALESCE statement in the VALUES. As done in this example.

I can even attempt to use a SELECT WHERE statement. As done in this example.

I can attempt to use an LEFT JOIN statement. As done in this example.

Which is great for SQLite. There appears to be multiple ways to skin the same cat. Me being a novice I am now confused. It isn't clear which approach I should be using.

I am looking for a solution that can be done in one sql statement.

* UPDATE *

I found a two transaction solution. I'm still on the hunt for a single transaction solution.

This works, but uses two transactions:

 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://stackoverflow.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();
                }
            }
        }
like image 877
sapbucket Avatar asked Sep 21 '17 16:09

sapbucket


People also ask

What does insert or ignore do SQLite?

insert or ignore ... will insert the row(s) and ignore rows which violation any constraint (other than foreign key constraints).

How do you populate a table in SQLite?

To insert data into a table, you use the INSERT statement. SQLite provides various forms of the INSERT statements that allow you to insert a single row, multiple rows, and default values into a table. In addition, you can insert a row into a table using data provided by a SELECT statement.

How does insert and replace work?

You can use the INSERT OR REPLACE statement to write new rows or replace existing rows in the table. The syntax and behavior of the INSERT OR REPLACE statement is similar to the INSERT statement. Unlike the INSERT statement, the INSERT OR REPLACE statement does not generate an error if a row already exists.


1 Answers

SQL

INSERT OR REPLACE INTO UserProgress (id, status, level) SELECT 'id value', 'status value', 'level value' WHERE NOT EXISTS (SELECT * FROM UserProgress WHERE id = 'id value' AND status = 'COMPLETE');

(where id value, status value and level value are inserted as appropriate)

Demo

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

Explanation

The EXISTS part is used to find out whether there is an existing row in the table with the same id whose status value is 'COMPLETE'. If this condition is matched, nothing is done (due to the WHERE NOT). Otherwise, the row with the specified id is either INSERTed if not present or UPDATEd with the specified values if present (due to the INSERT OR REPLACE).

like image 103
Steve Chambers Avatar answered Oct 03 '22 09:10

Steve Chambers