<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	>

<channel>
	<title>3Types</title>
	<atom:link href="http://www.3types.com/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://www.3types.com</link>
	<description>Notes about databases, books, programming, and other things that interest me.</description>
	<pubDate>Mon, 26 Jan 2009 01:02:51 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.7</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Baking bread</title>
		<link>http://www.3types.com/?p=22</link>
		<comments>http://www.3types.com/?p=22#comments</comments>
		<pubDate>Mon, 26 Jan 2009 01:00:25 +0000</pubDate>
		<dc:creator>3Types</dc:creator>
		
		<category><![CDATA[Personal]]></category>

		<category><![CDATA[cooking]]></category>

		<guid isPermaLink="false">http://www.3types.com/2009/01/25/baking-bread/</guid>
		<description><![CDATA[For the last few weeks I&#8217;ve been baking bread. It&#8217;s been a lot of fun, and much of the bread has been great!
It started just before Christmas when we got a Kitchenaid mixer that my wife had wanted for a while. I decided to try it out on bread dough.

During one of our book-buying sprees [...]]]></description>
			<content:encoded><![CDATA[<p>For the last few weeks I&#8217;ve been baking bread. It&#8217;s been a lot of fun, and much of the bread has been great!</p>
<p>It started just before Christmas when we got a Kitchenaid mixer that my wife had wanted for a while. I decided to try it out on bread dough.</p>
<p><span id="more-22"></span></p>
<p>During one of our book-buying sprees a couple of years ago, we picked up <em>The Bread Baker&#8217;s Apprentice</em>, by Peter Reinhart. I had tried out his technique for pizza dough (in <em>American Pie</em>) with success, so I started to study his bread baking techniques. He&#8217;s very big on preparing portions of the dough ahead of time and allowing it to &#8220;ferment&#8221;, and he also talks a lot about the chemical reactions that go on when preparing and baking the dough; I&#8217;m not sure that I follow all of the details, but I got the basic idea and decided to start with his recipe for French bread.</p>
<p>The first day I prepared the portion to be &#8220;fermented&#8221; - just flour, water, salt, and yeast, mixed in the mixer then kneaded with the dough hook, then stored overnight in the refrigerator. I finished the loaves the next day, with more flour, water, salt, and yeast, mixed with yesterday&#8217;s dough, then kneaded with the dough hook. After various periods of resting and rising, I shaped the dough into three long skinny cylinders, preheated the oven (and pizza stone) as hot as it would go, and slid the loaves onto the pizza stone. After about 20 minutes, I had these beautiful baguettes on the cooling rack, and in another hour I was savoring some delicious bread.</p>
<p>Since then I&#8217;ve tried whole wheat loaves (pretty good, but I used some rye flour that gave it a slightly &#8220;off&#8221; taste; I&#8217;ll probably try it again with corn meal instead), Viennese bread (very nice sandwich bread), and, my favorite, <em>Pain a l&#8217;Ancienne</em> (&#8221;bread made in the traditional way&#8221;). The <em>pain a l&#8217;ancienne</em> is truly amazing - a very crusty, airy baguette<em> </em>with a very subtle sweetness, and it&#8217;s made only with flour, water, yeast, and salt.</p>
<p>What&#8217;s nice about all of these recipes is that most of the work is done by the mixer, and most of the time involved is just waiting for the dough to rise or ferment or whatever else it&#8217;s supposed to do. My actual working time is maybe 15 minutes the first day, followed by another 15 to 20 minutes the next day.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.3types.com/?feed=rss2&amp;p=22</wfw:commentRss>
		</item>
		<item>
		<title>Upgrading my reports</title>
		<link>http://www.3types.com/?p=19</link>
		<comments>http://www.3types.com/?p=19#comments</comments>
		<pubDate>Sat, 17 Jan 2009 22:21:44 +0000</pubDate>
		<dc:creator>3Types</dc:creator>
		
		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Access]]></category>

		<category><![CDATA[Excel]]></category>

		<category><![CDATA[Reports]]></category>

		<category><![CDATA[SQLServer]]></category>

		<guid isPermaLink="false">http://www.3types.com/2009/01/17/upgrading-my-reports/</guid>
		<description><![CDATA[I&#8217;m in the middle of a long (6 months or more) project to upgrade all of my reports. I&#8217;m doing this for two major reasons. We&#8217;re gradually migrating the data warehouse from SQL Server 2000 to SQL Server 2005, and as I move my projects to a new 2005 server I&#8217;m trying to review my [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m in the middle of a long (6 months or more) project to upgrade all of my reports. I&#8217;m doing this for two major reasons. We&#8217;re gradually migrating the data warehouse from SQL Server 2000 to SQL Server 2005, and as I move my projects to a new 2005 server I&#8217;m trying to review my code to take advantage of new SQL 2005 features. At least as important is the fact that we&#8217;re in the process of putting in a new billing system, which means that most of the data sources for my programs are going to be changing.</p>
<p><span id="more-19"></span>
<p>I&#8217;m preparing for the new data sources by rewriting my programs to refer to views rather than the underlying tables; I&#8217;m hoping that will enable me to just change the views when necessary, rather than re-write the stored procedures.</p>
<p>I took advantage of the quiet time between Christmas and New Years to upgrade one of my earliest projects, a series of sales reports I call &#8220;POSFlash&#8221;. When I first started this job, I inherited a few old Access 97 reports that told our key national account reps what books their accounts sold in the previous couple of weeks. This point-of-sale (&#8221;POS&#8221;) data is crucial to our business - reps would review the report every Monday morning to find fast-selling titles that they could get reorders on. But the Access 97 reports gave only limited sales data (limited number of titles and only the most recent weeks of sales), and the reports took a long time to run.</p>
<p>So I converted the reports to run directly on SQL Server with stored procedures and used Access XP merely as a front-end. That is, I use Access to create or modify the report definitions (which customers to include, which titles, what fields, etc.), and once the SQL Server stored procedure generates a weekly report and stores it in a report table, the Access program downloads just the report data, loads it into an Excel spreadsheet, and formats it for the users.</p>
<p>Moving the process over to the new 2005 went quite smoothly. The code in the primary stored procedure is a little under 1,000 lines, but it didn&#8217;t require a lot of rewriting. (The biggest change was that I was able to use SQL 2005&#8217;s new TOP (@x) feature to remove a few dozen lines of code.) The biggest surprise was when I finished debugging the code and discovered that I was able to generate most of the weekly reports in 30 seconds or so - versus the more typical 30-60 minutes required by the old version! (I&#8217;m sure that at least 95% of the speed improvement is because the new server is faster, not because of any of my tweaks . . .)</p>
<p>Once the new program was in place on the new server, I set up a Reporting Services report to run the master procedure at the appropriate intervals (4 or 5 times a day on Monday and Tuesday, less frequently as the week goes on - at times chosen based on when the underlying data is loaded into our data warehouse), and for the week of January 5th I ran the old and new reports in parallel.</p>
<p>There were a few glitches during the week, mostly due to typos or other errors in the snippets of code (stored in tables) that specified where the data for each report was to be found. By the end of the week I had quashed those bugs, and for the week of January 12th we switched over to the new version. As far as I can tell, none of my users noticed the difference, except that possibly the reports were available a little bit sooner than before.</p>
<p>Obviously I&#8217;m pleased that the upgrade went well!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.3types.com/?feed=rss2&amp;p=19</wfw:commentRss>
		</item>
		<item>
		<title>Asynchronous connection from Excel to SQL Server - How-to</title>
		<link>http://www.3types.com/?p=12</link>
		<comments>http://www.3types.com/?p=12#comments</comments>
		<pubDate>Sun, 28 Dec 2008 06:38:13 +0000</pubDate>
		<dc:creator>3Types</dc:creator>
		
		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Excel]]></category>

		<category><![CDATA[howto]]></category>

		<category><![CDATA[SQLServer]]></category>

		<guid isPermaLink="false">http://www.3types.com/?p=12</guid>
		<description><![CDATA[Many of my programs involve pulling data from our data warehouse, manipulating it a bit, and presenting the results to the user. Because it can time to get data out of the data warehouse (anywhere from a few seconds to 30 minutes for more complicated queries), the program can easily look like its frozen while [...]]]></description>
			<content:encoded><![CDATA[<p>Many of my programs involve pulling data from our data warehouse, manipulating it a bit, and presenting the results to the user. Because it can time to get data out of the data warehouse (anywhere from a few seconds to 30 minutes for more complicated queries), the program can easily look like its frozen while the user is waiting. In the case of Excel macros, a running query can freeze up not only Excel, but make the whole computer seem sluggish.<br />
<span id="more-12"></span><br />
This problem can be greatly alleviated by creating an asynchronous connection to the server. In brief, with a normal (synchronous) connection, the program will call a stored procedure or execute a SQL statement on the server and then wait for the results to come back. While it&#8217;s waiting, nothing else can be done with the program - or with the calling program (Excel or Access, for example).</p>
<p>An asynchronous connection, on the other hand, basically sends off the SQL statement and says &#8220;I&#8217;m going to do some other things now, let me know when the results are ready and I&#8217;ll pick them up.&#8221; By taking the control back right away, the local program can give the user some simple feedback (for example, a status message saying the program has been running for x minutes); more importantly, the user can more easily switch back and forth to other programs, without the sluggishness that comes from waiting for a synchronous program to finish.</p>
<p>I figured out how to create asynchronous connections from Access a few years ago; it&#8217;s a bit complicated, but not too bad, because Access forms seem to be tailor-made to handle them. But my attempts to do the same thing from Excel macros kept leading to dead-ends - until I finally had a breakthrough a few months ago and figured out how to make it work!</p>
<p>Following is a step-by-step description of how to set up an asynchronous connection from an Excel macro. I know it works in Excel 2007 connecting to SQL Server 2005; I&#8217;m pretty sure it will also work with Excel 2003 and SQL Server 2000, but I haven&#8217;t tested them.</p>
<p>There are three key components to an asynchronous connection:</p>
<ol>
<li>A user form that creates an ADODB connection and command, and launches the SQL query.</li>
<li>An &#8220;ExecuteComplete&#8221; routine (in the form module) that will run when the command is finished and the recordset has been returned.</li>
<li>A timer that lets the program retain control while waiting for the command to be finished.</li>
</ol>
<p>Here&#8217;s how I set these components up.</p>
<p>In the declarations section of the user form, declare an ADODB connection and recordset, using the &#8220;withevents&#8221; keyword:</p>
<pre>
	dim withevents cn as ADODB.Connection
	dim withevents rs as ADODB.Recordset
</pre>
<p>Also declare any variables that need to be shared between the Run routine and the ExecuteComplete routine.</p>
<p><strong>In the Run routine</strong></p>
<p>(Presumably this is in the &#8220;OnClick&#8221; event for the Run button.)</p>
<p>Set the connection to a new connection (&#8221;set cn = new adodb.connection&#8221;), specify the connection string and properties, and open it.</p>
<p>Create the command and parameters.</p>
<p>Before executing the command, start the timer (see below for details).</p>
<pre>
	StartTimer "Macro1"
	intTimers = intTimers + 1
	dtStartMacro1 = Now
</pre>
<p>Then open the recordset with this syntax:</p>
<pre>
	set rs = new adodb.recordset
	rs.open Source:=cmd, Option:=adAsyncExecute
	DoEvents
</pre>
<p>That&#8217;s the end of the Run procedure.</p>
<p><strong>In the ExecuteComplete routine</strong></p>
<p>(NOTE: As far as I can tell, this is the ONLY significant event that&#8217;s fired. I can&#8217;t get cn_InfoMessage to fire, or rs_FetchComplete or rs_FetchProgress.)</p>
<p>First, stop the timer (see below for details).</p>
<pre>
	intTimers = intTimers - 1
	StopTimer "Macro1"
</pre>
<p>Then check the adStatus parameter. If adStatus = adStatusOK, then go ahead and process the results. If not, then use the pError parameter to return the error message to the user.</p>
<p><strong>The timer</strong></p>
<p>This is the part of the process that eluded me for so long! Access forms have a built-in timer property that is much more intuitive than the way Excel does it . . .</p>
<p>These need to be in a regular module, NOT a form or other class module!</p>
<p>Declare these global variables:</p>
<pre>
	Global intTimers As Integer
</pre>
<p>	(this lets you know how many timers are currently running)</p>
<pre>
	Global dtStart&lt;proc&gt; As Date
</pre>
<p>(one of these for each macro that may use the timer)</p>
<p>Declare these variables at the top (so they have module scope):</p>
<pre>
	Const intInterval As Integer = 10
	Dim dblTime As Double
</pre>
<p>Create these Public Subs:</p>
<p>NOTE: timers must be started and stopped with EXACTLY the same parameters; hence the need to store the start time in a variable with modular scope! See the Excel help file for more details on the OnTime method.</p>
<pre>
	Public Sub StartTimer(strMacro As String)
		Select Case strMacro
		Case "Macro1"
			dblTime = Now + TimeSerial(0, 0, intInterval)
			Application.OnTime EarliestTime:=dblTime _
					, Procedure:="UpdateForm_Macro1", Schedule:=True
			DoEvents
		End Select
	End Sub
</pre>
<pre>
	Public Sub StopTimer(strMacro As String)
		On Error Resume Next
		Select Case strMacro
		Case "Macro1"
			Application.OnTime EarliestTime:=dblTime _
					, Procedure:="UpdateForm_Macro1", Schedule:=False
		End Select
	End Sub
</pre>
<p>Then for each macro, create an UpdateForm_&lt;Macro&gt; sub. This updates the userform (and does any other desired processing), and re-calls the timer (the &#8220;SecondsToTime()&#8221; function is a small function I wrote to translate seconds into a more meaningful time string; the &#8220;lblStatus&#8221; control is a label on the userform that provides status messages):</p>
<pre>
	Public Sub UpdateForm_Macro1()
		With frmMacro1.Controls("lblStatus")
			.Caption = "Waiting for the server for " _
				 &amp; SecondsToTime(DateDiff("s", dtStartMacro1, Now))
			StartTimer "Macro1"
	End With

	End Sub
</pre>
<p><strong>Allowing a graceful exit</strong></p>
<p>One of the best things about an asynchronous connection is that you can allow a way for the user to bail out of the macro if it&#8217;s taking too long. With a synchronous connection, the only way to cut it short is to forcibly close Excel via Task Manager (and obviously that&#8217;s not the ideal way to close Excel!).</p>
<p>To enable a graceful exit, add a variable bRunning (boolean) to the form declaration section, and set the variable to true just before kicking off the SQL command in the Run command.</p>
<p>Add this routine to the userform:</p>
<pre>
	Function OKtoClose() As Boolean
		OKtoClose = True
		On Error Resume Next

		If bRunning Then
			If MsgBox("Are you sure you want to stop running this macro?" _
					, vbDefaultButton2 + vbYesNo) = vbYes Then
				StopTimer "Macro1"
				cn.Cancel
				Set cn = Nothing
				OKtoClose = True
		Else
			OKtoClose = False
		End If
	Else
		OKtoClose = True
	End If
End Function
</pre>
<p>Then, in the userform&#8217;s _QueryClose event, add this pre:</p>
<pre>
	Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
		If Not OKtoClose Then
			Cancel = 1
		End If
End Sub
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.3types.com/?feed=rss2&amp;p=12</wfw:commentRss>
		</item>
		<item>
		<title>Reflections on turning 30</title>
		<link>http://www.3types.com/?p=10</link>
		<comments>http://www.3types.com/?p=10#comments</comments>
		<pubDate>Fri, 26 Dec 2008 02:38:37 +0000</pubDate>
		<dc:creator>3Types</dc:creator>
		
		<category><![CDATA[Publishing]]></category>

		<guid isPermaLink="false">http://www.3types.com/?p=10</guid>
		<description><![CDATA[I recently realized that next month (January, 2009) will mark my 30th anniversary in publishing.
I got my first publishing job in January, 1979, when I interviewed with the personnel director at G. P. Putnam&#8217;s. I had decided that there was no point in continuing with my graduate school studies (the job market was awful, and [...]]]></description>
			<content:encoded><![CDATA[<p>I recently realized that next month (January, 2009) will mark my 30th anniversary in publishing.</p>
<p>I got my first publishing job in January, 1979, when I interviewed with the personnel director at G. P. Putnam&#8217;s. I had decided that there was no point in continuing with my graduate school studies (the job market was awful, and I was getting pretty tired of academia), so I had this vague notion of becoming an editor.</p>
<p><span id="more-10"></span></p>
<p>So I visited an employment agency, which sent me to meet with Dorothy Rudo, Putnam&#8217;s personnel director, to interview for one of their perpetually available publicity assistant jobs. Dorothy quickly decided I wasn&#8217;t cut out for publicity, but she liked me. I think what clinched a job for me was the typing test I took. She asked me to sit at her typewriter and type a sample, which I did with no problem. I then discovered that this was a brand new electric typewriter that she hadn&#8217;t figured out how to turn on yet, so my competence with the fancy new technology landed me a job!</p>
<p>I spent a year and a half working as Dorothy&#8217;s assistant and as assistant to the president, doing a little bit of everything. It was a great, practical introductory course in publishing.</p>
<p>When I decided it was time to leave New York, I approached Putnam&#8217;s sales manager to see if there were any openings for sales reps. I had no selling experience, but I had worked in a bookstore during college, and I got a shot at a territory in St. Louis (which was used for several years as a &#8220;training territory&#8221; by many publishers).</p>
<p>I found the job suited me very well, and I enjoyed it a lot. I mean, what&#8217;s not to like about driving around, visiting bookstores, and talking to them about new books coming out? Just about all my customers were friendly, and I very much enjoyed working out of my home, on my own schedule.</p>
<p>After a year and a half in St. Louis, I took a territory in Southern California, and (aside from a brief, disastrous year back in NY) I&#8217;ve been here ever since.</p>
<p>Meanwhile, the job of a sales rep has changed dramatically since I started. I initially worked for mid-sized publishers and covered territories with 80 - 120 customers; I saw each customer once a season (with two or three selling seasons per year) and each sales call took maybe a half a day, except for the stores where I did a complete inventory of their stock (which typically added another half a day).</p>
<p>I almost never saw customers more than once a season (except for the largest ones in my immediate vicinity), and my only other contact with them was by mail and (occasionally) phone.</p>
<p>A few times each week, I&#8217;d gather up all the orders I&#8217;d collected (on NCR pads for new title orders and in multi-page booklets for backlist orders) and mail them to the warehouse.</p>
<p>Since then, the number of independent bookstores and local wholesalers has shrunk dramatically, and publishers have consolidated so that there are very few &#8220;mid-sized&#8221; houses left. Now the average sales rep for a large publisher has perhaps 10 to 20 customers, each customer is seen several times during the season, orders are placed electronically (either through the stores&#8217; own computer system or through the rep&#8217;s laptop), and the rep probably spends more time discussing marketing plans than actually selling books.</p>
<p>Instead of selling a list of perhaps 100 new titles and 500 - 1,000 backlist titles, today&#8217;s large publisher rep has a list of 700 - 1,000 titles per season and a backlist of 5,000 - 10,000 titles!</p>
<p>And of course communication with accounts is now mostly by email or phone. About the only things that are mailed these days are catalogs - and it looks like within the next year or so even catalogs will be replaced by &#8220;e-catalogs&#8221; (always up-to-date, with lots of bells and whistles - but harder to circulate among a staff of buyers, and a lot less portable . . .).</p>
<p>Despite the drastic changes, the rep job is still one of the best in the business.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.3types.com/?feed=rss2&amp;p=10</wfw:commentRss>
		</item>
		<item>
		<title>Sales Macros</title>
		<link>http://www.3types.com/?p=8</link>
		<comments>http://www.3types.com/?p=8#comments</comments>
		<pubDate>Fri, 26 Dec 2008 00:43:56 +0000</pubDate>
		<dc:creator>3Types</dc:creator>
		
		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Excel]]></category>

		<category><![CDATA[SQLServer]]></category>

		<guid isPermaLink="false">http://www.3types.com/2008/12/25/sales-macros/</guid>
		<description><![CDATA[One of the programs that I&#8217;m happiest with is an Excel add-in that I call Sales Macros. It&#8217;s a set of Excel macros that I wrote to automate all sorts of tasks commonly performed by folks in the Sales department.
We have several different online systems that will provide information about our titles - but most [...]]]></description>
			<content:encoded><![CDATA[<p>One of the programs that I&#8217;m happiest with is an Excel add-in that I call Sales Macros. It&#8217;s a set of Excel macros that I wrote to automate all sorts of tasks commonly performed by folks in the Sales department.</p>
<p>We have several different online systems that will provide information about our titles - but most of them work on one title at a time. So if a rep or a sales assistant needs, say, updated prices for a spreadsheet full of titles, she would need to look up each title individually. But with Sales Macros, the whole spreadsheet can be filled in with a single operation!</p>
<p><span id="more-8"></span>
<p>It started off with programs that I needed for myself; I think the first one was a routine to check the validity of ISBNs (the industry&#8217;s standard book number) and restore the leading 0s that Excel frequently dropped.</p>
<p>My big breakthrough was when I figured out how to write a SQL Server stored procedure that would accept a list of ISBNs as a parameter, query our data warehouse for various bits of data (title, price, on-sale date, sales . . .), and return a recordset back to Excel for loading into the spreadsheet.</p>
<p>The basic structure is the same, then, for a half-dozen key macros. The user is presented with a dialog box that asks for the spreadsheet column containing the ISBNs, and presents a list of available data fields that the macro will return; the user chooses which fields he wants and which column in the worksheet will hold the data. (The program tries to guess at the appropriate columns, based on the headers, but everything can be changed or corrected easily.)</p>
<p>Once the user makes his selections and clicks the &#8220;Run&#8221; button, Excel assembles a list of the ISBNs (in the format &#8220;Row#,ISBN|Row#,ISBN . . .&#8221;) and passes that as a parameter to the appropriate stored procedure. The procedure then breaks apart the ISBN parameter into a temporary table and uses that table to pull the desired data from the data warehouse. Once all the data has been assembled, the stored procedure returns the data in the form of a recordset to the Excel macro.</p>
<p>Once Excel receives the recordset, it loads the worksheet using the row number and the user-selected column assignments. In most cases, the whole process takes a minute or two (up to 10 minutes or more if the spreadsheet has thousands of ISBNs) - and that&#8217;s obviously a huge time savings over looking up the data on a title-by-title basis!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.3types.com/?feed=rss2&amp;p=8</wfw:commentRss>
		</item>
	</channel>
</rss>
