1972 MGB GT – Part 1 – A project to keep my sanity!

I had originally intended on keeping a separate blog or web-site to log my progress on this project, but have decided to just expand this blog, rather than manage yet another site.

In late October 2008 my dad informed me that he would be giving me, my brothers and my sister a little of his share of his inheritance. My dad, his brothers and sisters had split some inheritance that Granny had left after she had passed. She passed away earlier in 2008 after suffering for several years from Cancer. True to her nature and not wanting people to fuss, she had kept her suffering a secret almost to the end, just getting on with life and only telling the family when she started to get too ill.

Though it was not a lot of money I did not want to waste it on bills or similar… So I decided it was about time to renew an old hobby, classic cars… and possibly introduce my kids to some dirt and oil.

For many years now my main hobby has been has been my job and vice versa (software development and computer programming)… hobby since 1990 job since 1997, with any spare time between the two being taken up by the kids, parenthood and the occasional computer game or xbox distraction.

In order to keep the old brain fresh for coding I figure it is time to get my fingernails dirty again.


Prior to spending most of my time in front of a computer screen I used to spend a reasonable amount under a bonnet or re-wiring my old bangers. Admittedly most of the time it was out of necessity, rather than desire… spending most of my money on books, computer hardware, OU courses and the kids meant that all of my earlier cars were bangers run on a shoe string. Saying that there was always a lot of satisfaction in doing the repairs and maintenance myself or with the help of my dad…

First car I bought was a 1973 Opel Ascona, purchased for the pricey sum of £200, this was before owning a classic became more fashionable and the prices went up quite a bit.

Though it required a fair upkeep during it’s life, it went on for a surprising number of years passing between myself and my father a couple of times… There was then the 1988 Ford Sierra Estate, actually this is the car I learned to drive in before getting the Opel when I passed my test, I swapped the Opel with my Dad for this and took on the repayments for a while. Then there was a 1973 Volkswagen Beetle (that needed completely rewired, having melted all the wiring and somehow just got a MOT… always checked the electrics on a car I bought after this one and never trusted a MOT), then a Fiat 126 (my dad bought this but I used it for a while) and a 1982 Ford Capri.

 

I then moved onto more modern cars working my way through a Rover Mini Metro, a Ford Focus, a Citroen Picasso, a Volvo 940 Turbo, a Ford Mondeo and currently a Chrysler Voyager.

And that’s just the cars I have been through (my dad’s list is much longer and more impressive, including a bubble car, Ford Cortina, various customs, a Ford Anglia and many more).


So… I had a few pennies from my Granny and decided to get an old classic in need of some repairs as a way to entertain myself on weekends in my garage. I had always wanted to repair and restore an old classic rather than just keep it running out of necessity.

Having researched various types of classic cars I decided that I would go for an MGB GT. There was several reasons for this…

Firstly I had always wanted one when I was younger but they had always been out-with my budget and once I could have afforded one, well one word… "kids", so not really practical. Though the money I was getting would not normally be enough for a MGB GT; with the economic climate looking bleaker and bleaker it seemed it would be a buyers market for second-hand cars, bringing prices much closer to my budget.

Other factors came into play as well, such as the great availability of reasonably priced replacement parts for the MGB GT, through the various owners clubs… it was after all one of the most popular British small sports cars built. There is also an abundance of books and other materials on them.

Having settled on the idea and to my surprise getting instant approval from my wife, I set about trying to find one. It soon became obvious that the best place to get a good deal would be on eBay, buying a car through eBay is something I never thought I would do, but prices elsewhere still seemed high.

I was ideally looking for something 1972 or earlier (something that would be road tax exempt here in the UK, so I don’t have to worry about the yearly cost as much). It had to be something needing some work, but was currently running and have a MOT so that I could get it home.

Having browsed and monitored auctions for several weeks, it was looking like I would only be able to get something post 1973 for the money I had available. So I bid on a couple of cars from 1973. Though I was the highest bidder on both they never reached their reserve and the sellers were expecting more than double what the auctions reached and more than I was willing to pay.

I was in no rush and wasn’t about to be convinced to part with more money than I had budgeted.

I continued monitoring and caught sight of a red 1972 MGB GT, with no reserve that was in need of some restoration and maintenance… over the course of the week I emailed the seller numerous detailed questions, he was extremely helpful answering all my questions and sending me additional photos of various problem areas that would need repaired.

Come the end of the auction I luckily won it by one pence. The wife got a giggle that I won the auction by only one pence!

I made arrangements with the owner to pick it up from Heathrow airport and booked my flights down for the following weekend. Flying down on the first flight on 29th Nov, I spent the day (coldest of the year, in car with no working heater) driving back up.

The car did have a slight misfire on the journey back, but I just kept the revs low and took my time and it got me back home with only a few minor hiccups.

The next morning the misfire was much more noticeable and it was apparent that would be my first task, but I will leave that for my next post.

Our family pets (the Zoo!).

