|
6 min de lectura
|

SQL on FHIR: ValueSets de primera clase

Resumen del artículo

Una consulta SQL necesita tanto datos de pacientes como terminología. Estamos explorando cómo declarar un ValueSet como dependencia junto a una ViewDefinition, y luego exponer su pertenencia como una relación o una función member_of. El grupo de trabajo se inclina actualmente por la relación, pero el siguiente paso es implementar, experimentar y decidir.

Resumir este artículo con:
ChatGPTPerplexityClaudeGrok

Una pregunta: ¿qué pacientes tienen un diagnóstico de diabetes?

Comenzamos con una tarea sencilla:

Devolver los pacientes cuyo código de diagnóstico pertenezca al ValueSet de diabetes.

Necesitamos dos entradas: los códigos de diagnóstico de los pacientes y los códigos incluidos en el ValueSet. Una tabla de condiciones nos proporciona la primera, pero no la segunda.

A continuación se muestran tres filas de diagnóstico y los dos miembros de nuestro ValueSet de diabetes de ejemplo:

conditions:
  - patient_id: p1
    system: http://snomed.info/sct
    version: "2026-02"
    code: "73211009"
  - patient_id: p2
    system: http://snomed.info/sct
    version: "2026-02"
    code: "44054006"
  - patient_id: p3
    system: http://snomed.info/sct
    version: "2026-02"
    code: "22298006"

diabetes_codes:
  - system: http://snomed.info/sct
    version: "2026-02"
    code: "73211009"
    display: Diabetes mellitus
  - system: http://snomed.info/sct
    version: "2026-02"
    code: "44054006"
    display: Type 2 diabetes mellitus

La respuesta debe ser p1 y p2. El código de diagnóstico de p3 no está en este ValueSet. Construiremos ambas entradas y probaremos dos formas de plantear la misma pregunta.

Los ejemplos usan YAML por legibilidad y versiones de terminología ilustrativas. Este ValueSet mínimo no constituye una definición clínica completa. Los fragmentos de recursos no forman un paquete ejecutable, y la interfaz de ValueSet sigue siendo una propuesta.

Aquí identifico dos fases. Cuando aplanamos recursos FHIR, podemos normalizar la terminología — por ejemplo, traducir códigos de diagnóstico mediante funciones FHIRPath. Posteriormente, cuando construimos una cohorte, necesitamos usar terminología dentro de una consulta SQL. Son tareas relacionadas, pero requieren interfaces diferentes.

Para algunas medidas, ya tomamos un ValueSet expandido, lo tratamos como una tabla y escribimos SQL contra él. La cuestión es cómo convertir eso en una interfaz común, en lugar de algo que cada implementación inventa por su cuenta.

John Grimes conectó esto con el problema que los servidores de terminología ya resuelven:

«Este es el problema que el servidor de terminología resuelve para preparar datos de terminología en tiempo de ejecución. Efectivamente estamos intentando hacer lo mismo para casos de uso analíticos.»

— John Grimes, 15 de septiembre (ligeramente editado)

Primero, convertir Conditions en filas

Una ViewDefinition de SQL on FHIR describe cómo extraer filas y columnas de recursos FHIR mediante FHIRPath. No contiene SQL.

A continuación se muestra una vista simplificada sobre Condition:

resourceType: ViewDefinition
url: http://example.org/ViewDefinition/conditions
version: 1.0.0
name: conditions
status: draft
resource: Condition
select:
  - column:
      - name: patient_id
        path: subject.getReferenceKey(Patient)
    select:
      - forEach: code.coding
        column:
          - name: system
            path: system
          - name: version
            path: version
          - name: code
            path: code

El select anidado combina el identificador de paciente de la Condition con cada elemento de code.coding. En estos ejemplos, se asume que las referencias de pacientes son Patient/p1, Patient/p2 y Patient/p3, y que el ejecutor representa sus claves como p1, p2 y p3. Las representaciones de claves reales las define el ejecutor; deben ser coherentes con la vista de Patient correspondiente. getReferenceKey(Patient) restringe el tipo de referencia a Patient.

Usamos codificaciones versionadas para que la concordancia sea explícita; los datos reales frecuentemente omiten la versión.

Ahora tenemos datos de pacientes que podemos consultar. Aún necesitamos saber qué códigos se consideran diabetes.

Declarar ambas entradas de la vista SQL

Una vista o consulta SQL opera sobre las filas producidas por las ViewDefinitions. SQL on FHIR representa las consultas SQL mediante un recurso FHIR Library. Su lista relatedArtifact declara las dependencias: type: depends-on indica que se necesita una entrada, resource la identifica y label le asigna un nombre local para SQL.

El 1 de septiembre, propuse aplicar el mismo patrón a los ValueSets:

