/posts  ·   /talks  ·   /boat  ·   /#30DayMapChallenge 2020 | 2022  ·  /About

ArcSDE objectid values

Some distant years ago working on an ArcSDE database in PostgreSQL I needed to manipulate SDE-managed tables straight in the database and not through ArcGIS desktop or any of its toolsets at the same time making it possible for desktop users do the same for the same datasets. And by manage I mean your usual insert/update/delete commands. It’s really straight-forward but if you need your data to be usable through ArcGIS afterwards as well then you really need to keep an eye on the objectid values of tables (or … feature classes).

After working through some possible solutions (for example creating the table in the database and registering it with SDE) the simplest and cleanest solution which would be easily migratable through different environments (dev->test->..->production) ended up being db column default values.

The whole process then consisted of creating your tables/feature classes with ArcGIS desktop tools (e.g through recordset XML imports which were stored in a version control system) and then running some extra SQL to set up things on the database side. Making life easier for DB administrators.

Revisiting the past now because somehow I stumbled upon the, ummmmm…. syntactical complexity for aquiring objectids in SQLServer powered SDE database as compared to PostgreSQL.

NOTE: I’m not saying this is the best (or even the only) way of achieving the possibility of managing your data flow in a database in accordance with SDE rules so the whole thing will not blow up and render your dataset unusable for ArcGIS. Please be advised that there are dangers involved and use this approach only at your own risk!

NOTE 2: I’m writing the following PostgreSQL queries by heart - they should be correct to as much as I can remember. Still, as I don’t have access to PostgreSQL powered SDE database at the moment I can’t test what I’m saying and I do apologize for any errors and omissions. If there’s anything amiss please drop me a line.

If you’re interested then the official primer can be found here

sde i-tables

First off - the i-tables in a SDE database. Those basically act as DB sequences but are realized rather as autocreated tables with two supporting functions, one for getting oid values and one for returning unused oid values. Not really sure why, but my guess is that this architectural choice as opposed to using native DB based sequences has to do with the possibility to copy-paste your dataset over different database brands (and versions) including the Esri’s own file geodatabase format. Although, if I remember correctly - this reason actually doesn’t make sense because any time you copy-paste a a table or feature class its oid sequence is rewritten anyway as 1-based ( or some cases 0-based) and gaps filled - so it really does not act as an object identifier as the name suggests.

So any time you create a table/feature class in ArcGIS desktop a corresponding i-table with its support functions is created in your RDBMS. SDE differentiates between the myriad of them by adding an incremental number to the name. For example: assuming your table (or feature class) is in a db schema is called foo, you’d end up with a table foo.i<some_number>, and functions foo.i<some_number>_get_ids(*args) and foo.i<some_number>_return_ids(*args). The <some_number> variable is derived from the sde system table called sde.sde_table_registry and maps to the corresponding registration_id value for that table. In order to find the registration_id value for a table called bar you’d execute a query:

select registration_id
from sde.sde_table_registry
where table_name = 'bar';

Now it gets tricky if we have tables (… or feature classes) with the same name in different DB schemas. SDE has notion of RDBMS schemas but readily confuses them with database users (or roles). This means that in order to be able to create a database table foo.bar using ArcGIS you’d actually need to use a sde connection with the username foo. Which in turn means that the previous query taking into account also the db schema (or in reality - the table’s “owner”) would be:

select registration_id
from sde.sde_table_registry
where table_name = 'bar' and schema = 'foo';

Although then again, even this ownership of a relation is rather on a logical not physical db level - the ownership is not reinforced in the RDBMS. It’s simply a column that sometimes is used as a DB username and sometimes a DB schema. As it gets very messy and confusing and error-prone for ArcGIS very fast so better stick with the idea that DB schema = DB username = most probably DB relation owner because the schema should be owned by that user, and don’t ask any difficult questions. And… if this was not enough - if you’re working with SQLServer instead of PostgreSQL then you have to use owner instead of schema in the previous query.

