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.


![Terminology is fun: CodeableConcept.coding[]](/assets/images/articles/horizontal-4.avif?v=3egb.mw5)