«Podemos hacer referencia a una ViewDefinition en la vista SQL a través de relatedArtifact, darle un nombre de tabla y usarla en un join. Podemos emplear un enfoque similar para el ValueSet: mi vista SQL depende de un ValueSet, y le estoy asignando un nombre.»

— Nikolai Ryzhikov, 1 de septiembre (ligeramente editado)

Para nuestra consulta de diabetes, la declaración de dependencia propuesta tiene el siguiente aspecto en este fragmento de Library:

resourceType: Library
url: http://example.org/Library/patients-with-diabetes
status: draft
type:
  coding:
    - system: http://hl7.org/fhir/uv/sql-on-fhir/CodeSystem/LibraryTypesCodes
      code: sql-query
relatedArtifact:
  - type: depends-on
    label: conditions
    resource: http://example.org/ViewDefinition/conditions|1.0.0
  - type: depends-on
    label: diabetes_codes
    resource: http://example.org/ValueSet/diabetes|2026
content:
  - contentType: application/sql
    url: https://example.org/queries/patients-with-diabetes.sql

La primera dependencia proporciona las filas de condiciones. La segunda proporciona la pertenencia al ValueSet. La parte tras | solicita una versión concreta de ese artefacto.

El SQL en sí pertenece a Library.content: un Attachment con contentType: application/sql. Aquí, url apunta a un archivo SQL ilustrativo; alternativamente, data puede contener el SQL codificado en base64. Las dos consultas siguientes son contenidos alternativos de ese archivo. Las mostramos como bloques YAML legibles sql: |, no como campos adicionales de Library.

Eso es lo que entiendo por ValueSets de primera clase: la consulta declara explícitamente la terminología que necesita, junto a sus vistas de datos. El ejecutor — el software que ejecuta la consulta — resuelve las dependencias. SQL utiliza sus nombres locales en lugar de resolver las URL por sí mismo.

John describió la misma separación desde el lado del ejecutor:

«Simplemente estás diciendo que tienes una dependencia de este value set. […] Para la abstracción SQL, queremos mantenerlo extremadamente simple, especialmente para los casos sencillos.»

— John Grimes, 15 de septiembre (ligeramente editado)

Pertenencia, no un algoritmo de expansión

La segunda dependencia proporciona la pertenencia a diabetes_codes mostrada al principio.

Owen Loveluck preguntó si la abstracción debería ser una vista en lugar de una tabla. Esa distinción importa: una relación no tiene que ser una tabla física. Puede ser una vista, datos en caché o algo evaluado cuando se necesite. Tal como lo expresé el 8 de septiembre:

«Podemos tratar el ValueSet pre-expandido como una relación. No nos importa cómo llegamos hasta allí — ¿es una consulta de expansión dinámica, o material pre-expandido cargado en la base de datos? Pero puedo hacer un join y construir mi medida.»

— Nikolai Ryzhikov, 8 de septiembre (ligeramente editado)

La propuesta no estandariza cómo se evalúan ValueSet.compose, ECL o VCL. Describe cómo se pone la pertenencia resultante a disposición de SQL.

Opción A: unir el ValueSet como una relación

El ejecutor expone diabetes_codes como una relación con nombre. Nuestra consulta hace un join contra ella:

# Query text for illustration; `sql` is not a FHIR Library field.
sql: |
  SELECT DISTINCT c.patient_id
  FROM conditions c
  JOIN diabetes_codes vs
    ON vs.system = c.system
   AND vs.version = c.version
   AND vs.code = c.code

expected_patient_ids: [p1, p2]

Opción B: comprobar la pertenencia con una función

La misma dependencia podría estar disponible mediante member_of:

# Alternative query text, using the proposed function.
sql: |
  SELECT DISTINCT c.patient_id
  FROM conditions c
  WHERE member_of(
    c.system, c.version, c.code, 'diabetes_codes'
  )

expected_patient_ids: [p1, p2]

¿Qué cambia entre las dos opciones?

Ambas consultas devuelven p1 y p2. Lo que difiere es cómo escribimos la consulta y qué más permite la interfaz.

Se trata de SQL ordinario. La pertenencia es visible: podemos inspeccionar los códigos, contarlos o incluir display en nuestra salida. Si la cohorte es incorrecta, podemos examinar ambas entradas.

Las propiedades adicionales también pueden ser columnas. Gino Canessa señaló esto al hablar de códigos que no son seleccionables:

«Si "no seleccionable" es una propiedad que te importa, la conviertes en una columna durante la extracción y entonces tienes acceso a ella directamente en SQL. Las funciones no permiten eso con tanta facilidad.»

— Gino Canessa, 15 de septiembre (ligeramente editado)

El coste es un join de múltiples columnas que se repite. El borrador también necesita una regla de unicidad clara para (system, version, code): las filas de pertenencia duplicadas pueden multiplicar los resultados de la consulta. DISTINCT protege esta lista de pacientes, pero no cualquier agregado que alguien pueda escribir más adelante.

