How to use Excel VBA to extract Memo field from Access Database? - excel

I have an Excel spreadsheet. I am connecting to an Access database via ODBC. Something along then lines of:
Set dbEng = CreateObject("DAO.DBEngine.40")
Set oWspc = dbEng.CreateWorkspace("ODBCWspc", "", "", dbUseODBC)
Set oConn = oWspc.OpenConnection("Connection", , True, "ODBC;DSN=CLIENTDB;")
Then I use a query and fetch a result set to get some table data.
Set oQuery = oConn.CreateQueryDef("tmpQuery")
oQuery.Sql = "SELECT idField, memoField FROM myTable"
Set oRs = oQuery.OpenRecordset
The problem now arises. My field is a dbMemo because the maximum content length is up to a few hundred chars. It's not that long, and in fact the value I'm reading is only a dozen characters. But Excel just doesn't seem able to handle the Memo field content at all. My code...
ActiveCell = oRs.Fields("memoField")
...gives error Run-time error '3146': ODBC--call failed.
Any suggestions? Can Excel VBA actually get at memo field data? Or is it just completely impossible. I get exactly the same error from GetChunk as well.
ActiveCell = oRs.Fields("memoField").GetChunk(0, 2)
...also gives error Run-time error '3146': ODBC--call failed.
Converting to a text field makes everything work fine. However some data is truncated to 255 characters of course, which means that isn't a workable solution.

Try Range.CopyFromRecordset to see if it works.
Try using CStr(oRs.Fields("memoField")) and assign to Value2 of the Range/ActiveCell.
Try making the memo field the last physical column in table. A memo field is read only when retrieved. There was/may-still-be an issue with memo fields that are not at physically at the end of a table.
All I can think of right now.

Related

Excel table names contain a '$' when pulled from a schema in VB.net

I have a list box that I'm trying to populate in an application to contain Excel tab names or Access tables. It's created by a simple schema grab:
dtSheet = OpenCon.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, New Object() {Nothing, Nothing, Nothing, "TABLE"})
It then gets put into a simple list (of String):
For Each Row In dtSheet.Rows
ListOfSheets.Add(Row("TABLE_NAME").ToString())
Next
This works fine if I open up an Access database, the names of the tables are listed nicely. However, when I use it for Excel, I get symbols such as $ and quotes "'". I'd like to trim this off to just have the tab names but so far I haven't been able to find anything to help my issue specifically.
My suspicion is that the answer is in the {Nothing...."TABLE"} object. I'm a little light on how filters like this work and I was having issues wrapping my head around it after reading the .NET documentation.
Another idea would be to do some post string alteration trimming but I'd like to see if there was an easier way to get the simple string name that I want from the start.
Any help would be appreciated.
Based on the discussion from jmcihinney, I built a slightly better code.
dtSheet = OpenCon.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, New Object() {Nothing, Nothing, Nothing, "TABLE"})
dtSheet.Columns.Add("Table_Text", Type.GetType("System.String"))
For i As Integer = dtSheet.Rows.Count - 1 To 0 Step -1
If Microsoft.VisualBasic.Strings.Right(dtSheet.Rows(i)("TABLE_NAME"), 1) <> "$" Then
dtSheet.Rows.RemoveAt(i)
Else
dtSheet.Rows(i)("TABLE_Text") = Replace(dtSheet.Rows(i)("TABLE_NAME"), "$", "")
End If
Next
I essentially add a column to the datatable that is built from the list pull. To remove anything that isn't a table (e.g. named ranges, Sheet views) I check the Table_Name column for the suffix of "$". Note I go backwards to keep from messing with any indexing.
Then in the Else statement I put a "Replace" to make the table_text a "$"less version of Table_Name
That way I can then build my listbox like this:
Me.lbTableList.DataSource = dtsheet
Me.lbTableList.DisplayMember = "TABLE_Text"
Me.lbTableList.ValueMember = "TABLE_NAME"
This makes my table look nice regardless if I loaded an Access database or Excel file. This also allows me to just pass the TABLE_NAME to the connection command and not worry whether there is a "$" in the name or not:
DBCmd.CommandText = "SELECT * FROM [" & strTable & "]"