To go along with our 3 boys we have 2 dogs… Robbie a cross breed, and Sophie a Jack Russell Terrier, both of whom we adopted.

Robbie we adopted Nearly 8 years ago from the NCDL (now Dogs Trust), he was around 5 years old at the time so is getting on a bit now but despite recently going deaf he still has that puppy glint in his eyes. 
 

Sophie (who was previously called Honey), we adopted in spring 2008… my Mother had taken her in from someone living in Auchinleck when she moved to Melrose having lived abroad for a few years, but found her a bit much with work and other commitments, so she came to live with us and was renamed to Sophie.
 

Sophie was around 2 years old when we adopted her and her arrival brought a new lease of life to Robbie, who seems to have enjoyed teaching her how to play fight, eat carrots and many other things.
 

 


 

In addition to the two dogs, we have a hamster, Gilbert, and the wife got herself 4 baby Giant African snails a couple of months ago.

 


 

For Christmas this year, our family expanded again, with the addition of a further two pets…

To our surprise our youngest, Caelan, expressed a strong desire to have a snake several months back. Despite being only four and normally jumping from one thing to the next in a blink of an eye, he kept constant with this one (much to mummy’s delight since she had been dying for an excuse to get one).

So as a nice Christmas surprise, a 3 month old Miami Corn Snake would be his "big" present this year. This was much to his delight since, since he seemed to have expecting a toy one, given they are not common pets. He instantly loved it, was very eager to hold it (even attempting to give it a kiss) and he named it "Boy-snake"!

 


 

The second pet to join our family this Christmas, yet to be named, was a Red Knee Tarantula. 

Kieren, the middle boy, had mentioned sometime ago that he would like a Tarantula. Not to discourage something that would make most kids squirm we told him that he couldn’t just declare that he wanted one… They need looking after, can live for a long time and he doesn’t know what is required or what they need.

To his credit he went off on his own and researched it with impressive vigour. Researching which types are best for pets and indeed which are best as a beginner pet. He researched what they eat, how long they live, how to look after one and showed great enthusiasm giving us lots of information about them. This gave the wife the green light to go off and get a baby spiderling for his Christmas.

Yesterday the Spider shed it’s first skin and ‘Boy-snake’ shed it’s first skin (with us) on boxing day. 

Customizing Visual Studio 2008 Prerequisites to allow for Visual C++ 2005 Runtime


Ran into a problem a while back after upgrading to Visual Studio 2008 and thought I would share it.

When upgrading a group of projects that included an installer project which in turn includes the Visual C++ Runtime (2005) as a prerequisite, to Visual Studio 2008, it is not immediately obvious that the Visual C++ Runtime included in the prerequisites for Visual Studio 2008, is the 2008 version only.

The application I am currently working on uses a third party component whose prerequisite include the Visual C++ 2005 Runtime, so it is not under my control which version it needs. Components developed for C++ 2005 will not register with the C++ 2008 Runtime installed.

In order to include the 2005 runtime in the prerequisites list within you must add them in as custom prerequisites for Visual Studio 2008 installers.

This requires getting a copy of the Visual C++ 2005 Runtime installers and placing them in the Visual Studio 2008 folder (or vice versa). You will need to rename the folders as the folders and file names for 2005 and 2008 C++ Runtimes are the same. Once in the folder they will also need modified so that you can tell which are which (2005 or 2008), you do this by modifying the "package.xml" file held in the "en" folder of each package.

Once modified Visual Studio will pick up the new list and allow you to select the correct version.

Visual Studio 2008’s packages folder has changed and is now in the SDKs folder, on 32bit OS systems this will be "C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages" and on 64bit OS systems it is "C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages" (default paths).

For Visual Studio 2005 the packages folder is in a sub folder of the install path, on 32bit OS systems this is "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages" and on 64bit systems it is "C:\Program Files (x86)\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages" (default paths).

In case you do not have access to the packages for the previous versions, I have uploaded them to my SkyDrive, click here to download.

Paul B
MCP

Supporting custom built email applications (using TELNET to verify connectivity).


As a software developer and/or someone who is involved in the support of software, it is often useful to prove to a client that the software is not at fault. This is doubly important if your application manages emails, given the business relevance email systems have in modern times.

As a developer who may have developed Microsoft Office based Applications in VBA, you may know that if Office get’s stuck waiting on a bit code to complete or some part of the application is taking considerable time to process something, it will appear to freeze. "DoEvents" and some sort of progress windows is always advised, but not always possible if the thing that is being processed is out-with your control.

An example of this would be if you have an Microsoft Office based Application that receives, files and controls emails. If there is a problem with a clients email server, or the pop3 is running very slow, a Microsoft Office based Application would appear to freeze as you cannot process the incoming email on a separate thread.

