Being fairly new to SSIS and the ETL process, I was wondering if there is anyway to loop though a record set within a DataFlowTask and pass each row (deriving parameters from the row) into a Stored Procedure (the next step in the ETL phase). Once i have passed the row into the stored procedure, I want the results from each iteration to be written to a Table.
Does anyone know how to do this?
Thanks.
Any OLEDB command transformation (which is used to execute a database command) in an SSIS dataflow is executed once per input row - which I think is the behaviour you want. More details here.
In your scenario, the minimum you would need would be:
Data Source -> OLEDB Command -> Data Target
Note that this isn't going to give great performance - it might be better to try and refactor your stored procedure to operate on the whole input set in one go (in which case you'd use an Execute SQL task in the control flow).
The following structure would work:
Create an object variable. (recordset_object)
Create an string variable. (record_string)
Create an "Execute SQL Command" in the control flow. The command should return the record set that you want to loop through.
In the "Execute SQL Command", in the General tab set the Result Set = Full result set.
In the "Execute SQL Command", in the Result Set tab set the Result Name = 0 and Variable Name = (recordset_object).
Create a "Foreach Loop Container" and create a precedence constraint between the "Execute SQL Command" and the "Foreach Loop Container".
In the "Foreach Loop Container", in the Collection tab set Enumerator = Foreach ADO Enumerator.
In the "Foreach Loop Container", in the Collection tab set the ADO object source variable = User::recordset_object.
In the "Foreach Loop Container", in the Collection tab set the Enumeration mode = Rows in the first table.
In the "Foreach Loop Container", in the Variable Mappings tab set teh Variable = User::record_string and the Index = 0.
In the "Foreach Loop Container" in the design surface of the Control Flow, add an "Execute SQL Command".
For the child "Execute SQL Command", you can (13) set the SQLStatement to either use a variable that generates the code you want to execute, or (14) map in a parameter, or (15) make the record_string a SQL command that is executed by the code.
If you use a variable, then it could be something like User::sql_code_string and its value could be something like "EXEC schema.some_stored_procedure '" + #[record_string] + "';". You would then set the SQLSourceType in the General tab of the child "Execute SQL Command" = Variable and set the SQLStatement to User::sql_code_string.
If you use a parameter, in the child "Execute SQL Command" in the Parameter Mapping
tab set Variable Name = User::record_string, Direction = Input, Data Type = VARCHAR, Parameter Name = 0, Parameter Size = -1. In the General tab of the child "Execute SQL Command", set the SQLStatement to "EXEC schema.some_stored_procedure ?".
Similar to 13, but instead of creating a separate variable, you can execute User::record_string. This could work if the content of record_string that was returned by your data set is the query you want to execute.
I generally prefer this approach over #Ed's solution you can include additional steps for each record. For instance, I often add in additional objects in my Control Flow like Script Tasks, Data Flows, and Execute SQL Commands. It's a more flexible, easy to understand approach from my perspective, but #Ed's solution definately meets the criteria of your question.
Good luck and let me know if you need clarification on the instructions.
Related
On a form, a user is able to select a value of either 1 or 2. This number is on an unbound control called CountVal.
When the user selects the submit button, an update query is ran. The following is the query.
UPDATE UserData_T SET UserQuantity = Forms!MainUser_F!CountVal.value;
The query, when run separately, runs as should. The issue comes when I call it in the submit button click event. I use the CurrentDb.Execute method. This method throws a
3061 error of "Too few parameters'.
I have found through much research that control based parameters do not work with the execute method. It works when I set the value as either 1 or 2 but not through the control. I need to have this dynamic.
I have also tried the DAO.Database.OpenRecordset() method, however I am struggling with looping through each record. Logically, I would gravitate to a for each but I'm not finding any references on using this loop with a DAO recordset. Also, wouldn't looping through the recordset be extremely slow? Could I also add an if statement to the query itself?
In MS Access, stored queries can refer to open form or report controls as parameters. However, queries called via DAO methods like CurrentDb.Execute, do not see the GUI interface and hence cannot evaluate form or report controls.
So to continue using form controls as is, simply save your SQL statement as a stored query and call it with DoCmd.OpenQuery (which do not need to be closed for action queries like UPDATE, INSERT, DELETE). This is the coding counterpart to clicking the stored query via Navigation Pane.
DoCmd.OpenQuery "mySavedUpdateQuery"
Do note the above will raise prompts of data changes. To suppress such prompts, use DoCmd.SetWarnings:
DoCmd.SetWarnings False
DoCmd.OpenQuery "mySavedUpdateQuery"
DoCmd.SetWarnings True
You must concatenate the value to build the SQL:
Dim Sql As String
Sql = "UPDATE UserData_T SET UserQuantity = " & Forms!MainUser_F!CountVal.Value & ""
I'm trying to extract data from an Access 2016 database using SQL and VBA. Below is the code I'm trying to use and every time I run it, I get a "No value given for one or more parameters". I've also shown what I see in the immediate window.
vsql = "SELECT [ResDate],[ResNanme],[ResStart],[ResEnd] FROM [TrainingRoom] where Month([ResDate]) = " & MonNo
Set RecSet1 = Connection.Execute(vsql, dbrows, adCmdText)
Immediate Window:
SELECT [ResDate],[ResNanme],[ResStart],[ResEnd] FROM [TrainingRoom] where(Month([ResDate])) = 11
I don't see anything wrong but I'm sure this is user error. The "MonNo" variable is declared as an integer.
Any suggestions would be greatly appreciated. Thanks for the help.....
I've never used Execute method to open ADO recordset, only Open.
Assuming Connection is a declared and set variable - but it is a reserved word and really should not use as a variable. If code is behind same db pulling data from, could just use CurrentDb with DAO recordset variable and OpenRecordset method. Example that accepts default parameters for optional arguments:
Dim RecSet1 As DAO.Recordset
Set RecSet1 = CurrentDb.OpenRecordset(vsql)
When I use this SQL in Access, it works fine. In VBA, the result is to create a new table with no records. It must be a dumb mistake on my part but can't find it. Many thanks for any help or point in right direction.
Mike
strSQL = "SELECT tempconsoltb.[sub-accountNumber],tempconsoltb.[sub-accountDescription], tempconsoltb.sumOfCMBaseCurr INTO tblTempConsolTB FROM tempConsolTB;"
Set qdf = CurrentDb.CreateQueryDef("", strSQL)
qdf.Parameters(0) = Forms!frmMain.txtPM
qdf.Parameters(1) = Forms!frmMain.txtPY
qdf.Parameters(2) = Forms!frmMain.cboBaseFX
qdf.Parameters(3) = Forms!frmMain.cboConsolCo
qdf.Execute
Try checking that the objects in your forms are named properly and are actually feeding the values into the query. You can always use the Watch function in your compiler to see the values there or create a string variable that you initialize its value within the code to see what your actual query and put a watch on it when running the code.
I sometime extract plenty of data from a very old database (MS Access). The serialized output is stored as YAML files and these files are locally used by other scripts to speed up the process.
Sometimes we to an update of the local files by extracting the (possible) new data from the database. This extraction is quite long and I would like to avoid it if the content of the concerned tables is the same as the last extraction.
Is it possible to get a sort of signature of the state of a table, or part of the table ?
In other words this would help understanding my question:
signature = db.GetSignature('SELECT * FROM foo where bar = 1')
if local_foo.signature != signature:
local_foo = db.Extract('SELECT * FROM foo where bar = 1')
What solutions could I use?
Using triggers
If you have the luxury of controlling the insert/update/delete functionality of the original Access database, the best/safest solution would be to implement database triggers to enable tracking. That way you could easily at least store a "last modified" value or keep a table that is responsible for storing extensive tracking information.
Unfortunately Access doesn't support triggers (unless you're using 2010+, see below), but you could implement triggers using VBA in the database.
Access 2010 introduced data macros, but I don't think that's an option here!
Using the scripting language
If you can't use database triggers, perhaps you could use a workflow like this:
Execute query and get entire result (single collection)
Turn the query result/collection into a JSON string (e.g. json.dumps() in Python)
Get a hash of the JSON string (e.g. hashlib.sha1() & hashObject.hexdigest() in Python)
Compare hash against the last stored hash of the query result.
Using VBA
To keep things database-side (to avoid transferring data), it could be useful to try generating the hash using VBA in the Access database.
You could use the hashing algorithm code mentioned in this SO post: https://stackoverflow.com/a/14749855
Example:
Using this SHA1 code: https://gist.github.com/anonymous/573a875dac68a4af560d
Dim d As DAO.Database
Dim r As DAO.Recordset
Dim s As String
Set d = CurrentDb()
Set r = d.OpenRecordset("SELECT foo, bar, baz FROM foobar")
s = ""
While Not r.EOF
s = s & r!foo & "," & r!bar & "," & r!baz & ";"
r.MoveNext
Wend
r.Close
Set r = Nothing
Set d = Nothing
s = SHA1TRUNC(s)
I have the following scenario:
I'm looping through files in a directory.
For each file, I have to capture part of the file name and I have to use it as a parameter on the "Update Exceptions" task.
How do I save part of the file name (which the loop is looping through) and then pass it as a parameter for the stored procedure in "Update Exceptions?"
I already have the function to extract the string from the file name, I just need to know where to put it.
Again, I want to grab string X from the file name that the loop is at and then pass this variable to the stored procedure in the "Update Exceptions" task.
Can someone give me a hand?
Create a variable of type Object. Mind the (container) scope of the variable.
Map the output of a task to the variable.
Use it in your ForEach loop.
Here's one option:
Create two String variables called CurrentFile and CurrentFileSubstring
In your FELC task, map User::CurrentFile to 0
Create a Script Task between Move File to Processed Folder and Update Exceptions
Give your Script Task Read access to CurrentFile and ReadWrite access to CurrentFileSubstring
Put your function into the Script Task code. Read the value from CurrentFile and write the substring value from your function to CurrentFileSubstring.
Dts.Variables[User::CurrentFileSubstring].Value = MySubstringFunction(CurrentFile);
Your Update Exceptions task can now use the CurrentFileSubstring value.
For more information on looping over files, see this link