But coming back to the original subject. The same table ( sde.sde_table_registry) will also tell you the name of the objectid column for a relation. Which usually is objectid. Except for those cases when it isn’t.

Anyway. Now that we have a registration_id for the table / feature class ( imagine it’s 15 for example for our foo.bar table) we’re interested in, we can do a

select *
from foo.i15

which returns something in the line of

id_type base_id num_ids last_id
2 5342 -1 5342

Most probably the only point to make here is the base_id column which tells us that the last number that was pulled out of this pseudo-sequence was 5342 and id_type which in the case of an objectid-kinda-rowid-type seems to be 2.

We’re not going to go much more into detail with this as most probably hand-managing this table will bring about apocalypse for the SDE itself and other ArcGIS products depending on it. If you’re interested then there’s some official documentation available for this on the inter-webs aswell.

Instead we’ll use the aforementioned function to retrieve the row identifier values.

Getting fresh objectids

In order to reserve a fresh unused objectid, the simplest way is to ask for it from the corresponding i-getid-function. It takes two parameters - the id type and the number of ids you’d like to reserve.

Knowing the table’s registration id (our foo.bar was 15 which we established before) we can do (PostgreSQL-style)

select foo.i15_get_ids(2, 1).*;

returning

o_base_id o_num_ids
5343 1

so now we know that we have 1 objectid value (5543) that we can use without confusing the SDE. If you need 10 fresh objectids then simply ask for 10:

select foo.i15_get_ids(2, 10).*;

returning

o_base_id o_num_ids
5344 10

Returning unused objectids

Unless you’re planning on having more rows in your table / feature class than the max 32bit integer, then don’t. And if you are still planning to, then you could be better off with not using ArcSDE at all.

Wrapping up

To achieve no more further hassle and get on with your inserts in the DB and letting data be managed in desktop aswell, the simplest solution (for PostgreSQL) to get default next objectid is to wrap this up in a function like

drop function if exists public.i_get_objectid(varchar, varchar);

create function public.i_get_objectid(table_schema varchar, table_name varchar)
returns integer as
$$
declare
    oid integer;
    rid integer;
    --safeguarding against possible injection
    _table_name regclass := format('%I.%I', table_schema, table_name);
begin
    execute '
        select registration_id as rid
        from sde.sde_table_registry
        where schema = $1
        and table_name = $2'
        into rid
        using table_schema, table_name;
    if rid is null then
        raise undefined_table
            using message = format(
                'Relation "%s.%s" is not registered with SDE!',
                table_schema, table_name
            );
    end if;
    execute
        format(
            'select o_base_id from %s.i%s_get_ids(2, 1)',
            table_schema, rid
        )
    into oid;
    return oid:
end;
$$
languge plpgsql security invoker;
comment on function public.i_get_objectid(varchar, varchar) is
    'Get new ESRI objectid for table in schema. Will not check for invoker''s '
    'INSERT privilege for relation but does check for input relation''s existence '
    'and registration status with SDE.';

and then, granted that all the execution privileges have been set accordingly

we can simply

alter table foo.bar
    alter column objectid set default public.i_get_objectid('foo', 'bar');

to reduce the hassle during in-database inserts to this table.

Why?

This why is not actually about why this db-default-value driven approach should be used. Unless there’s no other possibility - don’t use it :). And with the availability (and usability) of open source GIS software today there’s always one other option - stop using ArcSDE altogether.

This why is more about the SQLServer thing I was talking about before. Remember, in PG we could simply

select foo.i15_get_ids(2, 10).*;

Right? In SQLServer this same result is achieved by

DECLARE @o_baseid int, @o_numobtained int;
EXEC foo.i15_get_ids
    @id_type = 2, @num_requested_ids = 10,
    @base_id = @o_baseid OUTPUT, @num_obtained_ids = @o_numobtained OUTPUT;
SELECT @o_baseid as baseid, @o_numobtained as numobtained;

So, yes…