Other applications based on other languages would be less likely to freeze as the developer will likely have the opportunity to run such processes on a separate thread to the main application. As such the end user would most likely be completely unaware that slow connection or problem with their ISP existed. The developer would have the opportunity to cancel a process after a set timeout if it continued to fail to respond and report it to the user. Even so the client may still assume the blame is with the software rather than their service provider.

It therefore becomes essential to prove your case to a client.


One of the easiest and effective ways of proving your application is not at fault is to use a telnet client.

A telnet client is often overlooked as a tool, as people might assume it is only used to connect to a telnet service but it is very useful tool if you wish to check your email connectivity or even if you just want to see how many emails are waiting for you.

NOTE: Windows has always had it’s own telnet client built in, accessible via the command prompt. As of Windows Vista, it is now an optional component, that you must switch on (via the "Turn Windows Features On or Off" window in "Programs and Features", which is accessed via the control panel).

To run telnet simply open the command prompt on your system and type TELNET and press return.

You will then be in the telnet client. To connect to an email server you must use the OPEN command, ensuring you use the correct port number. The format of the command is: –

open mymail.myserver.com portnumber

Email port numbers, by default, are port 25 for sending and port 110 for receiving emails.


Retrieving emails using Telnet client (POP3)…

So if you wanted to check your incoming mail from your ISP you could type: –

open popmail.ispserver.com 110

You should then get a response code 220 followed by some server details, if your connection was successful and the server is available.

NOTE: Most servers will disconnect quite quickly if you do not submit a request.

To check your inbox you must then authenticate yourself. This is done using the USER command followed by your login name. Once you get an OK response from the server for the user name you must then enter your password with the PASS command. For example: –

user myemail@mydomain.com

Response comes back along the lines of +OK

pass mypassword

Response comes back along the lines of +OK

NOTE: Most basic telnet clients (including the Windows one) do not like it when you use the delete or backspace buttons. So if you miss-type a command you must re enter it, not just delete and type over. This is because the delete character is sent with the command rather than deleting the content you just typed.

Once connected there are several commands you can use, some are: –

LIST
This will show a list of all the emails in your inbox, often only showing the email ID and the size.

RETR +
Where + is the email ID number will retrieve an email (i.e RETR 1 will retrieve email id 1).

DELE +
Where + is the email ID number will delete an email (i.e DELE 1 will delete email id 1).

QUIT
Will close your connection to the server.

If there is a problem with a pop3 server running slow you can use the RETR command to demonstrate the download response of the server when collecting an email. It is not unusual for a pop3 server to slow considerably, it may be a hardware problem, it may be backing up to a backup device, it may even be a DoS (Denial of Service) attack against the server. When using the RETR command the response time is visible as you can see progressively how long it takes for the message to be received as each chunk of data is received and displayed.


Sending emails using Telnet client (SMTP)…

Similarly you can use telnet to check the availability of a clients SMTP server for sending emails.

From the telnet client type: –

open smtpmail.ispserver.com 25

You should get a 220 response message back.

Then you will need to declare where you are sending from, this should really be the fully qualified domain name as seen by the outside world. To do this you must use the HELO command (sometimes EHLO). For example: –

HELO mydomain.com

You should get a 250 response back.

This much tells you if your SMTP server is available.

It gets a bit more complicated if your SMTP server requires authentication, as you must first encode your username and password as Base64. There are some tools online that can do this (though I would never recommend supplying your username and password to one), as well as a few tools you can download.

In the example I use below the base64 encoding of the word username is dXNlcm5hbWU= and of the word password is cGFzc3dvcmQ=

If your SMTP server did require authentication and your username was username. Then the command would be: –

auth login dXNlcm5hbWU=

From which you should get a 334 response back asking for password, if your password was password you would simply respond.

cGFzc3dvcmQ=

From which you should get a 235 response.

Once connected, with ESMTP if required or without if not, you can test the ability to send messages. To send a message you must use the MAIL FROM command, the RCPT TO command and the DATA command. For example: –

MAIL FROM: myemail@mydomain.com

Response should come back as a 250 message.

RCPT TO: myrecipient@theirdomain.com

Response should come back as a 250 message.

DATA<press return>
Subject: My Test Email<press return>
<press return>
My test message goes here.<press return>
.<press return>

The final response you should get back is another 250 message saying it has been sent or queued.


Now obviously if you can send and receive emails with telnet and not your application, and you are certain the problem is not with your application then the cause of your original problem would more likely be some firewall or anti-virus software blocking your application.

Paul B
MCP

How to… Showing a sub-query containing multiple rows within one column of a SELECT statement (using CROSS APPLY and SQL’s XML abilities)

It is often necessary to associate multiple rows within one table to a single row within another table, for example several contacts associated with one company, or several categories mapped to one company or contact.

This raises the problem of how to allow your end users to search on multiple items rather than just one, and only display the main route item once.

Let me explain a scenario that you might encounter: –

You have a contacts table, which is linked to a companies table, which is again liked to a keywords table. Any one company can have multiple contacts and multiple keywords associated with it.

