19

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();
                }
            }
        }
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
sapbucket
  • 6,795
  • 15
  • 57
  • 94

1 Answers1

25

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 so, 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).

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • That worked great for my small test with ten or so rows. Do you know if I will have any performance issues with a large number of rows? – sapbucket Sep 27 '17 at 22:50
  • 1
    I don't see how performance could really be improved since all that's being done in the WHERE clause is to look up the row by its primary key, which is needed anyway to do the insert/update. (Not an expert on SQLite but would expect the database to optimise so the row lookup is only done once per query). By all means keep the bounty open in case other methods might be suggested that you can then compare with. – Steve Chambers Sep 28 '17 at 06:43