have used Stack Overflow as a resource hundreds of times, but my first time posting a question for some help!
I've got a table in SQL Server 2005 which contains 4 nVarChar(Max) fields.
I'm trying to pull out the data from an Access (2010) VBA Module using ADO 2.8
I'm connecting using SQL driver SQLNCLI10
(I can't use a linked table, as the 'table' I will ultimately be querying is a Table-Valued Function)
When I then print / use the recordset, the data is getting jumbled and concatenated with other fields in the same record - with a bunch of obscure characters thrown in.
The VBA: (various other methods were tried with the same result)
Sub TestWithoutCasting()
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim i As Integer
cn.Open "Data Source=ART;DataTypeCompatibility=80;MARS Connection=True;"
Set rs = cn.Execute("SELECT * FROM JobDetail WHERE JobID = 2558 ORDER BY SeqNo ASC")
Do While Not rs.EOF
For i = 1 To rs.Fields.Count
Debug.Print rs.Fields(i).Name & ": " & rs.Fields(i).Value
Next i
rs.MoveNext
Loop
End Sub
Example Output:
SeqNo: 1
CommandID: 2
Parameter1: 2 Daily Report é [& some other chars not showing on here]
Parameter2: [Null]
Parameter3: [Null]
Parameter4: [Null]
Description: Daily Report
Active: False
Expected Output:
SeqNo: 1
CommandID: 2
Parameter1: SELECT Day_Number ,Day_Text ,Channel_Group_ID [...etc]
Parameter2: [Null]
Parameter3: [Null]
Parameter4: [Null]
Description: Daily Report
Active: False
So, it's grabbing bits of data from other fields instead of the correct data (in this case, it's an SQL statement)
I then tried casting the nvarchar(max) fields as text at source
View Created:
CREATE VIEW TestWithCast
AS
SELECT jd.JobID, jd.SeqNo, jd.CommandID
,cast(jd.Parameter1 as text) as Parameter1
,cast(jd.Parameter2 as text) as Parameter2
,cast(jd.Parameter3 as text) as Parameter3
,cast(jd.Parameter4 as text) as Parameter4
,jd.[Description]
,jd.Active
FROM JobDetail jd
Now, I initially had some luck here - using the same code as above does bring back data - but when I use this code in my main code (which jumps in & out of other procedures); as soon as I've queried the first result of the recordset, it appears to wipe the rest of the records / fields, setting them to Null. I also tried setting the value of each field to a variable whilst the rest of the vba runs before getting the next record - but this doesn't help either.
It almost feels like I need to dump the recordset into a local Access table, and query from there - which is a bazaar workaround for what is already a workaround (by casting as text).
I there something I'm completely missing here, or do I indeed need to cast as text and load to a local table?
Thanks for any help - it's driving me mad!
ps. Hope I've given the right level of detail / info - please let me know if I missed anything key.
EDIT:
Yikes, I think I've done it / found the issue...
I changed the driver to SQLSRV32 (v6.01) - and seems to work fine directly against the text casted field.
So... why would it work with an older driver but not the newer 'recommended' (by various sources I read) as the one to use.
And... will there be a significant drawback in using this over the native client?
EDIT 2:
Ok, I've tried a few drivers on a few machines, in each case with both the TEXT CASTING and Directly to VARCHAR MAX..
[On my windows 7 machine w/ SQLSMS 2008]
SQL Native Client 10.0 - Neither method works reliably with this driver
SQL Server 6.01 - BOTH methods appear to work reliably - further testing needed though
[On our production server w/ SQLS 2005]
SQL Native Client (v2005.90) - Does not work at all with varchar(max), but DOES work with text casting
SQL Server (v2008.86) - BOTH methods appear to work reliably - further testing needed though
This should make deployment interesting!
It's not a real answer, because I did not test it, but ... You are using a "DataTypeCompatibility=80" parameter in your connection. As far as I know, DataTypeCompatibility=80 refers to SQL Server 2000, where the nvarchar(max) field type was still not implemented.
I had the same problem, solved it by converting the field to an nvarchar(1000). Would be an easy, compatible solution for your problem if 1000 chars is enough.
Related
I'm trying to retrieve specific data from SQL Server via ADODB.recordset connection, and certain records are not found for some reason, (while they are found when running the same query in SQL SERVER itself) and I can't figure out why.
My code:
Dim adset As Object, cnct As Object
Set adset = CreateObject("ADODB.recordset")
Set cnct = thiscnn
adset.Open "SELECT prt FROM prt WHERE prt = 2204018", cnct, 1
While in other module:
Public thiscnn As Object
...
Set thiscnn = CreateObject("ADODB.Connection")
thiscnn.Open "DSN=...;Description=...;DATABASE=...;;UID=...;PWD=..."
thiscnn.CommandTimeout = 30
I'm talking here about code working well for years.
The thought that such a simple and useful part of my program which is in vast use, doesn't fetch the data loyally, drives me nuts. I need to give my customers proper, real and true information. I can't have such code sometimes returning and sometimes not returning data.
I know other ways of fetching data, but it is very important for me to understand why this simple way doesn't work all of a sudden in specific cases. It's so peculiar.
I would be greatful for any clue as to what could be happening in my case.
I tried fetching the same query in SQL SERVER itself, and worked fine - but that's the problem, it works in SQL SERVER, but not in VBA with this code above.
I tried looking for differences between records getting fetched and those which don't, and didn't see any special difference.
I tried refetching the same record a few times, but the fetching status for those specific records which don't get fetched (via ADODB.connection / recordset) stays the same.
I tried fetching the same records using the BETWEEN operator rather than the equals (=) operator, yet with no success.
I tried ordering by id column (prt), perhaps the problem is from certain id and up.
I tried ordering by the timestamp column thinking maybe from certain timestamp we have a problem - but that didn't give me any clue.
I thought to cast timestamp to date, but I found out that it's only rowversion and doesn't preserve a date or time.
I tried ordering by row size using this, thinking maybe certain ro size is to much for some odd reason, but that didn't help either.
Just a guess, but can it be some dirty data that isn't persisted?
Try selecting with nolock:
SELECT prt FROM prt (NOLOCK) WHERE prt = 2204018
And perhaps change cursor type:
adset.Open "SELECT prt FROM prt WHERE prt = 2204018", cnct, 2
Or you are looking into wrong database. Usually these errors are something stupid :D
until few days ago everything was working fine and then I run into this problem.
I wrote some code for Access vba in Microsoft 365 that run SQL query on some local and connected tables.
One of this connected table has a field set as data type Number - Decimal. As I mention, few days ago this field start to return empty string. This are few steps I try to investigate the problem.
I made a local copy of the connected table to make sure the problem was not coming from outside. No difference
I create a simple query access - SELECT * FROM [NameTable] and all the data were there
I run the same query in vba and the field in question return an empty string
I run the access query within vba
Set qdfNew = dbs.QueryDefs("Pippo")
Set RS = qdfNew.OpenRecordset
If Not (RS.EOF And RS.BOF) Then
RS.MoveLast
RS.MoveFirst
For iCurRec = 0 To RS.RecordCount - 1
Debug.Print RS.Fields("HSL_QUANTITA").Value
RS.MoveNext
Next iCurRec
End If
RS.Close
It returns an empty string
5. I change the data type of the table into Number - Integer, Long, Single and Double and in all these cases the query in vba return correct value
6. I modify the code in this way
Set qdfNew = dbs.QueryDefs("Pippo")
Set RS = qdfNew.OpenRecordset
If Not (RS.EOF And RS.BOF) Then
RS.MoveLast
RS.MoveFirst
For iCurRec = 0 To RS.RecordCount - 1
Debug.Print TypeName(RS.Fields("HSL_QUANTITA").Value)
RS.MoveNext
Next iCurRec
End If
RS.Close
While changing the data type the code return in the immediate windows:
String -> Number-Decimal
Single -> Number-Single precision
Double -> Number-Double precision
Integer -> Number-Integer
Long -> Number-Long
It looks like since few days ago vba cannot convert the decimal to a String anymore
I do not own the connected table hence I cannot change the data type.
I try to report the problem to the the Office help desk but they cannot solve the problem since is vba related.
Any suggestion?
Thanks
You have been hit by a recent bug:
Access VBA/DAO code may crash or report incorrect data for Decimal columns
Notice the included link for a temporary work-around.
I've created an application that uses the following logic to query data from SQL to my MS Access App.
Using an ODBC connection I execute a stored procedure
Using This is assigned as a Pass-Through Query to pull the data locally.
It looks something like this:
strSQL = "EXEC StoredProcedure " & Variable & "
Call ChangeQueryDef("qryPassThrough", strSQL)
Call SQLPassThrough(strQDFName:="qryPassThrough", _
strSQL:=strSQL, strConnect:=gODBCConn)
Me.frmDataSheet.Form.RecordSource = "qryPassThrough"
But, recently we have upgraded our SQL Server to 2016 using a high availability failover system - hence our connection string has changed to connect to a listener like so:
gODBCConn = "ODBC;Driver= {SQL Server Native Client 11.0};Trusted_Connection=Yes;Regional=Yes;Database=" & varDB & ";MultiSubnetFailover=Yes;IntegratedSecurity=SSPI;Server=tcp:SERVER_LISTENER,1433;"
However, it looks like using SQL Server Native Client in the connection string is not the same as what we originally had which was SQL Server. Certain data types have changed and do not work in Access.
Is there a better way for me to query data from SQL and persist/display this data in access using ADO or an alternative method?
EDIT Based on Comment:
The issue I'm having is that I have tables in SQL using the data type: Decimal(12,2). With some testing and experimenting this seems to fail when using an ODBC pass-through query. But changing the data type to Float seems to work fine. Then there are other datatypes which seem to error too which I've not managed to find yet. It just seems there are a few difference which I'm not aware of and I'm keen to find a better way to load data into my Access App.
EDIT 2
This is the error message I get relating to the data type issue.
Sounds like you're not really interested in making the underlying data structure compatible with Access, so:
How to load an ADODB recordset into a datasheet form
Create the form
First, create a datasheet form. For this example, we're going to name our form frmDynDS. Populate the form with 256 text boxes, named Text0 to Text255. To populate the form with the text boxes, you can use the following helper function while the form is in design view:
Public Sub DynDsPopulateControls()
Dim i As Long
Dim myCtl As Control
For i = 0 To 255
Set myCtl = Application.CreateControl("frmDynDS", acTextBox, acDetail)
myCtl.NAME = "Text" & i
Next i
End Sub
VBA to bind a recordset to the form
First, we're going to allow the form to persist, by allowing it to reference itself:
(all on in the code module for frmDynDS)
Public Myself As Object
Then, we're going to add VBA to make it load a recordset. I'm using Object instead of ADODB.Recordset to allow it to both take DAO and ADODB recordsets.
Public Sub LoadRS(myRS As Object)
Dim i As Long
Dim myTextbox As textbox
Dim fld As Object
i = 0
With myRS
For Each fld In myRS.Fields
Set myTextbox = Me.Controls("Text" & i)
myTextbox.Properties("DatasheetCaption").Value = fld.NAME
myTextbox.ControlSource = fld.NAME
myTextbox.ColumnHidden = False
i = i + 1
Next fld
End With
For i = i To 255
Set myTextbox = Me.Controls("Text" & i)
myTextbox.ColumnHidden = True
Next i
Set Me.Recordset = myRS
End Sub
Use the form
(all in the module of the form using frmDynDS)
As an independent datasheet form
Dim frmDS As New Form_frmDynDS
frmDS.Caption = "My ADO Recordset"
frmDS.LoadRS MyAdoRS 'Where MyAdoRS is an open ADODB recordset
Set frmDS.Myself = frmDS
frmDS.Visible = True
frmDS.SetFocus
Note that you're allowed to have multiple instances of this form open, each bound to different recordsets.
As a subform (leave the subform control unbound)
Me.MySubformControl.SourceObject = "frmDynDS"
Me.MySubformControl.Form.LoadRS MyAdoRS 'Where MyAdoRS is an open ADODB recordset
Warning: Access uses the command text when sorting and filtering the datasheet form. If it contains a syntax error for Access (because it's T-SQL), you will get an error when trying to sort/filter. However, when the syntax is valid, but the SQL can't be executed (for example, because you're using parameters, which are no longer available), then Access will hard crash, losing any unsaved changes and possibly corrupting your database. Even if you disable sorting/filtering, you can still trigger the hard crash when attempting to sort. You can use comments in your SQL to invalidate the syntax, avoiding these crashes.
You previously used the pretty ancient, original ODBC Driver for SQL Server simply named SQL Server. You made the right decision to use a newer driver to support your failover cluster. But I would not recommend to use SQL Server Native Client. Microsoft says, It is not recommended to use this driver for new development.
Instead I would use the Microsoft ODBC Driver 13.1 for SQL Server. This is the most recent and recommended (by Microsoft) ODBC Driver for SQL Server.
Your main issue seems to be a translation issue between Access and SQL Server via the ODBC layer. So, using the more modern driver might very well make this problem go away. - I do not know if it solves your problem, but this is the very first thing I would try.
I don't normally program in MS Access VBA so forgive my question if it's stupid.
So I'm using MS Access 2010 as a front end and SQL Server 2014 as a backend. (I don't have a choice in frontend interface so please no suggestions on alternate options).
I'd like to programatically link SQL server's backend to my MS Access frontend. I read here at DJ Steele's DSN-less connection page that I can use the code he provided here to make a DSN-less connection to SQL server as a backend.
So I copied that into a VBA Access module and opened another module and ran this code to run the DJ Steele code in an attempt to connect to a small SQL Server database I made:
Option Compare Database
Sub runThis()
FixConnections "AServerNameHere", "MS_Access_BackEnd_Test"
End Sub
As far as I can tell from the VBA debugger it gets to
Set dbCurrent = DBEngine.Workspaces(0).Databases(0)
then that value seems to be empty. I'm not sure how else to proceed with this since as far as I was able to find this is one of the few full examples of a DSN-less connection I could find.
I'd like to not use the DSN method of linking a SQL server to a database since that would require me to go visit people and their computers in order to make the links. (And who'd want to to that? LOL)
I've also looked at similar questions that were linked to me while writing this question and this was close to what I wanted, but it kept giving me "Compile error: Constant expression required" for input of:
LinkTable "MS_Access_BackEnd_Test", "Table_1"
and
LinkTable "MS_Access_BackEnd_Test", "Table_1", , "AServerNameHere"
Again I'm not familiar with MS Access VBA so forgive the question if it's lame.
Looking at DJ Steele's code, I got it working apart from the line
' Unfortunately, I'm current unable to test this code,
' but I've been told trying this line of code is failing for most people...
' If it doesn't work for you, just leave it out.
tdfCurrent.Attributes = typNewTables(intLoop).Attributes
which I had to comment out.
Using Set dbCurrent = CurrentDb() does essentially the same thing as Set dbCurrent = DBEngine.Workspaces(0).Databases(0) but the latter is meant to be a lot faster ... which these days means it takes 10 microseconds instead of 100 :-o
You still need dbCurrent as a reference to the current Access front end which is where the linked table objects live, even if the data is coming from elsewhere.
Edit: working for me
I added a debug.print line to monitor what's going on
' Build a list of all of the connected TableDefs and
' the tables to which they're connected.
For Each tdfCurrent In dbCurrent.TableDefs
Debug.Print tdfCurrent.Name, tdfCurrent.Connect
If Len(tdfCurrent.Connect) > 0 Then
If UCase$(Left$(tdfCurrent.Connect, 5)) = "ODBC;" Then
...
and then
? currentdb().TableDefs("dbo_Person").Connect
ODBC;DSN=TacsData;APP=Microsoft Office 2003;WSID=TESTXP;DATABASE=TacsData;Trusted_Connection=Yes;QuotedId=No
FixConnections "TESTXP\SQLEXPRESS", "TacsData"
MSysAccessObjects
MSysAccessXML
...
MSysRelationships
Table1
dbo_Person ODBC;DSN=TacsData;APP=Microsoft Office 2003;WSID=TESTXP;DATABASE=TacsData;Trusted_Connection=Yes;QuotedId=No
? currentdb().TableDefs("dbo_Person").Connect
ODBC;DRIVER=sql server;SERVER=TESTXP\SQLEXPRESS;APP=Microsoft Office 2003;WSID=TESTXP;DATABASE=TacsData;Trusted_Connection=Yes
Warning: Classic ASP Ahead. :)
I'm working on a legacy Classic ASP application and I'm running into an oddity with an ADODB.Recordset object.
I have a SQL2012 database table containing a particular field. Let's call it AnnoyingField. Its datatype in SQL is 'date'.
The ASP opens an ADODB.Recordset with a SELECT on the table to collect the fields, then does some looping to do its work:
For each Field in rs.Fields
typeid = rs(Field.Name).Type
'do stuff based on type
For some reason, the Type for AnnoyingField is coming back as 202 (nvarchar) rather than one of the expected types for date (133 or even 7). This is causing some issues further in the code.
I tested with another field of 'datetime' type and the Recordset code returned the expected Type for a datetime field.. 135.
Anyone have an idea why the 'date' fields are returning as an nvarchar?
Changing the database fields from date to datetime in this case might not be possible, even though it might be the logical path to take to get expected data.
Date fields are newer than your version of ADODB. So it doesn't understand what it's getting.
You may be able to cheer it up by using
select convert(datetime, AnnoyingField) from CrazyFuturisticTable
You may also get the correct result if you upgrade your ADODB version to 2.8 and/or connect using the SQL Native Client. Obviously, I haven't tried this, because I live in 2014.
If you keep it a little simpler, cut ADO out of the picture and use isdate and "convert date" and vartype.
For each Field in rs.Fields
Field=rs(Field.Name)
if isdate(Field) then Field=cdate(Field) ' just in case
typeid = VarType(Field)
'do stuff based on type
Vartype
http://www.w3schools.com/vbscript/func_vartype.asp
ADO .type can report more "types"
http://www.w3schools.com/ado/prop_para_type.asp
But I think what vartype can offer should help people in 95% of cases.
Word of warning with cdate, depending on what the server locale is set too or your Session.LCID (in your code) is set will determine what format the date will be formatted to. Shouldn't be a problem for most people but obviously test to see if you get the expected result.
I am using Classic ASP / VBScript and have found that for SQL2012 using SQL Native driver ("Provider=SQLNCLI11"), and the ADO module provided by Windows (presumably Vista or newer [mine is Windows Servr2012 R2 - the version [MyDbConn.version] shows as 6.3) this now works again.
I had been looking for / expecting the Field.Type to be adDate [7], but am actually getting adDBDate [133]