Presto Content Management System
Microsoft SQL Server to MySQL database conversion

<- ->

Documentation > Presto Developer's Manual > Under The Hood > The Database

A general article on why to convert, and on conversion tasks is located at http://dev.mysql.com/tech-resources/articles/migrating-from-microsoft.html   Presto database conversion is a simpler case, which focuses on a very good script for converting Microsoft SQL Server databases to MySQL, by Michael Kofler (this is offered in the above article).  This works well in converting Presto's installation SQL Server database template into MySQL.  It gets around many of the field conversion problems encountered when using a MySQL administration program to do the import. 

However it does run into one problem with autoincrement fields that start with a value of 0.  For these fields, one must clear out the autoincrement function on the SQL Server field before doing the import, and reaply it after the import is complete.

' this program converts Microsoft SQL Server databases to MySQL databases

' (c) 2001-2003 Michael Kofler
'     http://www.kofler.cc/mysql
'     mssql2mysql-at-kofler.cc

' LICENSE: GPL (Gnu Public License)
' VERSION: 0.10 (Nov 12th 2003)
'
' HISTORY:
' 0.01 (Jan. 18 2001): initial version
' 0.02 (June 23 2001): better handling of decimal numbers
'   by <dave.whitla-at-ocean.net.au>
' 0.03 (August 7 2001): compatibility with Office 97
'   by <DaveMeaker-at-angelshade.com>
'   (uncomment Replace function at the end of the script!)
' 0.04 (September 30 2001): slightly faster (ideas by
'    janivar-at-acos.no)
' 0.05 (April 8 2002): compatible with MyODBC 3.51
'    (set variable MyODBCVersion accordingly!)
'    thanks to Silvio Iaccarino
' 0.05a (October 17 2002): support for BIGINT
'    thanks to Ugo Gioia
' 0.06 (December 18 2002): support for adBinary (thanks to Roberto Alicata)
'    changed handling of TIMESTAMP (Michael Kofler):
'      TIMESTAMP (MSSQL) --> TINYBLOB (MySQL)
' 0.07 (Jan. 27th 2003): support no-standard ports for MySQL
' 0.08 (Apr. 30th 2003): Boolean now converts to 1/0 instead of -1/0
'      for True/False (Michael Kofler)
' 0.09 (Aug. 19th 2003): better GUID conversion (Hermann Wiesner)
' 0.10 (Nov. 12th 2003): better handling of table and field names
'      in function MySQLName (Carlo)

' USEAGE:
' 1) copy this code into a new VBA 6 module
'    (i.e. start Excel 2000 or Word 2000 or another
'     program with VBA editor, hit Alt+F11, execute
'     Insert|Module, insert code)
'    OR copy code into an empty form of a new VB6 project
'
' 2) change the constants at the beginning of the code,
' 3) hit F5 and execute Main()
'    the program now connects to Microsoft SQL Server
'    and converts the database; the resulting SQL commands
'    are either saved in an ASCII file or executed immediately

' FUNCTION:
' converts both schema (tables and indices) and
' data (numbers, strings, dates etc.)
' handles table and column names which are not legal in MySQL (see MySQLName())

' LIMITATIONS:
' no foreign keys            (not yet supported by MySQL)
' no SPs, no triggers        (not yet supported by MySQL)
' no views                   (not yet supported by MySQL)
' no user defined data types (not yet supported by MySQL)
' AUTO_INCREMENTs: MySQL does not support tables with more
'   than one AUTO_INCREMENT column; the AUTO_INCREMENT column
'   must also be a key column; the converter does not check this,
'   so use MSSQL to add an index to the AUTO_INCREMENT column
'   before starting the conversion
' no privileges/access infos (the idea of logins/users in M$ SQL Server
'     is incompatible with user/group/database/table/column
'     privileges of MySQL)
' cannot handle ADO type adFileTime yet
' GUIDs not tested
' fairly slow and no visible feedback during conversion process
'   for example, it takes 80 seconds to convert Northwind (2.8 MB data)
'   with M$SQL running on PII 350 (CPU=0) and this script running in
'   Excel 2000 on PII 400 (CPU=100); unfortunately, compiling the program
'   with VB6 does not make it any faster
'   tip: test with MAX_RECORDS = 10 first to see if it works for you at all

