Updating Stock Quotes
I answer about four questions a month on the newsgroups. I'm off to a roaring start in April, but I don't like my answer much. I never know just how "correct" an answer to give. So I gave an answer that I felt was appropriate for the original poster and I'll give a slightly different answer here.
The OP has an external query pulling stock quotes from Yahoo! Finance. He's using their csv download option, but just querying the csv rather than downloading it and loading it into Excel. One problem with that is that it puts all of the data in one column. If there's a built-in way to fix that, I don't know it. The other problem is that the list of companies he wants to see is variable and lives in a range of cells. I actually don't know if these are his problems, but I like to pretend.
My objectives are to create a web query to Yahoo, add a parameter that points to a range, and parse the data into multiple columns. Does that sound like fun? Well, I'm doing it anyway.
I can't create the query the old fashioned way. The web query user interface is like a browser; if you try to point to a CSV file, the browser wants to download it. So I create a web query to whatever table is on whatever page that happens to open up. Basically, I'm creating a query that I will later edit to point to where I want.

I pick any old arrow on the Dell website and create a query. I get a message that my query doesn't return any data, but I just OK past that message because I know I'll be making changes. Next I go to the VBE, and specifically the Immediate Window, to change my query.

The part you can't see says this:
wshquotes.QueryTables(1).Connection = "URL;http://quote.yahoo.com/d/quotes.csv?s=^GSPC[""EnterTicker"",""Ticker""]&f=nl1c"
If this URL isn't exactly what you want, don't worry. Alex has done all the legwork that will allow you to get whatever data you want. For me, I'm getting the name, last, and change for the S&P500 Index plus whatever else I enter. The "whatever else I enter" part is the parameter and is the name/prompt pair of strings inside the square brackets. When I Refresh the QueryTable, it asks me for a Ticker. I enter MSFT and the web query returns some data.
I have a parameter, but it doesn't point to a range, so that's next. The range B1:B10 will hold whatever ticker symbols I want and D1 will put them in the format I need for the URL.

The formula in D1 is ="+"&B1&IF(NOT(ISBLANK(B2)),"+"&B2,"")&IF(NOT(ISBLANK(B3)),"+"&B3,"")&IF(NOT(ISBLANK(B4)),"+"&B4,"")
&IF(NOT(ISBLANK(B5)),"+"&B5,"")&IF(NOT(ISBLANK(B6)),"+"&B6,"")&IF(NOT(ISBLANK(B7)),"+"&B7,"")&IF(NOT(ISBLANK(B8)),"+"&B8,"")
&IF(NOT(ISBLANK(B9)),"+"&B9,"")&IF(NOT(ISBLANK(B10)),"+"&B10,"")
Inspiring, huh? If a ticker exists, it puts a "+" in front of it and adds it to the list. Now I can click on the Query Parameters button on the External Data Toolbar and point to D1.

I also click on the Data Range Properties button and check the box to make the table automatically refresh.

OK, I have a parameterized web query that refreshes automatically. Unfortunately, it gives me this

Everything in one column. Yuck. Time to get fancy. I know that Text to Columns will parse the results, but I'm not going to manually do that every time the QueryTable refreshes. Fortunately, QueryTables have events. Two events, to be precise. I'll be using the AfterRefresh event. I'll bet you can guess what the other one is.
Other than out brief jaunt into the Immediate Window, we've been pretty much in Excel's UI up until now. It's time to step through the looking glass into VBA and (gasp) class modules. Open the VBE (Alt+F11) and add a class module and a module to your project. Name the class module (F4) CQTEvents and name the module MEntryPoints. In your class module, put this code:
Private WithEvents mobjQTable As QueryTable
Private Sub Class_Terminate()
Set mobjQTable = Nothing
End Sub
Public Property Get QTable() As QueryTable
Set QTable = mobjQTable
End Property
Public Property Set QTable(objQTable As QueryTable)
Set mobjQTable = objQTable
End Property
Private Sub mobjQTable_AfterRefresh(ByVal Success As Boolean)
Application.DisplayAlerts = False
mobjQTable.ResultRange.TextToColumns _
Destination:=mobjQTable.ResultRange.Cells(1), _
DataType:=xlDelimited, _
TextQualifier:=xlTextQualifierDoubleQuote, _
Comma:=True
Application.DisplayAlerts = True
End Sub
Of course I never use public variables in a class because I'm a class module snob. So I have a private variable declared WithEvents that exposes the events of the QueryTable variable. Because it's a private variable, I need public get and set statements so code outside of the class can access it. Finally, I create the AfterRefresh event (using the code pane drop downs) and simply call the TextToColumns method to parse out the QueryTable results. If you try to TextToColumns into a range that already contains data, you'll get a message. I use DisplayAlerts to avoid that.
The only thing left is to tell my class which QueryTable I care about. I do that in the standard module with this code:
Public clsQTEvents As CQTEvents
Sub Auto_Open()
Set clsQTEvents = New CQTEvents
Set clsQTEvents.QTable = wshQuotes.QueryTables(1)
End Sub
Sub Auto_Close()
Set clsQTEvents = Nothing
End Sub
That's so easy I don't even have to explain it. Run Auto_Open and wait for your QueryTable to refresh. Then go fix your typos and you're all set. And you thought class module were difficult. If you're following along at home, be sure to do this during trading hours. Having those one minute updates where nothing changes isn't too exciting.



