DAO to .mdb, ADO to .mdf comparison - sql-server

This code editing a recordset based on joined tables works in DAO/.mdb database
RS.Edit
RS.fields("fieldA").value = 0 'in table A
RS.fields("fieldB").value = 0 ' in table B
RS.Update
The code was converted to ado on a sql server database and it failed with an error message:
Run-time error '-2147467259' (80004005)' :
Cannot insert or update columns from multiple tables.
However it appears to work if it is altered like so :
RS.fields("fieldA").value = 0 'in table A
RS.Update
RS.fields("fieldB").value = 0 ' in table B
RS.Update
Is this a normal way to do things with sql server or is there a gotcha to it.
I ask because when trying to find a solution (before I put in the extra update statement) I changed the recordset type to batchoptimistic and I got no error messge but only one table's record was edited.

Apparently, the data source of your recordset is an SQL returning data from multiple tables. Yes, it's normal that you can only update one table at a time. If you want to update values from multiple tables in a single, atomic step (so that no other client use can read the "intermediate value", where one table is changed but the other is not), you need to use a transaction.

Related

MS Access DB front end SQL Server Back End Unable to Open any form as New Record after table migration

I have similar issue to one previously posted but none of the answers helped.
I cannot open any form in my DB to a new record.
This all worked before moving tables from Access DB to SQL Server and linking back to them.
All tables have an ID column of datatype integer set as primary key on the server.
All tables have a RowVersion column of datatype datestamp.
All forms are based on a single table as record source.
All columns of datatype = bit have default value set to 0 on the SQL Server.
In Access form, properties are set to allow additions = yes, allow edits = yes, I also set data entry = yes as well which wasn't necessary before SQL server hosted the tables but it didn't help anyway.
I tried on button click event that opens the form:
Private Sub OpenFormNameBtn_Click()
DoCmd.OpenForm "FormName", , , "[CustID]=" & Me![CustID] & " AND [EnvID]= '" & Me![EnvID] & "'"
Forms!DeploymentsF.Form.AllowAdditions = False
Forms!DeploymentsF.Form.AllowAdditions = True
DoCmd.GoToRecord , , acNewRec
End Sub
NOTE: the criteria of custid and envId worked when the tables were in access and I need them to stay this way.
The above caused the auto number ID to appear on the form for an instant but then it disappeared just as quickly! In the code behind the submit button at the bottom of the form (to write the record to the table), it errors out and I see where the Form's ID field = Null in debug so it couldn't write the id in the update statement to the table. I CAN, however, insert a record into the table with a query insert statement on the SQL server no problem AND I can edit records in forms and sub forms and it writes to the tables on the SQL Server.
I also tried a simple form with no criteria and no pre-filling and got the error, "Can't go to the specified record"
I tried button click event: DoCmd.OpenForm "FormName", , , , acFormAdd.
Anyone seen this before and have a solution? I'm out of ideas.

T-SQL - SELECT SCOPE_IDENTITY does not work in vba code