' DATA:
' Unicode string can be converted either to ANSI strings or to BLOBs
' (Unicode --> BLOB is untested, though)

' INTERNALS:
' method:   read database schema using DMO
'           read data using a ADO recordset

' NECESSARY LIBRARIES:
'   ADODB  (tested with 2.5, should also run with all versions >=2.1)
'   MyODBC (testet with 2.50.36 and 3.51)
'   SQLDMO (tested with the version provided by M$ SQL Server 7 / MSDE 1)
'   SCRIPTING


Option Explicit
Option Compare Text

' -------------- change these constants before use!

                                  'M$ SQL Server
Const MSSQL_SECURE_LOGIN = True   'login type (True for NT security)
Const MSSQL_LOGIN_NAME = ""       'login name (for NT security use "" here)
Const MSSQL_PASSWORD = ""         'password   (for NT security use "" here)
Const MSSQL_HOST = "(local)"       'if localhost: use "(local)"
Const MSSQL_DB_NAME = "laidlaw" 'database name

Const OUTPUT_TO_FILE = 0          '1 --> write file;
                                  '0 --> connect to MySQL, execute SQL commands directly
                                 
                                  'output file (only needed if OUTPUT_TO_FILE=1)
Const OUTPUT_FILENAME = "c:\export.sql"

                                  'connect to MySQL (only needed if OUTPUT_TO_FILE=0)
Const MYSQL_USER_NAME = "root"    'login name
Const MYSQL_PASSWORD = ""   'password
Const MYSQL_HOST = "localhost"    'if localhost: use "localhost"
Const MYSQL_PORT = 3306           'change if you use another port
Const MyODBCVersion = "MySQL ODBC 3.51 Driver"     'for MyODBC 2.51.*; if you use MyODBC 3.51*, use
                                  'this setting instead: "MySQL ODBC 3.51 Driver"

Const NEW_DB_NAME = "test2"            'name of new MySQL database ("" if same as M$SQL db name)
                                  'conversion options
Const UNICODE_TO_BLOB = False      'unicode --> BLOBs (True) or ASCII (False)
Const DROP_DATABASE = True        'begin with DROP dbname?
Const MAX_RECORDS = 0             'max. nr of records per table (0 for all records, n for testing purposes)


' ----------------------------- don't change below here (unless you know what you are doing)

Const SQLDMOIndex_DRIPrimaryKey = 2048
Const SQLDMOIndex_Unique = 2
Const adEmpty = 0
Const adTinyInt = 16
Const adSmallInt = 2
Const adInteger = 3
Const adBigInt = 20
Const adUnsignedTinyInt = 17
Const adUnsignedSmallInt = 18
Const adUnsignedInt = 19
Const adUnsignedBigInt = 21
Const adSingle = 4
Const adDouble = 5
Const adCurrency = 6
Const adDecimal = 14
Const adNumeric = 131
Const adBoolean = 11
Const adError = 10
Const adUserDefined = 132
Const adVariant = 12
Const adIDispatch = 9
Const adIUnknown = 13
Const adGUID = 72
Const adDate = 7
Const adDBDate = 133
Const adDBTime = 134
Const adDBTimeStamp = 135
Const adBSTR = 8
Const adChar = 129
Const adVarChar = 200
Const adLongVarChar = 201
Const adWChar = 130
Const adVarWChar = 202
Const adLongVarWChar = 203
Const adBinary = 128
Const adVarBinary = 204
Const adLongVarBinary = 205
Const adChapter = 136
Const adFileTime = 64
Const adPropVariant = 138
Const adVarNumeric = 139
Const adArray = &H2000

Public dmoApplic 'As New SQLDMO.Application  'SQLDMO Application object
Public dmoSrv    'As New SQLDMO.SQLServer    'SQLDMO Server object
Public mssqlConn 'As New Connection          'ADO Connection to M$ SQL Server
Public mysqlConn 'As New Connection          'ADO Connection to MySQL
Public fso       'As Scripting.FileSystemObject
Public fileout   'AS FSO.TextStream

Public Sub Main()
  Set dmoApplic = CreateObject("SQLDMO.Application")
  Set dmoSrv = CreateObject("SQLDMO.SQLServer")
  Set mssqlConn = CreateObject("ADODB.Connection")
  Set mysqlConn = CreateObject("ADODB.Connection")
  Set fso = CreateObject("Scripting.FileSystemObject")
  ConnectToDatabases
  ConvertDatabase
  MsgBox "done"
End Sub

' connect to M$ SQL Server and MySQL
Private Sub ConnectToDatabases()
  dmoSrv.LoginTimeout = 10
  On Error Resume Next
 
  ' DMO connection to M$ SQL Server
  If MSSQL_SECURE_LOGIN Then
    dmoSrv.LoginSecure = True
    dmoSrv.Connect MSSQL_HOST
  Else
    dmoSrv.LoginSecure = False
    dmoSrv.Connect MSSQL_HOST, MSSQL_LOGIN_NAME, MSSQL_PASSWORD
  End If
  If Err Then
    MsgBox "Sorry, cannot connect to M$ SQL Server. " & _
      "Please edit the MSSQL constats at the beginning " & _
      "of the code." & vbCrLf & vbCrLf & Error
    End
  End If
 
  ' ADO connection to M$ SQL Server
  Dim tmpCStr$
  tmpCStr = _
    "Provider=SQLOLEDB;" & _
    "Data Source=" & MSSQL_HOST & ";" & _
    "Initial Catalog=" & MSSQL_DB_NAME & ";"
  If MSSQL_SECURE_LOGIN Then
    tmpCStr = tmpCStr & "Integrated Security=SSPI"
  Else
    tmpCStr = tmpCStr & _
      "User ID=" & MSSQL_LOGIN_NAME & ";" & _
      "Password=" & MSSQL_PASSWORD
  End If
  mssqlConn.ConnectionString = tmpCStr
  mssqlConn.Open
  If Err Then
    MsgBox "Sorry, cannot connect to M$ SQL Server. " & _
      "Please edit the MSSQL constats at the beginning " & _
      "of the code." & vbCrLf & vbCrLf & Error
    End
  End If
 
  ' ADO connection to MySQL or open output file
  If (OUTPUT_TO_FILE = 0) Then
    mysqlConn.ConnectionString = _
      "Provider=MSDASQL;" & _
      "Driver=" & MyODBCVersion & ";" & _
      "Server=" & MYSQL_HOST & ";" & _
      "UID=" & MYSQL_USER_NAME & ";" & _
      "PWD=" & MYSQL_PASSWORD & ";" & _
      "Port=" & MYSQL_PORT
    mysqlConn.Open
    If Err Then
      MsgBox "Sorry, cannot connect to MySQL. " & _
        "Please edit the MYSQL constats at the beginning " & _
        "of the code." & vbCrLf & vbCrLf & Error
      End
    End If
  Else
    Set fileout = fso.CreateTextFile(OUTPUT_FILENAME)
  End If
End Sub

Private Sub ConvertDatabase()
  ' copy database schema
  Dim dmoDB 'As SQLDMO.Database
  Set dmoDB = dmoSrv.Databases(MSSQL_DB_NAME)
  DBDefinition dmoDB
  ' copy data
  CopyDB dmoDB
End Sub

' build SQL code to define one column
' ColDefinition$(col As SQLDMO.Column)
Function ColDefinition$(col)
  Dim cdef$
  cdef = MySQLName(col.Name) & " " & DataType(col)
  If col.Default <> "" Then
    cdef = cdef & " DEFAULT " & col.Default
  End If
  If col.AllowNulls Then
    cdef = cdef & " NULL"
  Else
    cdef = cdef & " NOT NULL"
  End If
  If col.Identity Then
    cdef = cdef & " AUTO_INCREMENT"
  End If
  ColDefinition = cdef
End Function

' datatype transition M$ SQL Server --> MySQL
' DataType$(col As SQLDMO.Column)
Function DataType$(col)
  Dim oldtype$, length&, precision&, scal&
  Dim newtype$
 
  oldtype = col.PhysicalDatatype
  length = col.length
  precision = col.NumericPrecision
  scal = col.NumericScale
  If LCase(oldtype) = "money" Then
    precision = 19
    scal = 4
  ElseIf LCase(oldtype) = "smallmoney" Then
    precision = 10
    scal = 4
  End If
 
  Select Case LCase(oldtype)
 
  ' integers
  Case "bit", "tinyint"
    newtype = "TINYINT"
  Case "smallint"
    newtype = "SMALLINT"
  Case "int"
    newtype = "INT"
  Case "bigint"
    newtype = "BIGINT"
 
  ' floating points
  Case "float"
    newtype = "DOUBLE"
  Case "real"
    newtype = "FLOAT"
  Case "decimal", "numeric", "money", "smallmoney"
    newtype = "DECIMAL(" & precision & ", " & scal & ")"
 
  ' strings
  Case "char"
    If length < 255 Then
      newtype = "CHAR(" & length & ")"
    Else
      newtype = "TEXT"
    End If
  Case "varchar"
    If length < 255 Then
      newtype = "VARCHAR(" & length & ")"
    Else
      newtype = "TEXT"
    End If
  Case "text"
    newtype = "LONGTEXT"
   
  ' unicode strings
  Case "nchar"
    If UNICODE_TO_BLOB Then
      newtype = "BLOB"
    Else
      If length <= 255 Then
        newtype = "CHAR(" & length & ")"
      Else
        newtype = "TEXT"
      End If
    End If
  Case "nvarchar"
    If UNICODE_TO_BLOB Then
      newtype = "BLOB"
    Else
      If length <= 255 Then
        newtype = "VARCHAR(" & length & ")"
      Else
        newtype = "TEXT"
      End If
    End If
 
  Case "ntext"
    If UNICODE_TO_BLOB Then
      newtype = "LONGBLOB"
    Else
      newtype = "LONGTEXT"
    End If
   
  ' date/time
  Case "datetime", "smalldatetime"
    newtype = "DATETIME"
  Case "timestamp"
    newtype = "TINYBLOB"
   
  ' binary and other
  Case "uniqueidentifier"
    newtype = "TINYBLOB"
  Case "binary", "varbinary"
    newtype = "BLOB"
  Case "image"
    newtype = "LONGBLOB"
 
  Case Else
    Stop
  End Select
 
  DataType = newtype
End Function

' IndexDefinition$(tbl As SQLDMO.Table, idx As SQLDMO.Index)
Function IndexDefinition$(tbl, idx)
  Dim i&
  Dim tmp$
  Dim col 'As SQLDMO.Column
  ' don't deal with system indices (used i.e. to ensure ref. integr.)
  If Left$(idx.Name, 1) = "_" Then Exit Function
  ' index type (very incomplete !!!)
  If idx.Type And SQLDMOIndex_DRIPrimaryKey Then
    tmp = tmp & "PRIMARY KEY"
  ElseIf idx.Type And SQLDMOIndex_Unique Then
    tmp = tmp & "UNIQUE " & MySQLName(idx.Name)
  Else
    tmp = tmp & "INDEX " & MySQLName(idx.Name)
  End If
  ' index columns
  tmp = tmp & "("
  For i = 1 To idx.ListIndexedColumns.Count
    Set col = idx.ListIndexedColumns(i)
    tmp = tmp & MySQLName(col.Name)
    ' specify index length
    If (col.PhysicalDatatype = "nchar" Or col.PhysicalDatatype = "nvarchar" Or col.PhysicalDatatype = "ntext") And UNICODE_TO_BLOB = True Then
      ' 2 byte per unicode char!
      tmp = tmp & "(" & IIf(col.length * 2 < 255, col.length * 2, 255) & ")"
    ElseIf Right$(DataType(col), 4) = "BLOB" Or Right$(DataType(col), 4) = "TEXT" Then
      tmp = tmp & "(" & IIf(col.length < 255, col.length, 255) & ")"
    End If
    ' separate, if more than one index column
    If i < idx.ListIndexedColumns.Count Then tmp = tmp & ","
  Next
  tmp = tmp & ")"
  IndexDefinition = tmp
End Function

' build SQL code to define one table
' TableDefinition$(tbl As SQLDMO.Table)
Function TableDefinition$(tbl)
  Dim i&
  Dim tmp$, ixdef$
  ' table
  tmp = "CREATE TABLE " & _
        NewDBName(tbl.Parent) & "." & MySQLName(tbl.Name) & vbCrLf & "("
  For i = 1 To tbl.Columns.Count
    tmp = tmp & ColDefinition(tbl.Columns(i))
    If i < tbl.Columns.Count Then
      tmp = tmp & ", " & vbCrLf
    End If
  Next
  ' indices
  For i = 1 To tbl.Indexes.Count
    ixdef = IndexDefinition(tbl, tbl.Indexes(i))
    If ixdef <> "" Then
      tmp = tmp & ", " & vbCrLf & ixdef
    End If
  Next
  tmp = tmp & ")"
  TableDefinition = tmp
End Function

' build SQL code to define database (all tables)
' DBDefinition(db As SQLDMO.Database)
Sub DBDefinition(db)
  Dim i&
  Dim sql, dbname$
  dbname = NewDBName(db)
  If DROP_DATABASE Then
    sql = "DROP DATABASE IF EXISTS " & dbname
    ExecuteSQL sql
  End If
  sql = "CREATE DATABASE " & dbname
  ExecuteSQL sql
  For i = 1 To db.Tables.Count
    If Not db.Tables(i).SystemObject Then
      sql = TableDefinition(db.Tables(i))
      ExecuteSQL sql
    End If
  Next
End Sub

' copy content of all M$ SQL Server tables to new MySQL database
' CopyDB(msdb As SQLDMO.Database)
Sub CopyDB(msdb)
  Dim i&
  Dim tmp$
  ExecuteSQL "USE " & NewDBName(msdb)
  For i = 1 To msdb.Tables.Count
    If Not msdb.Tables(i).SystemObject Then
      CopyTable msdb.Tables(i)
    End If
  Next
End Sub

' copy content of one table from M$ SQL Server to MySQL
' CopyTable(mstable As SQLDMO.Table)
Sub CopyTable(mstable)
  Dim rec ' As Recordset
  Dim sqlInsert$, sqlValues$
  Dim i&, recordCounter&
  Set rec = CreateObject("ADODB.Recordset")
  rec.Open "SELECT * FROM [" & mstable.Name & "]", mssqlConn
  ' build beginning statement of SQL INSERT command
  ' for example: INSERT INTO tablename (column1, column2)
  sqlInsert = "INSERT INTO " & MySQLName(mstable.Name) & " ("
  For i = 0 To rec.Fields.Count - 1
    sqlInsert = sqlInsert & MySQLName(rec.Fields(i).Name)
    If i <> rec.Fields.Count - 1 Then
      sqlInsert = sqlInsert & ", "
    End If
  Next
  sqlInsert = sqlInsert & ") "
  ' for each recordset in M$SS table: build sql statement
  Do While Not rec.EOF
    sqlValues = ""
    For i = 0 To rec.Fields.Count - 1
      sqlValues = sqlValues & DataValue(rec.Fields(i))
      If i <> rec.Fields.Count - 1 Then
        sqlValues = sqlValues & ", "
      End If
    Next
    ExecuteSQL sqlInsert & " VALUES(" & sqlValues & ")"
    rec.MoveNext
    ' counter
    recordCounter = recordCounter + 1
    If MAX_RECORDS <> 0 Then
      If recordCounter >= MAX_RECORDS Then Exit Do
    End If
  Loop
End Sub