Now say you want to allow a multi keyword search against the keywords table, but the search must contain both keywords for the same company and you only want one row per contact to be returned. You may also simple want to display all the keywords associated with a company when viewing a list of contacts.

Traditionally this would have been quite complex to achieve. However with Microsoft SQL 2005 and above’s support for the CROSS APPLY syntax, this becomes a whole lot easier to achieve.


CROSS APPLY

Put simply CROSS APPLY is an INNER or OUTER JOIN between a table and a table-valued function.

A simple example is one commonly shown for the Microsoft AdventureWorks smaple database: –

USE

AdventureWorks
GO

CREATE

FUNCTION
   
Sales
.fnTopNOrders(
        @CustomerID
AS int, @n AS INT) RETURNS TABLE AS RETURN

    SELECT TOP(@n) SalesOrderID, ShipDate = convert(char(10), ShipDate,112),
       
TotalDue=convert(varchar,TotalDue,1) 
   
FROM AdventureWorks.Sales.SalesOrderHeader
   
WHERE CustomerID = @CustomerID ORDER BY TotalDue DESC
GO

SELECT
   

StoreName=s.Name, [Top].ShipDate, [Top].SalesOrderID, 
    TotalDue
=‘$’+[Top].TotalDue
FROM AdventureWorks.Sales.Store AS s
JOIN AdventureWorks.Sales.Customer AS c
ON s.CustomerID = c.CustomerID
CROSS APPLY
    AdventureWorks.Sales.fnTopNOrders(c.CustomerID, 5) AS [Top]
WHERE CustomerType=‘S’
ORDER BY StoreName, convert(money,TotalDue) DESC
GO

In this sample the table valued function returns the top ‘n’ (largest) orders for a store. The SELECT statement would normally be limited to the stores, but with the CROSS APPLY join in place the store information can be combined (joined) with the results for the top orders (in this case the top 5 orders).

The same result could have been achieved previously by using a temporary table and joining with that, but that can be tedious to code and maintain. Using CROSS APPLY to join the table to the function’s results is much neater and can be quite powerful.


Back to the problem…

So how does CROSS APPLY help us list all our keywords in one column for our company?

Well the answer comes from SQL’s XML features… which can return results as a table-value, with just one row and one column… and we can manipulate the format of the output so we have something nice and neat to work with.


FOR XML Clause

The FOR XML Clause has several modes that can run, which will determine the shape of that resulting XML. These modes are RAW, AUTO, EXPLICIT or PATH.

Without going into too much detail the mode we are interested in to help us with our problem is the PATH mode. So using the FOR XML PATH clause in our query.