I am working on a project which consists transferring a few thousands of Excel rows to a SQL Server (it was also called T-SQL if I'm right?) database. I put together some logic in VBA to shape up the data.
Let me give you some context first about this. The data I'm about to transfer are invoice files. On each row, there are code of the stock items, prices, invoice number, invoice date, name of the client etc. These needs to be transferred to the database of a proprietary ERP system.
There are two tables on the database which I'm interested in for now. First one holds the header data for the invoice (client data, date, invoice number, invoice total etc.). Second table holds the information on the stock items (what has been sold, how many and for how much money etc).
After each insert onto the first table, I have to get the inserted row's primary key, in order to insert rows to the second table, which requires the PK of the first table on each row.
Now, my approach was to use the SCOPE_IDENTITY() function of the T-SQL. When I try to do it on the database directly via SQL Server Management Studio, it works without a hitch.
But when I try to use it in the code, it returns an empty recordset.
Code I'm using is as follows:
Public Function Execute(query As String, Optional is_batch As Boolean = False) As ADODB.Recordset
If conn.State = 0 Then
OpenConnection
End If
Set rs = conn.Execute(query) 'this is the actual query to be executed
Dim identity As ADODB.Recordset 'this rs supposed to hold the PK of inserted row, but returns an empty recordset
Set identity = conn.Execute("SELECT SCOPE_IDENTITY();")
If TypeName(identity.Fields(0).Value) = "Null" Then
pInsertedId = -1
Else
pInsertedId = identity.Fields(0).Value 'I'm saving it in an object variable, to access it easily later on
End If
Set Execute = rs 'to be returned to the caller
'closing the connection is handled outside this procedure
End Function
When I run this on VBA, second query SELECT SCOPE_IDENTITY();just returns an empty recordset. Same query works successfully when ran on the db directly.
Actually I'm able to pull this off by other means. There is a UUID column which I'm supposed to insert to the row in the first table. I can just simply query the table with this UUID and get the PK, but I'm just curious why this won't work.
Any ideas?
Your code doesn't insert any data, so no identity values are generated in the current scope, as defined in the official documentation for SCOPE_IDENTITY():
Returns the last identity value inserted into an identity column in the same scope. A scope is a module: a stored procedure, trigger, function, or batch. Therefore, if two statements are in the same stored procedure, function, or batch, they are in the same scope.
Your code effectively is the same as inserting data in one query window in SSMS and querying SCOPE_IDENTITY() in another query window. Well, this isn't how it works. You must query it in the same scope, i.e. a stored procedure, trigger, function, or batch. Otherwise, use ID values generated by you and insert them with the data.

SSIS - Dynamically loop over multiple databases

I have to consolidate data from from 1000+ databases having the same structure/tables in one unique DB.
DBs may be added and removed potentially on a daily basis so I need to retrieve the list of DBs dynamically and run the dynamically generated SQL query to extract data on each of them.
I designed the Data Flow with a query from a variable that is working fine if executed with a static value:
With a SQL task I get the list of instances, I loop over the them and with a nested Foreach Loop/SQL task I retrieve the database names and create the dynamic SQL with the following statement (DB name is anonymized):
SELECT 'select ''' + name + ''' as DatabaseName, ID from ' + name + '.[dbo].[Orders] as querytext FROM sys.databases WHERE name LIKE ( 'XXX%_%' );
This part is also working fine:
How can I use the result of the SQL task "Execute SQL Task - Get query text" as query to be executed in the Source "OLE DB Source 1" (part of "Data Flow Task 3")?
I tried mapping an Object variable "User::SqlCommandFromSQLTask" in the result set of the SQL task, then set it up as ADO object source variable and with a Script task convert it to string and pass the value to the variable SqlStringFromSQLTask3 (used as source in "OLE DB Source 1") but I get the error Violation of PRIMARY KEY constraint, like if the data flow is always running with a static value I set up as default:
While, if I remove the value from the variable panel, I get the error "Command text was not set for the command object.", even changing the property DelayValidation of the Data Flow to false.
Any help is much appreciated.
When I have used SSIS to connect to multiple SQL Server boxes, I have stored those SQL Server connection strings in a table in a central database. Then I use a query of that table as the input to the foreach loop data flow task. If we ever have to change a sql server connection string, which does happen, we just update that table with the newest value.

Excel - SQL Query - ## Temp Table

I am trying to create a global temp table using the results from one query, which can then be selected as a table and manipulated further several times without having to reprocess the data over and over.
This works perfectly in SQL management studio, but when I try to add the table through an Excel query, the table can be referenced at that time, but it is not created in Temporary Tables in the tempdb database.
I have broken it down into a simple example.
If I run this in SQL management studio, the result of 1 is returned as expected, and the table ##testtable1 is created in Temporary Tables
set nocount on;
select 1 as 'Val1', 2 as 'Val2' into ##testtable1
select Val1 from ##testtable1
I can then run another select on this table, even in a different session, as you'd expect. E.g.
Select Val2 from ##testtable1
If I don't drop ##testtable1, running the below in a query in Excel returns the result of 2 as you'd expect.
Select Val2 from ##testtable1
However, if I run the same Select... into ##testtable1 query directly in Excel, that correctly returns the result of 1, but the temptable is not created.
If I then try to run
Select Val2 from ##testtable1
As a separate query, it errors saying "Invalid object name '##testtable1'
The table is not listed within Temporary Tables in SQL management studio.
It is as if it is performing a drop on the table after the query has finished executing, even though I am not calling a drop.
How can I resolve this?
Read up on global temp tables(GTT). They persist as long as there is a session referencing it. In SSMS, if you close the session that created the GTT prior to using it in another session, the GTT would be discarded. This is what is happening in Excel. Excel creates a connection, executes and disconnects. Since there are no sessions using the GTT when Excel disconnects, the GTT is discarded.
I would highly recommend you create a normal table rather than use a GTT. Because of their temporary nature and dependence on an active session, you may get inconsistent results when using a GTT. If you create a normal table instead, you can be certain it will still exist when you try to use it later.
The code to create/clean the table is pretty simple.
IF OBJECT_ID('db.schema.tablename') IS NOT NULL
TRUNCATE TABLE [tablename]
ELSE
CREATE [tablename]...
GO
You can change the truncate to a delete to clean up a specific set of data and place it at the start of each one of your queries.
is it possible you could use a view? assuming that you are connecting to 5 DBs on the same server can you union the data together in a view:
CREATE VIEW [dbo].[testView]
AS
SELECT *
FROM database1.dbo.myTable
UNION
SELECT *
FROM database2.dbo.myTable
Then in excel:
Data> New Query > From Database > FromSQL Server Database
enter DB server
Select the view from the appropriate DB - done :)
OR call the view however you are doing it (e.g. vba etc.)
equally you could use a stored procedure and call that from VBA .. basically anything that moves more of the complexity to the server side to make your life easier :D
You can absolutely do this. Notice how I'm building a temp table from SQL called 'TmpSql' ...this could be any query you want. Then I set it to recordset 1. Then I create another recordset 2, that goes and gets the temp table data.
Imagine if you were looping on the first cn.Execute where TmpSql is changing.. This allows you to build a Temporary table coming from many sources or changing variables. This is a powerful solution.
cn.open "Provider= ..."
sql = "Select t.* Into #TTable From (" & TmpSql & ") t "
Set rs1 = cn.Execute(sql)
GetTmp = "Select * From #TTable"
rs2.Open GetTmp, cn, adOpenDynamic, adLockBatchOptimistic
If Not rs2.EOF Then Call Sheets("Data").Range("A2").CopyFromRecordset(rs2)
rs2.Close
rs1.Close
cn.Close

Update ADO Recordset for a Calculated Field

I'm using the following sql for an ADO recordset against a SQLServer backend in VB6:
select c.name, taxid=
case when exists(select 1 from sometable where fld='abc')
then c.SSN else null end
When I try to update the taxid field in a row within the recordset locally ADO complains with the error "Multiple-step operation generated errors. Check each status value." I assume it's bothered by the fact that the taxid field is coming from a calculated field and not a raw table column. For my purposes I'm never going to be persisting these changes back to the database so I'm looking for a way to tell ADO that have no intent to persist changes so that it will allow me to change the data locally.
I think that #HK1's suggestion is a good one, though I'm not sure what happens to your ability to alter any of the values in the recordset whether the column you're trying to update is computed or not. It's been a long time since I played with classic ADO but if the recordset is disconnected it may become read only at that point.
But if you have no interest in using the recordset to perform updates, and you need to alter values locally, perhaps you should consider storing the results in a local array first? That way you can minimize the locking and cursor options of the recordset, for example, and immediately close the recordset and free up those resources.
rs.Open cmd, conn, adOpenForwardOnly, adLockReadOnly
Dim MyArray
MyArray = rs.GetRows()
rs.Close: set rs = nothing
Now you can manipulate MyArray however you want...

Resources