diff --git a/lib/db-ctl-base.c b/lib/db-ctl-base.c
index 6ef3e7001640..c15e1767b24d 100644
--- a/lib/db-ctl-base.c
+++ b/lib/db-ctl-base.c
@@ -251,15 +251,26 @@ get_row_by_id(struct ctl_context *ctx,
     }
 
     const struct ovsdb_idl_row *referrer = NULL;
-    ovs_assert(id->name_column->type.value.type == OVSDB_TYPE_VOID);
 
-    enum ovsdb_atomic_type key = id->name_column->type.key.type;
-    if (key == OVSDB_TYPE_INTEGER) {
+    /* Figure out the 'key' and 'value' types for the column that we're going
+     * to look at.  One of these ('name_type') is the type of the name we're
+     * going to compare against 'record_id'.  */
+    enum ovsdb_atomic_type key, value, name_type;
+    if (!id->key) {
+        name_type = key = id->name_column->type.key.type;
+        value = OVSDB_TYPE_VOID;
+    } else {
+        key = OVSDB_TYPE_STRING;
+        name_type = value = id->name_column->type.value.type;
+    }
+
+    /* We only support integer and string names (so far). */
+    if (name_type == OVSDB_TYPE_INTEGER) {
         if (!record_id[0] || record_id[strspn(record_id, "0123456789")]) {
             return NULL;
         }
     } else {
-        ovs_assert(key == OVSDB_TYPE_STRING);
+        ovs_assert(name_type == OVSDB_TYPE_STRING);
     }
 
     const struct ovsdb_idl_class *class = ovsdb_idl_get_class(ctx->idl);
@@ -269,19 +280,34 @@ get_row_by_id(struct ctl_context *ctx,
                                                                id_table);
          row != NULL;
          row = ovsdb_idl_next_row(row)) {
-        const struct ovsdb_datum *name = ovsdb_idl_get(
-            row, id->name_column, key, OVSDB_TYPE_VOID);
-        if (name->n == 1) {
-            const union ovsdb_atom *atom = &name->keys[0];
-            if (key == OVSDB_TYPE_STRING
-                ? !strcmp(atom->string, record_id)
-                : atom->integer == strtoll(record_id, NULL, 10)) {
-                if (referrer) {
-                    ctl_fatal("multiple rows in %s match \"%s\"",
-                              id_table->name, record_id);
-                }
-                referrer = row;
+        /* Pick out the name column's data. */
+        const struct ovsdb_datum *datum = ovsdb_idl_get(
+            row, id->name_column, key, value);
+
+        /* Extract the name from the column. */
+        const union ovsdb_atom *name;
+        if (!id->key) {
+            name = datum->n == 1 ? &datum->keys[0] : NULL;
+        } else {
+            const union ovsdb_atom key
+                = { .string = CONST_CAST(char *, id->key) };
+            unsigned int i = ovsdb_datum_find_key(datum, &key,
+                                                  OVSDB_TYPE_STRING);
+            name = i == UINT_MAX ? NULL : &datum->values[i];
+        }
+        if (!name) {
+            continue;
+        }
+
+        /* If the name equals 'record_id', take it. */
+        if (name_type == OVSDB_TYPE_STRING
+            ? !strcmp(name->string, record_id)
+            : name->integer == strtoll(record_id, NULL, 10)) {
+            if (referrer) {
+                ctl_fatal("multiple rows in %s match \"%s\"",
+                          id_table->name, record_id);
             }
+            referrer = row;
         }
     }
     if (!referrer) {
diff --git a/lib/db-ctl-base.h b/lib/db-ctl-base.h
index e89140982d1e..29fea2bd94d9 100644
--- a/lib/db-ctl-base.h
+++ b/lib/db-ctl-base.h
@@ -240,7 +240,10 @@ void ctl_context_done(struct ctl_context *, struct ctl_command *);
 /* A way to identify a particular row in the database based on a user-provided
  * string.  If all fields are NULL, the struct is ignored.  Otherwise,
  * 'name_column' designates a column whose table is searched for rows that
- * match with the user string.  If a matching row is found, then:
+ * match with the user string.  If 'key' is NULL, then 'name_column' should be
+ * a string or integer-valued column; otherwise it should be a map from a
+ * string to one of those types and the value corresponding to 'key' is what is
+ * matched.  If a matching row is found, then:
  *
  *    - If 'uuid_column' is NULL, the matching row is the final row.
  *
@@ -249,6 +252,7 @@ void ctl_context_done(struct ctl_context *, struct ctl_command *);
  */
 struct ctl_row_id {
     const struct ovsdb_idl_column *name_column;
+    const char *key;
     const struct ovsdb_idl_column *uuid_column;
 };
 
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index d227ceeb25b3..3deebfa71f92 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -3045,18 +3045,19 @@ cmd_set_ssl(struct ctl_context *ctx)
 
 static const struct ctl_table_class tables[NBREC_N_TABLES] = {
     [NBREC_TABLE_LOGICAL_SWITCH].row_ids[0]
-    = {&nbrec_logical_switch_col_name, NULL},
+    = {&nbrec_logical_switch_col_name, NULL, NULL},
 
     [NBREC_TABLE_LOGICAL_SWITCH_PORT].row_ids[0]
-    = {&nbrec_logical_switch_port_col_name, NULL},
+    = {&nbrec_logical_switch_port_col_name, NULL, NULL},
 
     [NBREC_TABLE_LOGICAL_ROUTER].row_ids[0]
-    = {&nbrec_logical_router_col_name, NULL},
+    = {&nbrec_logical_router_col_name, NULL, NULL},
 
     [NBREC_TABLE_LOGICAL_ROUTER_PORT].row_ids[0]
-    = {&nbrec_logical_router_port_col_name, NULL},
+    = {&nbrec_logical_router_port_col_name, NULL, NULL},
 
-    [NBREC_TABLE_ADDRESS_SET].row_ids[0] = {&nbrec_address_set_col_name, NULL},
+    [NBREC_TABLE_ADDRESS_SET].row_ids[0]
+    = {&nbrec_address_set_col_name, NULL, NULL},
 };
 
 static void
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index 922ae8c4ef52..5d81b6b479de 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -1050,15 +1050,16 @@ cmd_set_ssl(struct ctl_context *ctx)
 
 
 static const struct ctl_table_class tables[SBREC_N_TABLES] = {
-    [SBREC_TABLE_CHASSIS].row_ids[0] = {&sbrec_chassis_col_name, NULL},
+    [SBREC_TABLE_CHASSIS].row_ids[0] = {&sbrec_chassis_col_name, NULL, NULL},
 
     [SBREC_TABLE_PORT_BINDING].row_ids[0] =
-    {&sbrec_port_binding_col_logical_port, NULL},
+    {&sbrec_port_binding_col_logical_port, NULL, NULL},
 
     [SBREC_TABLE_MAC_BINDING].row_ids[0] =
-    {&sbrec_mac_binding_col_logical_port, NULL},
+    {&sbrec_mac_binding_col_logical_port, NULL, NULL},
 
-    [SBREC_TABLE_ADDRESS_SET].row_ids[0] = {&sbrec_address_set_col_name, NULL},
+    [SBREC_TABLE_ADDRESS_SET].row_ids[0]
+    = {&sbrec_address_set_col_name, NULL, NULL},
 };
 
 
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
index 3bbfba7f0329..9fe3df03af66 100644
--- a/utilities/ovs-vsctl.c
+++ b/utilities/ovs-vsctl.c
@@ -2291,39 +2291,41 @@ cmd_get_aa_mapping(struct ctl_context *ctx)
 
 
 static const struct ctl_table_class tables[OVSREC_N_TABLES] = {
-    [OVSREC_TABLE_BRIDGE].row_ids[0] = {&ovsrec_bridge_col_name, NULL},
+    [OVSREC_TABLE_BRIDGE].row_ids[0] = {&ovsrec_bridge_col_name, NULL, NULL},
 
     [OVSREC_TABLE_CONTROLLER].row_ids[0]
-    = {&ovsrec_bridge_col_name, &ovsrec_bridge_col_controller},
+    = {&ovsrec_bridge_col_name, NULL, &ovsrec_bridge_col_controller},
 
-    [OVSREC_TABLE_INTERFACE].row_ids[0] = {&ovsrec_interface_col_name, NULL},
+    [OVSREC_TABLE_INTERFACE].row_ids[0]
+    = {&ovsrec_interface_col_name, NULL, NULL},
 
-    [OVSREC_TABLE_MIRROR].row_ids[0] = {&ovsrec_mirror_col_name, NULL},
+    [OVSREC_TABLE_MIRROR].row_ids[0] = {&ovsrec_mirror_col_name, NULL, NULL},
 
-    [OVSREC_TABLE_MANAGER].row_ids[0] = {&ovsrec_manager_col_target, NULL},
+    [OVSREC_TABLE_MANAGER].row_ids[0]
+    = {&ovsrec_manager_col_target, NULL, NULL},
 
     [OVSREC_TABLE_NETFLOW].row_ids[0]
-    = {&ovsrec_bridge_col_name, &ovsrec_bridge_col_netflow},
+    = {&ovsrec_bridge_col_name, NULL, &ovsrec_bridge_col_netflow},
 
-    [OVSREC_TABLE_PORT].row_ids[0] = {&ovsrec_port_col_name, NULL},
+    [OVSREC_TABLE_PORT].row_ids[0] = {&ovsrec_port_col_name, NULL, NULL},
 
     [OVSREC_TABLE_QOS].row_ids[0]
-    = {&ovsrec_port_col_name, &ovsrec_port_col_qos},
+    = {&ovsrec_port_col_name, NULL, &ovsrec_port_col_qos},
 
     [OVSREC_TABLE_SFLOW].row_ids[0]
-    = {&ovsrec_bridge_col_name, &ovsrec_bridge_col_sflow},
+    = {&ovsrec_bridge_col_name, NULL, &ovsrec_bridge_col_sflow},
 
     [OVSREC_TABLE_FLOW_TABLE].row_ids[0]
-    = {&ovsrec_flow_table_col_name, NULL},
+    = {&ovsrec_flow_table_col_name, NULL, NULL},
 
     [OVSREC_TABLE_IPFIX].row_ids[0]
-    = {&ovsrec_bridge_col_name, &ovsrec_bridge_col_ipfix},
+    = {&ovsrec_bridge_col_name, NULL, &ovsrec_bridge_col_ipfix},
 
     [OVSREC_TABLE_AUTOATTACH].row_ids[0]
-    = {&ovsrec_bridge_col_name, &ovsrec_bridge_col_auto_attach},
+    = {&ovsrec_bridge_col_name, NULL, &ovsrec_bridge_col_auto_attach},
 
     [OVSREC_TABLE_FLOW_SAMPLE_COLLECTOR_SET].row_ids[0]
-    = {&ovsrec_flow_sample_collector_set_col_id, NULL},
+    = {&ovsrec_flow_sample_collector_set_col_id, NULL, NULL},
 };
 
 static void
diff --git a/vtep/vtep-ctl.c b/vtep/vtep-ctl.c
index 05c9ef8e3dae..bbdf57fad8d7 100644
--- a/vtep/vtep-ctl.c
+++ b/vtep/vtep-ctl.c
@@ -2178,18 +2178,19 @@ cmd_set_manager(struct ctl_context *ctx)
 /* Parameter commands. */
 static const struct ctl_table_class tables[VTEPREC_N_TABLES] = {
     [VTEPREC_TABLE_LOGICAL_SWITCH].row_ids[0]
-    = {&vteprec_logical_switch_col_name, NULL},
+    = {&vteprec_logical_switch_col_name, NULL, NULL},
 
-    [VTEPREC_TABLE_MANAGER].row_ids[0] = {&vteprec_manager_col_target, NULL},
+    [VTEPREC_TABLE_MANAGER].row_ids[0]
+    = {&vteprec_manager_col_target, NULL, NULL},
 
     [VTEPREC_TABLE_PHYSICAL_PORT].row_ids[0]
-    = {&vteprec_physical_port_col_name, NULL},
+    = {&vteprec_physical_port_col_name, NULL, NULL},
 
     [VTEPREC_TABLE_PHYSICAL_SWITCH].row_ids[0]
-    = {&vteprec_physical_switch_col_name, NULL},
+    = {&vteprec_physical_switch_col_name, NULL, NULL},
 
     [VTEPREC_TABLE_LOGICAL_ROUTER].row_ids[0]
-    = {&vteprec_logical_router_col_name, NULL},
+    = {&vteprec_logical_router_col_name, NULL, NULL},
 };
 
 
