Una pregunta: ¿qué pacientes tienen un diagnóstico de diabetes?
Empecemos 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 lo primero, pero no lo segundo.
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 deberÃa ser p1 y p2. El código de diagnóstico de p3 no está en este ValueSet. Construiremos ambas entradas e intentaremos dos formas de plantear la misma pregunta.
Los ejemplos utilizan YAML por legibilidad y versiones de terminologÃa ilustrativas. Este pequeño ValueSet no constituye una definición clÃnica completa. Los fragmentos de recursos no forman un paquete ejecutable, y la interfaz del ValueSet sigue siendo una propuesta.
Aquà veo dos fases. Cuando aplanamos recursos FHIR, podemos normalizar la terminologÃa — por ejemplo, traducir códigos de diagnóstico mediante funciones FHIRPath. Más adelante, al construir 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 sobre é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 relacionó 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
Un 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 del paciente de la Condition con cada elemento de code.coding. En estos ejemplos, asuma que las referencias de paciente son Patient/p1, Patient/p2 y Patient/p3, y que el ejecutor representa sus claves como p1, p2 y p3. Las representaciones reales de claves 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 correspondencia sea explÃcita; los datos reales suelen omitir la versión.
Ahora disponemos de 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 los ViewDefinitions. SQL on FHIR representa las consultas SQL mediante un recurso Library de FHIR. Su lista relatedArtifact declara 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 un ViewDefinition en la vista SQL mediante relatedArtifact, asignarle un nombre de tabla y usarlo en un join. Podemos utilizar un enfoque similar para el ValueSet: mi vista SQL depende de un ValueSet, y le estoy dando un nombre.»
— Nikolai Ryzhikov, 1 de septiembre (ligeramente editado)
Para nuestra consulta de diabetes, la declaración de dependencia propuesta tiene este aspecto en el 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 fichero SQL ilustrativo; alternativamente, data puede contener el SQL codificado en base64. Las dos consultas que se muestran a continuación son contenidos alternativos de ese fichero. Las presentamos como bloques YAML legibles sql: |, no como campos adicionales de Library.
Esto 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 URLs por sà mismo.
John describió la misma separación desde el lado del ejecutor:
«Simplemente estás diciendo que tengo una dependencia de este value set. […] Para la abstracción SQL, queremos mantenerlo muy 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 por qué ser una tabla fÃsica. Puede ser una vista, datos en caché o algo evaluado cuando sea necesario. Como expresé el 8 de septiembre:
«Podemos tratar el ValueSet pre-expandido como una relación. No nos importa cómo hemos llegado hasta aquà— ¿es una consulta de expansión dinámica, o datos pre-expandidos cargados 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 a disposición de SQL la pertenencia resultante.
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:
# Texto de consulta a modo ilustrativo; `sql` no es un campo de Library de FHIR.
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:
# Texto de consulta alternativo, usando la función propuesta.
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 nos permite hacer 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 debatir sobre códigos que no son seleccionables:
«Si "no seleccionable" es una propiedad que le interesa, la convierte en una columna durante la extracción y entonces tiene acceso a ella directamente en SQL. Las funciones no permiten eso con la misma facilidad.»
— Gino Canessa, 15 de septiembre (ligeramente editado)
El coste es un join de múltiples columnas repetido. 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 utilizar 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, deben coincidir en la pertenencia para las mismas entradas e instantánea 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
Hay dos versiones diferentes en el ejemplo: diabetes|2026 identifica la definición del ValueSet, mientras que cada fila de pertenencia lleva una versión de CodeSystem. Fijar únicamente el ValueSet no fija necesariamente todas las dependencias de terminologÃa utilizadas para expandirlo.
Me importa esto porque una vez rompimos la validación en Aidbox usando «latest». El validador recogió los estados de encuentro modificados de R5, y la validación falló en servidores R4 en producción. Desde entonces fijamos todo lo que podemos y actualizamos las versiones de forma deliberada.
Para nuestra consulta, el riesgo es que cambie la lista de pacientes aunque el SQL no haya cambiado. Si dos ejecutores resuelven una dependencia sin versión de forma diferente, pueden devolver respuestas distintas. Owen planteó exactamente esa preocupación en la reunión.
El borrador propone una pertenencia coherente para cada trabajo, registrando las versiones resueltas y rechazando la resolución ambigua. Pero cómo deben funcionar las dependencias sin versión sigue abierto. Nuestro ejemplo evita deliberadamente esa ambigüedad; una implementación necesita gestionarla de forma explÃcita, sin adivinar 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. 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 desarrollar cosas en la especificación y la implementación de referencia para hacernos una idea.»
— John Grimes, 15 de septiembre (muletillas eliminadas)
Empezaremos con consultas como esta y comprobaremos 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 simple: una consulta debe poder decir dependo de este ValueSet. El experimento deberÃa ayudarnos a decidir cómo los ejecutores lo exponen a SQL y qué debe soportar toda 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 terminologÃa por su cuenta
En Health Samurai, desarrollamos 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 ambos de forma gratuita. Explore SQL on FHIR en Aidbox, y use Termbox para trabajar con terminologÃa y expansiones de ValueSet. Empiece con sus propios datos y un ValueSet que utilice realmente; esa es una prueba mejor que cualquier demostración.
La interfaz de ValueSet de primera clase aquà tratada sigue siendo una propuesta. Los entornos de prueba 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 han sido ligeramente editados para mayor legibilidad.
Véase también: Reuniones del WG de SQL on FHIR y Análisis FHIR con SQL on FHIR: interoperable por diseño para la imagen completa de ViewDefinition / SQLView / SQLQuery en la que encaja este trabajo sobre ValueSet.


![Terminology is fun: CodeableConcept.coding[]](/assets/images/articles/horizontal-5.avif?v=2ef8.muw)

