Blow it away and start again 🙂
When we are working in a dynamic environment, we need to push through changes quickly and a prerequisite for this means starting from scratch. Starting with a blank database and deploying the build from a clean state. If you can’t drop and recreate the database, then the next best option is to clean it out, delete all the objects and recreate them as part of your build.
You can scrub tables in several ways. The one that I propose here is a sequential (and dynamic method) that is relatively straight forward (and of course we can look into BIML for it). The template for the pattern is given in the pseudo code below;
Assume there are tables to delete While there are tables to delete then table_list <- get the list of tables foreach table in table_list, try to delete the table determine if there are (still) tables to delete
In, SSIS, this looks like the following pattern and I have included a list of variables that I used to run the package. There some more advanced features of SSIS being used here which I will call out;
- We use TABLE_COUNT (initial value 1) to control the continued execution of the package (that is, the WHILE_TABLE_EXISTS container). This container runs while the TABLE_COUNT value is greater than 1 (or my database still has tables).
- The TABLES_IN_DB is an object that holds a list of table names, this is the result of the SQL Command GET_TABLE_LIST. The purpose here, is to query the database metadata in order to determine a list of names.
- The iterator FOREACH_RECORD_IN_TALBE_LIST enumerates over each record in the TALBES_IN_DB (assigning the table name to the variable TABLE_NAME). Within that container, we generate what SQL to execute (ie the drop command) in the GENERATE_SQL expression by assigning it to the SQL variable. Then we execute that variable via an Execute Command. Since we do not want the task to fail if the command does not work (after all there may be some dependencies between tables and execution order).
- Finally, after the first batch of executions has run (and hopefully all the tables are deleted), we recount the tables in the database, storing the values in the TABLE_COUNT variable. Control is then passed back to the WHILE_TABLES_EXIST to see if there are tables in the database and determine whether the process should begin again.
This process is very generic and can be applied in any database. There’s also no need for BIML Script in code (since we do not rely on the generation of tasks which specifically rely on data). Here is the full snippet;
<Biml xmlns="http://schemas.varigence.com/biml.xsd"> <Connections> <OleDbConnection Name="FM_MYSQL" ConnectionString="provider=MSDataShape;server=foodmart_mysql;uid=foodmart_admin;pwd=whats_my_password?" DelayValidation="true" /> </Connections> <Packages> <Package Name="01-Clean_MySQL" ProtectionLevel="EncryptAllWithUserKey" ConstraintMode="Linear"> <!-- these variables are created in the packages scope --> <Variables> <Variable Name="TABLES_IN_DB" DataType="Object"> </Variable> <Variable Name="TABLE_COUNT" DataType="Int32">1</Variable> </Variables> <Tasks> <!-- the first container (while records exists) note the evaluation expresssion --> <ForLoop Name="WHILE TABLES EXIST" ConstraintMode="Linear"> <LoopTestExpression>@TABLE_COUNT>=1</LoopTestExpression> <!-- tasks within the container are contained in a tasks tag --> <Tasks> <!-- get the list of table (names) .. note the record set is assigned to the variable TABLES_IN_DB --> <ExecuteSQL Name="GET_TABLE_LIST" ResultSet="Full" ConnectionName="FM_MYSQL"> <DirectInput>SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='foodmart';</DirectInput> <Results> <Result Name="0" VariableName="User.TABLES_IN_DB" /> </Results> </ExecuteSQL> <!-- Iterate over each record in the TABLES_IN_DB variable Note how we assign the current record to the TABLE_NAME variable --> <ForEachAdoLoop Name="FOREACH_RECORD_IN_TABLE_LIST" SourceVariableName="User.TABLES_IN_DB" ConstraintMode="Linear"> <Variables> <Variable Name="TABLE_NAME" DataType="String"> </Variable> <Variable Name="SQL" DataType="String"> </Variable> </Variables> <VariableMappings> <VariableMapping Name="0" VariableName="User.TABLE_NAME" /> </VariableMappings> <Tasks> <!-- Generate the SQL Statement –> <Expression Name="GENERATE SQL" Expression="@[User::SQL]="DROP TABLE foodmart." + @[User::TABLE_NAME]"/> <!-- Execute the SQL Statement (based on the user variable (SQL)) –> <ExecuteSQL Name="DELETE TABLE" ConnectionName="FM_MYSQL" DelayValidation="true" FailPackageOnFailure="false"> <VariableInput VariableName="User.SQL" /> </ExecuteSQL> </Tasks> </ForEachAdoLoop> <!-- Recount Tables and store in the variable (which is passed back to the parent container) --> <ExecuteSQL Name="GET_TABLES_COUNT" ResultSet="SingleRow" ConnectionName="FM_MYSQL"> <DirectInput>SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='foodmart';</DirectInput> <Results> <Result Name="0" VariableName="User.TABLE_COUNT" /> </Results> </ExecuteSQL> </Tasks> </ForLoop> </Tasks> </Package> </Packages> </Biml>
You might notice that I am using an MYSQL ODBC database connection. Other features are commented in the code.
As stated, there is no reliance on BIML Script here, so you may ask ‘Why use BIML?’. The reason for this is that the snippet includes samples of a few controls and how they are configured in different circumstances.