I’ve been exploring other database systems, in order to determine how to import data from them using SQL Server Integration Services (SSIS). My first step though was to create some test data. I wanted something familiar, so I decided to export the Adventure Works Data Warehouse sample database and import into MongoDB. While I had many options I decided the simplest way was to first export the data to CSV files, then use the MongoDB utility mongimport. Naturally I turned to PowerShell to create an automated, reusable process.
First, if you need the Adventure Works DW database, you’ll find it at http://msftdbprodsamples.codeplex.com/. Second, I did my export from a special version of Adventure Works DW I created called AdventureWorksDW2014. This is optional, but if you want to have a version of Adventure Works DW updated with current dates, see my post at https://arcanecode.com/2013/12/08/updating-adventureworksdw2012-for-2014/. Third, I assume you are familiar with MongoDB, but if you want to learn more go to http://www.mongodb.org/.
Below is the PowerShell 3 script I created. The script is broken into four regions. The first, User Settings, contains the variables that you the user might need to change to get the script to run. It has things like the name of the SQL Server database, the path to MongoDB, etc.
The second region, Common, establishes variables that are used by the remaining two regions. You shouldn’t need to change or alter these. The third region accesses SQL Server and exports each table in the selected database to a CSV format file.
The final region, “Generate MongoDB import commands”, creates a batch (.BAT) file which has all the commands needed to run mongoimport for each CSV file. I decided not to have the PowerShell script execute the .BAT file so it could be reviewed before it is run. There might be some tables you don’t want to import, etc.
It is also quite easy to adapt this script to just create CSV files from SQL Server without using the MongoDB piece. Simply remove the fourth and final region, then in the Common and User Settings regions remove any variables what begin with the prefix “mongo”.
As the comments do a good job of explaining what happens I’ll let you review the included documentation for step by step instructions.
#==================================================================================================
# SQLtoCSVtoMongoDb.ps1
# Robert C. Cain | @ArcaneCode | http://arcanecode.com
#
# If you need a simple way to export data from SQL Server to MongoDb, here is one way to do it.
# The script starts by setting up some variables to the server environment (see the User Settings
# region)
#
# Next, it exports data from each table in the selected database to individual CSV files.
# Finally, it generates a batch file which executes mongoimport for each csv file to import
# into MongoDb.
#
# I broke this into four regions so if all that is desired is a simple export of data to CSVs,
# you can simply omit the final region along with any variables that begin with "mongo".
#
# While I could have gone ahead and run the batch file at the end, I chose not to in order to
# give you time to review the output prior to running the batch file.
#==================================================================================================
Clear-Host
#region User Settings
# In this section, set the variables so they are appropriate for your project / environment
# This is the spot where you want to store the generated CSVs.
# Make sure it does NOT end in a \
$csvPath = "C:\mongodb"
# If you are running this on a computer other than the server, set the name of the server below
$sqlServer = $env:COMPUTERNAME
# If you have a named instance be sure replace "default" with the name of the instance
$sqlInstance = "\default"
# Enter the name of the database to export below
$sqlDatabaseName = "AdventureWorksDW2014"
# The settings below only apply to the MongoDB code generation
# Assemble path to mongodb. This assumes utlities are stored in the default bin folder
$mongoPath = "C:\mongodb"
$mongoImport = "$mongoPath\bin\mongoimport"
# Set the server name and port
$mongoHost = "localhost" # Leave blank to default to localhost
$mongoPort = "" # Leave blank to default to 27107
# Set the user name and password, leave blank if it isn’t needed
$mongoUser = ""
$mongoPW = ""
# Enter the name of the database to import to.
$mongoDatabaseName = "AdventureWorksDW2014"
# Upserts are REALLY slow, especially on large datasets. Setting this to $true will turn off
# the upsert option. If set to true, you are responsible for either deleting all documents
# in the collection before hand, or allowing the risk of duplicates.
#
# Setting to false will enable the upsert option for mongoimport, and attempt to determine the
# keys and (if found) add them to the final mongoimport command.
$mongoNoUpsert = $true
#endregion
#region Common ————————————————————————————
# This section sets variables used by both regions below. There is no need to alter anything
# in this region.
# Import the SQLPS provider (if it’s not already loaded)
if (-not (Get-PSProvider SqlServer))
{ Import-Module SQLPS -DisableNameChecking }
# Assemble the full servername \ instance
$sqlServerInstance = "$sqlServer\$sqlInstance"
# Assemble the full path for the SQL Provider to get to the database
$sqlDatabaseLocation = "SQLSERVER:\sql\$sqlServerInstance\databases\$sqlDatabaseName"
# Now tack on the Tables ‘folder’ to the SQL Provider path, the move there
$sqlTablesLocation = $sqlDatabaseLocation + "\Tables"
Set-Location $sqlTablesLocation
# Get a list of tables in this database
$sqlTables = Get-ChildItem
#endregion
#region Export SQL Data —————————————————————————
# In this section we will export data from each table in the database to a CSV file.
# WARNING: If the CSV file exists, it will be overwritten.
# These are just used to display informational messages during processing
$sqlTableIterator = 0
$sqlTableCount = $sqlTables.Count
# Iterate over each table in the database
foreach($sqlTable in $sqlTables)
{
$sqlTableName = $sqlTable.Schema + "." + $sqlTable.Name
# I’ll grant you the next little bit of formatting for the progress messages is a bit
# OCD on my part, but I like my output formatted and easy to read.
$sqlTableIterator++
$padCount = " " * (1 + $sqlTableCount.ToString().Length – $sqlTableIterator.ToString().Length)
$sqlTableIteratorFormatted = $padCount + $sqlTableIterator
if( $sqlTableName.Length -gt 50 )
{ $padTable = " " }
else
{ $padTable = " " * (50 – $sqlTableName.Length) }
Write-Host -ForegroundColor White -NoNewline "Processing Table $sqlTableIteratorFormatted of $sqlTableCount : $sqlTableName $padTable"
# If the instance is "default", we have to exclude it when we use Invoke-SqlCmd
if($sqlInstance.ToLower() -eq "\default")
{ $sqlSI = $sqlServer }
else
{ $sqlSI = $sqlServerInstance }
# Load an object with all the data in the table
# Note if you have especially large tables you may need to modify this
# section to break things into smaller chunks.
$sqlCmd = "SELECT * FROM " + $sqlTableName
$sqlData = Invoke-Sqlcmd -Query $sqlCmd `
-ServerInstance $sqlSI `
-SuppressProviderContextWarning `
# Now write the data out.
# Note utf8 encoding is important, as it is all mongoimport understands
# Also need to omit the Type Info header PowerShell wants to write out
Write-Host -ForegroundColor Yellow " Writing to table $sqlTableName.csv"
$sqlData | Export-Csv -NoTypeInformation -Encoding "utf8" -Path "$csvPath\$sqlTableName.csv"
}
# Just add a blank line after the processing ends
Write-Host
#endregion
#region Generate MongoDB import commands ———————————————————-
# In this region we will generage the commands to import our newly exported data
# into an existing database in MongoDB. This is an example of our desired output (wrapped
# onto multiple lines for readability, in the output it will be a single line):
# C:\mongodb>bin\mongoimport –host localhost -port 27107
# –db AdventureWorksDW2014 –collection DimSalesReason
# –username Me –password mySuperSecureP@ssW0rd!
# –type csv –headerline –file DimSalesReason.csv
# –upsert –upsertFields SalesReasonKey
# Note several of these parameters are optional, and could use defaults, or be potentially
# omitted from the final output, based on the choices at the very beginning of this script
# Feel free to alter the $mongoCommand as needed for other circumstances
# Final warning, the database must already exist in MongoDb in order to import the data. This
# script will not generate the database for you.
# Create the name for the batch file we will generate
$mongoBat = $csvPath + "\Import_SQL_" + $sqlDatabaseName + "_to_MongoDb_" + $mongoDatabaseName + ".bat"
# See if file exists, if so delete it
if (Test-Path $mongoBat)
{ Remove-Item $mongoBat }
# These are just used to display informational messages during processing
$sqlTableIterator = 0
$sqlTableCount = $sqlTables.Count
# mongoimport allows us to do upserts, helping to eliminate duplicate rows on import.
#
# To make an upsert work there has to be a key column to match up on. Fortunately,
# most tables in the SQL Server world have Primary Keys, so we can find out what
# columns those are and add it to the command. Note if there is no PK in SQL Server,
# no upsert will be attempted.
#
# Note though that upserts are REALLY slow, so the option to skip them is
# built into the script and set at the top (mongoNoUpsert). The generated batch file
# assumes that either a) you have deleted all data from the collection ahead of time,
# or b) you are OK with the risk of duplicate data.
# Iterate over each table in the database to build the mongoimport command
foreach($sqlTable in $sqlTables)
{
$sqlTableName = $sqlTable.Schema + "." + $sqlTable.Name
# A bit more OCD progress messages
$sqlTableIterator++
$padCount = " " * (1 + $sqlTableCount.ToString().Length – $sqlTableIterator.ToString().Length)
$sqlTableIteratorFormatted = $padCount + $sqlTableIterator
Write-Host -ForegroundColor Green "Building mongoimport command for table $sqlTableIteratorFormatted of $sqlTableCount : $sqlTableName"
# Begin building the command
$mongoCommand = "$mongoImport "
if ($mongoHost.Length -ne 0)
{ $mongoCommand += "–host $mongoHost " }
if ($mongoPort.Length -ne 0)
{ $mongoCommand += "–port $mongoPort " }
$mongoCommand += "–db $mongoDatabaseName –collection $sqlTableName "
if ($mongoUser.Length -ne 0)
{ $mongoCommand += " –username $mongoUser –password $mongoPW " }
$mongoCommand += " –type csv –headerline –file $csvPath\$sqlTableName.csv "
# Build the upsert clause, if the user has elected to use it.
if ($mongoNoUpsert -eq $false)
{
$mongoPKs = ""
foreach($sqlIndex in $sqlTable.Indexes)
{
if($sqlIndex.IndexKeyType -eq ‘DriPrimaryKey’)
{
foreach($sqlCol in $sqlIndex.IndexedColumns) #$sqlPKColumns)
{
if ($mongoPKs.Length -ne 0)
{ $mongoPKs += "," }
# Note column names are returned with [ ] around them, and must be removed
# Have to use -replace instead of .Replace() because $sqlCol is an column not a string
$mongoPKs += ($sqlCol -replace "\[", "") -replace "\]", ""
}
$mongoCommand += " –upsert –upsertFields $mongoPKs"
}
}
}
# Append the command to the batch file
$mongoCommand | Out-File -FilePath $mongoBat -Encoding utf8 -Append
}
# Just add a blank line after the processing ends
Write-Host
#endregion
