Approaching from the Bow
← Log
2026-10-06  ·  infra  ·  13 min read

A Practical Logistics Ontology in Oracle AI Database

A shipment, a truck, and a temperature sensor are different objects, but a logistics question often connects all three: Which truck carries this shipment? Which sensor is on that truck? What do its readings mean for the shipment?

This walkthrough builds a small RDF ontology in Oracle AI Database, loads example facts, runs two inference queries, and follows a shipment to its sensor.

From ontology to temperature alert: the shipment class hierarchy, the connected facts linking E001, C01, and S01, and the sensor readings that trigger the alert.

What the ontology describes

RDF represents a fact as a triple: subject → relationship → object.

Truck C01     → transports → Shipment E001
Truck C01     → has sensor → Sensor S01
Shipment E001 → destination → Madrid

Other triples describe the vocabulary rather than an individual shipment:

Same-day shipment → is a type of → Urgent shipment
Urgent shipment   → is a type of → Shipment

The second group makes inference possible. If E004 is a same-day shipment, RDFS can also classify it as urgent and as a shipment. Oracle supports this class hierarchy through its RDFS rulebase and inverse relationships through OWLPrime. Oracle: ontologies and supported vocabularies.

1. Create the RDF network and graphs

The SQL below assumes that you are connected as ONTOLOGIA to a database where this user has permission to create a private RDF network and quota on the USERS tablespace. Run it once in a fresh setup; the names already exist in our completed demo.

First create the network:

BEGIN
  SEM_APIS.CREATE_RDF_NETWORK(
    'USERS',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Then create three graphs inside it:

BEGIN
  SEM_APIS.CREATE_RDF_GRAPH(
    'LOGISTICA', NULL, NULL,
    network_owner => 'ONTOLOGIA', network_name => 'LOG_NET'
  );

  SEM_APIS.CREATE_RDF_GRAPH(
    'FLOTA', NULL, NULL,
    network_owner => 'ONTOLOGIA', network_name => 'LOG_NET'
  );

  SEM_APIS.CREATE_RDF_GRAPH(
    'SENSORES', NULL, NULL,
    network_owner => 'ONTOLOGIA', network_name => 'LOG_NET'
  );
END;
/

LOGISTICA holds shipment types and shipments; FLOTA holds truck relationships; SENSORES holds sensors and readings. The NULL table and column arguments are required for graphs in a private RDF network. Oracle: creating an RDF network, creating RDF graphs.

2. Load the vocabulary and example facts

The following statements use SPARQL INSERT DATA through Oracle's SEM_APIS.UPDATE_RDF_GRAPH. The log: prefix abbreviates https://ejemplo.org/log/; it identifies our example terms and does not cause Oracle to fetch a web page. Oracle: updating an RDF graph.

Load the shipment hierarchy and four shipments:

BEGIN
  SEM_APIS.UPDATE_RDF_GRAPH(
    'LOGISTICA',
    'PREFIX log: <https://ejemplo.org/log/>
     PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
     PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
     INSERT DATA {
       log:EnvioUrgente rdfs:subClassOf log:Envio .
       log:EnvioInternacional rdfs:subClassOf log:Envio .
       log:EnvioMismoDia rdfs:subClassOf log:EnvioUrgente .

       log:E001 rdf:type log:EnvioUrgente .
       log:E002 rdf:type log:EnvioInternacional .
       log:E003 rdf:type log:Envio .
       log:E004 rdf:type log:EnvioMismoDia .

       log:E001 log:codigoEnvio "E001" .
       log:E002 log:codigoEnvio "E002" .
       log:E003 log:codigoEnvio "E003" .
       log:E004 log:codigoEnvio "E004" .
       log:E001 log:destino "Madrid" .
     }',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Load the truck relationship. The first triple says that transporta and transportadoPor are inverse properties:

BEGIN
  SEM_APIS.UPDATE_RDF_GRAPH(
    'FLOTA',
    'PREFIX log: <https://ejemplo.org/log/>
     PREFIX owl: <http://www.w3.org/2002/07/owl#>
     INSERT DATA {
       log:transporta owl:inverseOf log:transportadoPor .
       log:C01 log:transporta log:E001 .
       log:C01 log:codigoCamion "C01" .
     }',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Finally, connect the truck to its sensor and record the shipment's temperature limit and five readings:

BEGIN
  SEM_APIS.UPDATE_RDF_GRAPH(
    'SENSORES',
    'PREFIX log: <https://ejemplo.org/log/>
     PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
     PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
     INSERT DATA {
       log:C01 log:tieneSensor log:S01 .
       log:S01 rdf:type log:SensorTemperatura .
       log:S01 log:codigoSensor "S01" .
       log:E001 log:temperaturaMaximaC "8"^^xsd:decimal .

       log:L001 log:deSensor log:S01 .
       log:L001 log:temperaturaC "4"^^xsd:decimal .
       log:L001 log:instante "2026-10-06T07:00:00Z"^^xsd:dateTime .

       log:L002 log:deSensor log:S01 .
       log:L002 log:temperaturaC "10"^^xsd:decimal .
       log:L002 log:instante "2026-10-06T07:10:00Z"^^xsd:dateTime .

       log:L003 log:deSensor log:S01 .
       log:L003 log:temperaturaC "11"^^xsd:decimal .
       log:L003 log:instante "2026-10-06T07:13:00Z"^^xsd:dateTime .

       log:L004 log:deSensor log:S01 .
       log:L004 log:temperaturaC "9"^^xsd:decimal .
       log:L004 log:instante "2026-10-06T07:17:00Z"^^xsd:dateTime .

       log:L005 log:deSensor log:S01 .
       log:L005 log:temperaturaC "5"^^xsd:decimal .
       log:L005 log:instante "2026-10-06T07:20:00Z"^^xsd:dateTime .
     }',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

This is a deliberately small model. We formally defined a class hierarchy and an inverse property; properties such as tieneSensor are used as explicit relationships without adding domain and range axioms.

3. Build the inferred graphs

The example's inference queries need inferred graphs created after loading their source triples:

BEGIN
  SEM_APIS.CREATE_INFERRED_GRAPH(
    'LOG_RDFS',
    SEM_MODELS('LOGISTICA'),
    SEM_RULEBASES('RDFS'),
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );

  SEM_APIS.CREATE_INFERRED_GRAPH(
    'FLOTA_OWL',
    SEM_MODELS('FLOTA'),
    SEM_RULEBASES('OWLPRIME'),
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Oracle materializes deductions for each specified graph and rulebase combination. If those source triples change later, the inferred graph must be kept current before relying on its deductions. Oracle: creating inferred graphs.

Example 1: Which resources are shipments?

The following query asks for instances of Envio while applying RDFS inference:

SELECT id_envio
FROM TABLE(SEM_MATCH(
  'PREFIX log: <https://ejemplo.org/log/>
   PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
   SELECT ?id_envio
   WHERE {
     ?envio rdf:type log:Envio .
     ?envio log:codigoEnvio ?id_envio .
   }',
  SEM_MODELS('LOGISTICA'),
  SEM_RULEBASES('RDFS'),
  NULL, NULL, NULL, NULL, NULL, NULL,
  'ONTOLOGIA', 'LOG_NET'
));

Expected result: E001, E002, E003, and E004. E004 was explicitly typed only as EnvioMismoDia; RDFS follows EnvioMismoDia → EnvioUrgente → Envio.

The inverse-property example uses OWLPrime:

SELECT envio$_suffix AS shipment_id,
       camion$_suffix AS truck_id
FROM TABLE(SEM_MATCH(
  'PREFIX log: <https://ejemplo.org/log/>
   SELECT ?envio ?camion
   WHERE { ?envio log:transportadoPor ?camion . }',
  SEM_MODELS('FLOTA'),
  SEM_RULEBASES('OWLPRIME'),
  NULL, NULL, NULL, NULL, NULL, NULL,
  'ONTOLOGIA', 'LOG_NET'
));

Expected result: E001 and C01. We stored "C01 transports E001"; the inverse property supports the reverse question.

Example 2: Follow the shipment to its sensor

This query needs no inference. It matches explicit facts across the three graphs:

SELECT id_envio, destino, id_camion, id_sensor
FROM TABLE(SEM_MATCH(
  'PREFIX log: <https://ejemplo.org/log/>
   SELECT ?id_envio ?destino ?id_camion ?id_sensor
   WHERE {
     ?envio log:codigoEnvio ?id_envio .
     ?envio log:destino ?destino .
     ?camion log:transporta ?envio .
     ?camion log:codigoCamion ?id_camion .
     ?camion log:tieneSensor ?sensor .
     ?sensor log:codigoSensor ?id_sensor .
   }',
  SEM_MODELS('LOGISTICA', 'FLOTA', 'SENSORES'),
  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
  'ONTOLOGIA', 'LOG_NET'
));

Expected result: E001 | Madrid | C01 | S01.

Names beginning with ? are variables. Repeating ?envio means that each matching line refers to the same shipment. SEM_MODELS supplies the three graphs, and TABLE(SEM_MATCH(...)) exposes the SPARQL result as SQL rows. This query's rulebase argument is NULL, so it uses the explicit relationships we loaded. Oracle: querying RDF data with SEM_MATCH.

Where the temperature alert fits

E001 has an 8 °C limit. Its sensor readings are 4 °C at 07:00, then 10 °C, 11 °C, and 9 °C at 07:10, 07:13, and 07:17 UTC, followed by 5 °C at 07:20.

The RDF relationships establish which sensor belongs to the truck carrying E001 and which temperature limit applies to E001. An analytic SQL rule can then determine that three consecutive high readings lasted from 07:10 to 07:17, peaking at 11 °C. The duration rule is an operational calculation; the ontology supplies its meaning and context.

There is an important boundary to this small example: it assumes C01 carries E001 throughout the readings. A production model should record when a shipment was assigned to a truck. Otherwise, readings from a later trip could be attributed to the wrong shipment.

Takeaway

This example shows two distinct uses of an ontology. Inference derives broader shipment types and an inverse transport relationship. Graph traversal follows the explicitly stored links among a shipment, truck, and sensor. Both are useful, but they do different jobs—and the time-based alert remains a separate calculation over the connected data.


Una ontología práctica de logística en Oracle AI Database

Un envío, un camión y un sensor de temperatura son objetos distintos, pero una pregunta logística suele conectar los tres: ¿qué camión transporta este envío? ¿Qué sensor lleva ese camión? ¿Qué significan sus lecturas para el envío?

En este tutorial construimos una pequeña ontología RDF en Oracle AI Database, cargamos datos de ejemplo, ejecutamos dos consultas con inferencia y seguimos la relación entre un envío y su sensor.

De la ontología a la alerta térmica: la jerarquía de clases de envío, los hechos conectados entre E001, C01 y S01, y las lecturas del sensor que disparan la alerta.

Qué describe la ontología

RDF representa un hecho como una tripleta: sujeto → relación → objeto.

Camión C01  → transporta → Envío E001
Camión C01  → tiene sensor → Sensor S01
Envío E001 → destino → Madrid

Otras tripletas describen el vocabulario, en lugar de un envío concreto:

Envío del mismo día → es un tipo de → Envío urgente
Envío urgente       → es un tipo de → Envío

Este segundo grupo permite hacer inferencias. Si E004 es un envío del mismo día, RDFS también puede clasificarlo como urgente y como envío. Oracle admite esta jerarquía de clases mediante su conjunto de reglas RDFS y las relaciones inversas mediante OWLPrime. Oracle: ontologías y vocabularios admitidos.

1. Crear la red RDF y los grafos

El SQL siguiente supone que estamos conectados como ONTOLOGIA a una base donde ese usuario tiene permiso para crear una red RDF privada y cuota en el tablespace USERS. Hay que ejecutarlo una sola vez en un entorno nuevo; estos nombres ya existen en nuestra demostración.

Primero creamos la red:

BEGIN
  SEM_APIS.CREATE_RDF_NETWORK(
    'USERS',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Después creamos tres grafos dentro de ella:

BEGIN
  SEM_APIS.CREATE_RDF_GRAPH(
    'LOGISTICA', NULL, NULL,
    network_owner => 'ONTOLOGIA', network_name => 'LOG_NET'
  );

  SEM_APIS.CREATE_RDF_GRAPH(
    'FLOTA', NULL, NULL,
    network_owner => 'ONTOLOGIA', network_name => 'LOG_NET'
  );

  SEM_APIS.CREATE_RDF_GRAPH(
    'SENSORES', NULL, NULL,
    network_owner => 'ONTOLOGIA', network_name => 'LOG_NET'
  );
END;
/

LOGISTICA guarda tipos de envío y envíos; FLOTA, las relaciones de los camiones; y SENSORES, los sensores y sus lecturas. Los argumentos NULL para tabla y columna son los que corresponden a los grafos de una red RDF privada. Oracle: creación de una red RDF, creación de grafos RDF.

2. Cargar el vocabulario y los datos de ejemplo

Las instrucciones siguientes usan INSERT DATA de SPARQL mediante SEM_APIS.UPDATE_RDF_GRAPH. El prefijo log: abrevia https://ejemplo.org/log/: identifica los términos del ejemplo y no hace que Oracle visite esa página web. Oracle: actualización de un grafo RDF.

Cargamos la jerarquía de tipos y cuatro envíos:

BEGIN
  SEM_APIS.UPDATE_RDF_GRAPH(
    'LOGISTICA',
    'PREFIX log: <https://ejemplo.org/log/>
     PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
     PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
     INSERT DATA {
       log:EnvioUrgente rdfs:subClassOf log:Envio .
       log:EnvioInternacional rdfs:subClassOf log:Envio .
       log:EnvioMismoDia rdfs:subClassOf log:EnvioUrgente .

       log:E001 rdf:type log:EnvioUrgente .
       log:E002 rdf:type log:EnvioInternacional .
       log:E003 rdf:type log:Envio .
       log:E004 rdf:type log:EnvioMismoDia .

       log:E001 log:codigoEnvio "E001" .
       log:E002 log:codigoEnvio "E002" .
       log:E003 log:codigoEnvio "E003" .
       log:E004 log:codigoEnvio "E004" .
       log:E001 log:destino "Madrid" .
     }',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Cargamos la relación del camión. La primera tripleta indica que transporta y transportadoPor son propiedades inversas:

BEGIN
  SEM_APIS.UPDATE_RDF_GRAPH(
    'FLOTA',
    'PREFIX log: <https://ejemplo.org/log/>
     PREFIX owl: <http://www.w3.org/2002/07/owl#>
     INSERT DATA {
       log:transporta owl:inverseOf log:transportadoPor .
       log:C01 log:transporta log:E001 .
       log:C01 log:codigoCamion "C01" .
     }',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Por último, asociamos el camión con su sensor y registramos el límite de temperatura del envío y cinco lecturas:

BEGIN
  SEM_APIS.UPDATE_RDF_GRAPH(
    'SENSORES',
    'PREFIX log: <https://ejemplo.org/log/>
     PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
     PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
     INSERT DATA {
       log:C01 log:tieneSensor log:S01 .
       log:S01 rdf:type log:SensorTemperatura .
       log:S01 log:codigoSensor "S01" .
       log:E001 log:temperaturaMaximaC "8"^^xsd:decimal .

       log:L001 log:deSensor log:S01 .
       log:L001 log:temperaturaC "4"^^xsd:decimal .
       log:L001 log:instante "2026-10-06T07:00:00Z"^^xsd:dateTime .

       log:L002 log:deSensor log:S01 .
       log:L002 log:temperaturaC "10"^^xsd:decimal .
       log:L002 log:instante "2026-10-06T07:10:00Z"^^xsd:dateTime .

       log:L003 log:deSensor log:S01 .
       log:L003 log:temperaturaC "11"^^xsd:decimal .
       log:L003 log:instante "2026-10-06T07:13:00Z"^^xsd:dateTime .

       log:L004 log:deSensor log:S01 .
       log:L004 log:temperaturaC "9"^^xsd:decimal .
       log:L004 log:instante "2026-10-06T07:17:00Z"^^xsd:dateTime .

       log:L005 log:deSensor log:S01 .
       log:L005 log:temperaturaC "5"^^xsd:decimal .
       log:L005 log:instante "2026-10-06T07:20:00Z"^^xsd:dateTime .
     }',
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Es un modelo deliberadamente pequeño. Definimos formalmente una jerarquía de clases y una propiedad inversa. Otras propiedades, como tieneSensor, se usan como relaciones explícitas, sin añadir axiomas de dominio y rango.

3. Crear los grafos inferidos

Las consultas con inferencia necesitan grafos inferidos creados después de cargar sus tripletas de origen:

BEGIN
  SEM_APIS.CREATE_INFERRED_GRAPH(
    'LOG_RDFS',
    SEM_MODELS('LOGISTICA'),
    SEM_RULEBASES('RDFS'),
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );

  SEM_APIS.CREATE_INFERRED_GRAPH(
    'FLOTA_OWL',
    SEM_MODELS('FLOTA'),
    SEM_RULEBASES('OWLPRIME'),
    network_owner => 'ONTOLOGIA',
    network_name  => 'LOG_NET'
  );
END;
/

Oracle materializa las deducciones correspondientes a cada combinación de grafo y conjunto de reglas. Si después cambian las tripletas de origen, hay que mantener actualizado el grafo inferido antes de confiar en sus deducciones. Oracle: creación de grafos inferidos.

Ejemplo 1: ¿qué recursos son envíos?

Esta consulta busca instancias de Envio aplicando inferencia RDFS:

SELECT id_envio
FROM TABLE(SEM_MATCH(
  'PREFIX log: <https://ejemplo.org/log/>
   PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
   SELECT ?id_envio
   WHERE {
     ?envio rdf:type log:Envio .
     ?envio log:codigoEnvio ?id_envio .
   }',
  SEM_MODELS('LOGISTICA'),
  SEM_RULEBASES('RDFS'),
  NULL, NULL, NULL, NULL, NULL, NULL,
  'ONTOLOGIA', 'LOG_NET'
));

Resultado esperado: E001, E002, E003 y E004. E004 solo se declaró explícitamente como EnvioMismoDia; RDFS sigue la cadena EnvioMismoDia → EnvioUrgente → Envio.

El ejemplo de propiedad inversa utiliza OWLPrime:

SELECT envio$_suffix AS id_envio,
       camion$_suffix AS id_camion
FROM TABLE(SEM_MATCH(
  'PREFIX log: <https://ejemplo.org/log/>
   SELECT ?envio ?camion
   WHERE { ?envio log:transportadoPor ?camion . }',
  SEM_MODELS('FLOTA'),
  SEM_RULEBASES('OWLPRIME'),
  NULL, NULL, NULL, NULL, NULL, NULL,
  'ONTOLOGIA', 'LOG_NET'
));

Resultado esperado: E001 y C01. Guardamos «C01 transporta E001»; la propiedad inversa permite responder la pregunta formulada al revés.

Ejemplo 2: seguir el envío hasta su sensor

Esta consulta no necesita inferencia. Busca hechos explícitos en los tres grafos:

SELECT id_envio, destino, id_camion, id_sensor
FROM TABLE(SEM_MATCH(
  'PREFIX log: <https://ejemplo.org/log/>
   SELECT ?id_envio ?destino ?id_camion ?id_sensor
   WHERE {
     ?envio log:codigoEnvio ?id_envio .
     ?envio log:destino ?destino .
     ?camion log:transporta ?envio .
     ?camion log:codigoCamion ?id_camion .
     ?camion log:tieneSensor ?sensor .
     ?sensor log:codigoSensor ?id_sensor .
   }',
  SEM_MODELS('LOGISTICA', 'FLOTA', 'SENSORES'),
  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
  'ONTOLOGIA', 'LOG_NET'
));

Resultado esperado: E001 | Madrid | C01 | S01.

Los nombres que empiezan por ? son variables. Repetir ?envio significa que las líneas deben referirse al mismo envío. SEM_MODELS aporta los tres grafos y TABLE(SEM_MATCH(...)) presenta el resultado SPARQL como filas SQL. El argumento del conjunto de reglas es NULL, así que esta consulta utiliza las relaciones explícitas que cargamos. Oracle: consultas RDF con SEM_MATCH.

Dónde encaja la alerta de temperatura

E001 tiene un límite de 8 °C. Su sensor registró 4 °C a las 07:00; después 10 °C, 11 °C y 9 °C a las 07:10, 07:13 y 07:17 UTC; y finalmente 5 °C a las 07:20.

Las relaciones RDF establecen qué sensor pertenece al camión que transporta E001 y qué límite de temperatura se aplica a E001. Una regla analítica en SQL puede determinar entonces que las tres lecturas altas consecutivas abarcan de 07:10 a 07:17, con un pico de 11 °C. El cálculo de duración es una regla operativa; la ontología aporta su significado y contexto.

Este ejemplo tiene un límite importante: supone que C01 transporta E001 durante todas esas lecturas. Un modelo de producción debería registrar cuándo se asignó cada envío al camión. De lo contrario, una lectura de un viaje posterior podría atribuirse al envío equivocado.

Conclusión

El ejemplo muestra dos usos distintos de una ontología. La inferencia deduce tipos de envío más generales y la relación inversa de transporte. La exploración del grafo sigue los vínculos explícitos entre envío, camión y sensor. Ambos usos son valiosos, pero cumplen funciones diferentes; la alerta basada en el tiempo sigue siendo un cálculo aparte sobre los datos conectados.