La función es concisa, encaja en expresiones booleanas y no multiplica las filas de entrada. Su implementación podría usar una búsqueda eficiente en lugar de un join. Cuál rinde mejor depende del motor.

La contrapartida es la portabilidad: member_of es una función propuesta de SQL on FHIR, no una función SQL estándar. Los motores necesitan soportarla o traducirla. Responde a una pregunta de pertenencia, pero no expone los miembros ni devuelve su texto de visualización.

Si un ejecutor soporta ambas interfaces, deberían coincidir en la pertenencia para las mismas entradas y el mismo snapshot de terminología. Nuestras dos consultas deberían devolver los mismos pacientes.

Un ejecutor también necesita declarar si soporta la relación, la función o ambas. Que los resultados de pertenencia coincidan no hace por sí solo que una consulta sea portable.

Ambas opciones necesitan reglas de versión claras

En el ejemplo hay dos versiones diferentes: diabetes|2026 identifica la definición del ValueSet, mientras que cada fila de pertenencia lleva una versión del CodeSystem. Fijar únicamente el ValueSet no fija necesariamente todas las dependencias de terminología utilizadas para expandirlo.

Esto me importa porque en una ocasión rompimos la validación en Aidbox usando «latest». El validador recogió los estados de encounter modificados de R5, y la validación falló en servidores R4 en producción. Desde entonces fijamos todo lo posible y actualizamos las versiones de forma deliberada.

Para nuestra consulta, el riesgo es obtener una lista de pacientes diferente aunque el SQL no haya cambiado. Si dos ejecutores resuelven de forma distinta una dependencia sin versionar, pueden devolver respuestas diferentes. Owen planteó exactamente esa preocupación en la reunión.

El borrador propone una pertenencia consistente para cada trabajo, registrando las versiones resueltas y rechazando la resolución ambigua. Pero cómo deben funcionar las dependencias sin versionar sigue siendo una cuestión abierta. Nuestro ejemplo evita deliberadamente esa ambigüedad; una implementación necesita gestionarla de forma explícita, no resolverla en silencio.

Implementar, experimentar y luego decidir

El grupo de trabajo se inclina actualmente por la relación como base, con la función como conveniencia opcional. Eso es una orientación, no una decisión. Ambas opciones tienen propiedades útiles, y necesitamos probarlas con consultas reales.

John Grimes ofreció un siguiente paso práctico:

«Creo que deberíamos crear una rama y empezar a construir cosas en la especificación y la implementación de referencia, solo para hacernos una idea.»

— John Grimes, 15 de septiembre (muletillas eliminadas)

Comenzaremos con consultas como esta y verificaremos tres cosas: ¿devuelven las interfaces los mismos pacientes?, ¿cómo gestionan las versiones?, ¿y qué tan fáciles son de implementar y usar? También necesitamos probar propiedades adicionales y medir el rendimiento. Entonces podremos decidir qué debe soportar cada ejecutor.

El objetivo inmediato es sencillo: una consulta debe poder declarar que depende de este ValueSet. El experimento debería ayudarnos a decidir cómo los ejecutores lo exponen a SQL y qué debe soportar cada implementación.

Únase a la discusión sobre Terminology en SQL on FHIR en Zulip, y siga las reuniones del grupo de trabajo de SQL on FHIR. Traiga una consulta que necesite ejecutar, un ValueSet que sea difícil de gestionar, o experiencia implementando cualquiera de los enfoques — esos ejemplos nos ayudarán a probar las opciones y decidir.

Pruebe SQL on FHIR y la terminología por usted mismo

En Health Samurai, construimos ambos lados de esto: Aidbox para trabajar con datos FHIR y SQL on FHIR, y Termbox para terminología FHIR. Son tecnologías que desarrollamos y usamos nosotros mismos — y que trasladamos al grupo de trabajo como experiencia práctica, no solo como ideas de diseño.

Puede probar ambas de forma gratuita. Explore SQL on FHIR en Aidbox, y use Termbox para trabajar con terminología y expansiones de ValueSet. Comience con sus propios datos y un ValueSet que utilice realmente; es una prueba mejor que cualquier demo.

La interfaz de ValueSet de primera clase que se discute aquí sigue siendo una propuesta. Las pruebas le permiten explorar las capacidades existentes de los dos productos, no una versión ya publicada de esta propuesta.


Basado en las discusiones del grupo de trabajo de SQL on FHIR del 1, 8 y 15 de septiembre de 2026, y en mi borrador de abstracción de ValueSet. Los fragmentos de las reuniones están ligeramente editados para mayor legibilidad.

Véase también: reuniones del WG de SQL on FHIR y analítica interoperable con SQL on FHIR para ver el panorama completo de ViewDefinition / SQLView / SQLQuery en el que encaja este trabajo sobre ValueSet.

Compartir este artículo
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

Get the latest articles on FHIR, interoperability, and healthcare IT.