Creating a Query from an Excel file

by Kyle Hayes on October 12th, 2006

Today at work I had the need to retrieve the data from a sheet in a Microsoft Excel file. ColdFusion does not provide this functionality natively. Before reading on, please note that to use the below syntax, you must be allowed to create Java objects on your ColdFusion server. If this has been restricted because you are on a shared host, than this solution will not work for you.

I have created a ready-made function that will take care of the dirty work associated with getting a query from an Excel file. All you have to do is provide the relative file name of the Excel file you would like to read, and the name of a sheet within the Excel file that you would eventually like to query.






var c = "";
var stmnt = "";
var rs = "";
var sql = "Select * from [#sheetName#$]";
var myQuery = "";
arguments.filename = expandPath(arguments.filename);

if(len(trim(arguments.filename)) and fileExists(arguments.filename))
{
try
{
CreateObject("java","java.lang.Class").forName("sun.jdbc.odbc.JdbcOdbcDriver");
c = CreateObject("java","java.sql.DriverManager").getConnection("jdbc:odbc:Driver={Microsoft Excel Driver (*.xls)};DBQ=" & arguments.filename );
stmnt = c.createStatement();
rs = stmnt.executeQuery(sql);
myQuery = CreateObject('java','coldfusion.sql.QueryTable').init(rs);
}
catch(any e)
{
// error-handling code }
}
}
return myQuery;


To execute the above code, you would simply call the function with the appropriate information as so:


myQuery = getExcelSheet("/mydocs/foobar.xls","customers");

As you can see I had to delve into create a few Java objects. The JDBC ODBC driver has support for reading Excel files which is what makes this all possible. The rest is fairly self-explainable. If you need further explanation, feel free to post a comment.

Popular Posts

From Uncategorized

