From 213f57d59678a5101f264899f8974ece827126a4 Mon Sep 17 00:00:00 2001 From: Slava T <4713633+slavat@users.noreply.github.com> Date: Tue, 31 Aug 2021 12:04:26 -0400 Subject: [PATCH] Initial commit Initial commit of ARM templates, SQL Scripts, and Readme instructions. --- ARM/data_factory.json | 1369 +++++++++++++++++ ARM/data_factory_parameters.json | 21 + ARM/synapse_workspace.json | 204 +++ ARM/synapse_workspace_parameters.json | 24 + ...Initialize Database - Drop All Objects.sql | 130 ++ .../Initialize/001. Create Synthea Schema.sql | 1 + .../002. Create Synthea Staging Tables.sql | 213 +++ .../Initialize/003. Create OMOP Schema.sql | 1 + .../Initialize/004. Create OMOP Tables.sql | 508 ++++++ .../Initialize/005. Create vocab Schema.sql | 1 + .../Initialize/006. Create vocab Tables.sql | 78 + ETL Scripts/Load/001. Load person.sql | 60 + ETL Scripts/Load/010. Load AllVisitTable.sql | 184 +++ ETL Scripts/Load/020. AAVITable.sql | 52 + .../030. Load source_to_source_vocab_map.sql | 69 + ...040. Load source_to_standard_vocab_map.sql | 77 + .../Load/050. Load final_visit_ids.sql | 55 + ETL Scripts/Load/070. Load condition_era.sql | 116 ++ .../Load/080. Load condition_occurrence.sql | 52 + ETL Scripts/Load/090. Load death.sql | 40 + .../Load/100. Load device_exposure.sql | 50 + ETL Scripts/Load/120. Load drug_exposure.sql | 125 ++ ETL Scripts/Load/125. Load drug_era.sql | 287 ++++ ETL Scripts/Load/130. Load measurement.sql | 129 ++ .../Load/140. Load observation_period.sql | 23 + ETL Scripts/Load/150. Load observation.sql | 140 ++ .../Load/170. Load procedure_occurrence.sql | 46 + ETL Scripts/Load/180. Load visit_detail.sql | 71 + .../Load/190. Load visit_occurrence.sql | 59 + ETL Scripts/Load/900. Load cdm_source.sql | 25 + .../999. Create Indexes on CDM Tables.sql | 208 +++ Images/AthenaVocabularies.png | Bin 0 -> 56949 bytes Images/DataFactoryDeployment.png | Bin 0 -> 24459 bytes Images/DataFactoryParameters.png | Bin 0 -> 59022 bytes Images/DownloadRepository.png | Bin 0 -> 17132 bytes Images/ETLScripts_Initialize.png | Bin 0 -> 30463 bytes Images/ETLScripts_Load.png | Bin 0 -> 69263 bytes Images/SynapseWSDeployment.png | Bin 0 -> 24736 bytes Images/SyntheaFiles.png | Bin 0 -> 54606 bytes Images/Synthea_OMOP_Pipelines.png | Bin 0 -> 53015 bytes ...nthea_OMOP_on_Synapse_Solution_Diagram.png | Bin 0 -> 100728 bytes Images/VocabularyFiles.png | Bin 0 -> 34811 bytes README.md | 217 ++- 43 files changed, 4633 insertions(+), 2 deletions(-) create mode 100644 ARM/data_factory.json create mode 100644 ARM/data_factory_parameters.json create mode 100644 ARM/synapse_workspace.json create mode 100644 ARM/synapse_workspace_parameters.json create mode 100644 ETL Scripts/Initialize/000. Initialize Database - Drop All Objects.sql create mode 100644 ETL Scripts/Initialize/001. Create Synthea Schema.sql create mode 100644 ETL Scripts/Initialize/002. Create Synthea Staging Tables.sql create mode 100644 ETL Scripts/Initialize/003. Create OMOP Schema.sql create mode 100644 ETL Scripts/Initialize/004. Create OMOP Tables.sql create mode 100644 ETL Scripts/Initialize/005. Create vocab Schema.sql create mode 100644 ETL Scripts/Initialize/006. Create vocab Tables.sql create mode 100644 ETL Scripts/Load/001. Load person.sql create mode 100644 ETL Scripts/Load/010. Load AllVisitTable.sql create mode 100644 ETL Scripts/Load/020. AAVITable.sql create mode 100644 ETL Scripts/Load/030. Load source_to_source_vocab_map.sql create mode 100644 ETL Scripts/Load/040. Load source_to_standard_vocab_map.sql create mode 100644 ETL Scripts/Load/050. Load final_visit_ids.sql create mode 100644 ETL Scripts/Load/070. Load condition_era.sql create mode 100644 ETL Scripts/Load/080. Load condition_occurrence.sql create mode 100644 ETL Scripts/Load/090. Load death.sql create mode 100644 ETL Scripts/Load/100. Load device_exposure.sql create mode 100644 ETL Scripts/Load/120. Load drug_exposure.sql create mode 100644 ETL Scripts/Load/125. Load drug_era.sql create mode 100644 ETL Scripts/Load/130. Load measurement.sql create mode 100644 ETL Scripts/Load/140. Load observation_period.sql create mode 100644 ETL Scripts/Load/150. Load observation.sql create mode 100644 ETL Scripts/Load/170. Load procedure_occurrence.sql create mode 100644 ETL Scripts/Load/180. Load visit_detail.sql create mode 100644 ETL Scripts/Load/190. Load visit_occurrence.sql create mode 100644 ETL Scripts/Load/900. Load cdm_source.sql create mode 100644 ETL Scripts/Load/999. Create Indexes on CDM Tables.sql create mode 100644 Images/AthenaVocabularies.png create mode 100644 Images/DataFactoryDeployment.png create mode 100644 Images/DataFactoryParameters.png create mode 100644 Images/DownloadRepository.png create mode 100644 Images/ETLScripts_Initialize.png create mode 100644 Images/ETLScripts_Load.png create mode 100644 Images/SynapseWSDeployment.png create mode 100644 Images/SyntheaFiles.png create mode 100644 Images/Synthea_OMOP_Pipelines.png create mode 100644 Images/Synthea_OMOP_on_Synapse_Solution_Diagram.png create mode 100644 Images/VocabularyFiles.png diff --git a/ARM/data_factory.json b/ARM/data_factory.json new file mode 100644 index 0000000..389ccd8 --- /dev/null +++ b/ARM/data_factory.json @@ -0,0 +1,1369 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "companyTla": { + "type": "string", + "metadata": { + "description": "This is a Three Letter Acronym for your company name. 'CON' for Contoso for example." + } + }, + "deploymentType": { + "type": "string", + "defaultValue": "poc", + "allowedValues": [ + "devtest", + "poc", + "prod", + "shared" + ], + "metadata": { + "description": "Specify deployment type: DevTest, POC, Prod, Shared. This will also be used in the naming convention." + } + }, + "ADLS_url": { + "type": "string", + "metadata": "URL of the Azure Data Lake Storage Account. E.g., https://mystorageaccount.dfs.core.windows.net/'" + }, + "ADLS_accountKey": { + "type": "secureString", + "metadata": "Secure string for 'accountKey' of 'ADLS'" + }, + "SynapseDedicatedSQLPool_connectionString": { + "type": "secureString", + "defaultValue": "Source=mySynapseSQLPool.sql.azuresynapse.net;Initial Catalog=myDatabaseName;User ID=mySQLAdminUserName;Password=myPassword;", + "metadata": "Secure string for 'connectionString' of 'SynapseDedicatedSQLPool'" + } + }, + "variables": { + "factoryName": "[toLower(concat(parameters('companyTla'),parameters('deploymentType'), 'df1'))]", + "factoryId": "[concat('Microsoft.DataFactory/factories/', variables('factoryName'))]", + "location": "[resourceGroup().location]" + }, + "resources": [ + { + "name": "[variables('factoryName')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "properties": {}, + "dependsOn": [], + "location": "[variables('location')]", + "identity": { + "type": "SystemAssigned" + } + }, + { + "name": "[concat(variables('factoryName'), '/Synapse_DW')]", + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "properties": { + "description": "Connection to the destination database.", + "linkedServiceName": { + "referenceName": "SynapseDedicatedSQLPool", + "type": "LinkedServiceReference" + }, + "parameters": { + "sinkTableName": { + "type": "string" + }, + "sinkSchemaName": { + "type": "string" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "type": "AzureSqlDWTable", + "schema": [], + "typeProperties": { + "schema": { + "value": "@dataset().sinkSchemaName", + "type": "Expression" + }, + "table": { + "value": "@dataset().sinkTableName", + "type": "Expression" + } + } + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/linkedServices/SynapseDedicatedSQLPool')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/ADLS_ETLScripts')]", + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "properties": { + "description": "Connection to a folder containing SQL scripts used for data transformation.", + "linkedServiceName": { + "referenceName": "ADLS", + "type": "LinkedServiceReference" + }, + "parameters": { + "SourceContainer": { + "type": "string" + }, + "SourceDirectory": { + "type": "string" + }, + "SourceFile": { + "type": "string" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@dataset().SourceFile", + "type": "Expression" + }, + "folderPath": { + "value": "@dataset().SourceDirectory", + "type": "Expression" + }, + "fileSystem": { + "value": "@dataset().SourceContainer", + "type": "Expression" + } + }, + "columnDelimiter": "~", + "rowDelimiter": "~", + "escapeChar": "", + "quoteChar": "" + }, + "schema": [ + { + "type": "String" + } + ] + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/linkedServices/ADLS')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/ADLS_AthenaVocabularyCSVs')]", + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "properties": { + "description": "Connection to Athena vocabulary source files in CSV format.", + "linkedServiceName": { + "referenceName": "ADLS", + "type": "LinkedServiceReference" + }, + "parameters": { + "fileName": { + "type": "string" + }, + "SourceContainer": { + "type": "string" + }, + "SourceDirectory": { + "type": "string" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@dataset().fileName", + "type": "Expression" + }, + "folderPath": { + "value": "@dataset().SourceDirectory", + "type": "Expression" + }, + "fileSystem": { + "value": "@dataset().SourceContainer", + "type": "Expression" + } + }, + "columnDelimiter": "\t", + "rowDelimiter": "\n", + "escapeChar": "", + "firstRowAsHeader": true, + "quoteChar": "" + }, + "schema": [] + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/linkedServices/ADLS')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/ADLS_SyntheaCSVs')]", + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "properties": { + "description": "Connection to your source data store.", + "linkedServiceName": { + "referenceName": "ADLS", + "type": "LinkedServiceReference" + }, + "parameters": { + "fileName": { + "type": "string" + }, + "SourceContainer": { + "type": "string" + }, + "SourceDirectory": { + "type": "string" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@dataset().fileName", + "type": "Expression" + }, + "folderPath": { + "value": "@dataset().SourceDirectory", + "type": "Expression" + }, + "fileSystem": { + "value": "@dataset().SourceContainer", + "type": "Expression" + } + }, + "columnDelimiter": ",", + "rowDelimiter": "\n", + "escapeChar": "\"", + "firstRowAsHeader": true, + "quoteChar": "\"" + }, + "schema": [] + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/linkedServices/ADLS')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/ADLS')]", + "type": "Microsoft.DataFactory/factories/linkedServices", + "apiVersion": "2018-06-01", + "properties": { + "annotations": [], + "type": "AzureBlobFS", + "typeProperties": { + "url": "[parameters('ADLS_url')]", + "accountKey": { + "type": "SecureString", + "value": "[parameters('ADLS_accountKey')]" + } + } + }, + "dependsOn": [ + "[variables('factoryId')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/SynapseDedicatedSQLPool')]", + "type": "Microsoft.DataFactory/factories/linkedServices", + "apiVersion": "2018-06-01", + "properties": { + "annotations": [], + "type": "AzureSqlDW", + "typeProperties": { + "connectionString": "[parameters('SynapseDedicatedSQLPool_connectionString')]" + } + }, + "dependsOn": [ + "[variables('factoryId')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/03-Stage Vocabulary Data to Synapse Tables')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "properties": { + "description": "Copy CSV files with Athena vocabulary data to corresponding staging tables in the Synapse Dedicated SQL Pool.", + "activities": [ + { + "name": "Get CSV Files", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "ADLS_AthenaVocabularyCSVs", + "type": "DatasetReference", + "parameters": { + "fileName": "/", + "SourceContainer": "@pipeline().parameters.SourceContainer", + "SourceDirectory": "@pipeline().parameters.SourceDirectory" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + } + }, + { + "name": "For Each CSV File", + "description": "For each CSV file, check if a corresponding table exists in the destination database and, if so, load the data file into the destination table.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Get CSV Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Get CSV Files').output.childItems", + "type": "Expression" + }, + "activities": [ + { + "name": "Check Whether Destination Table Exists", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Lookup Sink Table", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(activity('Lookup Sink Table').output.firstRow.TableExists,1)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Copy Data File to Corresponding Staging Table", + "description": "Copy data file to the corresponding destination table. It is assumed that the data file and the table share a common schema.", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Entity", + "value": "@item().name" + } + ], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "SqlDWSink", + "preCopyScript": { + "value": "@{concat('TRUNCATE TABLE [', pipeline().parameters.StagingSchemaName, '].[', replace(item().name,'.csv',''), ']')}", + "type": "Expression" + }, + "allowPolyBase": true, + "polyBaseSettings": { + "rejectValue": 0, + "rejectType": "value", + "useTypeDefault": false + }, + "disableMetricsCollection": false + }, + "enableStaging": false + }, + "inputs": [ + { + "referenceName": "ADLS_AthenaVocabularyCSVs", + "type": "DatasetReference", + "parameters": { + "fileName": "@item().name", + "SourceContainer": "@pipeline().parameters.SourceContainer", + "SourceDirectory": "@pipeline().parameters.SourceDirectory" + } + } + ], + "outputs": [ + { + "referenceName": "Synapse_DW", + "type": "DatasetReference", + "parameters": { + "sinkTableName": { + "value": "@replace(item().name,'.csv','')", + "type": "Expression" + }, + "sinkSchemaName": { + "value": "@pipeline().parameters.StagingSchemaName", + "type": "Expression" + } + } + } + ] + }, + { + "name": "Copy Data To Permanent Table", + "type": "SqlServerStoredProcedure", + "dependsOn": [ + { + "activity": "Copy Data File to Corresponding Staging Table", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "storedProcedureName": "sp_executesql", + "storedProcedureParameters": { + "stmt": { + "value": { + "value": "@concat('INSERT INTO [', pipeline().parameters.SinkSchemaName, '].[', replace(item().name,'.csv',''), '] SELECT * FROM [',pipeline().parameters.StagingSchemaName,'].[',replace(item().name,'.csv', ''),']')", + "type": "Expression" + }, + "type": "String" + } + } + }, + "linkedServiceName": { + "referenceName": "SynapseDedicatedSQLPool", + "type": "LinkedServiceReference" + } + } + ] + } + }, + { + "name": "Lookup Sink Table", + "description": "Lookup Whether the Destination Table Exists", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Entity", + "value": "@item().name" + } + ], + "typeProperties": { + "source": { + "type": "SqlDWSource", + "sqlReaderQuery": { + "value": "@concat('SELECT COUNT(*) AS TableExists\nFROM INFORMATION_SCHEMA.TABLES\nWHERE TABLE_SCHEMA = ''',pipeline().parameters.StagingSchemaName, '''\n\tAND TABLE_NAME = ''',replace(item().name,'.csv',''),'''')\n", + "type": "Expression" + }, + "queryTimeout": "02:00:00", + "partitionOption": "None" + }, + "dataset": { + "referenceName": "Synapse_DW", + "type": "DatasetReference", + "parameters": { + "sinkTableName": "INFORMATION_SCHEMA", + "sinkSchemaName": "TABLES" + } + } + } + } + ] + } + } + ], + "parameters": { + "SourceContainer": { + "type": "string", + "defaultValue": "synthea-omop" + }, + "SourceDirectory": { + "type": "string", + "defaultValue": "Vocab SNOMED_LOINC_CVX_RXNORM" + }, + "SinkSchemaName": { + "type": "string", + "defaultValue": "omop" + }, + "StagingSchemaName": { + "type": "string", + "defaultValue": "vocab" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "lastPublishTime": "2021-05-16T15:15:16Z" + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/datasets/ADLS_AthenaVocabularyCSVs')]", + "[concat(variables('factoryId'), '/datasets/Synapse_DW')]", + "[concat(variables('factoryId'), '/linkedServices/SynapseDedicatedSQLPool')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/01-Initialize Database')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "properties": { + "description": "Create database tables:\n - Synthea staging tables\n - OMOP CDM tables", + "activities": [ + { + "name": "Get ETL Script Files", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "ADLS_ETLScripts", + "type": "DatasetReference", + "parameters": { + "SourceContainer": "@pipeline().parameters.SourceContainer", + "SourceDirectory": "@pipeline().parameters.SourceDirectory", + "SourceFile": "/" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + } + }, + { + "name": "For Each ETL Script", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Get ETL Script Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Get ETL Script Files').output.childItems", + "type": "Expression" + }, + "batchCount": 1, + "activities": [ + { + "name": "Get ETL Script Content", + "description": "Retrieve the content of the SQL script", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Script", + "value": "@item().name" + } + ], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "ADLS_ETLScripts", + "type": "DatasetReference", + "parameters": { + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectory", + "type": "Expression" + }, + "SourceFile": { + "value": "@item().name", + "type": "Expression" + } + } + } + } + }, + { + "name": "Execute ETL Script", + "type": "SqlServerStoredProcedure", + "dependsOn": [ + { + "activity": "Get ETL Script Content", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Script", + "value": "@item().name" + } + ], + "typeProperties": { + "storedProcedureName": "sp_executesql", + "storedProcedureParameters": { + "stmt": { + "value": { + "value": "@replace(replace(replace(activity('Get ETL Script Content').output.firstRow.Prop_0,'[synthea]',concat('[',pipeline().parameters.SyntheaSchemaName,']')),'[omop]',concat('[',pipeline().parameters.OMOPSchemaName,']')),'[vocab]',concat('[',pipeline().parameters.VocabSchemaName,']'))", + "type": "Expression" + }, + "type": "String" + } + } + }, + "linkedServiceName": { + "referenceName": "SynapseDedicatedSQLPool", + "type": "LinkedServiceReference" + } + } + ] + } + } + ], + "parameters": { + "SourceContainer": { + "type": "string", + "defaultValue": "synthea-omop" + }, + "SourceDirectory": { + "type": "string", + "defaultValue": "ETL Scripts/Initialize" + }, + "SyntheaSchemaName": { + "type": "string", + "defaultValue": "synthea" + }, + "OMOPSchemaName": { + "type": "string", + "defaultValue": "omop" + }, + "VocabSchemaName": { + "type": "string", + "defaultValue": "vocab" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "lastPublishTime": "2021-05-16T05:58:20Z" + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/datasets/ADLS_ETLScripts')]", + "[concat(variables('factoryId'), '/linkedServices/SynapseDedicatedSQLPool')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/02-Stage Synthea CSV files to Synapse Tables')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "properties": { + "description": "Copy Synthea CSV files to corresponding staging tables in the Synapse Dedicated SQL Pool", + "activities": [ + { + "name": "Get CSV Files", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "ADLS_SyntheaCSVs", + "type": "DatasetReference", + "parameters": { + "fileName": "/", + "SourceContainer": "@pipeline().parameters.SourceContainer", + "SourceDirectory": "@pipeline().parameters.SourceDirectory" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + } + }, + { + "name": "For Each CSV File", + "description": "For each CSV file, check if a corresponding table exists in the destination database and, if so, load the data file into the destination table.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Get CSV Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Get CSV Files').output.childItems", + "type": "Expression" + }, + "activities": [ + { + "name": "Check Whether Destination Table Exists", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Lookup Sink Table", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(activity('Lookup Sink Table').output.firstRow.TableExists,1)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Copy Data File to Corresponding Sink Table", + "description": "Copy data file to the corresponding destination table. It is assumed that the data file and the table share a common schema.", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Entity", + "value": "@item().name" + } + ], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "SqlDWSink", + "preCopyScript": { + "value": "@{concat('TRUNCATE TABLE [', pipeline().parameters.SinkSchemaName, '].[', replace(item().name,'.csv',''), ']')}", + "type": "Expression" + }, + "allowCopyCommand": true, + "copyCommandSettings": {}, + "disableMetricsCollection": false + }, + "enableStaging": false + }, + "inputs": [ + { + "referenceName": "ADLS_SyntheaCSVs", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@item().name", + "type": "Expression" + }, + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectory", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "Synapse_DW", + "type": "DatasetReference", + "parameters": { + "sinkTableName": { + "value": "@replace(item().name,'.csv','')", + "type": "Expression" + }, + "sinkSchemaName": { + "value": "@pipeline().parameters.SinkSchemaName", + "type": "Expression" + } + } + } + ] + } + ] + } + }, + { + "name": "Lookup Sink Table", + "description": "Lookup whether a destination table that matches the name of the source file exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Entity", + "value": "@item().name" + } + ], + "typeProperties": { + "source": { + "type": "SqlDWSource", + "sqlReaderQuery": { + "value": "@concat('SELECT COUNT(*) AS TableExists\nFROM INFORMATION_SCHEMA.TABLES\nWHERE TABLE_SCHEMA = ''',pipeline().parameters.SinkSchemaName, '''\n\tAND TABLE_NAME = ''',replace(item().name,'.csv',''),'''')\n", + "type": "Expression" + }, + "queryTimeout": "02:00:00", + "partitionOption": "None" + }, + "dataset": { + "referenceName": "Synapse_DW", + "type": "DatasetReference", + "parameters": { + "sinkTableName": "INFORMATION_SCHEMA", + "sinkSchemaName": "TABLES" + } + } + } + } + ] + } + } + ], + "parameters": { + "SourceContainer": { + "type": "string", + "defaultValue": "synthea-omop" + }, + "SourceDirectory": { + "type": "string", + "defaultValue": "SyntheticMass/COVID100k" + }, + "SinkSchemaName": { + "type": "string", + "defaultValue": "synthea" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "lastPublishTime": "2021-05-16T01:07:33Z" + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/datasets/ADLS_SyntheaCSVs')]", + "[concat(variables('factoryId'), '/datasets/Synapse_DW')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/04-Transform Data and Load CDM Tables')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "properties": { + "description": "Transform Synthea data in staging tables and load CDM tables in OMOP-compatible format.", + "activities": [ + { + "name": "Get ETL Script Files", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "ADLS_ETLScripts", + "type": "DatasetReference", + "parameters": { + "SourceContainer": "@pipeline().parameters.SourceContainer", + "SourceDirectory": "@pipeline().parameters.SourceDirectory", + "SourceFile": "/" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + } + }, + { + "name": "For Each ETL Script", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Get ETL Script Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Get ETL Script Files').output.childItems", + "type": "Expression" + }, + "batchCount": 1, + "activities": [ + { + "name": "Get ETL Script Content", + "description": "Retrieve the content of the SQL script", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Script", + "value": "@item().name" + } + ], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "ADLS_ETLScripts", + "type": "DatasetReference", + "parameters": { + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectory", + "type": "Expression" + }, + "SourceFile": { + "value": "@item().name", + "type": "Expression" + } + } + } + } + }, + { + "name": "Execute ETL Script", + "type": "SqlServerStoredProcedure", + "dependsOn": [ + { + "activity": "Get ETL Script Content", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "7.00:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [ + { + "name": "Script", + "value": "@item().name" + } + ], + "typeProperties": { + "storedProcedureName": "sp_executesql", + "storedProcedureParameters": { + "stmt": { + "value": { + "value": "@replace(replace(activity('Get ETL Script Content').output.firstRow.Prop_0,'[synthea]',concat('[',pipeline().parameters.SyntheaSchemaName,']')),'[omop]',concat('[',pipeline().parameters.OMOPSchemaName,']'))", + "type": "Expression" + }, + "type": "String" + } + } + }, + "linkedServiceName": { + "referenceName": "SynapseDedicatedSQLPool", + "type": "LinkedServiceReference" + } + } + ] + } + } + ], + "parameters": { + "SourceContainer": { + "type": "string", + "defaultValue": "synthea-omop" + }, + "SourceDirectory": { + "type": "string", + "defaultValue": "ETL Scripts/Load" + }, + "SyntheaSchemaName": { + "type": "string", + "defaultValue": "synthea" + }, + "OMOPSchemaName": { + "type": "string", + "defaultValue": "omop" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "lastPublishTime": "2021-05-16T01:07:33Z" + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/datasets/ADLS_ETLScripts')]", + "[concat(variables('factoryId'), '/linkedServices/SynapseDedicatedSQLPool')]" + ] + }, + { + "name": "[concat(variables('factoryName'), '/00-Execute Synthea-OMOP ETL')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "properties": { + "description": "Execute the entire ETL Process", + "activities": [ + { + "name": "01-Initialize Database", + "type": "ExecutePipeline", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "01-Initialize Database", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectoryInitialize", + "type": "Expression" + }, + "SyntheaSchemaName": { + "value": "@pipeline().parameters.SyntheaSchemaName", + "type": "Expression" + }, + "OMOPSchemaName": { + "value": "@pipeline().parameters.OMOPSchemaName", + "type": "Expression" + }, + "VocabSchemaName": { + "value": "@pipeline().parameters.VocabSchemaName", + "type": "Expression" + } + } + } + }, + { + "name": "02-Stage Synthea CSV files to Synapse Tables", + "type": "ExecutePipeline", + "dependsOn": [ + { + "activity": "01-Initialize Database", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "02-Stage Synthea CSV files to Synapse Tables", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectorySynthea", + "type": "Expression" + }, + "SinkSchemaName": { + "value": "@pipeline().parameters.SyntheaSchemaName", + "type": "Expression" + } + } + } + }, + { + "name": "03-Stage Vocabulary Data to Synapse Tables", + "type": "ExecutePipeline", + "dependsOn": [ + { + "activity": "01-Initialize Database", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "03-Stage Vocabulary Data to Synapse Tables", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectoryVocabulary", + "type": "Expression" + }, + "SinkSchemaName": { + "value": "@pipeline().parameters.OMOPSchemaName", + "type": "Expression" + }, + "StagingSchemaName": { + "value": "@pipeline().parameters.VocabSchemaName", + "type": "Expression" + } + } + } + }, + { + "name": "04-Transform Data and Load CDM Tables", + "type": "ExecutePipeline", + "dependsOn": [ + { + "activity": "02-Stage Synthea CSV files to Synapse Tables", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "03-Stage Vocabulary Data to Synapse Tables", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "04-Transform Data and Load CDM Tables", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "SourceContainer": { + "value": "@pipeline().parameters.SourceContainer", + "type": "Expression" + }, + "SourceDirectory": { + "value": "@pipeline().parameters.SourceDirectoryLoad", + "type": "Expression" + }, + "SyntheaSchemaName": { + "value": "@pipeline().parameters.SyntheaSchemaName", + "type": "Expression" + }, + "OMOPSchemaName": { + "value": "@pipeline().parameters.OMOPSchemaName", + "type": "Expression" + } + } + } + } + ], + "parameters": { + "SourceContainer": { + "type": "string", + "defaultValue": "synthea-omop" + }, + "SourceDirectoryInitialize": { + "type": "string", + "defaultValue": "ETL Scripts/Initialize" + }, + "SyntheaSchemaName": { + "type": "string", + "defaultValue": "synthea" + }, + "OMOPSchemaName": { + "type": "string", + "defaultValue": "omop" + }, + "SourceDirectorySynthea": { + "type": "string", + "defaultValue": "SyntheticMass/COVID100k" + }, + "SourceDirectoryVocabulary": { + "type": "string", + "defaultValue": "Vocab SNOMED_LOINC_CVX_RXNORM" + }, + "SourceDirectoryLoad": { + "type": "string", + "defaultValue": "ETL Scripts/Load" + }, + "VocabSchemaName": { + "type": "string", + "defaultValue": "vocab" + } + }, + "folder": { + "name": "Synthea-OMOP ETL" + }, + "annotations": [], + "lastPublishTime": "2021-05-16T06:03:29Z" + }, + "dependsOn": [ + "[variables('factoryId')]", + "[concat(variables('factoryId'), '/pipelines/01-Initialize Database')]", + "[concat(variables('factoryId'), '/pipelines/02-Stage Synthea CSV files to Synapse Tables')]", + "[concat(variables('factoryId'), '/pipelines/03-Stage Vocabulary Data to Synapse Tables')]", + "[concat(variables('factoryId'), '/pipelines/04-Transform Data and Load CDM Tables')]" + ] + } + ] +} \ No newline at end of file diff --git a/ARM/data_factory_parameters.json b/ARM/data_factory_parameters.json new file mode 100644 index 0000000..6984e02 --- /dev/null +++ b/ARM/data_factory_parameters.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "companyTla": { + "value": "GEN-UNIQUE-3" + }, + "ADLS_accountKey": { + "value": "" + }, + "deploymentType": { + "value": "poc" + }, + "SynapseDedicatedSQLPool_connectionString": { + "value": "Source=mySynapseSQLPool.sql.azuresynapse.net;Initial Catalog=myDatabaseName;User ID=mySQLAdminUserName;Password=myPassword;" + }, + "ADLS_url": { + "value": "https://.dfs.core.windows.net" + } + } +} \ No newline at end of file diff --git a/ARM/synapse_workspace.json b/ARM/synapse_workspace.json new file mode 100644 index 0000000..13501f7 --- /dev/null +++ b/ARM/synapse_workspace.json @@ -0,0 +1,204 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "companyTla": { + "type": "string", + "metadata": { + "description": "This is a Three Letter Acronym for your company name. 'CON' for Contoso for example." + } + }, + "allowAllConnections": { + "type": "string", + "allowedValues": [ + "true", + "false" + ], + "defaultValue": "true" + }, + "deploymentType": { + "type": "string", + "defaultValue": "poc", + "allowedValues": [ + "devtest", + "poc", + "prod", + "shared" + ], + "metadata": { + "description": "Specify deployment type: DevTest, POC, Prod, Shared. This will also be used in the naming convention." + } + }, + "sqlAdministratorLogin": { + "type": "string", + "metadata": { + "description": "The username of the SQL Administrator" + } + }, + "sqlAdministratorLoginPassword": { + "type": "securestring", + "metadata": { + "description": "The password for the SQL Administrator" + } + }, + "sku": { + "type": "string", + "defaultValue": "DW200c", + "allowedValues": [ + "DW100c", + "DW200c", + "DW300c", + "DW400c", + "DW500c", + "DW1000c" + ], + "metadata": { + "description": "Select the SKU of the SQL pool." + } + } + }, + "variables": { + "location": "[resourceGroup().location]", + "synapseName": "[toLower(concat(parameters('companyTla'),parameters('deploymentType')))]", + "dlsName": "[toLower(concat('dls',parameters('companyTla'),parameters('deploymentType')))]", + "dlsFsName": "[toLower(concat(variables('dlsName'),'fs1'))]", + "sqlPoolName": "[toLower(concat(variables('workspaceName'),'p1'))]", + "workspaceName": "[toLower(concat(variables('synapseName'),'ws1'))]", + "metadataSync": false + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "name": "[variables('dlsName')]", + "location": "[variables('location')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "accessTier": "Hot", + "supportsHttpsTrafficOnly": true, + "isHnsEnabled": true + }, + "resources": [ + { + "name": "[concat('default/', variables('dlsFsName'))]", + "type": "blobServices/containers", + "apiVersion": "2019-06-01", + "dependsOn": [ + "[variables('dlsName')]" + ], + "properties": { + "publicAccess": "None" + } + } + ] + }, + { + "type": "Microsoft.Synapse/workspaces", + "apiVersion": "2019-06-01-preview", + "name": "[variables('workspaceName')]", + "location": "[variables('location')]", + "identity": { + "type": "SystemAssigned" + }, + "dependsOn": [ + "[variables('dlsName')]", + "[variables('dlsFsName')]" + ], + "properties": { + "defaultDataLakeStorage": { + "accountUrl": "[reference(variables('dlsName')).primaryEndpoints.dfs]", + "filesystem": "[variables('dlsFsName')]" + }, + "sqlAdministratorLogin": "[parameters('sqlAdministratorLogin')]", + "sqlAdministratorLoginPassword": "[parameters('sqlAdministratorLoginPassword')]", + "managedVirtualNetwork": "default" + }, + "resources": [ + { + "condition": "[equals(parameters('allowAllConnections'),'true')]", + "type": "firewallrules", + "apiVersion": "2019-06-01-preview", + "name": "allowAll", + "location": "[variables('location')]", + "dependsOn": [ "[variables('workspaceName')]" ], + "properties": { + "startIpAddress": "0.0.0.0", + "endIpAddress": "255.255.255.255" + } + }, + { + "type": "firewallrules", + "apiVersion": "2019-06-01-preview", + "name": "AllowAllWindowsAzureIps", + "location": "[variables('location')]", + "dependsOn": [ "[variables('workspaceName')]" ], + "properties": { + "startIpAddress": "0.0.0.0", + "endIpAddress": "0.0.0.0" + } + }, + { + "type": "managedIdentitySqlControlSettings", + "apiVersion": "2019-06-01-preview", + "name": "default", + "location": "[variables('location')]", + "dependsOn": [ "[variables('workspaceName')]" ], + "properties": { + "grantSqlControlToManagedIdentity": { + "desiredState": "Enabled" + } + } + } + ] + }, + { + "type": "Microsoft.Synapse/workspaces/sqlPools", + "apiVersion": "2019-06-01-preview", + "name": "[concat(variables('workspaceName'), '/', variables('sqlPoolName'))]", + "location": "[variables('location')]", + "sku": { + "name": "[parameters('sku')]" + }, + "dependsOn": [ + "[variables('workspaceName')]" + ], + "properties": { + "createMode": "Default", + "collation": "SQL_Latin1_General_CP1_CI_AS" + }, + "resources": [ + { + "condition": "[variables('metadataSync')]", + "type": "metadataSync", + "apiVersion": "2019-06-01-preview", + "name": "config", + "location": "[variables('location')]", + "dependsOn": [ + "[variables('sqlPoolName')]" + ], + "properties": { + "Enabled": "[variables('metadataSync')]" + } + } + ] + }, + { + "scope": "[concat('Microsoft.Storage/storageAccounts/', variables('dlsName'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "name": "[guid(uniqueString(variables('dlsName')))]", + "location": "[variables('location')]", + "dependsOn": [ + "[variables('workspaceName')]" + ], + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[reference(resourceId('Microsoft.Synapse/workspaces', variables('workspaceName')), '2019-06-01-preview', 'Full').identity.principalId]", + "principalType": "ServicePrincipal" + } + } + ] +} \ No newline at end of file diff --git a/ARM/synapse_workspace_parameters.json b/ARM/synapse_workspace_parameters.json new file mode 100644 index 0000000..4491f04 --- /dev/null +++ b/ARM/synapse_workspace_parameters.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "companyTla": { + "value": "GEN-UNIQUE-3" + }, + "allowAllConnections": { + "value": "true" + }, + "deploymentType": { + "value": "poc" + }, + "sqlAdministratorLogin": { + "value": "GEN-UNIQUE-6" + }, + "sqlAdministratorLoginPassword": { + "value": "GEN-PASSWORD" + }, + "sku": { + "value": "DW200c" + } + } +} \ No newline at end of file diff --git a/ETL Scripts/Initialize/000. Initialize Database - Drop All Objects.sql b/ETL Scripts/Initialize/000. Initialize Database - Drop All Objects.sql new file mode 100644 index 0000000..59975c1 --- /dev/null +++ b/ETL Scripts/Initialize/000. Initialize Database - Drop All Objects.sql @@ -0,0 +1,130 @@ +IF OBJECT_ID('tempdb..#Commands') IS NOT NULL + DROP TABLE #Commands +IF OBJECT_ID('tempdb..#Drops') IS NOT NULL + DROP TABLE #Drops + +SET NOCOUNT ON; + +-- Set this to a value to only drop objects in specific schemas. +DECLARE @OnlyInSchemas NVARCHAR(1000) +SET @OnlyInSchemas = N'[synthea],[vocab],[omop]'; +SET @OnlyInSchemas = REPLACE(REPLACE(@OnlyInSchemas,'[', ''), ']', '') + +CREATE TABLE #Commands ( + [Description] NVARCHAR(MAX), + [Line] NVARCHAR(MAX), + [DateAdded] DATETIME +) WITH (HEAP); + +CREATE TABLE #Drops ( + [Type] NVARCHAR(2), + [Template] NVARCHAR(MAX) +) WITH (HEAP); + +-- -- -- -- -- OBJECTS NOT ASSOCIATED WITH TABLES -- -- -- -- -- +INSERT INTO #Drops +SELECT N'AF', N'DROP AGGREGATE $S.$O;' UNION +SELECT N'FN', N'DROP FUNCTION $S.$O;' UNION +SELECT N'FS', N'DROP FUNCTION $S.$O;' UNION +SELECT N'FT', N'DROP FUNCTION $S.$O;' UNION +SELECT N'IF', N'DROP FUNCTION $S.$O;' UNION +SELECT N'P', N'DROP PROCEDURE $S.$O;' UNION +SELECT N'SN', N'DROP SYNONYM $S.$O;' UNION +SELECT N'SQ', N'DROP QUEUE $S.$O;' UNION +SELECT N'TR', N'DROP TRIGGER $S.$O;' UNION +SELECT N'TT', N'DROP TYPE $S.$O;' UNION +SELECT N'TF', N'DROP FUNCTION $S.$O;'; + +INSERT INTO #Commands +SELECT QUOTENAME(RTRIM([S].[name])) + '.' + QUOTENAME(RTRIM([O].[name])), + REPLACE(REPLACE([D].[Template], '$S', QUOTENAME(RTRIM([S].[name]))), '$O', QUOTENAME(RTRIM([O].[name]))), + GETDATE() + FROM [sys].[objects] AS [O] + INNER JOIN [sys].[schemas] AS [S] ON [O].[schema_id] = [S].[schema_id] + INNER JOIN #Drops AS [D] ON [O].[type] COLLATE Latin1_General_CS_AS = [D].[Type] COLLATE Latin1_General_CS_AS + WHERE [S].[name] COLLATE Latin1_General_CS_AS IN (SELECT value FROM STRING_SPLIT(@OnlyInSchemas, ',')) + AND [S].[name] COLLATE Latin1_General_CS_AS <> 'sys' + AND [O].[is_ms_shipped] = 0; + +-- -- -- -- -- OBJECTS ASSOCIATED WITH TABLES -- -- -- -- -- +DELETE FROM #Drops; +INSERT INTO #Drops +SELECT N'C', N'ALTER TABLE $TS.$TO DROP CONSTRAINT $O;' UNION +SELECT N'D', N'ALTER TABLE $TS.$TO DROP CONSTRAINT $O;' UNION +SELECT N'F', N'ALTER TABLE $TS.$TO DROP CONSTRAINT $O;' UNION +SELECT N'PK', N'ALTER TABLE $TS.$TO DROP CONSTRAINT $O;'; + +INSERT INTO #Commands +SELECT QUOTENAME(RTRIM([S].[name])) + '.' + QUOTENAME(RTRIM([PO].[name])) + '::' + QUOTENAME(RTRIM([O].[name])), + REPLACE(REPLACE(REPLACE([D].[Template], '$TS', QUOTENAME(RTRIM([S].[name]))), '$O', QUOTENAME(RTRIM([O].[name]))), '$TO', QUOTENAME(RTRIM([PO].[name]))), + GETDATE() + FROM [sys].[objects] AS [O] + INNER JOIN [sys].[objects] AS [PO] ON [O].[parent_object_id] = [PO].[object_id] + INNER JOIN [sys].[schemas] AS [S] ON [PO].[schema_id] = [S].[schema_id] + INNER JOIN #Drops AS [D] ON [O].[type] COLLATE Latin1_General_CS_AS = [D].[Type] COLLATE Latin1_General_CS_AS + WHERE [S].[name] COLLATE Latin1_General_CS_AS IN (SELECT value FROM STRING_SPLIT(@OnlyInSchemas, ',')) + AND [S].[name] COLLATE Latin1_General_CS_AS <> 'sys' + AND [O].[is_ms_shipped] = 0; + + +-- -- -- -- -- ACTUAL DROP -- -- -- -- -- +DELETE FROM #Drops; +INSERT INTO #Drops +SELECT N'U', N'DROP TABLE $S.$O;' UNION +SELECT N'V', N'DROP TABLE $S.$O;'; + +INSERT INTO #Commands +SELECT QUOTENAME(RTRIM([S].[name])) + '.' + QUOTENAME(RTRIM([O].[name])), + REPLACE(REPLACE([D].[Template], '$S', QUOTENAME(RTRIM([S].[name]))), '$O', QUOTENAME(RTRIM([O].[name]))), + GETDATE() + FROM [sys].[objects] AS [O] + INNER JOIN [sys].[schemas] AS [S] ON [O].[schema_id] = [S].[schema_id] + INNER JOIN #Drops AS [D] ON [O].[type] COLLATE Latin1_General_CS_AS = [D].[Type] COLLATE Latin1_General_CS_AS + WHERE [S].[name] COLLATE Latin1_General_CS_AS IN (SELECT value FROM STRING_SPLIT(@OnlyInSchemas, ',')) + AND [S].[name] COLLATE Latin1_General_CS_AS <> 'sys' + AND [O].[is_ms_shipped] = 0; + + +--Drop Schemas +INSERT INTO #Commands +SELECT QUOTENAME(RTRIM([S].[name])), + 'DROP SCHEMA ' + QUOTENAME(RTRIM([S].[name])), + GETDATE() + FROM [sys].[schemas] AS [S] + WHERE [S].[name] COLLATE Latin1_General_CS_AS IN (SELECT value FROM STRING_SPLIT(@OnlyInSchemas, ',')) + AND [S].[name] COLLATE Latin1_General_CS_AS <> 'sys' + AND [S].[schema_id] > 7; + + +-- -- -- -- -- TABLES -- -- -- -- -- +DECLARE @Description NVARCHAR(MAX); +DECLARE @Message NVARCHAR(MAX); +DECLARE @Command NVARCHAR(MAX); + + +WHILE (SELECT COUNT(*) FROM #Commands) > 0 + +BEGIN + +SELECT TOP 1 + @Description = Description, + @Command = [Line] +FROM #Commands +ORDER BY DateAdded ASC, Description ASC + + + SET @Message = N'Dropping ' + @Description + '...'; + PRINT @Message; + + BEGIN TRY + EXEC sp_executesql @Command; + END TRY + BEGIN CATCH + SET @Message = N'Failed to drop ' + @Description + ':'; + PRINT @Message; + PRINT ERROR_MESSAGE() + END CATCH + + DELETE FROM #Commands WHERE @Description = Description +END + diff --git a/ETL Scripts/Initialize/001. Create Synthea Schema.sql b/ETL Scripts/Initialize/001. Create Synthea Schema.sql new file mode 100644 index 0000000..3beee8c --- /dev/null +++ b/ETL Scripts/Initialize/001. Create Synthea Schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA [synthea] AUTHORIZATION DBO; \ No newline at end of file diff --git a/ETL Scripts/Initialize/002. Create Synthea Staging Tables.sql b/ETL Scripts/Initialize/002. Create Synthea Staging Tables.sql new file mode 100644 index 0000000..edfa2c1 --- /dev/null +++ b/ETL Scripts/Initialize/002. Create Synthea Staging Tables.sql @@ -0,0 +1,213 @@ +--DEFINE STAGING TABLES FOR NATIVE SYNTHEA DATA + +CREATE TABLE [synthea].ALLERGIES +( + START DATE NULL, + STOP DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].CAREPLANS +( + Id VARCHAR(1000) NULL, + START DATE NULL, + STOP DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL, + REASONCODE VARCHAR(255) NULL, + REASONDESCRIPTION VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].CONDITIONS +( + START DATE NULL, + STOP DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].DEVICES +( + START DATE NULL, + STOP DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL, + UDI VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].ENCOUNTERS +( + Id VARCHAR(1000) NULL, + START DATE NULL, + STOP DATE NULL, + PATIENT VARCHAR(1000) NULL, + ORGANIZATION VARCHAR(1000) NULL, + PROVIDER VARCHAR(1000) NULL, + PAYER VARCHAR(1000) NULL, + ENCOUNTERCLASS VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL, + BASE_ENCOUNTER_COST NUMERIC(18, 0) NULL, + TOTAL_CLAIM_COST NUMERIC(18, 0) NULL, + PAYER_COVERAGE NUMERIC(18, 0) NULL, + REASONCODE VARCHAR(100) NULL, + REASONDESCRIPTION VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].IMAGING_STUDIES +( + Id VARCHAR(1000) NULL, + DATE DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + SERIES_UId VARCHAR(1000) NULL, + BODYSITE_CODE VARCHAR(100) NULL, + BODYSITE_DESCRIPTION VARCHAR(255) NULL, + MODALITY_CODE VARCHAR(100) NULL, + MODALITY_DESCRIPTION VARCHAR(255) NULL, + INSTANCE_UId VARCHAR(1000) NULL, + SOP_CODE VARCHAR(100) NULL, + SOP_DESCRIPTION VARCHAR(255) NULL, + PROCEDURE_CODE VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + + +CREATE TABLE [synthea].IMMUNIZATIONS +( + DATE DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL, + BASE_COST NUMERIC(18, 0) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].MEDICATIONS +( + START DATE NULL, + STOP DATE NULL, + PATIENT VARCHAR(1000) NULL, + PAYER VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(1000) NULL, + BASE_COST NUMERIC(18, 0) NULL, + PAYER_COVERAGE NUMERIC(18, 0) NULL, + DISPENSES INT NULL, + TOTALCOST NUMERIC(18, 0) NULL, + REASONCODE VARCHAR(100) NULL, + REASONDESCRIPTION VARCHAR(255) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].OBSERVATIONS +( + DATE DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL, + VALUE VARCHAR(1000) NULL, + UNITS VARCHAR(100) NULL, + TYPE VARCHAR(100) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].ORGANIZATIONS +( + Id VARCHAR(1000) NULL, + NAME VARCHAR(1000) NULL, + ADDRESS VARCHAR(1000) NULL, + CITY VARCHAR(100) NULL, + STATE VARCHAR(100) NULL, + ZIP VARCHAR(100) NULL, + LAT NUMERIC(18, 0) NULL, + LON NUMERIC(18, 0) NULL, + PHONE VARCHAR(100) NULL, + REVENUE FLOAT NULL, + UTILIZATION VARCHAR(100) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].PATIENTS +( + Id VARCHAR(1000) NULL, + BIRTHDATE DATE NULL, + DEATHDATE DATE NULL, + SSN VARCHAR(100) NULL, + DRIVERS VARCHAR(100) NULL, + PASSPORT VARCHAR(100) NULL, + PREFIX VARCHAR(100) NULL, + FIRST VARCHAR(100) NULL, + LAST VARCHAR(100) NULL, + SUFFIX VARCHAR(100) NULL, + MAIDEN VARCHAR(100) NULL, + MARITAL VARCHAR(100) NULL, + RACE VARCHAR(100) NULL, + ETHNICITY VARCHAR(100) NULL, + GENDER VARCHAR(100) NULL, + BIRTHPLACE VARCHAR(100) NULL, + ADDRESS VARCHAR(100) NULL, + CITY VARCHAR(100) NULL, + STATE VARCHAR(100) NULL, + COUNTY VARCHAR(100) NULL, + ZIP VARCHAR(100) NULL, + LAT NUMERIC(18, 0) NULL, + LON NUMERIC(18, 0) NULL, + HEALTHCARE_EXPENSES NUMERIC(18, 0) NULL, + HEALTHCARE_COVERAGE NUMERIC(18, 0) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].PROCEDURES +( + DATE DATE NULL, + PATIENT VARCHAR(1000) NULL, + ENCOUNTER VARCHAR(1000) NULL, + CODE VARCHAR(100) NULL, + DESCRIPTION VARCHAR(255) NULL, + BASE_COST NUMERIC(18, 0) NULL, + REASONCODE VARCHAR(1000) NULL, + REASONDESCRIPTION VARCHAR(1000) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + +CREATE TABLE [synthea].PROVIDERS +( + Id VARCHAR(1000) NULL, + ORGANIZATION VARCHAR(1000) NULL, + NAME VARCHAR(100) NULL, + GENDER VARCHAR(100) NULL, + SPECIALITY VARCHAR(100) NULL, + ADDRESS VARCHAR(255) NULL, + CITY VARCHAR(100) NULL, + STATE VARCHAR(100) NULL, + ZIP VARCHAR(100) NULL, + LAT NUMERIC(18, 0) NULL, + LON NUMERIC(18, 0) NULL, + UTILIZATION NUMERIC(18, 0) NULL +) +WITH (DISTRIBUTION=ROUND_ROBIN); + + + + + + + diff --git a/ETL Scripts/Initialize/003. Create OMOP Schema.sql b/ETL Scripts/Initialize/003. Create OMOP Schema.sql new file mode 100644 index 0000000..1133105 --- /dev/null +++ b/ETL Scripts/Initialize/003. Create OMOP Schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA [omop] AUTHORIZATION DBO; \ No newline at end of file diff --git a/ETL Scripts/Initialize/004. Create OMOP Tables.sql b/ETL Scripts/Initialize/004. Create OMOP Tables.sql new file mode 100644 index 0000000..1f5c90d --- /dev/null +++ b/ETL Scripts/Initialize/004. Create OMOP Tables.sql @@ -0,0 +1,508 @@ +--pdw CDM DDL Specification for OMOP Common Data Model v5_3_1 + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].PERSON ( person_id integer NOT NULL, + gender_concept_id integer NOT NULL, + year_of_birth integer NOT NULL, + month_of_birth integer NULL, + day_of_birth integer NULL, + birth_datetime datetime NULL, + race_concept_id integer NOT NULL, + ethnicity_concept_id integer NOT NULL, + location_id integer NULL, + provider_id integer NULL, + care_site_id integer NULL, + person_source_value varchar(50) NULL, + gender_source_value varchar(50) NULL, + gender_source_concept_id integer NULL, + race_source_value varchar(50) NULL, + race_source_concept_id integer NULL, + ethnicity_source_value varchar(50) NULL, + ethnicity_source_concept_id integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].OBSERVATION_PERIOD (observation_period_id integer NOT NULL, + person_id integer NOT NULL, + observation_period_start_date date NOT NULL, + observation_period_end_date date NOT NULL, + period_type_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].VISIT_OCCURRENCE (visit_occurrence_id integer NOT NULL, + person_id integer NOT NULL, + visit_concept_id integer NOT NULL, + visit_start_date date NOT NULL, + visit_start_datetime datetime NULL, + visit_end_date date NOT NULL, + visit_end_datetime datetime NULL, + visit_type_concept_id Integer NOT NULL, + provider_id integer NULL, + care_site_id integer NULL, + visit_source_value varchar(50) NULL, + visit_source_concept_id integer NULL, + admitting_source_concept_id integer NULL, + admitting_source_value varchar(50) NULL, + discharge_to_concept_id integer NULL, + discharge_to_source_value varchar(50) NULL, + preceding_visit_occurrence_id integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].VISIT_DETAIL (visit_detail_id integer NOT NULL, + person_id integer NOT NULL, + visit_detail_concept_id integer NOT NULL, + visit_detail_start_date date NOT NULL, + visit_detail_start_datetime datetime NULL, + visit_detail_end_date date NOT NULL, + visit_detail_end_datetime datetime NULL, + visit_detail_type_concept_id integer NOT NULL, + provider_id integer NULL, + care_site_id integer NULL, + visit_detail_source_value varchar(50) NULL, + visit_detail_source_concept_id Integer NULL, + admitting_source_value Varchar(50) NULL, + admitting_source_concept_id Integer NULL, + discharge_to_source_value Varchar(50) NULL, + discharge_to_concept_id integer NULL, + preceding_visit_detail_id integer NULL, + visit_detail_parent_id integer NULL, + visit_occurrence_id integer NOT NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONDITION_OCCURRENCE (condition_occurrence_id integer NOT NULL, + person_id integer NOT NULL, + condition_concept_id integer NOT NULL, + condition_start_date date NOT NULL, + condition_start_datetime datetime NULL, + condition_end_date date NULL, + condition_end_datetime datetime NULL, + condition_type_concept_id integer NOT NULL, + condition_status_concept_id integer NULL, + stop_reason varchar(20) NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + condition_source_value varchar(50) NULL, + condition_source_concept_id integer NULL, + condition_status_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DRUG_EXPOSURE (drug_exposure_id integer NOT NULL, + person_id integer NOT NULL, + drug_concept_id integer NOT NULL, + drug_exposure_start_date date NOT NULL, + drug_exposure_start_datetime datetime NULL, + drug_exposure_end_date date NOT NULL, + drug_exposure_end_datetime datetime NULL, + verbatim_end_date date NULL, + drug_type_concept_id integer NOT NULL, + stop_reason varchar(20) NULL, + refills integer NULL, + quantity float NULL, + days_supply integer NULL, + sig VARCHAR(1000) NULL, + route_concept_id integer NULL, + lot_number varchar(50) NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + drug_source_value varchar(50) NULL, + drug_source_concept_id integer NULL, + route_source_value varchar(50) NULL, + dose_unit_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].PROCEDURE_OCCURRENCE (procedure_occurrence_id integer NOT NULL, + person_id integer NOT NULL, + procedure_concept_id integer NOT NULL, + procedure_date date NOT NULL, + procedure_datetime datetime NULL, + procedure_type_concept_id integer NOT NULL, + modifier_concept_id integer NULL, + quantity integer NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + procedure_source_value varchar(50) NULL, + procedure_source_concept_id integer NULL, + modifier_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DEVICE_EXPOSURE (device_exposure_id integer NOT NULL, + person_id integer NOT NULL, + device_concept_id integer NOT NULL, + device_exposure_start_date date NOT NULL, + device_exposure_start_datetime datetime NULL, + device_exposure_end_date date NULL, + device_exposure_end_datetime datetime NULL, + device_type_concept_id integer NOT NULL, + unique_device_id varchar(100) NULL, + quantity integer NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + device_source_value varchar(100) NULL, + device_source_concept_id integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].MEASUREMENT (measurement_id integer NOT NULL, + person_id integer NOT NULL, + measurement_concept_id integer NOT NULL, + measurement_date date NOT NULL, + measurement_datetime datetime NULL, + measurement_time varchar(10) NULL, + measurement_type_concept_id integer NOT NULL, + operator_concept_id integer NULL, + value_as_number float NULL, + value_as_concept_id integer NULL, + unit_concept_id integer NULL, + range_low float NULL, + range_high float NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + measurement_source_value varchar(50) NULL, + measurement_source_concept_id integer NULL, + unit_source_value varchar(50) NULL, + value_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].OBSERVATION (observation_id integer NOT NULL, + person_id integer NOT NULL, + observation_concept_id integer NOT NULL, + observation_date date NOT NULL, + observation_datetime datetime NULL, + observation_type_concept_id integer NOT NULL, + value_as_number float NULL, + value_as_string varchar(60) NULL, + value_as_concept_id Integer NULL, + qualifier_concept_id integer NULL, + unit_concept_id integer NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + observation_source_value varchar(50) NULL, + observation_source_concept_id integer NULL, + unit_source_value varchar(50) NULL, + qualifier_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DEATH ( person_id integer NULL, + death_date date NULL, + death_datetime datetime NULL, + death_type_concept_id integer NULL, + cause_concept_id integer NULL, + cause_source_value varchar(50) NULL, + cause_source_concept_id integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].NOTE (note_id integer NOT NULL, + person_id integer NOT NULL, + note_date date NOT NULL, + note_datetime datetime NULL, + note_type_concept_id integer NOT NULL, + note_class_concept_id integer NOT NULL, + note_title varchar(250) NULL, + note_text VARCHAR(1000) NOT NULL, + encoding_concept_id integer NOT NULL, + language_concept_id integer NOT NULL, + provider_id integer NULL, + visit_occurrence_id integer NULL, + visit_detail_id integer NULL, + note_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].NOTE_NLP (note_nlp_id integer NOT NULL, + note_id integer NOT NULL, + section_concept_id integer NULL, + snippet varchar(250) NULL, + "offset" varchar(50) NULL, + lexical_variant varchar(250) NOT NULL, + note_nlp_concept_id integer NULL, + note_nlp_source_concept_id integer NULL, + nlp_system varchar(250) NULL, + nlp_date date NOT NULL, + nlp_datetime datetime NULL, + term_exists varchar(1) NULL, + term_temporal varchar(50) NULL, + term_modifiers varchar(2000) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].SPECIMEN (specimen_id integer NOT NULL, + person_id integer NOT NULL, + specimen_concept_id integer NOT NULL, + specimen_type_concept_id integer NOT NULL, + specimen_date date NOT NULL, + specimen_datetime datetime NULL, + quantity float NULL, + unit_concept_id integer NULL, + anatomic_site_concept_id integer NULL, + disease_status_concept_id integer NULL, + specimen_source_id varchar(50) NULL, + specimen_source_value varchar(50) NULL, + unit_source_value varchar(50) NULL, + anatomic_site_source_value varchar(50) NULL, + disease_status_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].FACT_RELATIONSHIP (domain_concept_id_1 integer NOT NULL, + fact_id_1 integer NOT NULL, + domain_concept_id_2 integer NOT NULL, + fact_id_2 integer NOT NULL, + relationship_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].LOCATION (location_id integer NOT NULL, + address_1 varchar(50) NULL, + address_2 varchar(50) NULL, + city varchar(50) NULL, + state varchar(2) NULL, + zip varchar(9) NULL, + county varchar(20) NULL, + location_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CARE_SITE (care_site_id integer NOT NULL, + care_site_name varchar(255) NULL, + place_of_service_concept_id integer NULL, + location_id integer NULL, + care_site_source_value varchar(50) NULL, + place_of_service_source_value varchar(50) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].PROVIDER (provider_id integer NOT NULL, + provider_name varchar(255) NULL, + npi varchar(20) NULL, + dea varchar(20) NULL, + specialty_concept_id integer NULL, + care_site_id integer NULL, + year_of_birth integer NULL, + gender_concept_id integer NULL, + provider_source_value varchar(50) NULL, + specialty_source_value varchar(50) NULL, + specialty_source_concept_id integer NULL, + gender_source_value varchar(50) NULL, + gender_source_concept_id integer NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].PAYER_PLAN_PERIOD (payer_plan_period_id integer NOT NULL, + person_id integer NOT NULL, + payer_plan_period_start_date date NOT NULL, + payer_plan_period_end_date date NOT NULL, + payer_concept_id integer NULL, + payer_source_value varchar(50) NULL, + payer_source_concept_id integer NULL, + plan_concept_id integer NULL, + plan_source_value varchar(50) NULL, + plan_source_concept_id integer NULL, + sponsor_concept_id integer NULL, + sponsor_source_value varchar(50) NULL, + sponsor_source_concept_id integer NULL, + family_source_value varchar(50) NULL, + stop_reason_concept_id integer NULL, + stop_reason_source_value varchar(50) NULL, + stop_reason_source_concept_id integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].COST (cost_id integer NOT NULL, + cost_event_id integer NOT NULL, + cost_domain_id varchar(20) NOT NULL, + cost_type_concept_id integer NOT NULL, + currency_concept_id integer NULL, + total_charge float NULL, + total_cost float NULL, + total_paid float NULL, + paid_by_payer float NULL, + paid_by_patient float NULL, + paid_patient_copay float NULL, + paid_patient_coinsurance float NULL, + paid_patient_deductible float NULL, + paid_by_primary float NULL, + paid_ingredient_cost float NULL, + paid_dispensing_fee float NULL, + payer_plan_period_id integer NULL, + amount_allowed float NULL, + revenue_code_concept_id integer NULL, + revenue_code_source_value varchar(50) NULL, + drg_concept_id integer NULL, + drg_source_value varchar(3) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DRUG_ERA (drug_era_id integer NOT NULL, + person_id integer NOT NULL, + drug_concept_id integer NOT NULL, + drug_era_start_date datetime NOT NULL, + drug_era_end_date datetime NOT NULL, + drug_exposure_count integer NULL, + gap_days integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DOSE_ERA (dose_era_id integer NOT NULL, + person_id integer NOT NULL, + drug_concept_id integer NOT NULL, + unit_concept_id integer NOT NULL, + dose_value float NOT NULL, + dose_era_start_date datetime NOT NULL, + dose_era_end_date datetime NOT NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONDITION_ERA (condition_era_id integer NOT NULL, + person_id integer NOT NULL, + condition_concept_id integer NOT NULL, + condition_era_start_date datetime NOT NULL, + condition_era_end_date datetime NOT NULL, + condition_occurrence_count integer NULL ) +WITH (DISTRIBUTION = HASH(person_id)); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].METADATA (metadata_concept_id integer NOT NULL, + metadata_type_concept_id integer NOT NULL, + name varchar(250) NOT NULL, + value_as_string varchar(250) NULL, + value_as_concept_id integer NULL, + metadata_date date NULL, + metadata_datetime datetime NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CDM_SOURCE (cdm_source_name varchar(255) NOT NULL, + cdm_source_abbreviation varchar(25) NULL, + cdm_holder varchar(255) NULL, + source_description VARCHAR(1000) NULL, + source_documentation_reference varchar(255) NULL, + cdm_etl_reference varchar(255) NULL, + source_release_date date NULL, + cdm_release_date date NULL, + cdm_version varchar(10) NULL, + vocabulary_version varchar(20) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONCEPT (concept_id integer NOT NULL, + concept_name varchar(255) NOT NULL, + domain_id varchar(20) NOT NULL, + vocabulary_id varchar(20) NOT NULL, + concept_class_id varchar(20) NOT NULL, + standard_concept varchar(1) NULL, + concept_code varchar(50) NOT NULL, + valid_start_date date NOT NULL, + valid_end_date date NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].VOCABULARY (vocabulary_id varchar(20) NOT NULL, + vocabulary_name varchar(255) NOT NULL, + vocabulary_reference varchar(255) NOT NULL, + vocabulary_version varchar(255) NULL, + vocabulary_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DOMAIN (domain_id varchar(20) NOT NULL, + domain_name varchar(255) NOT NULL, + domain_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONCEPT_CLASS (concept_class_id varchar(20) NOT NULL, + concept_class_name varchar(255) NOT NULL, + concept_class_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONCEPT_RELATIONSHIP (concept_id_1 integer NOT NULL, + concept_id_2 integer NOT NULL, + relationship_id varchar(20) NOT NULL, + valid_start_date date NOT NULL, + valid_end_date date NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].RELATIONSHIP (relationship_id varchar(20) NOT NULL, + relationship_name varchar(255) NOT NULL, + is_hierarchical varchar(1) NOT NULL, + defines_ancestry varchar(1) NOT NULL, + reverse_relationship_id varchar(20) NOT NULL, + relationship_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONCEPT_SYNONYM (concept_id integer NOT NULL, + concept_synonym_name varchar(1000) NOT NULL, + language_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].CONCEPT_ANCESTOR (ancestor_concept_id integer NOT NULL, + descendant_concept_id integer NOT NULL, + min_levels_of_separation integer NOT NULL, + max_levels_of_separation integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].SOURCE_TO_CONCEPT_MAP (source_code varchar(50) NOT NULL, + source_concept_id integer NOT NULL, + source_vocabulary_id varchar(20) NOT NULL, + source_code_description varchar(255) NULL, + target_concept_id integer NOT NULL, + target_vocabulary_id varchar(20) NOT NULL, + valid_start_date date NOT NULL, + valid_end_date date NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].DRUG_STRENGTH (drug_concept_id integer NOT NULL, + ingredient_concept_id integer NOT NULL, + amount_value float NULL, + amount_unit_concept_id integer NULL, + numerator_value float NULL, + numerator_unit_concept_id integer NULL, + denominator_value float NULL, + denominator_unit_concept_id integer NULL, + box_size integer NULL, + valid_start_date date NOT NULL, + valid_end_date date NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].COHORT_DEFINITION (cohort_definition_id integer NOT NULL, + cohort_definition_name varchar(255) NOT NULL, + cohort_definition_description VARCHAR(1000) NULL, + definition_type_concept_id integer NOT NULL, + cohort_definition_syntax VARCHAR(1000) NULL, + subject_concept_id integer NOT NULL, + cohort_initiation_date date NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [omop].ATTRIBUTE_DEFINITION (attribute_definition_id integer NOT NULL, + attribute_name varchar(255) NOT NULL, + attribute_description VARCHAR(1000) NULL, + attribute_type_concept_id integer NOT NULL, + attribute_syntax VARCHAR(1000) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); \ No newline at end of file diff --git a/ETL Scripts/Initialize/005. Create vocab Schema.sql b/ETL Scripts/Initialize/005. Create vocab Schema.sql new file mode 100644 index 0000000..1e83019 --- /dev/null +++ b/ETL Scripts/Initialize/005. Create vocab Schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA [vocab] AUTHORIZATION DBO; \ No newline at end of file diff --git a/ETL Scripts/Initialize/006. Create vocab Tables.sql b/ETL Scripts/Initialize/006. Create vocab Tables.sql new file mode 100644 index 0000000..5b33e24 --- /dev/null +++ b/ETL Scripts/Initialize/006. Create vocab Tables.sql @@ -0,0 +1,78 @@ + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].CONCEPT (concept_id integer NOT NULL, + concept_name varchar(255) NOT NULL, + domain_id varchar(20) NOT NULL, + vocabulary_id varchar(20) NOT NULL, + concept_class_id varchar(20) NOT NULL, + standard_concept varchar(1) NULL, + concept_code varchar(50) NOT NULL, + valid_start_date varchar(20) NOT NULL, + valid_end_date varchar(20) NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].VOCABULARY (vocabulary_id varchar(20) NOT NULL, + vocabulary_name varchar(255) NOT NULL, + vocabulary_reference varchar(255) NOT NULL, + vocabulary_version varchar(255) NULL, + vocabulary_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].DOMAIN (domain_id varchar(20) NOT NULL, + domain_name varchar(255) NOT NULL, + domain_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].CONCEPT_CLASS (concept_class_id varchar(20) NOT NULL, + concept_class_name varchar(255) NOT NULL, + concept_class_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].CONCEPT_RELATIONSHIP (concept_id_1 integer NOT NULL, + concept_id_2 integer NOT NULL, + relationship_id varchar(20) NOT NULL, + valid_start_date varchar(20) NOT NULL, + valid_end_date varchar(20) NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].RELATIONSHIP (relationship_id varchar(20) NOT NULL, + relationship_name varchar(255) NOT NULL, + is_hierarchical varchar(1) NOT NULL, + defines_ancestry varchar(1) NOT NULL, + reverse_relationship_id varchar(20) NOT NULL, + relationship_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].CONCEPT_SYNONYM (concept_id integer NOT NULL, + concept_synonym_name varchar(1000) NOT NULL, + language_concept_id integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + + IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].CONCEPT_ANCESTOR (ancestor_concept_id integer NOT NULL, + descendant_concept_id integer NOT NULL, + min_levels_of_separation integer NOT NULL, + max_levels_of_separation integer NOT NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); + + +IF XACT_STATE() = 1 COMMIT; CREATE TABLE [vocab].DRUG_STRENGTH (drug_concept_id integer NOT NULL, + ingredient_concept_id integer NOT NULL, + amount_value float NULL, + amount_unit_concept_id integer NULL, + numerator_value float NULL, + numerator_unit_concept_id integer NULL, + denominator_value float NULL, + denominator_unit_concept_id integer NULL, + box_size integer NULL, + valid_start_date varchar(20) NOT NULL, + valid_end_date varchar(20) NOT NULL, + invalid_reason varchar(1) NULL ) +WITH (DISTRIBUTION = ROUND_ROBIN); \ No newline at end of file diff --git a/ETL Scripts/Load/001. Load person.sql b/ETL Scripts/Load/001. Load person.sql new file mode 100644 index 0000000..4fe188f --- /dev/null +++ b/ETL Scripts/Load/001. Load person.sql @@ -0,0 +1,60 @@ +INSERT INTO [omop].PERSON +( + PERSON_ID, + GENDER_CONCEPT_ID, + YEAR_OF_BIRTH, + MONTH_OF_BIRTH, + DAY_OF_BIRTH, + BIRTH_DATETIME, + RACE_CONCEPT_ID, + ETHNICITY_CONCEPT_ID, + LOCATION_ID, + PROVIDER_ID, + CARE_SITE_ID, + PERSON_SOURCE_VALUE, + GENDER_SOURCE_VALUE, + GENDER_SOURCE_CONCEPT_ID, + RACE_SOURCE_VALUE, + RACE_SOURCE_CONCEPT_ID, + ETHNICITY_SOURCE_VALUE, + ETHNICITY_SOURCE_CONCEPT_ID +) +SELECT ROW_NUMBER() OVER (ORDER BY p.id), + CASE UPPER(p.gender) + WHEN 'M' THEN + 8507 + WHEN 'F' THEN + 8532 + END, + YEAR(p.birthdate), + MONTH(p.birthdate), + DAY(p.birthdate), + p.birthdate, + CASE UPPER(p.race) + WHEN 'WHITE' THEN + 8527 + WHEN 'BLACK' THEN + 8516 + WHEN 'ASIAN' THEN + 8515 + ELSE + 0 + END, + CASE + WHEN UPPER(p.race) = 'HISPANIC' THEN + 38003563 + ELSE + 0 + END, + NULL, + NULL, + NULL, + p.id, + p.gender, + 0, + p.race, + 0, + p.ethnicity, + 0 +FROM [synthea].patients p +WHERE p.gender IS NOT NULL; diff --git a/ETL Scripts/Load/010. Load AllVisitTable.sql b/ETL Scripts/Load/010. Load AllVisitTable.sql new file mode 100644 index 0000000..1b951e0 --- /dev/null +++ b/ETL Scripts/Load/010. Load AllVisitTable.sql @@ -0,0 +1,184 @@ +IF OBJECT_ID('[omop].IP_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].IP_VISITS; +IF OBJECT_ID('[omop].ER_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].ER_VISITS; +IF OBJECT_ID('[omop].OP_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].OP_VISITS; +IF OBJECT_ID('[omop].ALL_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].ALL_VISITS; + +CREATE TABLE [omop].IP_VISITS +/* Inpatient visits */ +/* Collapse IP claim lines with <=1 day between them into one visit */ +WITH (DISTRIBUTION=ROUND_ROBIN) AS WITH CTE_END_DATES + AS (SELECT patient, + encounterclass, + DATEADD(DAY, 1, EVENT_DATE) AS END_DATE + FROM + ( + SELECT patient, + encounterclass, + EVENT_DATE, + EVENT_TYPE, + MAX(START_ORDINAL) OVER (PARTITION BY patient, + encounterclass + ORDER BY EVENT_DATE, + EVENT_TYPE + ROWS UNBOUNDED PRECEDING + ) AS START_ORDINAL, + ROW_NUMBER() OVER (PARTITION BY patient, + encounterclass + ORDER BY EVENT_DATE, + EVENT_TYPE + ) AS OVERALL_ORD + FROM + ( + SELECT patient, + encounterclass, + start AS EVENT_DATE, + -1 AS EVENT_TYPE, + ROW_NUMBER() OVER (PARTITION BY patient, encounterclass ORDER BY start, stop) AS START_ORDINAL + FROM [synthea].encounters + WHERE encounterclass = 'inpatient' + UNION ALL + SELECT patient, + encounterclass, + DATEADD(DAY, 1, stop), + 1 AS EVENT_TYPE, + NULL + FROM [synthea].encounters + WHERE encounterclass = 'inpatient' + ) RAWDATA + ) E + WHERE (2 * E.START_ORDINAL - E.OVERALL_ORD = 0)), + CTE_VISIT_ENDS + AS (SELECT MIN(V.id) AS encounter_id, + V.patient, + V.encounterclass, + V.start AS VISIT_START_DATE, + MIN(E.END_DATE) AS VISIT_END_DATE + FROM [synthea].encounters V + JOIN CTE_END_DATES E + ON V.patient = E.patient + AND V.encounterclass = E.encounterclass + AND E.END_DATE >= V.start + GROUP BY V.patient, + V.encounterclass, + V.start) +SELECT T2.encounter_id, + T2.patient, + T2.encounterclass, + T2.VISIT_START_DATE, + T2.VISIT_END_DATE +FROM +( + SELECT CTE_VISIT_ENDS.encounter_id, + patient, + encounterclass, + MIN(VISIT_START_DATE) AS VISIT_START_DATE, + CTE_VISIT_ENDS.VISIT_END_DATE + FROM CTE_VISIT_ENDS + GROUP BY CTE_VISIT_ENDS.encounter_id, + patient, + encounterclass, + CTE_VISIT_ENDS.VISIT_END_DATE +) T2; + +CREATE TABLE [omop].ER_VISITS +/* Emergency visits */ +/* collapse ER claim lines with no days between them into one visit */ +WITH (DISTRIBUTION=ROUND_ROBIN) AS SELECT T2.encounter_id, + T2.patient, + T2.encounterclass, + T2.VISIT_START_DATE, + T2.VISIT_END_DATE + FROM + ( + SELECT MIN(encounter_id) AS encounter_id, + patient, + encounterclass, + VISIT_START_DATE, + MAX(VISIT_END_DATE) AS VISIT_END_DATE + FROM + ( + SELECT CL1.id AS encounter_id, + CL1.patient, + CL1.encounterclass, + CL1.start AS VISIT_START_DATE, + CL2.stop AS VISIT_END_DATE + FROM [synthea].encounters CL1 + JOIN [synthea].encounters CL2 + ON CL1.patient = CL2.patient + AND CL1.start = CL2.start + AND CL1.encounterclass = CL2.encounterclass + WHERE CL1.encounterclass IN ( 'emergency', 'urgent' ) + ) T1 + GROUP BY patient, + encounterclass, + VISIT_START_DATE + ) T2; + +/* Outpatient visits */ +CREATE TABLE [omop].OP_VISITS +WITH (DISTRIBUTION=ROUND_ROBIN) AS WITH CTE_VISITS_DISTINCT + AS (SELECT MIN(id) AS encounter_id, + patient, + encounterclass, + start AS VISIT_START_DATE, + stop AS VISIT_END_DATE + FROM [synthea].encounters + WHERE encounterclass IN ( 'ambulatory', 'wellness', 'outpatient' ) + GROUP BY patient, + encounterclass, + start, + stop) +SELECT MIN(CTE_VISITS_DISTINCT.encounter_id) AS encounter_id, + patient, + encounterclass, + VISIT_START_DATE, + MAX(VISIT_END_DATE) AS VISIT_END_DATE +FROM CTE_VISITS_DISTINCT +GROUP BY patient, + encounterclass, + VISIT_START_DATE; + +CREATE TABLE [omop].all_visits +WITH (DISTRIBUTION=ROUND_ROBIN) +/* All visits */ +AS SELECT T1.encounter_id, + patient, + encounterclass, + T1.VISIT_START_DATE, + T1.VISIT_END_DATE, + ROW_NUMBER() OVER (ORDER BY patient) AS visit_occurrence_id + FROM + ( + SELECT IP_VISITS.encounter_id, + patient, + encounterclass, + IP_VISITS.VISIT_START_DATE, + IP_VISITS.VISIT_END_DATE + FROM [omop].IP_VISITS + UNION ALL + SELECT ER_VISITS.encounter_id, + patient, + encounterclass, + VISIT_START_DATE, + ER_VISITS.VISIT_END_DATE + FROM [omop].ER_VISITS + UNION ALL + SELECT OP_VISITS.encounter_id, + patient, + encounterclass, + VISIT_START_DATE, + OP_VISITS.VISIT_END_DATE + FROM [omop].OP_VISITS + ) T1; + +--Remove unnecessary tables +IF OBJECT_ID('[omop].IP_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].IP_VISITS; +IF OBJECT_ID('[omop].ER_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].ER_VISITS; +IF OBJECT_ID('[omop].OP_VISITS', 'U') IS NOT NULL + DROP TABLE [omop].OP_VISITS; diff --git a/ETL Scripts/Load/020. AAVITable.sql b/ETL Scripts/Load/020. AAVITable.sql new file mode 100644 index 0000000..9cfa7cf --- /dev/null +++ b/ETL Scripts/Load/020. AAVITable.sql @@ -0,0 +1,52 @@ +/*Assign VISIT_OCCURRENCE_ID to all encounters*/ + +IF OBJECT_ID('[omop].ASSIGN_ALL_VISIT_IDS', 'U') IS NOT NULL +BEGIN + DROP TABLE [omop].ASSIGN_ALL_VISIT_IDS; +END; + +CREATE TABLE [omop].ASSIGN_ALL_VISIT_IDS +WITH (DISTRIBUTION=ROUND_ROBIN) AS SELECT E.id AS encounter_id, + E.patient AS person_source_value, + E.start AS date_service, + E.stop AS date_service_end, + E.encounterclass, + AV.encounterclass AS VISIT_TYPE, + AV.VISIT_START_DATE, + AV.VISIT_END_DATE, + AV.visit_occurrence_id, + CASE + WHEN E.encounterclass = 'inpatient' + AND AV.encounterclass = 'inpatient' THEN + visit_occurrence_id + WHEN E.encounterclass IN ( 'emergency', 'urgent' ) THEN + (CASE + WHEN AV.encounterclass = 'inpatient' + AND E.start > AV.VISIT_START_DATE THEN + visit_occurrence_id + WHEN AV.encounterclass IN ( 'emergency', 'urgent' ) + AND E.start = AV.VISIT_START_DATE THEN + visit_occurrence_id + ELSE + NULL + END + ) + WHEN E.encounterclass IN ( 'ambulatory', 'wellness', 'outpatient' ) THEN + (CASE + WHEN AV.encounterclass = 'inpatient' + AND E.start >= AV.VISIT_START_DATE THEN + visit_occurrence_id + WHEN AV.encounterclass IN ( 'ambulatory', 'wellness', 'outpatient' ) THEN + visit_occurrence_id + ELSE + NULL + END + ) + ELSE + NULL + END AS VISIT_OCCURRENCE_ID_NEW + FROM [synthea].encounters AS E + JOIN [omop].all_visits AS AV + ON E.patient = AV.patient + AND E.start >= AV.VISIT_START_DATE + AND E.start <= AV.VISIT_END_DATE; diff --git a/ETL Scripts/Load/030. Load source_to_source_vocab_map.sql b/ETL Scripts/Load/030. Load source_to_source_vocab_map.sql new file mode 100644 index 0000000..febd44a --- /dev/null +++ b/ETL Scripts/Load/030. Load source_to_source_vocab_map.sql @@ -0,0 +1,69 @@ +--Use this code to map source codes to source concept ids; +IF OBJECT_ID('[omop].source_to_source_vocab_map', 'U') IS NOT NULL + DROP TABLE [omop].source_to_source_vocab_map; + + +CREATE TABLE [omop].source_to_source_vocab_map +WITH (DISTRIBUTION=ROUND_ROBIN) AS WITH CTE_VOCAB_MAP + AS (SELECT c.CONCEPT_CODE AS SOURCE_CODE, + c.CONCEPT_ID AS SOURCE_CONCEPT_ID, + c.CONCEPT_NAME AS SOURCE_CODE_DESCRIPTION, + c.VOCABULARY_ID AS SOURCE_VOCABULARY_ID, + c.DOMAIN_ID AS SOURCE_DOMAIN_ID, + c.CONCEPT_CLASS_ID AS SOURCE_CONCEPT_CLASS_ID, + c.VALID_START_DATE AS SOURCE_VALID_START_DATE, + c.VALID_END_DATE AS SOURCE_VALID_END_DATE, + c.INVALID_REASON AS SOURCE_INVALID_REASON, + c.CONCEPT_ID AS TARGET_CONCEPT_ID, + c.CONCEPT_NAME AS TARGET_CONCEPT_NAME, + c.VOCABULARY_ID AS TARGET_VOCABULARY_ID, + c.DOMAIN_ID AS TARGET_DOMAIN_ID, + c.CONCEPT_CLASS_ID AS TARGET_CONCEPT_CLASS_ID, + c.INVALID_REASON AS TARGET_INVALID_REASON, + c.STANDARD_CONCEPT AS TARGET_STANDARD_CONCEPT + FROM [omop].CONCEPT c + UNION + SELECT SOURCE_CODE, + SOURCE_CONCEPT_ID, + SOURCE_CODE_DESCRIPTION, + SOURCE_VOCABULARY_ID, + c1.DOMAIN_ID AS SOURCE_DOMAIN_ID, + c2.CONCEPT_CLASS_ID AS SOURCE_CONCEPT_CLASS_ID, + c1.VALID_START_DATE AS SOURCE_VALID_START_DATE, + c1.VALID_END_DATE AS SOURCE_VALID_END_DATE, + stcm.INVALID_REASON AS SOURCE_INVALID_REASON, + TARGET_CONCEPT_ID, + c2.CONCEPT_NAME AS TARGET_CONCEPT_NAME, + TARGET_VOCABULARY_ID, + c2.DOMAIN_ID AS TARGET_DOMAIN_ID, + c2.CONCEPT_CLASS_ID AS TARGET_CONCEPT_CLASS_ID, + c2.INVALID_REASON AS TARGET_INVALID_REASON, + c2.STANDARD_CONCEPT AS TARGET_STANDARD_CONCEPT + FROM [omop].SOURCE_TO_CONCEPT_MAP stcm + LEFT OUTER JOIN [omop].CONCEPT c1 + ON c1.CONCEPT_ID = stcm.SOURCE_CONCEPT_ID + LEFT OUTER JOIN [omop].CONCEPT c2 + ON c2.CONCEPT_ID = stcm.TARGET_CONCEPT_ID + WHERE stcm.INVALID_REASON IS NULL) +SELECT CTE_VOCAB_MAP.SOURCE_CODE, + CTE_VOCAB_MAP.SOURCE_CONCEPT_ID, + CTE_VOCAB_MAP.SOURCE_CODE_DESCRIPTION, + CTE_VOCAB_MAP.SOURCE_VOCABULARY_ID, + CTE_VOCAB_MAP.SOURCE_DOMAIN_ID, + CTE_VOCAB_MAP.SOURCE_CONCEPT_CLASS_ID, + CTE_VOCAB_MAP.SOURCE_VALID_START_DATE, + CTE_VOCAB_MAP.SOURCE_VALID_END_DATE, + CTE_VOCAB_MAP.SOURCE_INVALID_REASON, + CTE_VOCAB_MAP.TARGET_CONCEPT_ID, + CTE_VOCAB_MAP.TARGET_CONCEPT_NAME, + CTE_VOCAB_MAP.TARGET_VOCABULARY_ID, + CTE_VOCAB_MAP.TARGET_DOMAIN_ID, + CTE_VOCAB_MAP.TARGET_CONCEPT_CLASS_ID, + CTE_VOCAB_MAP.TARGET_INVALID_REASON, + CTE_VOCAB_MAP.TARGET_STANDARD_CONCEPT +FROM CTE_VOCAB_MAP; + +CREATE INDEX idx_source_vocab_map_source_code +ON [omop].source_to_source_vocab_map (SOURCE_CODE); +CREATE INDEX idx_source_vocab_map_source_vocab_id +ON [omop].source_to_source_vocab_map (SOURCE_VOCABULARY_ID); diff --git a/ETL Scripts/Load/040. Load source_to_standard_vocab_map.sql b/ETL Scripts/Load/040. Load source_to_standard_vocab_map.sql new file mode 100644 index 0000000..33f9a82 --- /dev/null +++ b/ETL Scripts/Load/040. Load source_to_standard_vocab_map.sql @@ -0,0 +1,77 @@ +-- Create mapping table as per logic in 3.1.2 Source to Standard Terminology +-- found in Truven_CCAE_and_MDCR_ETL_CDM_V5.2.0.doc +-- +IF OBJECT_ID('[omop].source_to_standard_vocab_map', 'U') IS NOT NULL + DROP TABLE [omop].source_to_standard_vocab_map; + +CREATE TABLE [omop].source_to_standard_vocab_map +WITH (DISTRIBUTION=ROUND_ROBIN) AS WITH CTE_VOCAB_MAP + AS (SELECT C.CONCEPT_CODE AS SOURCE_CODE, + C.CONCEPT_ID AS SOURCE_CONCEPT_ID, + C.CONCEPT_NAME AS SOURCE_CODE_DESCRIPTION, + C.VOCABULARY_ID AS SOURCE_VOCABULARY_ID, + C.DOMAIN_ID AS SOURCE_DOMAIN_ID, + C.CONCEPT_CLASS_ID AS SOURCE_CONCEPT_CLASS_ID, + C.VALID_START_DATE AS SOURCE_VALID_START_DATE, + C.VALID_END_DATE AS SOURCE_VALID_END_DATE, + C.INVALID_REASON AS SOURCE_INVALID_REASON, + C1.CONCEPT_ID AS TARGET_CONCEPT_ID, + C1.CONCEPT_NAME AS TARGET_CONCEPT_NAME, + C1.VOCABULARY_ID AS TARGET_VOCABULARY_ID, + C1.DOMAIN_ID AS TARGET_DOMAIN_ID, + C1.CONCEPT_CLASS_ID AS TARGET_CONCEPT_CLASS_ID, + C1.INVALID_REASON AS TARGET_INVALID_REASON, + C1.STANDARD_CONCEPT AS TARGET_STANDARD_CONCEPT + FROM [omop].CONCEPT C + JOIN [omop].CONCEPT_RELATIONSHIP CR + ON C.CONCEPT_ID = CR.CONCEPT_ID_1 + AND CR.INVALID_REASON IS NULL + AND LOWER(CR.RELATIONSHIP_ID) = CAST('maps to' AS VARCHAR(20)) + JOIN [omop].CONCEPT C1 + ON CR.CONCEPT_ID_2 = C1.CONCEPT_ID + AND C1.INVALID_REASON IS NULL + UNION + SELECT SOURCE_CODE, + SOURCE_CONCEPT_ID, + SOURCE_CODE_DESCRIPTION, + SOURCE_VOCABULARY_ID, + c1.DOMAIN_ID AS SOURCE_DOMAIN_ID, + c2.CONCEPT_CLASS_ID AS SOURCE_CONCEPT_CLASS_ID, + c1.VALID_START_DATE AS SOURCE_VALID_START_DATE, + c1.VALID_END_DATE AS SOURCE_VALID_END_DATE, + stcm.INVALID_REASON AS SOURCE_INVALID_REASON, + TARGET_CONCEPT_ID, + c2.CONCEPT_NAME AS TARGET_CONCEPT_NAME, + TARGET_VOCABULARY_ID, + c2.DOMAIN_ID AS TARGET_DOMAIN_ID, + c2.CONCEPT_CLASS_ID AS TARGET_CONCEPT_CLASS_ID, + c2.INVALID_REASON AS TARGET_INVALID_REASON, + c2.STANDARD_CONCEPT AS TARGET_STANDARD_CONCEPT + FROM [omop].SOURCE_TO_CONCEPT_MAP stcm + LEFT OUTER JOIN [omop].CONCEPT c1 + ON c1.CONCEPT_ID = stcm.SOURCE_CONCEPT_ID + LEFT OUTER JOIN [omop].CONCEPT c2 + ON c2.CONCEPT_ID = stcm.TARGET_CONCEPT_ID + WHERE stcm.INVALID_REASON IS NULL) +SELECT CTE_VOCAB_MAP.SOURCE_CODE, + CTE_VOCAB_MAP.SOURCE_CONCEPT_ID, + CTE_VOCAB_MAP.SOURCE_CODE_DESCRIPTION, + CTE_VOCAB_MAP.SOURCE_VOCABULARY_ID, + CTE_VOCAB_MAP.SOURCE_DOMAIN_ID, + CTE_VOCAB_MAP.SOURCE_CONCEPT_CLASS_ID, + CTE_VOCAB_MAP.SOURCE_VALID_START_DATE, + CTE_VOCAB_MAP.SOURCE_VALID_END_DATE, + CTE_VOCAB_MAP.SOURCE_INVALID_REASON, + CTE_VOCAB_MAP.TARGET_CONCEPT_ID, + CTE_VOCAB_MAP.TARGET_CONCEPT_NAME, + CTE_VOCAB_MAP.TARGET_VOCABULARY_ID, + CTE_VOCAB_MAP.TARGET_DOMAIN_ID, + CTE_VOCAB_MAP.TARGET_CONCEPT_CLASS_ID, + CTE_VOCAB_MAP.TARGET_INVALID_REASON, + CTE_VOCAB_MAP.TARGET_STANDARD_CONCEPT +FROM CTE_VOCAB_MAP; + +CREATE INDEX idx_vocab_map_source_code +ON [omop].source_to_standard_vocab_map (SOURCE_CODE); +CREATE INDEX idx_vocab_map_source_vocab_id +ON [omop].source_to_standard_vocab_map (SOURCE_VOCABULARY_ID); diff --git a/ETL Scripts/Load/050. Load final_visit_ids.sql b/ETL Scripts/Load/050. Load final_visit_ids.sql new file mode 100644 index 0000000..1b27d88 --- /dev/null +++ b/ETL Scripts/Load/050. Load final_visit_ids.sql @@ -0,0 +1,55 @@ +-- ============================================= +-- Create Date: 20210505 +-- Description: Create and load [omop].FINAL_VISIT_IDS table +-- ============================================= +IF OBJECT_ID('[omop].FINAL_VISIT_IDS', 'U') IS NOT NULL + DROP TABLE [omop].FINAL_VISIT_IDS; + +CREATE TABLE [omop].FINAL_VISIT_IDS +WITH (DISTRIBUTION=ROUND_ROBIN) AS SELECT encounter_id, + VISIT_OCCURRENCE_ID_NEW + FROM + ( + SELECT T1.PRIORITY, + T1.encounter_id, + T1.VISIT_OCCURRENCE_ID_NEW, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY T1.PRIORITY) AS RN + FROM + ( + SELECT *, + CASE + WHEN encounterclass IN ( 'emergency', 'urgent' ) THEN + (CASE + WHEN VISIT_TYPE = 'inpatient' + AND VISIT_OCCURRENCE_ID_NEW IS NOT NULL THEN + 1 + WHEN VISIT_TYPE IN ( 'emergency', 'urgent' ) + AND VISIT_OCCURRENCE_ID_NEW IS NOT NULL THEN + 2 + ELSE + 99 + END + ) + WHEN encounterclass IN ( 'ambulatory', 'wellness', 'outpatient' ) THEN + (CASE + WHEN VISIT_TYPE = 'inpatient' + AND VISIT_OCCURRENCE_ID_NEW IS NOT NULL THEN + 1 + WHEN VISIT_TYPE IN ( 'ambulatory', 'wellness', 'outpatient' ) + AND VISIT_OCCURRENCE_ID_NEW IS NOT NULL THEN + 2 + ELSE + 99 + END + ) + WHEN encounterclass = 'inpatient' + AND VISIT_TYPE = 'inpatient' + AND VISIT_OCCURRENCE_ID_NEW IS NOT NULL THEN + 1 + ELSE + 99 + END AS PRIORITY + FROM [omop].ASSIGN_ALL_VISIT_IDS + ) T1 + ) T2 + WHERE T2.RN = 1; \ No newline at end of file diff --git a/ETL Scripts/Load/070. Load condition_era.sql b/ETL Scripts/Load/070. Load condition_era.sql new file mode 100644 index 0000000..f427a0c --- /dev/null +++ b/ETL Scripts/Load/070. Load condition_era.sql @@ -0,0 +1,116 @@ +-- ============================================= +-- Create Date: 20210505 +-- Description: Insert condition era +-- This procedure is derived from: https://github.com/OHDSI/ETL-CMS/blob/master/SQL/create_CDMv5_condition_era.sql +-- ============================================= +IF OBJECT_ID('tempdb..#tmp_ce', 'U') IS NOT NULL + DROP TABLE #tmp_ce; + +WITH cteConditionTarget (condition_occurrence_id, person_id, condition_concept_id, condition_start_date, + condition_end_date + ) +AS (SELECT co.CONDITION_OCCURRENCE_ID, + co.PERSON_ID, + co.CONDITION_CONCEPT_ID, + co.CONDITION_START_DATE, + COALESCE(NULLIF(co.CONDITION_END_DATE, NULL), DATEADD(DAY, 1, CONDITION_START_DATE)) AS condition_end_date + FROM [omop].CONDITION_OCCURRENCE co +/* Depending on the needs of your data, you can put more filters on to your code. We assign 0 to our unmapped condition_concept_id's, + * and since we don't want different conditions put in the same era, we put in the filter below. + */ +---WHERE condition_concept_id != 0 +), + -------------------------------------------------------------------------------------------------------------- + cteEndDates (person_id, condition_concept_id, end_date) +AS -- the magic +(SELECT person_id, + condition_concept_id, + DATEADD(DAY, -30, event_date) AS end_date -- unpad the end date + FROM + ( + SELECT person_id, + condition_concept_id, + event_date, + event_type, + MAX(start_ordinal) OVER (PARTITION BY person_id, + condition_concept_id + ORDER BY event_date, + event_type + ROWS UNBOUNDED PRECEDING + ) AS start_ordinal, -- this pulls the current START down from the prior rows so that the NULLs from the END DATES will contain a value we can compare with + ROW_NUMBER() OVER (PARTITION BY person_id, + condition_concept_id + ORDER BY event_date, + event_type + ) AS overall_ord -- this re-numbers the inner UNION so all rows are numbered ordered by the event date + FROM + ( + -- select the start dates, assigning a row number to each + SELECT person_id, + condition_concept_id, + condition_start_date AS event_date, + -1 AS event_type, + ROW_NUMBER() OVER (PARTITION BY person_id, + condition_concept_id + ORDER BY condition_start_date + ) AS start_ordinal + FROM cteConditionTarget + UNION ALL + + -- pad the end dates by 30 to allow a grace period for overlapping ranges. + SELECT person_id, + condition_concept_id, + DATEADD(DAY, 30, condition_end_date), + 1 AS event_type, + NULL + FROM cteConditionTarget + ) RAWDATA + ) E + WHERE (2 * e.start_ordinal) - e.overall_ord = 0), + -------------------------------------------------------------------------------------------------------------- + cteConditionEnds (person_id, condition_concept_id, condition_start_date, era_end_date) +AS (SELECT c.person_id, + c.condition_concept_id, + c.condition_start_date, + MIN(e.end_date) AS era_end_date + FROM cteConditionTarget c + JOIN cteEndDates e + ON c.person_id = e.person_id + AND c.condition_concept_id = e.condition_concept_id + AND e.end_date >= c.condition_start_date + GROUP BY c.condition_occurrence_id, + c.person_id, + c.condition_concept_id, + c.condition_start_date) +-------------------------------------------------------------------------------------------------------------- + + +SELECT ROW_NUMBER() OVER (ORDER BY person_id) condition_era_id, + person_id, + condition_concept_id, + MIN(condition_start_date) AS condition_era_start_date, + era_end_date AS condition_era_end_date, + COUNT(*) AS condition_occurrence_count +INTO #tmp_ce +FROM cteConditionEnds +GROUP BY person_id, + condition_concept_id, + era_end_date; + +INSERT INTO [omop].CONDITION_ERA +( + CONDITION_ERA_ID, + PERSON_ID, + CONDITION_CONCEPT_ID, + CONDITION_ERA_START_DATE, + CONDITION_ERA_END_DATE, + CONDITION_OCCURRENCE_COUNT +) +SELECT CONDITION_ERA_ID, + PERSON_ID, + CONDITION_CONCEPT_ID, + CONDITION_ERA_START_DATE, + CONDITION_ERA_END_DATE, + CONDITION_OCCURRENCE_COUNT +FROM #tmp_ce; + diff --git a/ETL Scripts/Load/080. Load condition_occurrence.sql b/ETL Scripts/Load/080. Load condition_occurrence.sql new file mode 100644 index 0000000..6b69dbd --- /dev/null +++ b/ETL Scripts/Load/080. Load condition_occurrence.sql @@ -0,0 +1,52 @@ +INSERT INTO [omop].CONDITION_OCCURRENCE +( + CONDITION_OCCURRENCE_ID, + PERSON_ID, + CONDITION_CONCEPT_ID, + CONDITION_START_DATE, + CONDITION_START_DATETIME, + CONDITION_END_DATE, + CONDITION_END_DATETIME, + CONDITION_TYPE_CONCEPT_ID, + STOP_REASON, + PROVIDER_ID, + VISIT_OCCURRENCE_ID, + VISIT_DETAIL_ID, + CONDITION_SOURCE_VALUE, + CONDITION_SOURCE_CONCEPT_ID, + CONDITION_STATUS_SOURCE_VALUE, + CONDITION_STATUS_CONCEPT_ID +) +SELECT ROW_NUMBER() OVER (ORDER BY p.PERSON_ID) condition_occurrence_id, + p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID condition_concept_id, + c.start condition_start_date, + c.start condition_start_datetime, + c.stop condition_end_date, + c.stop condition_end_datetime, + 38000175 condition_type_concept_id, + CAST(NULL AS VARCHAR) stop_reason, + CAST(NULL AS INTEGER) provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + c.code condition_source_value, + srctosrcvm.SOURCE_CONCEPT_ID condition_source_concept_id, + NULL condition_status_source_value, + 0 condition_status_concept_id +FROM [synthea].conditions c + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = c.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Condition' + AND srctostdvm.TARGET_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = c.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctosrcvm.SOURCE_DOMAIN_ID = 'Condition' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = c.encounter + JOIN [omop].PERSON p + ON c.patient = p.PERSON_SOURCE_VALUE; + diff --git a/ETL Scripts/Load/090. Load death.sql b/ETL Scripts/Load/090. Load death.sql new file mode 100644 index 0000000..1e61339 --- /dev/null +++ b/ETL Scripts/Load/090. Load death.sql @@ -0,0 +1,40 @@ +-- NB: +-- We observe death records in both the encounters.csv and observations.csv file. +-- To find the death records in observations, use code = '69453-9'. This is a LOINC code +-- that represents an observation of the US standard certificate of death. To find the +-- corresponding cause of death, we would need to join to conditions on patient and description +-- (specifically conditions.description = observations.value). Instead, we can use the encounters table. +-- Encounters.code = '308646001' is the SNOMED observation of death certification. +-- The reasoncode column is the SNOMED code for the condition that caused death, so by using encounters +-- we get both the code for the death certification and the corresponding cause of death. + +INSERT INTO [omop].DEATH +( + PERSON_ID, + DEATH_DATE, + DEATH_DATETIME, + DEATH_TYPE_CONCEPT_ID, + CAUSE_CONCEPT_ID, + CAUSE_SOURCE_VALUE, + CAUSE_SOURCE_CONCEPT_ID +) +SELECT p.PERSON_ID person_id, + e.start death_date, + e.start death_datetime, + 38003566 death_type_concept_id, + srctostdvm.TARGET_CONCEPT_ID cause_concept_id, + e.reasoncode cause_source_value, + srctostdvm.SOURCE_CONCEPT_ID cause_source_concept_id +FROM [synthea].encounters e + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = e.reasoncode + AND srctostdvm.TARGET_DOMAIN_ID = 'Condition' + AND srctostdvm.SOURCE_DOMAIN_ID = 'Condition' + AND srctostdvm.TARGET_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].PERSON p + ON e.patient = p.PERSON_SOURCE_VALUE +WHERE e.code = '308646001'; + diff --git a/ETL Scripts/Load/100. Load device_exposure.sql b/ETL Scripts/Load/100. Load device_exposure.sql new file mode 100644 index 0000000..21e75b2 --- /dev/null +++ b/ETL Scripts/Load/100. Load device_exposure.sql @@ -0,0 +1,50 @@ +INSERT INTO [omop].DEVICE_EXPOSURE +( + DEVICE_EXPOSURE_ID, + PERSON_ID, + DEVICE_CONCEPT_ID, + DEVICE_EXPOSURE_START_DATE, + DEVICE_EXPOSURE_START_DATETIME, + DEVICE_EXPOSURE_END_DATE, + DEVICE_EXPOSURE_END_DATETIME, + DEVICE_TYPE_CONCEPT_ID, + UNIQUE_DEVICE_ID, + QUANTITY, + PROVIDER_ID, + VISIT_OCCURRENCE_ID, + VISIT_DETAIL_ID, + DEVICE_SOURCE_VALUE, + DEVICE_SOURCE_CONCEPT_ID +) +SELECT ROW_NUMBER() OVER (ORDER BY PERSON_ID) device_exposure_id, + p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID device_concept_id, + d.start device_exposure_start_date, + d.start device_exposure_start_datetime, + d.stop device_exposure_end_date, + d.stop device_exposure_end_datetime, + 38000267 device_type_concept_id, + d.udi unique_device_id, + CAST(NULL AS INT) quantity, + CAST(NULL AS INT) provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + d.code device_source_value, + srctosrcvm.SOURCE_CONCEPT_ID device_source_concept_id +FROM [synthea].devices d + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = d.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Device' + AND srctostdvm.TARGET_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = d.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'SNOMED' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = d.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = d.patient; + + diff --git a/ETL Scripts/Load/120. Load drug_exposure.sql b/ETL Scripts/Load/120. Load drug_exposure.sql new file mode 100644 index 0000000..b922db6 --- /dev/null +++ b/ETL Scripts/Load/120. Load drug_exposure.sql @@ -0,0 +1,125 @@ +INSERT INTO [omop].DRUG_EXPOSURE +( + DRUG_EXPOSURE_ID, + PERSON_ID, + DRUG_CONCEPT_ID, + DRUG_EXPOSURE_START_DATE, + DRUG_EXPOSURE_START_DATETIME, + DRUG_EXPOSURE_END_DATE, + DRUG_EXPOSURE_END_DATETIME, + VERBATIM_END_DATE, + DRUG_TYPE_CONCEPT_ID, + STOP_REASON, + REFILLS, + QUANTITY, + DAYS_SUPPLY, + SIG, + ROUTE_CONCEPT_ID, + LOT_NUMBER, + PROVIDER_ID, + VISIT_OCCURRENCE_ID, + VISIT_DETAIL_ID, + DRUG_SOURCE_VALUE, + DRUG_SOURCE_CONCEPT_ID, + ROUTE_SOURCE_VALUE, + DOSE_UNIT_SOURCE_VALUE +) +SELECT ROW_NUMBER() OVER (ORDER BY person_id) drug_exposure_id, + person_id, + drug_concept_id, + drug_exposure_start_date, + drug_exposure_start_datetime, + drug_exposure_end_date, + drug_exposure_end_datetime, + verbatim_end_date, + drug_type_concept_id, + stop_reason, + refills, + quantity, + days_supply, + sig, + route_concept_id, + lot_number, + provider_id, + visit_occurrence_id, + visit_detail_id, + drug_source_value, + drug_source_concept_id, + route_source_value, + dose_unit_source_value +FROM +( + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID drug_concept_id, + m.start drug_exposure_start_date, + m.start drug_exposure_start_datetime, + COALESCE(m.stop, m.start) drug_exposure_end_date, + COALESCE(m.stop, m.start) drug_exposure_end_datetime, + m.stop verbatim_end_date, + 38000175 drug_type_concept_id, + CAST(NULL AS VARCHAR) stop_reason, + 0 refills, + 0 quantity, + COALESCE(DATEDIFF(DAY, m.start, m.stop), 0) days_supply, + CAST(NULL AS VARCHAR) sig, + 0 route_concept_id, + 0 lot_number, + 0 provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + m.code drug_source_value, + srctosrcvm.SOURCE_CONCEPT_ID drug_source_concept_id, + CAST(NULL AS VARCHAR) route_source_value, + CAST(NULL AS VARCHAR) dose_unit_source_value + FROM [synthea].medications m + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = m.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Drug' + AND srctostdvm.TARGET_VOCABULARY_ID = 'RxNorm' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = m.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'RxNorm' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = m.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = m.patient + UNION ALL + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID drug_concept_id, + i.date drug_exposure_start_date, + i.date drug_exposure_start_datetime, + i.date drug_exposure_end_date, + i.date drug_exposure_end_datetime, + i.date verbatim_end_date, + 38000175 drug_type_concept_id, + CAST(NULL AS VARCHAR) stop_reason, + 0 refills, + 0 quantity, + 0 days_supply, + CAST(NULL AS VARCHAR) sig, + 0 route_concept_id, + 0 lot_number, + 0 provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + i.code drug_source_value, + srctosrcvm.SOURCE_CONCEPT_ID drug_source_concept_id, + CAST(NULL AS VARCHAR) route_source_value, + CAST(NULL AS VARCHAR) dose_unit_source_value + FROM [synthea].immunizations i + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = i.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Drug' + AND srctostdvm.TARGET_VOCABULARY_ID = 'CVX' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = i.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'CVX' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = i.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = i.patient +) tmp; diff --git a/ETL Scripts/Load/125. Load drug_era.sql b/ETL Scripts/Load/125. Load drug_era.sql new file mode 100644 index 0000000..102a925 --- /dev/null +++ b/ETL Scripts/Load/125. Load drug_era.sql @@ -0,0 +1,287 @@ +-- ============================================= +-- Create Date: 20210505 +-- Description: Insert condition occurrence +-- Code derived from: https://github.com/OHDSI/ETL-CMS/blob/master/SQL/create_CDMv5_drug_era_non_stockpile.sql +-- ============================================= + + +IF OBJECT_ID('tempdb..#tmp_de') IS NOT NULL + DROP TABLE #tmp_de; + +CREATE TABLE #tmp_de +WITH (DISTRIBUTION=ROUND_ROBIN) AS + WITH ctePreDrugTarget (drug_exposure_id, person_id, + ingredient_concept_id, + drug_exposure_start_date, + days_supply, + drug_exposure_end_date + ) + AS (SELECT d.DRUG_EXPOSURE_ID, + d.PERSON_ID, + c.CONCEPT_ID AS ingredient_concept_id, + d.DRUG_EXPOSURE_START_DATE AS drug_exposure_start_date, + d.DAYS_SUPPLY AS days_supply, + COALESCE( + ---NULLIF returns NULL if both values are the same, otherwise it returns the first parameter + NULLIF(DRUG_EXPOSURE_END_DATE, NULL), + ---If drug_exposure_end_date != NULL, return drug_exposure_end_date, otherwise go to next case + NULLIF(DATEADD( + DAY, + DAYS_SUPPLY, + DRUG_EXPOSURE_START_DATE + ), DRUG_EXPOSURE_START_DATE), + ---If days_supply != NULL or 0, return drug_exposure_start_date + days_supply, otherwise go to next case + DATEADD( + DAY, + 1, + DRUG_EXPOSURE_START_DATE + ) + ---Add 1 day to the drug_exposure_start_date since there is no end_date or INTERVAL for the days_supply + ) AS drug_exposure_end_date + FROM [omop].DRUG_EXPOSURE d + JOIN [omop].CONCEPT_ANCESTOR ca + ON ca.DESCENDANT_CONCEPT_ID = d.DRUG_CONCEPT_ID + JOIN [omop].CONCEPT c + ON ca.ANCESTOR_CONCEPT_ID = c.CONCEPT_ID + WHERE c.VOCABULARY_ID = 'RxNorm' + AND c.CONCEPT_CLASS_ID = 'Ingredient' + AND d.DRUG_CONCEPT_ID != 0 ---Our unmapped drug_concept_id's are set to 0, so we don't want different drugs wrapped up in the same era + AND COALESCE(d.DAYS_SUPPLY, 0) >= 0 + ---We have cases where days_supply is negative, and this can set the end_date before the start_date, which we don't want. So we're just looking over those rows. This is a data-quality issue. + ), + cteSubExposureEndDates (person_id, + ingredient_concept_id, + end_date + ) + AS + --- A preliminary sorting that groups all of the overlapping exposures into one exposure so that we don't double-count non-gap-days + (SELECT person_id, + ingredient_concept_id, + event_date AS end_date + FROM + ( + SELECT person_id, + ingredient_concept_id, + event_date, + event_type, + MAX(start_ordinal) OVER (PARTITION BY person_id, + ingredient_concept_id + ORDER BY event_date, + event_type + ROWS unbounded preceding + ) AS start_ordinal, + -- this pulls the current START down from the prior rows so that the NULLs + -- from the END DATES will contain a value we can compare with + ROW_NUMBER() OVER (PARTITION BY person_id, + ingredient_concept_id + ORDER BY event_date, + event_type + ) AS overall_ord + -- this re-numbers the inner UNION so all rows are numbered ordered by the event date + FROM + ( + -- select the start dates, assigning a row number to each + SELECT person_id, + ingredient_concept_id, + drug_exposure_start_date AS event_date, + -1 AS event_type, + ROW_NUMBER() OVER (PARTITION BY person_id, + ingredient_concept_id + ORDER BY drug_exposure_start_date + ) AS start_ordinal + FROM ctePreDrugTarget + UNION ALL + SELECT person_id, + ingredient_concept_id, + drug_exposure_end_date, + 1 AS event_type, + NULL + FROM ctePreDrugTarget + ) RAWDATA + ) E + WHERE (2 * E.start_ordinal) - E.overall_ord = 0), + cteDrugExposureEnds (person_id, drug_concept_id, + drug_exposure_start_date, + drug_sub_exposure_end_date + ) + AS (SELECT dt.person_id, + dt.ingredient_concept_id, + dt.drug_exposure_start_date, + MIN(e.end_date) AS drug_sub_exposure_end_date + FROM ctePreDrugTarget dt + JOIN cteSubExposureEndDates e + ON dt.person_id = e.person_id + AND dt.ingredient_concept_id = e.ingredient_concept_id + AND e.end_date >= dt.drug_exposure_start_date + GROUP BY dt.drug_exposure_id, + dt.person_id, + dt.ingredient_concept_id, + dt.drug_exposure_start_date), + -------------------------------------------------------------------------------------------------------------- + cteSubExposures (row_number, person_id, + drug_concept_id, + drug_sub_exposure_start_date, + drug_sub_exposure_end_date, + drug_exposure_count + ) + AS (SELECT ROW_NUMBER() OVER (PARTITION BY person_id, + drug_concept_id, + drug_sub_exposure_end_date + ORDER BY person_id + ), + person_id, + drug_concept_id, + MIN(drug_exposure_start_date) AS drug_sub_exposure_start_date, + drug_sub_exposure_end_date, + COUNT(*) AS drug_exposure_count + FROM cteDrugExposureEnds + GROUP BY person_id, + drug_concept_id, + drug_sub_exposure_end_date + --ORDER BY person_id, drug_concept_id + ), + -------------------------------------------------------------------------------------------------------------- + /*Everything above grouped exposures into sub_exposures if there was overlap between exposures. +*So there was no persistence window. Now we can add the persistence window to calculate eras. +*/ + -------------------------------------------------------------------------------------------------------------- + cteFinalTarget (row_number, person_id, + ingredient_concept_id, + drug_sub_exposure_start_date, + drug_sub_exposure_end_date, + drug_exposure_count, days_exposed + ) + AS (SELECT row_number, + person_id, + drug_concept_id, + drug_sub_exposure_start_date, + drug_sub_exposure_end_date, + drug_exposure_count, + DATEDIFF( + DAY, + drug_sub_exposure_start_date, + drug_sub_exposure_end_date + ) AS days_exposed + FROM cteSubExposures), + -------------------------------------------------------------------------------------------------------------- + cteEndDates (person_id, ingredient_concept_id, + end_date + ) + AS + -- the magic + (SELECT person_id, + ingredient_concept_id, + DATEADD(DAY, -30, event_date) AS end_date + -- unpad the end date + FROM + ( + SELECT person_id, + ingredient_concept_id, + event_date, + event_type, + MAX(start_ordinal) OVER (PARTITION BY person_id, + ingredient_concept_id + ORDER BY event_date, + event_type + ROWS UNBOUNDED PRECEDING + ) AS start_ordinal, + -- this pulls the current START down from the prior rows so that the NULLs + -- from the END DATES will contain a value we can compare with + ROW_NUMBER() OVER (PARTITION BY person_id, + ingredient_concept_id + ORDER BY event_date, + event_type + ) AS overall_ord + -- this re-numbers the inner UNION so all rows are numbered ordered by the event date + FROM + ( + -- select the start dates, assigning a row number to each + SELECT person_id, + ingredient_concept_id, + drug_sub_exposure_start_date AS event_date, + -1 AS event_type, + ROW_NUMBER() OVER (PARTITION BY person_id, + ingredient_concept_id + ORDER BY drug_sub_exposure_start_date + ) AS start_ordinal + FROM cteFinalTarget + UNION ALL + + -- pad the end dates by 30 to allow a grace period for overlapping ranges. + SELECT person_id, + ingredient_concept_id, + DATEADD( + DAY, + 30, + drug_sub_exposure_end_date + ), + 1 AS event_type, + NULL + FROM cteFinalTarget + ) RAWDATA + ) E + WHERE (2 * E.start_ordinal) - E.overall_ord = 0), + cteDrugEraEnds (person_id, drug_concept_id, + drug_sub_exposure_start_date, + drug_era_end_date, + drug_exposure_count, days_exposed + ) + AS (SELECT ft.person_id, + ft.ingredient_concept_id, + ft.drug_sub_exposure_start_date, + MIN(e.end_date) AS era_end_date, + drug_exposure_count, + days_exposed + FROM cteFinalTarget ft + JOIN cteEndDates e + ON ft.person_id = e.person_id + AND ft.ingredient_concept_id = e.ingredient_concept_id + AND e.end_date >= ft.drug_sub_exposure_start_date + GROUP BY ft.person_id, + ft.ingredient_concept_id, + ft.drug_sub_exposure_start_date, + drug_exposure_count, + days_exposed) +SELECT ROW_NUMBER() OVER (ORDER BY person_id) drug_era_id, + person_id, + drug_concept_id, + MIN(drug_sub_exposure_start_date) AS drug_era_start_date, + drug_era_end_date, + SUM(drug_exposure_count) AS drug_exposure_count, + DATEDIFF( + DAY, + '1970-01-01', + DATEADD( + DAY, + - (DATEDIFF(DAY, MIN(drug_sub_exposure_start_date), drug_era_end_date) + - SUM(days_exposed) + ), + drug_era_end_date + ) + ) AS gap_days +FROM cteDrugEraEnds dee +GROUP BY person_id, + drug_concept_id, + drug_era_end_date; + + +INSERT INTO [omop].DRUG_ERA +( + DRUG_ERA_ID, + PERSON_ID, + DRUG_CONCEPT_ID, + DRUG_ERA_START_DATE, + DRUG_ERA_END_DATE, + DRUG_EXPOSURE_COUNT, + GAP_DAYS +) +SELECT DRUG_ERA_ID, + PERSON_ID, + DRUG_CONCEPT_ID, + DRUG_ERA_START_DATE, + DRUG_ERA_END_DATE, + DRUG_EXPOSURE_COUNT, + GAP_DAYS +FROM #tmp_de; + + diff --git a/ETL Scripts/Load/130. Load measurement.sql b/ETL Scripts/Load/130. Load measurement.sql new file mode 100644 index 0000000..7b5ce12 --- /dev/null +++ b/ETL Scripts/Load/130. Load measurement.sql @@ -0,0 +1,129 @@ +INSERT INTO [omop].MEASUREMENT +( + MEASUREMENT_ID, + PERSON_ID, + MEASUREMENT_CONCEPT_ID, + MEASUREMENT_DATE, + MEASUREMENT_DATETIME, + MEASUREMENT_TIME, + MEASUREMENT_TYPE_CONCEPT_ID, + OPERATOR_CONCEPT_ID, + VALUE_AS_NUMBER, + VALUE_AS_CONCEPT_ID, + UNIT_CONCEPT_ID, + RANGE_LOW, + RANGE_HIGH, + PROVIDER_ID, + VISIT_OCCURRENCE_ID, + VISIT_DETAIL_ID, + MEASUREMENT_SOURCE_VALUE, + MEASUREMENT_SOURCE_CONCEPT_ID, + UNIT_SOURCE_VALUE, + VALUE_SOURCE_VALUE +) +SELECT ROW_NUMBER() OVER (ORDER BY person_id) measurement_id, + person_id, + measurement_concept_id, + measurement_date, + measurement_datetime, + measurement_time, + measurement_type_concept_id, + operator_concept_id, + value_as_number, + value_as_concept_id, + unit_concept_id, + range_low, + range_high, + provider_id, + visit_occurrence_id, + visit_detail_id, + measurement_source_value, + measurement_source_concept_id, + unit_source_value, + value_source_value +FROM +( + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID measurement_concept_id, + pr.date measurement_date, + pr.date measurement_datetime, + pr.date measurement_time, + 38000267 measurement_type_concept_id, + 0 operator_concept_id, + CAST(NULL AS FLOAT) value_as_number, + 0 value_as_concept_id, + 0 unit_concept_id, + CAST(NULL AS FLOAT) range_low, + CAST(NULL AS FLOAT) range_high, + 0 provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + pr.code measurement_source_value, + srctosrcvm.SOURCE_CONCEPT_ID measurement_source_concept_id, + CAST(NULL AS VARCHAR) unit_source_value, + CAST(NULL AS VARCHAR) value_source_value + FROM [synthea].procedures pr + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = pr.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Measurement' + AND srctostdvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = pr.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'SNOMED' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = pr.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = pr.patient + UNION ALL + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID measurement_concept_id, + o.date measurement_date, + o.date measurement_datetime, + o.date measurement_time, + 38000267 measurement_type_concept_id, + 0 operator_concept_id, + CASE + WHEN ISNUMERIC(o.value) = 1 THEN + CAST(o.value AS FLOAT) + ELSE + CAST(NULL AS FLOAT) + END value_as_number, + COALESCE(srcmap2.TARGET_CONCEPT_ID, 0) value_as_concept_id, + COALESCE(srcmap1.TARGET_CONCEPT_ID, 0) unit_concept_id, + CAST(NULL AS FLOAT) range_low, + CAST(NULL AS FLOAT) range_high, + 0 provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + o.code measurement_source_value, + COALESCE(srctosrcvm.SOURCE_CONCEPT_ID, 0) measurement_source_concept_id, + o.units unit_source_value, + o.value value_source_value + FROM [synthea].observations o + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = o.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Measurement' + AND srctostdvm.SOURCE_VOCABULARY_ID = 'LOINC' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + LEFT JOIN [omop].source_to_standard_vocab_map srcmap1 + ON srcmap1.SOURCE_CODE = o.units + AND srcmap1.TARGET_VOCABULARY_ID = 'UCUM' + AND srcmap1.SOURCE_VOCABULARY_ID = 'UCUM' + AND srcmap1.TARGET_STANDARD_CONCEPT = 'S' + AND srcmap1.TARGET_INVALID_REASON IS NULL + LEFT JOIN [omop].source_to_standard_vocab_map srcmap2 + ON srcmap2.SOURCE_CODE = o.value + AND srcmap2.TARGET_DOMAIN_ID = 'Meas value' + AND srcmap2.TARGET_STANDARD_CONCEPT = 'S' + AND srcmap2.TARGET_INVALID_REASON IS NULL + LEFT JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = o.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'LOINC' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = o.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = o.patient +) tmp; diff --git a/ETL Scripts/Load/140. Load observation_period.sql b/ETL Scripts/Load/140. Load observation_period.sql new file mode 100644 index 0000000..9dc0c32 --- /dev/null +++ b/ETL Scripts/Load/140. Load observation_period.sql @@ -0,0 +1,23 @@ +INSERT INTO [omop].OBSERVATION_PERIOD +( + OBSERVATION_PERIOD_ID, + PERSON_ID, + OBSERVATION_PERIOD_START_DATE, + OBSERVATION_PERIOD_END_DATE, + PERIOD_TYPE_CONCEPT_ID +) +SELECT ROW_NUMBER() OVER (ORDER BY PERSON_ID), + PERSON_ID, + start_date, + end_date, + 44814724 period_type_concept_id +FROM +( + SELECT p.PERSON_ID, + MIN(e.start) start_date, + MAX(e.stop) end_date + FROM [omop].PERSON p + JOIN [synthea].encounters e + ON p.PERSON_SOURCE_VALUE = e.patient + GROUP BY p.PERSON_ID +) tmp; diff --git a/ETL Scripts/Load/150. Load observation.sql b/ETL Scripts/Load/150. Load observation.sql new file mode 100644 index 0000000..8c3b49d --- /dev/null +++ b/ETL Scripts/Load/150. Load observation.sql @@ -0,0 +1,140 @@ +INSERT INTO [omop].OBSERVATION +( + OBSERVATION_ID, + PERSON_ID, + OBSERVATION_CONCEPT_ID, + OBSERVATION_DATE, + OBSERVATION_DATETIME, + OBSERVATION_TYPE_CONCEPT_ID, + VALUE_AS_NUMBER, + VALUE_AS_STRING, + VALUE_AS_CONCEPT_ID, + QUALIFIER_CONCEPT_ID, + UNIT_CONCEPT_ID, + PROVIDER_ID, + VISIT_OCCURRENCE_ID, + VISIT_DETAIL_ID, + OBSERVATION_SOURCE_VALUE, + OBSERVATION_SOURCE_CONCEPT_ID, + UNIT_SOURCE_VALUE, + QUALIFIER_SOURCE_VALUE +) +SELECT ROW_NUMBER() OVER (ORDER BY person_id) observation_id, + person_id, + observation_concept_id, + observation_date, + observation_datetime, + observation_type_concept_id, + value_as_number, + value_as_string, + value_as_concept_id, + qualifier_concept_id, + unit_concept_id, + provider_id, + visit_occurrence_id, + visit_detail_id, + observation_source_value, + observation_source_concept_id, + unit_source_value, + qualifier_source_value +FROM +( + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID observation_concept_id, + a.start observation_date, + a.start observation_datetime, + 38000280 observation_type_concept_id, + CAST(NULL AS NUMERIC) value_as_number, + CAST(NULL AS VARCHAR) value_as_string, + 0 value_as_concept_id, + 0 qualifier_concept_id, + 0 unit_concept_id, + CAST(NULL AS BIGINT) provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + a.code observation_source_value, + srctosrcvm.SOURCE_CONCEPT_ID observation_source_concept_id, + CAST(NULL AS VARCHAR) unit_source_value, + CAST(NULL AS VARCHAR) qualifier_source_value + FROM [synthea].allergies a + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = a.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Observation' + AND srctostdvm.TARGET_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = a.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctosrcvm.SOURCE_DOMAIN_ID = 'Observation' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = a.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = a.patient + UNION ALL + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID observation_concept_id, + c.start observation_date, + c.start observation_datetime, + 38000280 observation_type_concept_id, + CAST(NULL AS NUMERIC) value_as_number, + CAST(NULL AS VARCHAR) value_as_string, + 0 value_as_concept_id, + 0 qualifier_concept_id, + 0 unit_concept_id, + CAST(NULL AS BIGINT) provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + c.code observation_source_value, + srctosrcvm.SOURCE_CONCEPT_ID observation_source_concept_id, + CAST(NULL AS VARCHAR) unit_source_value, + CAST(NULL AS VARCHAR) qualifier_source_value + FROM [synthea].conditions c + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = c.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Observation' + AND srctostdvm.TARGET_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = c.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctosrcvm.SOURCE_DOMAIN_ID = 'Observation' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = c.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = c.patient + UNION ALL + SELECT p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID observation_concept_id, + o.date observation_date, + o.date observation_datetime, + 38000280 observation_type_concept_id, + CAST(NULL AS NUMERIC) value_as_number, + CAST(NULL AS VARCHAR) value_as_string, + 0 value_as_concept_id, + 0 qualifier_concept_id, + 0 unit_concept_id, + CAST(NULL AS BIGINT) provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + o.code observation_source_value, + srctosrcvm.SOURCE_CONCEPT_ID observation_source_concept_id, + CAST(NULL AS VARCHAR) unit_source_value, + CAST(NULL AS VARCHAR) qualifier_source_value + FROM [synthea].observations o + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = o.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Observation' + AND srctostdvm.TARGET_VOCABULARY_ID = 'LOINC' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = o.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'LOINC' + AND srctosrcvm.SOURCE_DOMAIN_ID = 'Observation' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = o.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = o.patient +) tmp; diff --git a/ETL Scripts/Load/170. Load procedure_occurrence.sql b/ETL Scripts/Load/170. Load procedure_occurrence.sql new file mode 100644 index 0000000..ce55913 --- /dev/null +++ b/ETL Scripts/Load/170. Load procedure_occurrence.sql @@ -0,0 +1,46 @@ +INSERT INTO [omop].PROCEDURE_OCCURRENCE +( + PROCEDURE_OCCURRENCE_ID, + PERSON_ID, + PROCEDURE_CONCEPT_ID, + PROCEDURE_DATE, + PROCEDURE_DATETIME, + PROCEDURE_TYPE_CONCEPT_ID, + MODIFIER_CONCEPT_ID, + QUANTITY, + PROVIDER_ID, + VISIT_OCCURRENCE_ID, + VISIT_DETAIL_ID, + PROCEDURE_SOURCE_VALUE, + PROCEDURE_SOURCE_CONCEPT_ID, + MODIFIER_SOURCE_VALUE +) +SELECT ROW_NUMBER() OVER (ORDER BY p.PERSON_ID) procedure_occurrence_id, + p.PERSON_ID person_id, + srctostdvm.TARGET_CONCEPT_ID procedure_concept_id, + pr.date procedure_date, + pr.date procedure_datetime, + 38000267 procedure_type_concept_id, + 0 modifier_concept_id, + CAST(NULL AS INTEGER) quantity, + CAST(NULL AS INTEGER) provider_id, + fv.VISIT_OCCURRENCE_ID_NEW visit_occurrence_id, + fv.VISIT_OCCURRENCE_ID_NEW + 1000000 visit_detail_id, + pr.code procedure_source_value, + srctosrcvm.SOURCE_CONCEPT_ID procedure_source_concept_id, + NULL modifier_source_value +FROM [synthea].procedures pr + JOIN [omop].source_to_standard_vocab_map srctostdvm + ON srctostdvm.SOURCE_CODE = pr.code + AND srctostdvm.TARGET_DOMAIN_ID = 'Procedure' + AND srctostdvm.TARGET_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.SOURCE_VOCABULARY_ID = 'SNOMED' + AND srctostdvm.TARGET_STANDARD_CONCEPT = 'S' + AND srctostdvm.TARGET_INVALID_REASON IS NULL + JOIN [omop].source_to_source_vocab_map srctosrcvm + ON srctosrcvm.SOURCE_CODE = pr.code + AND srctosrcvm.SOURCE_VOCABULARY_ID = 'SNOMED' + LEFT JOIN [omop].FINAL_VISIT_IDS fv + ON fv.encounter_id = pr.encounter + JOIN [omop].PERSON p + ON p.PERSON_SOURCE_VALUE = pr.patient; diff --git a/ETL Scripts/Load/180. Load visit_detail.sql b/ETL Scripts/Load/180. Load visit_detail.sql new file mode 100644 index 0000000..6c8ea5e --- /dev/null +++ b/ETL Scripts/Load/180. Load visit_detail.sql @@ -0,0 +1,71 @@ +-- ============================================= +-- Create Date: 20210505 +-- Description: Insert visit detail +-- For testing purposes, create populate VISIT_DETAIL +-- such that it's basically a copy of VISIT_OCCURRENCE +-- ============================================= + + +INSERT INTO [omop].VISIT_DETAIL +( + VISIT_DETAIL_ID, + PERSON_ID, + VISIT_DETAIL_CONCEPT_ID, + VISIT_DETAIL_START_DATE, + VISIT_DETAIL_START_DATETIME, + VISIT_DETAIL_END_DATE, + VISIT_DETAIL_END_DATETIME, + VISIT_DETAIL_TYPE_CONCEPT_ID, + PROVIDER_ID, + CARE_SITE_ID, + ADMITTING_SOURCE_CONCEPT_ID, + DISCHARGE_TO_CONCEPT_ID, + PRECEDING_VISIT_DETAIL_ID, + VISIT_DETAIL_SOURCE_VALUE, + VISIT_DETAIL_SOURCE_CONCEPT_ID, + ADMITTING_SOURCE_VALUE, + DISCHARGE_TO_SOURCE_VALUE, + VISIT_DETAIL_PARENT_ID, + VISIT_OCCURRENCE_ID +) +SELECT av.visit_occurrence_id + 1000000 visit_detail_id, + p.person_id person_id, + CASE LOWER(av.encounterclass) + WHEN 'ambulatory' THEN + 9202 + WHEN 'emergency' THEN + 9203 + WHEN 'inpatient' THEN + 9201 + WHEN 'wellness' THEN + 9202 + WHEN 'urgentcare' THEN + 9203 + WHEN 'outpatient' THEN + 9202 + ELSE + 0 + END visit_detail_concept_id, + av.visit_start_date visit_detail_start_date, + av.visit_start_date visit_detail_start_datetime, + av.visit_end_date visit_detail_end_date, + av.visit_end_date visit_detail_end_datetime, + 44818517 visit_detail_type_concept_id, + NULL provider_id, + NULL care_site_id, + 0 admitting_source_concept_id, + 0 discharge_to_concept_id, + lag(av.visit_occurrence_id) OVER (partition BY p.person_id ORDER BY av.visit_start_date) + 1000000 preceding_visit_detail_id, + av.encounter_id visit_detail_source_value, + 0 visit_detail_source_concept_id, + NULL admitting_source_value, + NULL discharge_to_source_value, + NULL visit_detail_parent_id, + av.visit_occurrence_id visit_occurrence_id +FROM [omop].all_visits av + JOIN [omop].person p + ON av.patient = p.person_source_value +WHERE av.visit_occurrence_id IN + ( + SELECT DISTINCT VISIT_OCCURRENCE_ID_NEW FROM [omop].FINAL_VISIT_IDS + ); \ No newline at end of file diff --git a/ETL Scripts/Load/190. Load visit_occurrence.sql b/ETL Scripts/Load/190. Load visit_occurrence.sql new file mode 100644 index 0000000..fb95ed7 --- /dev/null +++ b/ETL Scripts/Load/190. Load visit_occurrence.sql @@ -0,0 +1,59 @@ +INSERT INTO [omop].VISIT_OCCURRENCE +( + VISIT_OCCURRENCE_ID, + PERSON_ID, + VISIT_CONCEPT_ID, + VISIT_START_DATE, + VISIT_START_DATETIME, + VISIT_END_DATE, + VISIT_END_DATETIME, + VISIT_TYPE_CONCEPT_ID, + PROVIDER_ID, + CARE_SITE_ID, + VISIT_SOURCE_VALUE, + VISIT_SOURCE_CONCEPT_ID, + ADMITTING_SOURCE_CONCEPT_ID, + ADMITTING_SOURCE_VALUE, + DISCHARGE_TO_CONCEPT_ID, + DISCHARGE_TO_SOURCE_VALUE, + PRECEDING_VISIT_OCCURRENCE_ID +) +SELECT av.visit_occurrence_id, + p.person_id, + CASE LOWER(av.encounterclass) + WHEN 'ambulatory' THEN + 9202 + WHEN 'emergency' THEN + 9203 + WHEN 'inpatient' THEN + 9201 + WHEN 'wellness' THEN + 9202 + WHEN 'urgentcare' THEN + 9203 + WHEN 'outpatient' THEN + 9202 + ELSE + 0 + END, + av.visit_start_date, + av.visit_start_date, + av.visit_end_date, + av.visit_end_date, + 44818517, + NULL, + NULL, + av.encounter_id, + 0, + 0, + NULL, + 0, + NULL, + lag(av.visit_occurrence_id) OVER (partition BY p.person_id ORDER BY av.visit_start_date) +FROM [omop].all_visits av + JOIN [omop].person p + ON av.patient = p.person_source_value +WHERE av.visit_occurrence_id IN + ( + SELECT DISTINCT VISIT_OCCURRENCE_ID_NEW FROM [omop].FINAL_VISIT_IDS + ); \ No newline at end of file diff --git a/ETL Scripts/Load/900. Load cdm_source.sql b/ETL Scripts/Load/900. Load cdm_source.sql new file mode 100644 index 0000000..d62a63f --- /dev/null +++ b/ETL Scripts/Load/900. Load cdm_source.sql @@ -0,0 +1,25 @@ +INSERT INTO [omop].CDM_SOURCE +( + CDM_SOURCE_NAME, + CDM_SOURCE_ABBREVIATION, + CDM_HOLDER, + SOURCE_DESCRIPTION, + SOURCE_DOCUMENTATION_REFERENCE, + CDM_ETL_REFERENCE, + SOURCE_RELEASE_DATE, + CDM_RELEASE_DATE, + CDM_VERSION, + VOCABULARY_VERSION +) +SELECT 'Synthea synthetic health database', + 'Synthea', + 'OHDSI Community', + 'SyntheaTM is a Synthetic Patient Population Simulator. The goal is to output synthetic, realistic (but not real), patient data and associated health records in a variety of formats.', + 'https://synthetichealth.github.io/synthea/', + 'https://github.com/OHDSI/ETL-Synthea', + GETDATE(), -- NB: Set this value to the day the source data was pulled + GETDATE(), + 'v5.3', + VOCABULARY_VERSION +FROM [omop].VOCABULARY +WHERE VOCABULARY_ID = 'None'; diff --git a/ETL Scripts/Load/999. Create Indexes on CDM Tables.sql b/ETL Scripts/Load/999. Create Indexes on CDM Tables.sql new file mode 100644 index 0000000..e8a6964 --- /dev/null +++ b/ETL Scripts/Load/999. Create Indexes on CDM Tables.sql @@ -0,0 +1,208 @@ +--/*pdw OMOP CDM Indices +-- There are no unique indices created because it is assumed that the primary key constraints have been run prior to +-- implementing indices. +--*/ + +SELECT 1 AS Result + +--/************************ + +--Standardized clinical data + +--************************/ + +--CREATE CLUSTERED INDEX idx_person_id ON [omop].person (person_id ASC); +--CREATE INDEX idx_gender ON [omop].person (gender_concept_id ASC); + +--CREATE CLUSTERED INDEX idx_observation_period_id_1 +--ON [omop].observation_period (person_id ASC); + +--CREATE CLUSTERED INDEX idx_visit_person_id_1 +--ON [omop].visit_occurrence (person_id ASC); +--CREATE INDEX idx_visit_concept_id_1 +--ON [omop].visit_occurrence (visit_concept_id ASC); + +--CREATE CLUSTERED INDEX idx_visit_det_person_id_1 +--ON [omop].visit_detail (person_id ASC); +--CREATE INDEX idx_visit_det_concept_id_1 +--ON [omop].visit_detail (visit_detail_concept_id ASC); +--CREATE INDEX idx_visit_det_occ_id +--ON [omop].visit_detail (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_condition_person_id_1 +--ON [omop].condition_occurrence (person_id ASC); +--CREATE INDEX idx_condition_concept_id_1 +--ON [omop].condition_occurrence (condition_concept_id ASC); +--CREATE INDEX idx_condition_visit_id_1 +--ON [omop].condition_occurrence (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_drug_person_id_1 +--ON [omop].drug_exposure (person_id ASC); +--CREATE INDEX idx_drug_concept_id_1 +--ON [omop].drug_exposure (drug_concept_id ASC); +--CREATE INDEX idx_drug_visit_id_1 +--ON [omop].drug_exposure (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_procedure_person_id_1 +--ON [omop].procedure_occurrence (person_id ASC); +--CREATE INDEX idx_procedure_concept_id_1 +--ON [omop].procedure_occurrence (procedure_concept_id ASC); +--CREATE INDEX idx_procedure_visit_id_1 +--ON [omop].procedure_occurrence (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_device_person_id_1 +--ON [omop].device_exposure (person_id ASC); +--CREATE INDEX idx_device_concept_id_1 +--ON [omop].device_exposure (device_concept_id ASC); +--CREATE INDEX idx_device_visit_id_1 +--ON [omop].device_exposure (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_measurement_person_id_1 +--ON [omop].measurement (person_id ASC); +--CREATE INDEX idx_measurement_concept_id_1 +--ON [omop].measurement (measurement_concept_id ASC); +--CREATE INDEX idx_measurement_visit_id_1 +--ON [omop].measurement (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_observation_person_id_1 +--ON [omop].observation (person_id ASC); +--CREATE INDEX idx_observation_concept_id_1 +--ON [omop].observation (observation_concept_id ASC); +--CREATE INDEX idx_observation_visit_id_1 +--ON [omop].observation (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_death_person_id_1 +--ON [omop].death (person_id ASC); + +--CREATE CLUSTERED INDEX idx_note_person_id_1 ON [omop].note (person_id ASC); +--CREATE INDEX idx_note_concept_id_1 +--ON [omop].note (note_type_concept_id ASC); +--CREATE INDEX idx_note_visit_id_1 ON [omop].note (visit_occurrence_id ASC); + +--CREATE CLUSTERED INDEX idx_note_nlp_note_id_1 +--ON [omop].note_nlp (note_id ASC); +--CREATE INDEX idx_note_nlp_concept_id_1 +--ON [omop].note_nlp (note_nlp_concept_id ASC); + +--CREATE CLUSTERED INDEX idx_specimen_person_id_1 +--ON [omop].specimen (person_id ASC); +--CREATE INDEX idx_specimen_concept_id_1 +--ON [omop].specimen (specimen_concept_id ASC); + +--CREATE INDEX idx_fact_relationship_id1 +--ON [omop].fact_relationship (domain_concept_id_1 ASC); +--CREATE INDEX idx_fact_relationship_id2 +--ON [omop].fact_relationship (domain_concept_id_2 ASC); +--CREATE INDEX idx_fact_relationship_id3 +--ON [omop].fact_relationship (relationship_concept_id ASC); + +--/************************ + +--Standardized health system data + +--************************/ + +--CREATE CLUSTERED INDEX idx_location_id_1 +--ON [omop].location (location_id ASC); + +--CREATE CLUSTERED INDEX idx_care_site_id_1 +--ON [omop].care_site (care_site_id ASC); + +--CREATE CLUSTERED INDEX idx_provider_id_1 +--ON [omop].provider (provider_id ASC); + +--/************************ + +--Standardized health economics + +--************************/ + +--CREATE CLUSTERED INDEX idx_period_person_id_1 +--ON [omop].payer_plan_period (person_id ASC); + +--CREATE INDEX idx_cost_event_id ON [omop].cost (cost_event_id ASC); + +--/************************ + +--Standardized derived elements + +--************************/ + +--CREATE CLUSTERED INDEX idx_drug_era_person_id_1 +--ON [omop].drug_era (person_id ASC); +--CREATE INDEX idx_drug_era_concept_id_1 +--ON [omop].drug_era (drug_concept_id ASC); + +--CREATE CLUSTERED INDEX idx_dose_era_person_id_1 +--ON [omop].dose_era (person_id ASC); +--CREATE INDEX idx_dose_era_concept_id_1 +--ON [omop].dose_era (drug_concept_id ASC); + +--CREATE CLUSTERED INDEX idx_condition_era_person_id_1 +--ON [omop].condition_era (person_id ASC); +--CREATE INDEX idx_condition_era_concept_id_1 +--ON [omop].condition_era (condition_concept_id ASC); + +--/************************** + +--Standardized meta-data + +--***************************/ + +--CREATE CLUSTERED INDEX idx_metadata_concept_id_1 +--ON [omop].metadata (metadata_concept_id ASC); + +--/************************** + +--Standardized vocabularies + +--***************************/ + +--CREATE CLUSTERED INDEX idx_concept_concept_id +--ON [omop].concept (concept_id ASC); +--CREATE INDEX idx_concept_code ON [omop].concept (concept_code ASC); +--CREATE INDEX idx_concept_vocabluary_id +--ON [omop].concept (vocabulary_id ASC); +--CREATE INDEX idx_concept_domain_id ON [omop].concept (domain_id ASC); +--CREATE INDEX idx_concept_class_id ON [omop].concept (concept_class_id ASC); + +--CREATE CLUSTERED INDEX idx_vocabulary_vocabulary_id +--ON [omop].vocabulary (vocabulary_id ASC); + +--CREATE CLUSTERED INDEX idx_domain_domain_id +--ON [omop].domain (domain_id ASC); + +--CREATE CLUSTERED INDEX idx_concept_class_class_id +--ON [omop].concept_class (concept_class_id ASC); + +--CREATE CLUSTERED INDEX idx_concept_relationship_id_1 +--ON [omop].concept_relationship (concept_id_1 ASC); +--CREATE INDEX idx_concept_relationship_id_2 +--ON [omop].concept_relationship (concept_id_2 ASC); +--CREATE INDEX idx_concept_relationship_id_3 +--ON [omop].concept_relationship (relationship_id ASC); + +--CREATE CLUSTERED INDEX idx_relationship_rel_id +--ON [omop].relationship (relationship_id ASC); + +--CREATE CLUSTERED INDEX idx_concept_synonym_id +--ON [omop].concept_synonym (concept_id ASC); + +--CREATE CLUSTERED INDEX idx_concept_ancestor_id_1 +--ON [omop].concept_ancestor (ancestor_concept_id ASC); +--CREATE INDEX idx_concept_ancestor_id_2 +--ON [omop].concept_ancestor (descendant_concept_id ASC); + +--CREATE CLUSTERED INDEX idx_source_to_concept_map_3 +--ON [omop].source_to_concept_map (target_concept_id ASC); +--CREATE INDEX idx_source_to_concept_map_1 +--ON [omop].source_to_concept_map (source_vocabulary_id ASC); +--CREATE INDEX idx_source_to_concept_map_2 +--ON [omop].source_to_concept_map (target_vocabulary_id ASC); +--CREATE INDEX idx_source_to_concept_map_c +--ON [omop].source_to_concept_map (source_code ASC); + +--CREATE CLUSTERED INDEX idx_drug_strength_id_1 +--ON [omop].drug_strength (drug_concept_id ASC); +--CREATE INDEX idx_drug_strength_id_2 +--ON [omop].drug_strength (ingredient_concept_id ASC); \ No newline at end of file diff --git a/Images/AthenaVocabularies.png b/Images/AthenaVocabularies.png new file mode 100644 index 0000000000000000000000000000000000000000..129294b764d37c3c0f9665e4a5e1e1b21d6a1104 GIT binary patch literal 56949 zcmZ^~2{_c<`#(;~5>lqD*)^1XhLJrxF@%Ve-Pj4)m#GL7VknF?d&<6(orbL0cd};R z_rd(%dY1O)U$7au~p z-u89iN9vb~`Y&~y5HCGUT`UQdT`VmfU9Fv78tIY(LnhZ$6=WZK8m*m?S90ol9a8T4 zKADRB?XzNFLGYd=N(IXVW1yCxJ9K;Axbt7Tx|AY25q3ii}sv(OB>YzQq;N${0&CP)dW?Bm!Qumr(z?&7Sv?C zMdDgWo>s2SmKb_cAATp+U%z%q7=0@snx2+rA~(G3;9{7f)?+-mGvlmieA~HVg7Rx{ zvBXaWJqiuPGv7kaH{hrX(zw4c%S{9wg@cQcy^C6(P;1xzOk=PS+|pnFH}&VVv#=@J z(P`^l?w<KIs~6TSRd~?J8R;rn3$b9?)Io& z=!pH6mD#bd-Y_;`3k`OmB( zD&qLl-N`Go&m!&JluK;br}5{9-_j$b$JAG8H|p{J9)Jj|WzPC#_KToqOZffz^Vn&p zg%rw->QyVg#M9-7(ig3wGQ&-xnx_xQWoj2rXmZ_2UK@y_D<<6&zHMU}&q%r-7En`Q zH@*Q@V-lq` zyH9Iwg38YbC2B5w!t^Nu3UA=Ow-X)~HqfMCc5q`+>oOE&@XrdpR?@rl9$g~Co^|K@ z1zbK7w0{V0`i*<{iVPNHfqUj-Iwjk~*X=po*{KwH(sM4Pmkxw0P5 z{DPJVS@VtCm%~a?v^sjKUv+#5N6}M3ZJXFHnmj+%fB$#y;Zy}2(BsYO!zrILii{;9 zmZMib^JwL$yzaotykRi)qI!a09L�_hZ8Z%`*9kv2KbDyN|LZn7SeIIr41(T;4jf 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 T81dk=Mv)SG2FVxE`uINpQ0M~! literal 0 HcmV?d00001 diff --git a/Images/DataFactoryDeployment.png b/Images/DataFactoryDeployment.png new file mode 100644 index 0000000000000000000000000000000000000000..a9fd856d907640a479030f5ba1ff419124a0cbe8 GIT binary patch literal 24459 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 E0I^Ue2mk;8 literal 0 HcmV?d00001 diff --git a/Images/DataFactoryParameters.png b/Images/DataFactoryParameters.png new file mode 100644 index 0000000000000000000000000000000000000000..80a90025d039a72a2b9dc20a19ad4290f5e25ec9 GIT binary patch literal 59022 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