OLEDB query to fetch all column except few exceptional columns

I'm trying to read excel data into datatable object.But, some columns in excel is has corrupted data specifically of type DATE .
I'm using VB code to read the data with
OLEDB Connection
dtData = objExcelReader.ReadWorksheet(strFilePath.ToString, "select * from sheet1$", True)
With this code I get 'Not a legal OleAut date' error.
With ExcelReader,
objExcelReader = New ExcelIO.ExcelReader
objExcelReader.UseHeaders = True
objExcelReader.AllowSpacesInColumnName = blnAllowSpacesInColumnName
objExcelReader.ReadFormatting = True
objExcelReader.TrimStrings = True
objExcelReader.ClearColumnDatatypes()
objExcelReader.DefaultStartRow = intStartRow
dtData = objExcelReader.Read(strFilePath.ToString, thisApplication, strInputSheetName)
I get exception - 'Out of present range. (Exception from HRESULT: 0x8002000A (DISP_E_OVERFLOW))'
Using range in query, I'm able to fetch records. I need to include multiple range in that case.
dtData = objExcelReader.ReadWorksheet(strFilePath.ToString, "select * from sheet1$O:AK", True)
Can someone help me to include multiple range here like A:K & O:AK
Or to fix any of above mentioned issues and ignore those exceptional column.
Try not to put column containing dates in the first column of table.
Sort data in that way that
data column has data (with proper data format) in the fist row.
See more on corrupt data formats here:
https://dutchgemini.wordpress.com/2011/04/21/two-serious-flaws-with-excels-copyfromrecordset-method/
Check this out how I have overcome corrupt formatting in open recordset.
https://stackoverflow.com/a/33807584/1903793

How can I delete an object which is the subject of a loop?

The problem I have is that the user is copying from one content control and pasting it into another accidentally. When extracting the data from this form, it then picks up that extra CC and therefore the value twice over.
When pulling the data I'm trying to see if a CC has a ParentCC and then delete it, but I keep getting
Run time error 5825: Object has been deleted.
I can understand why but I'm unsure as to how get around it, nothing I've searched seems to work.
'With Word document Statement precedes this
For Each CCtrl In .ContentControls
CCtrlText = CCtrl.Range.Text
If Not CCtrl.ParentContentControl Is Nothing Then
CCtrl.ParentContentControl.Range.Text = CCtrlText
CCtrl.Delete
End If
Next
How can I remove this content control which is duplicated inside the other and retain the input information?
So after some messing around and looking into how the local variables properties changed as a stepped through the code I have found that the line:
CCtrl.ParentContentControl.Range.Text = CCtrlText
Was in effect replacing the Content Control (CC) in it's ParentCC range property with the input text, and therefore deleting the duplicated CC.
CCtrl.Delete was trying to delete an object that had already been deleted and that swhy it was throwing an error.
I think with a foreach loop you can't alter the contents of the list/array without impacting the function of the loop. If you instead use the indexers, it should allow you to alter the collection, since you are not impacting the loop (number to number):
Dim i As Integer
Dim c As ContentControl
For i = 1 To d.ContentControls.Count
Set c = d.ContentControls(i)
c.Delete
Next i

Pivot Tables - VBA

I want to select some values through VBA in Pivot Table which is linked to OLAP Cube.
As I know such modification can be realised by typing:
ActiveSheet.PivotTables("PivotTable1").PivotFields("[parameter].[parameter]").VisibleItemsList = Array("value1","value2","value3")
Since get list of parameters from cells in Excel sheet, I wrote simple function which - In mentioned example - returns:
""value1","value2","value3""
I can't use such string as parameter for Array function (as it recognize it as one string), so I've tried to convert it to Array of Variant, typing above code:
Dim tableVar() As Variant
myVar = Replace(myVar, Chr(34), "")
myVar = Split(myVar, ",")
lowerB =LBound(myVar)
upperB = UBound(myVar)
ReDim tablica(lowerB To upperB)
For i = lowerB To upperB
tableVar(i) = myVar(i)
Next i
Unfortunately it changes nothing - when I'm calling:
ActiveSheet.PivotTables("PivotTable1").PivotFields("[parameter].[parameter]").VisibleItemsList = tableVar
I'm still receiving an error message.
Could you help me, please?
you have a typo in your code, daty should say myVar.
(Either that or we're missing more details)
Stupid thing, but error message is simply correct - there's no such items in Cube:
Run-time error '1004': The item could not be found in the OLAP Cube
I gave incorrect parameter here:
ActiveSheet.PivotTables("PivotTable1").PivotFields("[parameter].[parameter]").VisibleItemsList = tableVar
My code was unnecessary complicated - sorry for wasting your time.
Now my problem will be - how to check if specific dimensions or whole Cube exist...
Thanks once more for help.

Can I import INTO excel from a data source without iteration?

Currently I have an application that takes information from a SQLite database and puts it to Excel. However, I'm having to take each DataRow, iterate through each item, and put each value into it's own cell and determine highlighting. What this is causing is 20 minutes to export a 9000 record file into Excel. I'm sure it can be done quicker than that. My thoughts are that I could use a data source to fill the Excel Range and then use the column headers and row numbers to format only those rows that need to be formatted. However, when I look online, no matter what I seem to type, it always shows examples of using Excel as a database, nothing about importing into excel. Unless I'm forgetting a key word or to. Now, this function has to be done in code as it's part of a bigger application. Otherwise I would just have Excel connect to the DB and pull the information itself. Unfortunately that's not the case. Any information that could assist me in quick loading an excel sheet would be appreciated. Thanks.Additional Information:Another reason why the pulling of the information from the DB has to be done in code is that not every computer this is loaded on will have Excel on it. The person using the application may simply be told to export the data and email it to their supervisor. The setup app includes the needed dlls for the application to make the proper format.Example Code (Current):
For Each strTemp In strColumns
excelRange = worksheet.Cells(1, nCounter)
excelRange.Select()
excelRange.Value2 = strTemp
excelRange.Interior.Color = System.Drawing.Color.Gray.ToArgb()
excelRange.BorderAround(Excel.XlLineStyle.xlContinuous, Excel.XlBorderWeight.xlThin, Excel.XlColorIndex.xlColorIndexAutomatic, Type.Missing)
nCounter += 1
Next
Now, this is only example code in terms of the iteration I'm doing. Where I'm really processing the information from the database I'm iterating through a dataTable's Rows, then iterating through the items in the dataRow and doing essentially the same as above; value by value, selecting the range and putting the value in the cell, formatting the cell if it's part of a report (not always gray), and moving onto the next set of data. What I'd like to do is put all of the data in the excel sheet (A2:??, not a row, but multiple rows) then iterate through the reports and format each row then. That way, the only time I iterate through all of the records is when every record is part of a report.
Ideal Code
excelRange = worksheet.Cells("A2", "P9000")
excelRange.DataSource = ds 'ds would be a queried dataSet, and I know there is no excelRange.DataSource.
'Iteration code to format cells
Update:
I know my examples were in VB, but it's because I was also trying to write a VB version of the application since my boss prefers VB. However, here's my final code using a Recordset. The ConvertToRecordset function was obtained from here.
private void CreatePartSheet(Excel.Worksheet excelWorksheet)
{
_dataFactory.RevertDatabase();
excelWorksheet.Name = "Part Sheet";
string[] strColumns = Constants.strExcelPartHeaders;
CreateSheetHeader(excelWorksheet, strColumns);
System.Drawing.Color clrPink = System.Drawing.Color.FromArgb(203, 192, 255);
System.Drawing.Color clrGreen = System.Drawing.Color.FromArgb(100, 225, 137);
string[] strValuesAndTitles = {/*...Column Names...*/};
List<string> lstColumns = strValuesAndTitles.ToList<string>();
System.Data.DataSet ds = _dataFactory.GetDataSet(Queries.strExport);
ADODB.Recordset rs = ConvertToRecordset(ds.Tables[0]);
excelRange = excelWorksheet.get_Range("A2", "ZZ" + rs.RecordCount.ToString());
excelRange.Cells.CopyFromRecordset(rs, rs.RecordCount, rs.Fields.Count);
int nFieldCount = rs.Fields.Count;
for (int nCounter = 0; nCounter < rs.RecordCount; nCounter++)
{
int nRowCounter = nCounter + 2;
List<ReportRecord> rrPartReports = _lstReports.FindAll(rr => rr.PartID == nCounter).ToList<ReportRecord>();
excelRange = (Excel.Range)excelWorksheet.get_Range("A" + nRowCounter.ToString(), "K" + nRowCounter.ToString());
excelRange.Select();
excelRange.NumberFormat = "#";
if (rrPartReports.Count > 0)
{
excelRange.Interior.Color = System.Drawing.Color.FromArgb(230, 216, 173).ToArgb(); //Light Blue
foreach (ReportRecord rr in rrPartReports)
{
if (lstColumns.Contains(rr.Title))
{
excelRange = (Excel.Range)excelWorksheet.Cells[nRowCounter, lstColumns.IndexOf(rr.Title) + 1];
excelRange.Interior.Color = rr.Description.ToUpper().Contains("TAG") ? clrGreen.ToArgb() : clrPink.ToArgb();
if (rr.Description.ToUpper().Contains("TAG"))
{
rs.Find("PART_ID=" + (nCounter + 1).ToString(), 0, ADODB.SearchDirectionEnum.adSearchForward, "");
excelRange.AddComment(Environment.UserName + ": " + _dataFactory.GetTaggedPartPrevValue(rs.Fields["POSITION"].Value.ToString(), rr.Title));
}
}
}
}
if (nRowCounter++ % 500 == 0)
{
progress.ProgressComplete = ((double)nRowCounter / (double)rs.RecordCount) * (double)100;
Notify();
}
}
rs.Close();
excelWorksheet.Columns.AutoFit();
progress.Message = "Done Exporting to Excel";
Notify();
_dataFactory.RestoreDatabase();
}
Can you use ODBC?
''http://www.ch-werner.de/sqliteodbc/
dbName = "c:\docs\test"
scn = "DRIVER=SQLite3 ODBC Driver;Database=" & dbName _
& ";LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;"
Set cn = CreateObject("ADODB.Connection")
cn.Open scn
Set rs = CreateObject("ADODB.Recordset")
rs.Open "select * from test", cn
Worksheets("Sheet3").Cells(2, 1).CopyFromRecordset rs
BTW, Excel is quite happy with HTML and internal style sheets.
I have used the Excel XML file format in the past to write directly to an output file or stream. It may not be appropriate for your application, but writing XML is much faster and bypasses the overhead of interacting with the Excel Application. Check out this Introduction to Excel XML post.
Update:
There are also a number of libraries (free and commercial) which can make creating excel document easier for example excellibrary which doesn't support the new format yet. There are others mentioned in the answers to Create Excel (.XLS and .XLSX) file from C#
Excel has the facility to write all the data from a ADO or DAO recordset in a single operation using the CopyFromRecordset method.
Code snippet:
Sheets("Sheet1").Range("A1").CopyFromRecordset rst
I'd normally recommend using Excel to pull in the data from SQLite. Use Excel's "Other Data Sources". You could then choose your OLE DB provider, use a connection string, what-have-you.
It sounds, however, that the real value of your code is the formatting of the cells, rather than the transfer of data.
Perhaps refactor the process to:
have Excel import the data
use your code to open the Excel spreadsheet, and apply formatting
I'm not sure if that is an appropriate set of processes for you, but perhaps something to consider?
Try this out:
http://office.microsoft.com/en-au/excel-help/use-microsoft-query-to-retrieve-external-data-HA010099664.aspx
Perhaps post some code, and we might be able to track down any issues.
I'd consider this chain of events:
query the SQLite database for your dataset.
move the data out of ADO.NET objects, and into POCO objects. Stop using DataTables/Rows.
use For Each to insert into Excel.

Resources