37 Comments
  1. macr0hard permalink

    Can you please advise how I can query it without using a
    sheetname, assuming there’s only 1 sheet in the file

    The problem I’m having is with the sheetname, it doesnt seem
    to take names with underscores and my file is generated
    automatically and its important to have underscores in the
    filename. However, excel just takes the filename and use that as
    the sheetname

  2. macr0hard permalink

    It was working yesterday but now everytime I try it gives me
    this error

    The value returned from function getExcelSheet() is not of type
    query.

    I havent changed anything!

  3. I will have to look to see if you can access a page via an index or something like that. As for your getExcelSheet() is not of type query, do you want to post or send me an email of your code. You can catch me online for

    Gtalk: mrkylehayes@gmail.com
    AIM: mrkylehayes
    yahoo: mrkylehayes@yahoo.com
    msn: cfwise@hotmail.com

  4. macr0hard permalink

    It works for some files and not others, i think it might depend on how the file is generated

    In order to fix the problem with the sheet name, I use the following code, and I have that error

    <cfoutput>
    <html xmlns:x="urn:schemas-microsoft-com:office:excel">
    <head>
    <!–[if gte mso 9]><xml>
    <x:ExcelWorkbook>
    <x:ExcelWorksheets>
    <x:ExcelWorksheet>
    <x:Name>#basename#1#DateFormat(today,"ddmmyyyy")#</x:Name>
    <x:WorksheetOptions>
    <x:Print>
    <x:ValidPrinterInfo/>
    </x:Print>
    </x:WorksheetOptions>
    </x:ExcelWorksheet>
    </x:ExcelWorksheets>
    </x:ExcelWorkbook>
    </xml><![endif]–>
    <body>
    <table>
    <tr>
    <cfloop list = "#qImages.columnList#" index="x">
    <td>#x#</td>
    </cfloop>
    </tr>
    <cfloop query="qImages">
    <tr>
    <cfloop list="#qImages.columnList#" index="i">
    <td>#Evaluate(i)#</td>
    </cfloop>
    </tr>
    </cfloop>
    </table>
    </body>
    </head>
    </html>
    </cfoutput>

    However, if i use the following code to generate, then most of the time it works fine

    <cfoutput>
    <cfset tabChar = chr(9)>
    <cfset newLine = chr(13) & chr(10)>
    <cfset sheet_headings=”>
    <cfloop list = "#qImages.columnList#" index="x">
    <cfset sheet_headings = listAppend(sheet_headings,x,’,')>
    </cfloop>
    #replace(sheet_headings,’,',tabchar,’all’)##newline#
    <cfloop query="qImages">
    <cfloop list="#qImages.columnList#" index="i">#evaluate(i)##iif(i eq listlast(qImages.columnList,’,'),de(newline),de(tabchar))#</cfloop>
    </cfloop>
    </cfoutput>

  5. macr0hard permalink

    I just want to add one more thing that if I open the generated file in excel, then resave it as an actual excel file then it works, so its gotta be something to do with how excel sees the file

  6. I see what you are saying. This code is not perfected and was only to be used from binary files that Excel generated. I never even thought about people generating their own from ColdFusion. That is very interesting.

    I developed it because my customer needed to be able to upload an Excel spreadsheet to the server, that had a list of employee id numbers and add them to a database. Let me know if you have any suggestions.

  7. Clyde permalink

    I’ve used CFX_Excel2Query with a great deal of success. It’s fast and get’s the job done.

    http://www.emerle.net/programming/display.cfm/t/cfx_excel2query

  8. Ah yes, but it does cost. I needed a free solution. But thanks for the link.

  9. You rock man! This thing saved my butt –
    I attempting to get an OPENROWSET call working in a secured environment and learned the hard way – that accessing and getting file permissions to do such a task was no small feat w/o lowering security.

    This will do it just fine!!

    Thank you

  10. Hey Kevin! I am glad I was able to help. I can only imagine everything that you must have gone through on the other route.

  11. C.P. permalink

    Hey Kyle – Thanks so much for posting this. I’d been trying to use a System DSN to access my excel file and that was causing some problems. This works great and I appreciate your posting it.

  12. Hey CP, I am glad I could help. It was quite useful for me as well!

  13. dceera permalink

    Hopefully someone is still reding this,as I would really like to use this function. I copied this exactly, but I get the error message:

    Context validation error for tag cfscript.
    The start tag must have a matching end tag. An explicit end tag can be provided by adding </cfscript>. If the body of the tag is empty you can use the shortcut <cfscript …/>.
    The CFML compiler was processing:

    a cfscript tag beginning on line 14, column 5.
    a cfscript tag beginning on line 14, column 5.

    ___
    ANy help would be appreciated. Thanks!

  14. I would double / triple check that your parenthesis and brackets are matching each other/closed –

    Looks like a syntax error in the cfscript.

  15. Are you still having issues? I double-checked the code that I posted above and it all seems good.

  16. I looked at the related ColdFusion Cookbook recipe (http://www.coldfusioncookbook.com/entry/114/How-do-I-return-a-query-from-an-Excel-file) that I had created as well based on this blog entry, it appears that other people were having the same issues as above. Ray, noticed there was missing bracket at the end some where and he fixed it.

    I also fixed it in the code above, so give it a try now. Thanks.

  17. Chris permalink

    But how do you find out how many sheets exist in an excel file?

  18. That’s a good question.

  19. Victor permalink

    Hey Kyle,

    This script looks really neat, and I could really use it. I tested it out, and first I got the error saying my <cfscript> didn’t have an end tag. I added another bracket at the bottom and that fixed that. But now I’m getting an error saying that the return type is not of type query. Any ideas? I’ll copy the part of the code I changed below:

    if(len(trim(arguments.filename)) and fileExists(arguments.filename)){
    try{
    CreateObject("java","java.lang.Class").forName("sun.jdbc.odbc.JdbcOdbcDriver");
    c = CreateObject("java","java.sql.DriverManager").getConnection("jdbc:odbc:Driver={Microsoft Excel Driver (*.xls)};DBQ=" & arguments.filename );
    stmnt = c.createStatement();
    rs = stmnt.executeQuery(sql);
    myQuery = CreateObject(‘java’,'coldfusion.sql.QueryTable’).init(rs);
    return myQuery;
    }catch(any e){
    // error-handling code }
    }
    }

    I also tried placing the "return myQuery;" statement in different places around the brackets. That didn’t fix it. Your help would be greatly appreciated! Thanks!

  20. Paul M. permalink

    Hey Victor, you may already have discovered this, but I just got the same error you described. I had the source file open in Excel, and it worked perfectly after I closed Excel and tried again.

    Kyle, this is SO smooth. Thanks for making it available to us!

  21. shaincf permalink

    hey Victor,
    I also got the error u were saying about , after making minor chnges it worked fine for me..
    it is due to 2 reasons
    1. Change the returntype="any" for function getExcelSheet
    2 . The file name might not be taking the right path.

    <!—Provide the full path of your excel file for the variable full_path—>
    <cfset variables.full_path = "D:\test.xls">
    <cfset myQuery = getExcelSheet(variables.full_path,"sheet1")>

    <cffunction name="getExcelSheet" access="public" output="false" returntype="any">
    <cfargument name="filename" required="true" type="string" />
    <cfargument name="sheetName" required="true" type="string" />
    <cfscript>
    var c = "";
    var stmnt = "";
    var rs = "";
    var sql = "Select * from [#sheetName#$]";
    var myQuery = "";
    if(len(trim(arguments.filename)) and fileExists(arguments.filename)){
    try{
    CreateObject("java","java.lang.Class").forName("sun.jdbc.odbc.JdbcOdbcDriver");
    c = CreateObject("java","java.sql.DriverManager").getConnection("jdbc:odbc:Driver={Microsoft Excel Driver (*.xls)};DBQ=" & arguments.filename );
    stmnt = c.createStatement();
    rs = stmnt.executeQuery(sql);
    myQuery = CreateObject(‘java’,'coldfusion.sql.QueryTable’).init(rs);
    }catch(any e){
    //// error-handling code
    }
    }
    return myQuery;
    </cfscript>
    </cffunction>

  22. sona permalink

    well as awesome as this is i’m running on mac os x. I was wondering if anyone out there has a solution for this. The function errors out on:
    CreateObject("java","java.lang.Class").forName("sun.jdbc.odbc.JdbcOdbcDriver");
    it can’t resolve the sun.jdbc. any help would be great. Thanks.

  23. Heh, unfortunately, when it comes to database drivers I am just as unknowing as you are. Perhaps anyone who is subscribed to this post will be able to comment.

  24. Thank you Kyle for providing this solution. Wanted to let you know that if the spreadsheet you are working with has multiple data types, the code will attempt to select the most used type and return that.
    In other words, I am working with a spreadsheet which has string and numerical data. When dumping, the numerical data has been cast as type string, and therefore shows up as [empty string]. Something to watch out for.

  25. We did find another solution:
    <cfquery name="qryGetData" datasource="#sDSN#">
    SELECT *
    FROM OPENROWSET(‘Microsoft.Jet.OLEDB.4.0′,’Excel 8.0;HDR=NO;IMEX=1;Database=D:/Inetpub/wwwroot/nchsql/scripts/data/1764-20080113-USD-E.xls’,[Glance$])
    </cfquery>

  26. Mark permalink

    Code works great. I am also in need of code to pull a list of availble sheet names. How would I go about gathering the Metadata?

  27. @Mark – This is a common question. Because of this, I have started to work on a solution. Stay posted, I hope to have a draft of a new ExcelFile CFC today.

  28. Mark permalink

    Kyle – I got it to work. This will return the list as an Array, of Sheet names in the given Excel document

    <cffunction name="getExcelSheet" access="public" output="false" returntype="any">
    <cfargument name="filename" required="true" type="string" />
    <cfscript>
    var c = "";
    var stmnt = "";
    var rs = "";
    var sheetList = "";
    //if(len(trim(arguments.filename)) and fileExists(arguments.filename)){
    try{
    CreateObject("java","java.lang.Class").forName("sun.jdbc.odbc.JdbcOdbcDriver");
    c = CreateObject("java","java.sql.DriverManager").getConnection("jdbc:odbc:Driver={Microsoft Excel Driver (*.xls)};DBQ=" & arguments.filename & ";" );
    stmnt = c.getMetaData();
    rs = stmnt.getTables(javacast("null", ""),javacast("null", ""),javacast("null", ""),javacast("null", ""));
    rsmd = rs.getMetaData();
    sheetList = ArrayNew(1);
    while(rs.next())
    {
    sheetList.addElement(rs.getString("TABLE_NAME"));
    }
    }catch(any e){
    //// error-handling code
    }
    //}
    return sheetList;
    </cfscript>
    </cffunction>

  29. Indeed that is how you do it, but I am encapsulating it into a CFC with connection management etc so that it has a clean API. Thanks though!

  30. Don permalink

    Kyle,
    Thank you for putting this up. It has saved me weeks of work and heartache. I really appreciate this.
    Don

  31. Justin permalink

    has anyone found a way to allow this code to read multiple excel spreadsheets? i need it to pull out values from two excel files, but can’t figure out a way to read more than one file at a time.
    any help would be EXTREMELY appreciated.

  32. Why couldn’t you just loop through the two files?

  33. It all depends how you have your two files available to you, I will assume their file references are in an array:

    <cfset queryArr = arrayNew(1) /> <!— Array to store returned queries —>

    <cfloop from="1" to="#arrayLen(myFileArr)#" index="i">
    <cfset arrayAppend(queryArr, getExcelSheet(myFileArr[i], ‘sheet1′)) />
    </cfloop>

    Obviously you have to customize that code based on how are you are storing your multiple file references, but that is the gist.

  34. Jack permalink

    Hi,

    This looks good but I’ve amateurish request, could I see how you actually impliment this? How do you actually query the inoformation and get it to display on your page? This code would be extremely helpful.

    Thank You,
    Jack

  35. @Jack, you can just simply run a <cfdump var="#query#" /> to see the contents.

    Otherwise, you can do <cftable query="#query#" /> to see a nicer display.

  36. Ryan Balchand permalink

    This works great for me except for one minor issue. Importing cells with integers return [empty string]. Any ideas?

    Example:
    COLA COLB
    name ryan (import)
    date 09/11/08 (import)
    quantity 3 (empty)

  37. Paul permalink

    Ideally you would add some code to tidy things up as you leave; particularly if there are errors with the Excel file or permissions issues, you need to close things up or reset the service to regain access to the .xls file. Inserting these lines just above the catch{} block seems to do the trick.

    rs.close();
    c.close();

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS