Back

Tip 42. Solving Row Changed Between Retrieve and Update Problems

The error 'Row changed between retrieve and update.' can be quite common and very annoying.

It essentially means that one or more updateable columns are have changed in the database since the row or rows were retrieved. There are many causes of the error and it can be tricky to track down.

Things to look for

  • Datetime Columns
  • Float Columns
  • Altering the Datawindow Status Manually
  • Altering the Database using Embedded SQL
  • Database Default values.
  • Database Triggers.
You can minimize occurrences of this error by using the ReselectRow() function after any changes to the database that are done using embedded sql or database defaults and triggers.

Also Setting the 'Update Properties' of the datawindow to use where clause based on 'Key Columns' instead of 'Key and Updateable Columns' will reduce the chances of the error. Unfortunately this does not always fit your project requirements.

To try to resolve this problem I have written some code to do two things.
  1. Minimize the effect of the error on users and let them continue
  2. Help me track offending columns to remove the error from future issues.
The code is placed in the dberror event of the datawindow object preferably and ancestor datawindow.
It uses a datastore to keep a copy of the problem row and reselects the data from the database. Each updateable column is checked with the original datawindow value. Differences are reset and the columns are shown in a new error message.

The sample code uses my datastore object. See A Datastore that Destroys Itself.

//---------------------------------------------------------------------------
//  Event Name : dberror for ua_dw_std
//     Purpose : Shows Row retreive error columns
//        Sets : None
//---------------------------------------------------------------------------
string   ls_col_name, ls_col_type, ls_column_id
Long     ll_index, ll_col_count
string   ls_update, ls_changed_columns
boolean  lb_changed
string   ls_db_error_description

			// Row changed between retrieve and update
IF sqldbcode = -3 THEN
   IF IsValid(this) THEN
      nvo_datastore lnvo_datastore
      lnvo_datastore.ds.DataObject = this.DataObject
      IF lnvo_datastore.ds.SetTransObject(SQLCA) = 1 THEN
         IF this.RowsCopy ( row, row, Primary!, lnvo_datastore.ds, 1, &
               Primary!) = -1 THEN
            MessageBox("Database Error", "row cannot be copied")
         END IF

         IF lnvo_datastore.ds.ReSelectRow(1) = -1 THEN
            ls_db_error_description = "The current datawindow has" + &
               " become out of sync with the database.~r~n" + &
               "Data could not be re-retrieved.~r~n" + &
               "You should close this window and try again."
            MessageBox("Database Error", ls_db_error_description)
         ELSE
            ls_changed_columns = ""

               // Traverse dw's columns making each validation error message
               // more user-friendly.
            ll_col_count = Long(Describe("Datawindow.Column.Count"))
            FOR ll_index=1 TO   ll_col_count
               ls_column_id = "#" + String(ll_index)
               ls_update = Describe(ls_column_id + ".Update")

               IF ls_update = "No" THEN
                  CONTINUE
               END IF

               // Get Column Details
               ls_col_name = Describe(ls_column_id + ".Name")
               ls_col_type = Describe(ls_column_id + ".ColType")

               lb_changed = FALSE

               // Reset the data in the original Datawindow if it has changed.
               CHOOSE CASE Upper(Left(ls_col_type, 5))
                  CASE 'CHAR('
                     string ls_one, ls_two
                     IF this.GetItemString(row, ll_index, Primary!, TRUE) <> &
                           lnvo_datastore.ds.GetItemString(1, ll_index) THEN
                        lb_changed = TRUE
                        this.SetItem(row, ll_index, &
                           lnvo_datastore.ds.GetItemString(1, ll_index))
                     END IF
                  CASE 'DATE'
                     IF this.GetItemDate(row, ll_index, Primary!, TRUE) <> &
                           lnvo_datastore.ds.GetItemDate(1, ll_index) THEN
                        lb_changed = TRUE
                        this.SetItem(row, ll_index, &
                           lnvo_datastore.ds.GetItemDate(1, ll_index))
                     END IF
                  CASE 'DATET'
                     IF this.GetItemDateTime(row, ll_index, Primary!, TRUE) <> &
                           lnvo_datastore.ds.GetItemDateTime(1, ll_index) THEN
                        lb_changed = TRUE
                        this.SetItem(row, ll_index, &
                           lnvo_datastore.ds.GetItemDateTime(1, ll_index))
                     END IF
                  CASE 'DECIM', 'NUMBE'
                     IF this.GetItemNumber(row, ll_index, Primary!, TRUE) <> &
                           lnvo_datastore.ds.GetItemNumber(1, ll_index) THEN
                        lb_changed = TRUE
                        this.SetItem(row, ll_index, &
                           lnvo_datastore.ds.GetItemNumber(1, ll_index))
                     END IF
                  CASE 'TIME', 'TIMES'
                     IF this.GetItemTime(row, ll_index, Primary!, TRUE) <> &
                           lnvo_datastore.ds.GetItemTime(1, ll_index) THEN
                        lb_changed = TRUE
                        this.SetItem(row, ll_index, &
                           lnvo_datastore.ds.GetItemTime(1, ll_index))
                     END IF
               END CHOOSE

               IF lb_changed THEN
                  IF Len(ls_changed_columns) > 0 THEN
                     ls_changed_columns += ", "
                  END IF

                  ls_changed_columns += ls_col_name

                  // Set the flag to say that it has not been changed.
                  this.SetItemStatus(row,ls_col_name, Primary!, NotModified!)
               END IF
            NEXT

            IF Len(ls_changed_columns) > 0 THEN
               ls_db_error_description = &
                  "The current datawindow has become out of sync with the database.~r~n" + &
                  "These column(s) have been reset~r~n" + ls_changed_columns + &
                  "~r~nYou may need to re-enter your changes and save again."
               MessageBox("Database Error",ls_db_error_description, Exclamation!)
                  // Stop the Powerbuilder error message
               RETURN 1
            END IF
         END IF
      END IF
   END IF
END IF

Added before 01 Jan 2000

Back