The PATH mode provides a simple way to mix elements and attributes, in order to output some XML based on our data (for more in-depth details take a look at http://msdn2.microsoft.com/en-us/library/ms189885.aspx and http://msdn2.microsoft.com/en-us/library/ms190922.aspx)

Now in our problem we are looking to retrieve all the keywords for a company and display them in one column. So lets see what we can get from SQL using the FOR XML Clause

If we simply used the following: –

SELECT Keyword FROM cont_keyword WHERE CompanyID = 19
FOR XML PATH

We would be returned one table with one row that had one column. The contents of which would be something like: –

<

row>
    <
Keyword>Reseller</Keyword>
</
row>
<
row>
    <
Keyword>Ireland</Keyword>
</
row>

This is not quite what we are looking for so we shall modify the PATH mode clause to exclude the row element: –

SELECT

Keyword FROM cont_keyword WHERE CompanyID = 19
FOR XML PATH() 
 

Now we get an output something like: –

<

Keyword>Reseller</Keyword>
<
Keyword>Ireland</Keyword>

Which is getting much closer to something we can use. Now when outputting something as XML in SQL we can tell it to output any column as plain text without any element wrappers. This would be done as follows: –

SELECT

Keyword AS [text()] FROM cont_keyword
WHERE CompanyID = 19 FOR XML PATH()

But this isn’t much use as it would just return one string with every keywords joined together.

ResellerIreland

Modifying our query a little more will allow us to separate our keywords: –

SELECT

‘{’ + Keyword + ‘},’ AS [text()]
FROM cont_keyword WHERE CompanyID = 19
FOR XML PATH()
 

I have chosen to surround my keywords with "{" and "}", this is to allow me to easily search for specific categories in a LIKE statement. If I simply separated them with commas then if there are categories that are similar (for example "Architect", "Structural Architect", etc), I would run into problems when constructing my LIKE clause. The query above would give a result looking something like: –

{Reseller},{Ireland},

Now this is something that we can use… well almost, you’ll notice if you run it, the table column is given a random XML name, as is the table the result is presented in.

SIDE NOTE: – You may have been tempted to use square brackets instead ("[" and "]") but we must remember that these are reserved characters in LIKE statements and would have to be escaped in any subsequent queries.

We could use something like: –

SELECT (SELECT ‘{’ + Keyword + ‘},’ AS [text()]
FROM cont_keyword WHERE CompanyID = 19
FOR XML PATH()) AS keywordlist
 

If we just need the column to have a legible name, but for our purposes we also need it defined as a table, which is where part of the CROSS APPLY syntax comes in…


Back to the problem

Now we shall use a simple CROSS APPLY join to join our companies table and keywords table-value together.

SELECT
    cont_companies
.*, 
    LEFT(kw.keywordlist, LEN(kw.keywordlist) 1) AS keywords
FROM 
    cont_companies
CROSS APPLY
    (SELECT ‘{’ + Keyword + ‘},’ AS [text()]
        FROM cont_keyword
        WHERE COMPANYID = cont_companies.Company_ID
        FOR XML PATH()) kw(keywordlist)

Now in the code above you will notice a few things, first of, we only select all the columns from the companies table, then we actually take the output from our XML table and trim of the last comma character. The CROSS APPLY join is done based on the CompanyID filed, so we will get a list of keywords for each company.

In this sample the keyword output is put into a table called ‘kw’, with the column ‘keywordlist’. ‘keywordlist’ contains the full output from the XML query we built above, which includes an additional comma on the end. In our select statement we disregard this because it is not needed, and should not be displayed at anypoint to end users.

We only select all the columns from the companies table, plus the trimmed keywords column, because we are not interested in any of the other data that will be in the ‘kw’ temporary table, so why return it.

For my purposes I’d actually define this query as a view… you need to watch however, because SQL 2005 Management Studio will tell you that it cannot parse the query if you edit or create this through the designed… Along with a couple of other features (like ‘newsequentialid()’) the SQL 2005 Management Studio isn’t able to parse this type of query and will tell you so.

Because the FOR XML clause will only ever return one row an alternative to using the CROSS APPLY join would be to call the FOR XML clause SELECT twice, for example: –

SELECT

cont_companies.*,
    LEFT((
        SELECT ‘{’ + Keyword + ‘},’ AS [text()]
        FROM cont_keyword
        WHERE CompanyID = cont_companies.Company_ID
        FOR XML PATH())
        ,
        LEN((
            SELECT ‘{’ + Keyword + ‘},’ AS [text()]
            FROM cont_keyword
            WHERE CompanyID = cont_companies.Company_ID
            FOR XML PATH())
        ) 1 ) AS keywords
FROM cont_companies

Now this will give the same result but would run the FOR XML clause query twice, and if we have several thousand companies with several keywords associated with each, then the overhead of running the FOR XML twice as many times as is needed will soon add up.

Using the CROSS APPLY join should therefore be more efficient and easier to manage.

Update: – 22nd March 2008
======================

On further usage I found that the resulting "keywords" column from both the queries shown above would, on occasion, return the data as binary data. As such they should have a CONVERT statement applied to them in order to correctly return the required data as a TEXT data-type, i.e.: –

SELECT
    cont_companies
.*, 
    CONVERT(TEXT,
    LEFT(
kw.keywordlist, LEN(kw.keywordlist) 1)
   
)
AS keywords
FROM 
    cont_companies
CROSS APPLY
    (SELECT ‘{’ + Keyword + ‘},’ AS [text()]
        FROM cont_keyword
        WHERE COMPANYID = cont_companies.Company_ID
        FOR XML PATH()) kw(keywordlist)

or

SELECT

cont_companies.*,
    CONVERT(TEXT, LEFT((
        SELECT ‘{’ + Keyword + ‘},’ AS [text()]
        FROM cont_keyword
        WHERE CompanyID = cont_companies.Company_ID
        FOR XML PATH())
        ,
        LEN((
            SELECT ‘{’ + Keyword + ‘},’ AS [text()]
            FROM cont_keyword
            WHERE CompanyID = cont_companies.Company_ID
            FOR XML PATH())
        ) 1 )) AS keywords
FROM cont_companies

The SQL & MS Access LIKE statement

Anyone who deals with SQL and Access queries is probably familiar with the LIKE statement and the use of the standard wildcards (% or *).

Less documented however is the additional wild cards that you should be aware of if you are building queries that will be run against your database when constructing more complicated LIKE statements.

The following describes the wildcard’s that are available in Microsoft SQL.

Microsoft T-SQL LIKE statement wildcards

Wildcard character

Description

Example

%

Any string of zero or more characters.

SELECT * FROM [myTable] WHERE [title] LIKE ‘%computer%’This will find all the entries the column ‘title’ from ‘myTable’ contains the word ‘computer’ anywhere in the field.

_ (underscore)

Any single character.

SELECT

* FROM [myTable] WHERE [surname] LIKE ‘sm_th’

This will find all five-letter last names that start with ‘sm’ and end with ‘th’ (for example Smith, Smeth, etc).

[…]

Any single character within the specified range ([a-f]) or set ([abcdef]) or a numeric range [0-9].

SELECT

* FROM [myTable] WHERE [surname] LIKE ‘[C-P]arsen’

This will find all the last names ending with arsen and starting with any single character between C and P, for example Carsen, Larsen, Karsen, Paresen, etc.

[^]

Any single character not within the specified range ([^a-f]) or set ([^abcdef]).

SELECT

* FROM [myTable] WHERE [surname] LIKE ‘de[^l]%’

This will get all the last names starting with de and where the following letter is not l, for example it will return Desmond, but not Delimetry.

In addition after an MS SQL LIKE statement you can include an additional definition of an escape character, which can be placed in front of a wildcard character so that it is treated as a regular character or you could the square brackets as escape characters for your query, for example: –

SELECT * FROM [discounts] WHERE [discount] LIKE ’30[%]’ 
                    or
SELECT * FROM [discounts] WHERE [discount]
   
LIKE ’30!%’ ESCAPE ‘!’

This will select any discounts that are 30%.

Reference: – http://msdn2.microsoft.com/en-us/library/ms179859.aspx


In Microsoft Access the wildcards and syntax is slightly different but similar

The following describes the wildcard’s that are available in Microsoft Access.

Microsoft Access Query LIKE statement wildcards

Wildcard character

Description

Example

*

Any string of zero or more characters.

SELECT

* FROM [myTable] WHERE [title] LIKE ‘*computer*’

This will find all the entries the column ‘title’ from ‘myTable’ contains the word ‘computer’ anywhere in the field.

?

Any single character.

SELECT * FROM [myTable] WHERE [surname] LIKE ‘sm?th’

This will find all five-letter last names that start with ‘sm’ and end with ‘th’ (for example Smith, Smeth, etc).

[…]

Any single character within the specified range ([a-f]) or set ([abcdef]).

SELECT

* FROM [myTable] WHERE [surname] LIKE ‘[C-P]arsen’ 

This will find all the last names ending with arsen and starting with any single character between C and P, for example Carsen, Larsen, Karsen, Paresen, etc.

[!…]

Any single character not within the specified range ([!a-f]) or set ([!abcdef]).

SELECT

* FROM [myTable] WHERE [surname] LIKE ‘de[!l]*’

This will get all the last names starting with de and where the following letter is not l, for example it will return Desmond, but not Delimetry.

#

Any single numeric character.

SELECT

* FROM [myTable] WHERE [postcode] LIKE ‘eh## #??’

This will get all the postcodes starting with EH followed by two numeric characters a space another numeric character and a further two characters, for example it would return EH54 6LY, EH55 8TZ, etc but not EH4 1NJ.

In Microsoft Access there is no ESCAPE definition so you must use square brackets, for example: –

SELECT

* FROM [discounts] WHERE [discount] LIKE ’30[%]’

This will select any discounts that are 30%.


Query Syntax in Microsoft Access on Linked SQL tables 

Now as anyone who has worked with Microsoft Access linked SQL tables will likely know, performing like queries can be complicated… this is because it can sometimes be difficult to determine where a SQL statement/query might be executed (on the SQL Server or my the local Microsoft Access engine).

As a general rule of thumb, if you are running the statement (Microsoft Access query or Access DAO Recordset) directly against the linked table, it will use the SQL Server syntax. For example: –

SELECT

* FROM tblContacts
   
WHERE Firstname LIKE ‘Pa%’ –[WILL WORK]

If however your WHERE clause is in a join query or your query runs some kind of function on some of the data. For example : –

[a bad example at that, yes I would never recommend a statement like this]
SELECT Nz(contactid, 1), * FROM tblContacts
   
WHERE Firstname LIKE ‘Pa%’ –[WILL NOT WORK]

This statement would not return the correct results and would have to be modified to the following: –

SELECT

Nz(contactid, 1), * FROM tblContacts
   
WHERE Firstname LIKE ‘Pa*’ –[WILL WORK]

A more likely scenario would be where you are joining two tables within your Microsoft Access query and you needed to perform a like statement against it. For example: –

SELECT

* FROM tblContacts
   
LEFT JOIN tblCompanies ON
    tblContacts
.CompanyID = tblCompanies.CompanyID
   
WHERE Firstname LIKE ‘Pa%’ –[WILL NOT WORK]

In MS Access this statement would not return the correct results, as the join is performed by MS Access and would have to be modified to the following: –

SELECT

* FROM tblContacts
   
LEFT JOIN tblCompanies ON
        tblContacts
.CompanyID = tblCompanies.CompanyID
   
WHERE Firstname LIKE ‘Pa*’ –[WILL WORK]

This also applies to any MS Access Queries based other queries, even if the other Query is a direct Query against a linked SQL table.


Something to remember when generating queries from code

Now you are most likely writing code that will generate your LIKE statements based on a user’s selections. This applies whether you are coding from MS Access or through ASP, ASP.NET, VB, etc… but not if you are running through parameterised objects, for example a .NET 2.0 dataset query.

If you have to code in your SQL you need to remember to change key characters, this means these reserved wildcards as well as the standard single quotes. For example (in VBA): –

SET

myRS = CurrentDB.OpenRecordset( _
   
"SELECT * FROM tblContacts WHERE FirstName LIKE ‘" & _
    Replace(someVariable,
"’","”",1,-1,1) & "%’", _
    dbOpenDynaset, dbSeeChanges)
‘[WILL NOT WORK]

Though this will work in most situations, if "someVariable" contained any of the wildcard characters, you will experience unexpected results. So you should really do something more like: –

SET

myRS = CurrentDB.OpenRecordset( _
  
"SELECT * FROM tblContacts WHERE FirstName LIKE ‘" & _
    Replace( _
     Replace( _
      Replace( _
       Replace( Replace( _
        Replace(someVariable,
"’","”",1,-1,1) _
       ,
"%","[%]",1,-1,1), "#","[#]",1,-1,1) _
      ,
"[","[[]",1,-1,1) _
     ,
"]","[]]",1,-1,1) _
    ,
"_","[_]",1,-1,1) & "%’", _
    dbOpenDynaset, dbSeeChanges)
‘[WILL WORK IF RAN AGAINST SQL LINKED TABLE]

Or if ran against MS Access table or situation described above: –

SET

myRS = CurrentDB.OpenRecordset( _
    "SELECT * FROM tblContacts WHERE FirstName LIKE ‘" & _
     Replace( _
      Replace( _
       Replace( _
        Replace( Replace( _
         Replace(someVariable,
"’","”",1,-1,1) _
        , "*","[*]",1,-1,1), "#","[#]",1,-1,1)
_
       ,
"[","[[]",1,-1,1) _
      ,
"]","[]]",1,-1,1) _
     ,
"?","[?]",1,-1,1) & "*’", _
    dbOpenDynaset, dbSeeChanges) 
‘[WILL WORK IF RAN AGAINST ACCESS TABLE/QUERY]

As with any form of coding it is usually a good idea to extract out common functions into a small library that can be used throughout your applications.

As such if you do build queries based on user input I’d recommend using a standard function to tidy up any users data and prevent possible conflicts or security flaws. The function could look something like (VBA): –

Function

fnTidyLikeQuery(ByVal myString As Object) As String

        On Error Goto ExitHere
   
‘as it does no harm to escape all characters 
    ‘(SQL or Access specific) this function can 
    ‘take care if both).

    If isNull(myString) Then
       
Goto ExitHere
   
Else
       
fnTidyLikeQuery = _
            Replace( Replace( _
             Replace( Replace( _
               Replace( Replace( _
                Replace( Replace( _
                 myString _
                ,
"’","”",1,-1,1), "#","[#]",1,-1,1) _
               ,
"*","[*]",1,-1,1), "[","[[]",1,-1,1) _
              ,
"%","[%]",1,-1,1), "_","[_]",1,-1,1) _
            
, "]","[]]",1,-1,1), "?","[?]",1,-1,1) 
   
End If

    Exit Funtion
ExitHere:
    fnTidyLikeQuery =
""
End Function

Function

fnTidyQueryString(byVal myString as Variant) as String

    On Error Goto ExitHere

    If isNull(myString) Then
       
Goto ExitHere
   
Else
        fnTidyQueryString= Replace(myString, "’","”",1,-1,1) 
   
End If

    Exit Funtion
ExitHere:
    fnTidyQueryString =
""
End Function

A sample query based on the above functions would look something like: –

SET

myRS = CurrentDB.OpenRecordset( _
    "SELECT * FROM tblContacts WHERE FirstName LIKE ‘*" & _
    fnTidyLikeQuery(someVariable) &
"*’ AND Surname = ‘" & _
    fnTidyQueryString(someOtherVariable) &
"’", _
    dbOpenDynaset, dbSeeChanges) 
‘[WILL WORK IF RAN AGAINST ACCESS TABLE/QUERY (uses * as wildcard)]


Some more samples of LIKE statements

SQL Clause

Access Equivalent

Result

Like ‘%’

Like ‘*’

All except blanks

Like ‘A%’

Like ‘A*’

Starting with ‘A’

Like ‘%A%’

Like ‘*A*’

‘A’ somewhere in the field

Like ‘[A,B,D]’
  or
Like ‘[ABD]’

Like ‘[A,B,D]’
  or
Like ‘[ABD]’

One character an ‘A’ or ‘B’ or ‘D’

Like ‘[A-C]%’

Like ‘[A-C]*’

One character ‘A’ through ‘C’ as the first character

Like ‘[A-C][A-H]%’

Like ‘[A-C][A-H]*’

‘A’ through ‘C’ as the 1st character and ‘A’ through ‘H’ as the 2nd character

Like ‘SM_TH’

Like ‘SM?TH’

Starting with ‘Sm’, ending with ‘th’, and anything for the 3rd character

Like ‘[0-9]%’

Like ‘[0-9]*’
  or
Like ‘#*’

Digit for the 1st character

Like ‘[^a-c]’

Like ‘[!a-c]’

Not in a range of letters ‘a’ through to ‘c’

Like ‘[^a-c]%’

Like ‘[!a-c]*’

Not start with a range of letters

Like ‘[^0-9]%’

Like ‘[!0-9]*’

Not start with a number

How To… Referencing The DAO Engine

In VB Script (or sometimes in general application development and office application development where late binding is needed), it is necessary to create a DAO engine object. Either to access database through DAO, or given there are better ways to access the data more likely to access properties within an Access Database file, and manage tables or hidden file properties for a legacy system.
 
Traditionally you may have used something like: –

Dim

DBEngine As Object
Set DBEngine = CreateObject("DAO.DBEngine")

Which you would expect to get the latest installed/registered version of the engine. This however nolonger works, so we must be a little more indepth and use a function to get our object. Such as: –

Public

Function DAO_DBEngine() As Object

On Error Resume Next

‘try Access 2007 (DAO Engine version 120)
Set GetDBEngine = CreateObject("DAO.DBEngine.120")

If Err.Number <> 0 Then

‘try DAO version 3.6
Err.Clear
Set GetDBEngine = CreateObject("DAO.DBEngine.36")

If Err.Number <> 0 Then

Set GetDBEngine = CreateObject("DAO.DBEngine.35")

End If

End If

End Function

If the function returns nothing the object has not been created, otherwise it will return the latest available version of DAO, and allow the querying of the database engine.

How To… Adding a standard field to all tables in a Large SQL Database

On occasion you may need to add a standard field to a group of tables within your database or to them all.
 
The following script sample shows how to add a standard field to all tables, simply filtering the table list will allow this to be aplied to a selection of tables… In this sample I show adding a Microsoft TimeStamp field to all tables within a database: –

 

USE

[MyDB]
GO

DECLARE

@TableNames TABLE (TableTempID bigint IDENTITY(1,1) PRIMARY KEY CLUSTERED, TableName varchar(2000))

INSERT INTO @TableNames (TableName) (select [name] from dbo.sysobjects where OBJECTPROPERTY(id, N‘IsUserTable’) = 1 AND OBJECTPROPERTY(id,‘ismsshipped’) = 0)

DECLARE @Counter bigint
DECLARE
@MaxTableCount bigint

SELECT @MaxTableCount = max(TableTempID) from @TableNames
SET @Counter = 1

DECLARE @TableName Varchar(2000)
DECLARE @sqlQuery nVarchar(4000)

WHILE (@Counter <= @MaxTableCount)
BEGIN

    SELECT @TableName = ltrim(rtrim(TableName)) FROM @TableNames WHERE TableTempID = @Counter
    SET @sqlQuery = ‘ALTER TABLE [‘ + @TableName +
    ‘] ADD MS_TSTAMP TIMESTAMP’
    EXECUTE sp_executesql @sqlQuery
    SET @Counter = @Counter + 1

END

  

How To… Changing TEXT or NTEXT field to a VARCHAR field

As there is no direct way of converting a TEXT or NTEXT field to VARCHAR, it is necessary to rename your existing field, create a new field of the required size and then copy the existing data across, converting it, and then dropping the old column.
 
The following sample shows how to achieve this: –

EXEC

sp_rename ‘table_name.original_field_name’, ‘temp_field_name’, ‘COLUMN’
go
ALTER TABLE [table_name] ADD [original_field_name] VARCHAR(1000)
go
UPDATE [table_name] SET [original_field_name] = SUBSTRING([temp_field_name], 1, 1000)
go
ALTER TABLE [table_name] DROP COLUMN [temp_field_name]
go

 
 

Working with Microsoft Word files – “-2147319779 Automation error Library not registered” error message when creating object.


A few weeks back I had a problem on a clients site, our application which interacts with Word files, was falling over when trying to create a Word object. This was not a problem with our coding as it has been working fine for many years and this problem was specific to a couple of workstations.

The error given was "-2147319779 Automation error Library not registered".

This error does happen if parts of Office become corrupt and sometimes if the normal.dot template file is corrupted, so tried all the usual fixes to correct these problems. None of these seemed to work and correct the fact that it could not create a Word object.

On further investigation I had found that the client had decided to try out Office 2007 on the two workstations in questions, they later decided they did not like it that much, uninstalled and re-installed Office 2003.

When Office 2007 is uninstalled, by default it leaves behind it’s Interop Assemblies. These must be removed separately, which the users had not done. Uninstalling ”Microsoft Office 2007 Primary Interop Assemblies” though the control panel -> add and remove programs, corrected the problem.

Problem: error code "-2147319779 Automation error Library not registered" when trying to work with Word files or Excel from separate application.
Cause: Installing Office 2007 and then removing it at later date replacing with previous version.
Resolution: Uninstall ”Microsoft Office 2007 Primary Interop Assemblies”.
Cause of Problem: Microsoft Office. 

Paul B
MCP