' data transition M$ SQL Server --> MySQL
' DataValue$(fld As ADO.Field)
Function DataValue$(fld)
  If IsNull(fld.Value) Then
    DataValue = "NULL"
  Else

    Select Case fld.Type
   
    ' integer numbers
    Case adBigInt, adInteger, adSmallInt, adTinyInt, adUnsignedBigInt, adUnsignedInt, adUnsignedSmallInt, adUnsignedTinyInt
      DataValue = fld.Value
   
    ' decimal numbers
    Case adCurrency, adDecimal, adDouble, adNumeric, adSingle, adVarNumeric
      DataValue = Str(fld.Value)
      If Not InStr(DataValue, ".") > 0 Then
        DataValue = DataValue & ".0"
      End If
     
    ' boolean
    Case adBoolean
      DataValue = IIf(fld.Value, 1, 0)
     
    ' date, time
    Case adDate, adDBDate, adDBTime
      DataValue = Format(fld.Value, "'yyyy-mm-dd Hh:Nn:Ss'")
    Case adDBTimeStamp
      DataValue = Format(fld.Value, "yyyymmddHhNnSs")
    Case adFileTime
      ' todo
      Beep
      Stop
     
    ' ANSI strings
    Case adBSTR, adChar, adLongVarChar, adVarChar
      DataValue = "'" & Quote(fld.Value) & "'"
   
    ' UNICODE strings
    Case adLongVarWChar, adVarWChar, adWChar
      If UNICODE_TO_BLOB = True Then
        DataValue = HexCodeStr(fld.Value)
      Else
        ' we hope the string only contains ANSI characters ...
        DataValue = "'" & Quote(fld.Value) & "'"
      End If
   
    ' binary and other
    Case adGUID
      DataValue = HexCodeGUID(fld.Value)
    Case adLongVarBinary, adVarBinary
      DataValue = HexCode(fld.Value)
   
    End Select
  End If
End Function

' converts a Byte-array into a hex string
' HexCode$(bytedata() As Byte)
Function HexCode(bytedata)
  Dim i&
  Dim tmp$
  tmp = ""
  For i = LBound(bytedata) To UBound(bytedata)
    If bytedata(i) <= 15 Then
      tmp = tmp + "0" + Hex(bytedata(i))
    Else
      tmp = tmp + Hex(bytedata(i))
    End If
  Next
  HexCode = "0x" + tmp
End Function

' converts a String into a hex string
' HexCode$(bytedata() As Byte)
Function HexCodeStr(bytedata)
  Dim i&, b&
  Dim tmp$
  tmp = ""
  For i = 1 To LenB(bytedata)
    b = AscB(MidB(bytedata, i, 1))
    If b <= 15 Then
      tmp = tmp + "0" + Hex(b)
    Else
      tmp = tmp + Hex(b)
    End If
  Next
  HexCodeStr = "0x" + tmp
End Function

' returns name of new database
' NewDBName$(db As SQLDMO.Database)
Function NewDBName$(db)
  If NEW_DB_NAME = "" Then
    NewDBName = db.Name
  Else
    NewDBName = NEW_DB_NAME
  End If
End Function

' quote ' " and \; replace chr(0) by \0
Function Quote(tmp)
  tmp = Replace(tmp, "\", "\\")
  tmp = Replace(tmp, """", "\""")
  tmp = Replace(tmp, "'", "\'")
  Quote = Replace(tmp, Chr(0), "\0")
End Function

' to translate MSSQL names to legal MySQL names
' replace blank, -, ( and ) by '_'
Function MySQLName(tmp)
  tmp = Replace(tmp, " ", "_")
  tmp = Replace(tmp, "-", "_")
  tmp = Replace(tmp, "(", "_")
  MySQLName = "`" & Replace(tmp, ")", "_") & "`"
End Function

' either execute SQL command or write it into file
Function ExecuteSQL(sql)
  If OUTPUT_TO_FILE Then
    fileout.WriteLine sql & ";"
    If Left$(sql, 6) <> "INSERT" Then
      fileout.WriteLine
    End If
  Else
    mysqlConn.Execute sql
  End If
End Function

' this event procedures starts the converter if it is run as a VB6 programm
Private Sub Form_Load()
  Main
  End
End Sub

' converts a GUID string into a hex string without format
Function HexCodeGUID(bytedata)
  Dim tmp$
  If Len(bytedata) = 38 Then
    tmp = Mid(bytedata, 2, 8) + Mid(bytedata, 11, 4) + Mid(bytedata, 16, 4) _
        + Mid(bytedata, 21, 4) + Mid(bytedata, 26, 12)
  Else
    tmp = "00"
  End If
  HexCodeGUID = "0x" + tmp
End Function


' uncomment the following lines if you are using Office 97,
' which does not have the Replace function include; please
' note that this Replace function is much slower than the
' built-in version in Office 2000/VB6
'
' to replace all occurrences of a string within a string
' Function Replace(tmp, fromStr, toStr)
'     Dim leftOff&
'     Dim pos&
'     leftOff = 1
'     Do While InStr$(l

<- ->

Valid XHTML 1.0!