Ever forget to send an attachment to someone? The following code will search your outgoing message text for the word "attach." You can add as many words as you want. The code needs to go in the ThisOutlookSession module in Outlook VBA; make sure macro security is not set to High.
Private Sub Application_ItemSend(ByVal Item as Object, Cancel as Boolean)
If Instr(1, Item.Body, "attach", vbTextCompare) > 0 Then
If Item.Attachments.Count = 0 Then
MsgBox "You forgot the attachment."
Cancel = True
End If
End If
End Sub
For more examples of VBA using Outlook, check out Sue Mosher's site OutlookCode.
Tuesday, October 26, 2004
Microsoft Access Performance Considerations
While Access is certainly NOT an enterprise solution for databases, it can certainly provide small to medium size businesses with a very valid solution for their database needs. It's very common to read discussions about how Access cannot handle a multi-user environment or is too slow to use across the network. Most of the time, I find there are major design flaws with the databases these people are using. For most developer, there a few simple configurations and code tricks that can be used to attain reasonable performance from Access.
First, it is VERY possible to create a successful client/server application using linked tables for up to 20 concurrent users. But there are lots of details you must fix to be successful. To get good performance, it's all about keeping the "wire" cool -- that is, making sure you minimize the amount of data you ask JET to fetch and the number of threads you have open to the file server.
One of the most insidious "wire" eaters in Access 2000 and later is the silly Subdatasheet feature. If you leave SubdatasheetName set to [Auto], Access opens an extra thread AND fetches the rows for any recordset built on the table. You'll see a dramatic improvement in performance if you go to all the tables in the data file on the server and set this property for each table to [None].
You must also design your application to minimize the data fetched to the client machine. It is NOT true that JET on the client always does a full table scan. If you apply a filter on an indexed field, JET first fetches the index blocks and then sends specific requests to the file server for the data pages it needs to satisfy the request. So, it's important to both define appropriate indexes on your tables AND to NEVER request "all the tea in china" by opening a form or report unfiltered.
You should design custom "query by form" forms into your application that prompt the user to focus on the records needed for the task at hand, and the QBF code should refuse to proceed unless the user specified at least one filter value. You should also avoid any combo boxes that return more than about 200 rows. Although Access does try to optimize the combo box row source by not fetching all the rows until the user types in the box or drops it down, fetching 1000+ rows in a combo box is a performance killer. Most successful applications provide a way for the user to filter the combo first - for example, by selecting a product category that then filters the product name list. One alternative is to write code in the Change event to lookup the closest value for the user as the user types the entry they want. Single probes via DLookup are much more efficient than fetching the 1000+ row recordset.
In applications that might release the last active thread by returning to an unbound switchboard, performance can be improved by keeping one recordset open in code as long as the application is running. When you close the last bound form or report, Access releases the thread to the server - and if someone else has the data database open, it can take seconds to reopen the thread. If you keep a thread open, other processes in the application will share the open thread.
For reports, consider fetching the "unnormalized" report data to a local table and bind the report to that. Depending on how your report is designed, the report engine can end up fetching all the data from the server four or five times before it displays the result. Also, if you open a large report in Print Preview and the user pages back and forth in the result, the report engine will fetch the data several times again.
Be advised that "upsizing" to SQL Server is not a panacea. If your appication is badly designed for client/server, it won't run any better on SQL Server, and might actually run worse. You should upsize only when you need to manage more than 200 meg of data, you need true security, or you need to support more than 20 simultaneous users.
As for lookups - don't use them, at least not in table definitions. Check out my previous discussion for more information.
First, it is VERY possible to create a successful client/server application using linked tables for up to 20 concurrent users. But there are lots of details you must fix to be successful. To get good performance, it's all about keeping the "wire" cool -- that is, making sure you minimize the amount of data you ask JET to fetch and the number of threads you have open to the file server.
One of the most insidious "wire" eaters in Access 2000 and later is the silly Subdatasheet feature. If you leave SubdatasheetName set to [Auto], Access opens an extra thread AND fetches the rows for any recordset built on the table. You'll see a dramatic improvement in performance if you go to all the tables in the data file on the server and set this property for each table to [None].
You must also design your application to minimize the data fetched to the client machine. It is NOT true that JET on the client always does a full table scan. If you apply a filter on an indexed field, JET first fetches the index blocks and then sends specific requests to the file server for the data pages it needs to satisfy the request. So, it's important to both define appropriate indexes on your tables AND to NEVER request "all the tea in china" by opening a form or report unfiltered.
You should design custom "query by form" forms into your application that prompt the user to focus on the records needed for the task at hand, and the QBF code should refuse to proceed unless the user specified at least one filter value. You should also avoid any combo boxes that return more than about 200 rows. Although Access does try to optimize the combo box row source by not fetching all the rows until the user types in the box or drops it down, fetching 1000+ rows in a combo box is a performance killer. Most successful applications provide a way for the user to filter the combo first - for example, by selecting a product category that then filters the product name list. One alternative is to write code in the Change event to lookup the closest value for the user as the user types the entry they want. Single probes via DLookup are much more efficient than fetching the 1000+ row recordset.
In applications that might release the last active thread by returning to an unbound switchboard, performance can be improved by keeping one recordset open in code as long as the application is running. When you close the last bound form or report, Access releases the thread to the server - and if someone else has the data database open, it can take seconds to reopen the thread. If you keep a thread open, other processes in the application will share the open thread.
For reports, consider fetching the "unnormalized" report data to a local table and bind the report to that. Depending on how your report is designed, the report engine can end up fetching all the data from the server four or five times before it displays the result. Also, if you open a large report in Print Preview and the user pages back and forth in the result, the report engine will fetch the data several times again.
Be advised that "upsizing" to SQL Server is not a panacea. If your appication is badly designed for client/server, it won't run any better on SQL Server, and might actually run worse. You should upsize only when you need to manage more than 200 meg of data, you need true security, or you need to support more than 20 simultaneous users.
As for lookups - don't use them, at least not in table definitions. Check out my previous discussion for more information.
The Access Web - The Evils of Lookup Fields in Tables
The Access Web - The Evils of Lookup Fields in Tables
Lookup fields are a hot topic for debate, that's for sure. If you frequent Access newsgroups or discussion forums, you've probably seen at least one heated thread about whether developers should use lookup fields in their tables. Personally, I tend to agree lookup fields should NOT be used. The misunderstanding most people have regarding this stance is that they believe the functionality of lookup fields is lost. Not true.
Unless you have a *rock solid* understanding of relational theory and the detailed structure of your database, it is a very bad idea. It gets you very little - it makes your table datasheet view more human readable, at the cost of concealing the actual contents of the table, and makes sorting and searching the table more difficult; it also makes it a tiny bit easier to put combo boxes on forms instead of textboxes. But I advise people never to use this misfeature; just put the combo boxes on Forms, which is what you should be using to look at your data anyway. Table datasheets are for debugging; and as such they should display what they actually contain rather than concealing it.
You can very easily store your tables with the actual data that they contain visible, and use a Query or a combo box or other control on a Form or Report to retrieve data from the linked ("lookup", if that's what you want to call it) table. What I object to is the perverse idea that you should define a lookup *FIELD* in your main table, and use it in your table datasheet. This accomplishes absolutely nothing other than to conceal the actual contents of your table, cause confusion about the relational structure of your data, and mislead users into trying to do data entry and searching in table datasheets instead of properly doing so on Forms.
It's perfectly easy to put a Combo Box - a "lookup", if you want to call it that - on a Form. The table lookup wizard makes this the default, but at the cost of concealing the actual contents of your table; encouraging you to use table datasheets for data entry, almost never a good idea; creating a new relationship between the main table and the lookup table even if such a relationship already exists; and adding redundant indexes to your table. If you leave the tables alone, and use a Form to do your data entry, you get the best of both worlds. Use table datasheets for debugging and design ONLY, and use the Form and the rich kit of tools that the Form provides for your data entry.
For more detailed information, follow this articles link to the MVPs Access site.
Lookup fields are a hot topic for debate, that's for sure. If you frequent Access newsgroups or discussion forums, you've probably seen at least one heated thread about whether developers should use lookup fields in their tables. Personally, I tend to agree lookup fields should NOT be used. The misunderstanding most people have regarding this stance is that they believe the functionality of lookup fields is lost. Not true.
Unless you have a *rock solid* understanding of relational theory and the detailed structure of your database, it is a very bad idea. It gets you very little - it makes your table datasheet view more human readable, at the cost of concealing the actual contents of the table, and makes sorting and searching the table more difficult; it also makes it a tiny bit easier to put combo boxes on forms instead of textboxes. But I advise people never to use this misfeature; just put the combo boxes on Forms, which is what you should be using to look at your data anyway. Table datasheets are for debugging; and as such they should display what they actually contain rather than concealing it.
You can very easily store your tables with the actual data that they contain visible, and use a Query or a combo box or other control on a Form or Report to retrieve data from the linked ("lookup", if that's what you want to call it) table. What I object to is the perverse idea that you should define a lookup *FIELD* in your main table, and use it in your table datasheet. This accomplishes absolutely nothing other than to conceal the actual contents of your table, cause confusion about the relational structure of your data, and mislead users into trying to do data entry and searching in table datasheets instead of properly doing so on Forms.
It's perfectly easy to put a Combo Box - a "lookup", if you want to call it that - on a Form. The table lookup wizard makes this the default, but at the cost of concealing the actual contents of your table; encouraging you to use table datasheets for data entry, almost never a good idea; creating a new relationship between the main table and the lookup table even if such a relationship already exists; and adding redundant indexes to your table. If you leave the tables alone, and use a Form to do your data entry, you get the best of both worlds. Use table datasheets for debugging and design ONLY, and use the Form and the rich kit of tools that the Form provides for your data entry.
For more detailed information, follow this articles link to the MVPs Access site.
Saturday, October 23, 2004
Filter a report using a form's filter
There is a relatively simple way of filtering a report by using the filter property of a form. This allows the user to filter any field on the form and then print the displayed records in a report. Now, instead of creating complex queries or multiple controls on a criteria form, you can let the users decide how to filter the report.
This form is based on the customers table from the Northwind sample database. The country field has been filtered to show only those customers located in Germany.
Now, place the following code behind the Preview Report button's On Click event:
DoCmd.OpenReport "rptCustomers", acPreview, , IIf(Me.FilterOn = True, Me.Filter, "")
Now, once the user clicks on the the Preview Report button, the report uses the filter property of the form, if a filter has been set.
This report is now filtered using the same filter used on the form.
This form is based on the customers table from the Northwind sample database. The country field has been filtered to show only those customers located in Germany.
Now, place the following code behind the Preview Report button's On Click event:
DoCmd.OpenReport "rptCustomers", acPreview, , IIf(Me.FilterOn = True, Me.Filter, "")
Now, once the user clicks on the the Preview Report button, the report uses the filter property of the form, if a filter has been set.
This report is now filtered using the same filter used on the form.
Tuesday, October 19, 2004
Lookout email search for Microsoft Outlook
Lookout email search for Microsoft Outlook - Lookout Software
Ok, one more on the software side of things - search Outlook for all those emails, contacts, and calendar items you thought you had lost!
You can use Lookout to search your:
This software was recently acquired by Microsoft and is tightly integrated with Outlook. Definitely worth checking out if you live and breath Outlook.
Ok, one more on the software side of things - search Outlook for all those emails, contacts, and calendar items you thought you had lost!
You can use Lookout to search your:
- Email messages
- Contacts, calendar, notes, tasks, etc.
- Data from exchange, POP, IMAP, PST files, Public Folders
- Files on your computer or other computers
- ... your wife's purse (okay, not true)
This software was recently acquired by Microsoft and is tightly integrated with Outlook. Definitely worth checking out if you live and breath Outlook.
Google Desktop Beta
How many times have you thought it would be great to be able to search your desktop as easily as Google searching the web?
Google Desktop Beta
- Find your email, files, web history and chats instantly
- View web pages you've seen, even when you're not online
- Search as easily as you do on Google
Google Desktop Beta
Ad Ware Removal and Detection
Lately, spam has taken a back seat to Ad Ware. What is Ad Ware and Spyware? Spyware and Adware is software made by publishers that allow them to snoop on your browsing activity, invade your privacy, and flood you with those horrible popups. If you are like most users on the internet, chances are you are probably infected with these applications. Unfortunately, most novice users don't know how to remove these pesky softwares. I've compiled a list of the software I recommend that can help you rid your computer of Ad Ware and Spyware.
While these programs are free, Ad-Aware has a professional version which does real-time monitoring - sort of like most firewall software. The free version is sufficient, but the professional version has some great tools that make it worth the upgrade price.
Ad-Aware
Spybot
HijackThis
These three are consistently the best reviewed in their categories, Spybot being the most prevalent. HijackThis uses a slightly different approach than Ad-Aware and Spybot. HijackThis scans your hard drives and registry for commonly used attack methods - not specific URLs and registry entrys like the other two. I would recommend loading all three. Several articles I have read indicate that Spybot and Ad-Aware will detect some entries the other doesn't.
More information and reviews can be obtained by reading:
CNET Spyware Reviews
Spybot Overview
While these programs are free, Ad-Aware has a professional version which does real-time monitoring - sort of like most firewall software. The free version is sufficient, but the professional version has some great tools that make it worth the upgrade price.
Ad-Aware
Spybot
HijackThis
These three are consistently the best reviewed in their categories, Spybot being the most prevalent. HijackThis uses a slightly different approach than Ad-Aware and Spybot. HijackThis scans your hard drives and registry for commonly used attack methods - not specific URLs and registry entrys like the other two. I would recommend loading all three. Several articles I have read indicate that Spybot and Ad-Aware will detect some entries the other doesn't.
More information and reviews can be obtained by reading:
CNET Spyware Reviews
Spybot Overview
Monday, October 18, 2004
What is XML?
Microsoft Office Assistance: XML for the uninitiated
XML for the uninitiated
A relatively common question in class these days - what is XML? Microsoft has provided support for it in most of their products, including, Office and SQL Server. XML really has revolutionized data distribution across the web. This article gives a very good overview in non techy terms.
XML for the uninitiated
A relatively common question in class these days - what is XML? Microsoft has provided support for it in most of their products, including, Office and SQL Server. XML really has revolutionized data distribution across the web. This article gives a very good overview in non techy terms.
Saturday, October 16, 2004
Free Virus Protection
One of the most common "excuses" I hear about not using virus protection is that it costs too much. After you buy the software, there are usually subscriptions you must pay for to keep your software up to date - preventing new viruses from attacking your computer. I have found this AVG Anti-virus to be one of the best virus protection programs available - and it's free for personal use.
What is RSS?
RSS is quickly becoming a new standard for reading news sites, blogs and other syndicated web sites. It's like a customized news reader. Click here to read an article on CNET that gives a good overview of RSS, how to use it, and what software you'll need to get started. Don't forget ... most blogs have a RSS feed. Add the link below to your RSS reader to receive the feed for this site.
http://dbageek.blogspot.com/atom.xml
I have found Pluck to be the best RSS reader. Best of all, it's FREE!
http://dbageek.blogspot.com/atom.xml
I have found Pluck to be the best RSS reader. Best of all, it's FREE!
Recommended Books
Here is a list of books I recommend for database development.
Access
SQL Server and Programming
Crystal Reports
Access
- Access 2002 Developer's Handbook
- Running Access 2000
- Access Inside and Out2000
- Access 2003 VBA Programmer's Reference
- Real World Access Database Protection and Security
- Building Microsoft Access Applications
SQL Server and Programming
- SQL Queries for Mere Mortals
- Microsoft SQL Server Administrator's Pocket Consultant
- Professional SQL Server 2000 DTS
- Inside SQL Server
- SQL Server Reporting Services
- Pro SQL Server Reporting Services
- The Guru's Guide to SQL Server Stored Procedures, XML and HTML
Crystal Reports
- Crystal Reports 10
- Crystal Reports .NET Programming
- CRCP Crystal Reports Certified Profession All-in-one Exam Guide
- XML In Easy Steps - Hard to find (only at Barnes & Noble stores - not online)
Thursday, October 14, 2004
Access Articles
Below are a list of useful links for Access classes by level, as well as, general Access articles.
Access 97 Expert Solutions - a little dated, but still very useful information; this is an online book.
Access Level 3:
Guide to Access database corruption
General Microsoft Access tips, tricks, and articles.
Access Performance Considerations
More Performance Tips
Should lookup fields be used?
Filter report using form's filter
Override shift key in Access
Disable prompts when sending Outlook
Green-bar reporting in Access
A more complete datediff function
Calculating first and last day of the month
Commonly used naming conventions
Retrieve current username from windows
Deleting all data from Access database
Microsoft Access technical limitations
Add bullets to Access reports
Retrieving random records with a query
Basing one combo box off another
Migrating Access Databases to SQL Server
Recover corrupt Access databases
Access Add-ins
Access 97 Expert Solutions - a little dated, but still very useful information; this is an online book.
Access Level 3:
Guide to Access database corruption
General Microsoft Access tips, tricks, and articles.
Access Performance Considerations
More Performance Tips
Should lookup fields be used?
Filter report using form's filter
Override shift key in Access
Disable prompts when sending Outlook
Green-bar reporting in Access
A more complete datediff function
Calculating first and last day of the month
Commonly used naming conventions
Retrieve current username from windows
Deleting all data from Access database
Microsoft Access technical limitations
Add bullets to Access reports
Retrieving random records with a query
Basing one combo box off another
Migrating Access Databases to SQL Server
Recover corrupt Access databases
Access Add-ins
Tuesday, October 05, 2004
Override the Shift key in Access
Using the startup properties in Access, the database window can be hidden when a database is open. This is usually turned on for most production databases. When the developer needs to make changes, the shift key is used when opening the database to override all startup options. This is a handy feature, but also allows other not so savy users to do the same. The AllowByPassKey property can be used to "password" protect this feature by enabling or disabling the shift key. However, because of the potential of locking yourself out of a database, Microsoft has not created this property by default.
The AllowByPassKey property is explained in detail in the VBA help file that comes with Access. However, the sample code provided in the help files does not use the fourth DDL argument when making a call to CreateProperty. This means that anyone who can open the database can programmatically reset the AllowBypassKey value.
Therefore, in order to restrict the change capabilities to only the Admins, set the fourth argument to True when calling CreateProperty. And don't lock yourself out!
Below is the code that can be used to create this property and some comments on how to use it. Check out the help file for more detail.
'*********** Code Start ***********
'This code was originally written by Michael Kaplan.
'It is not to be altered or distributed,
'except as part of an application.
'You are free to use it in any application,
'provided the copyright notice is left unchanged.
'
'Code Courtesy of
'Michael Kaplan
'
Function ChangePropertyDdl(stPropName As String, _
PropType As DAO.DataTypeEnum, vPropVal As Variant) _
As Boolean
' Uses the DDL argument to create a property
' that only Admins can change.
'
' Current CreateProperty listing in Access help
' is flawed in that anyone who can open the db
' can reset properties, such as AllowBypassKey
'
On Error GoTo ChangePropertyDdl_Err
Dim db As DAO.Database
Dim prp As DAO.Property
Const conPropNotFoundError = 3270
Set db = CurrentDb
' Assuming the current property was created without
' using the DDL argument. Delete it so we can
' recreate it properly
db.Properties.Delete stPropName
Set prp = db.CreateProperty(stPropName, _
PropType, vPropVal, True)
db.Properties.Append prp
' If we made it this far, it worked!
ChangePropertyDdl = True
ChangePropertyDdl_Exit:
Set prp = Nothing
Set db = Nothing
Exit Function
ChangePropertyDdl_Err:
If Err.Number = conPropNotFoundError Then
' We can ignore when the prop does not exist
Resume Next
End If
Resume ChangePropertyDdl_Exit
End Function
Function ChangeProperty(strPropName As String, _
varPropType As Variant, varPropValue As Variant) As Integer
' The current listing in Access help file which will
' let anyone who can open the db delete/reset any
' property created by using this function, since
' the call to CreateProperty doesn't use the DDL
' argument
'
Dim dbs As Database, prp As Property
Const conPropNotFoundError = 3270
Set dbs = CurrentDb
On Error GoTo Change_Err
dbs.Properties(strPropName) = varPropValue
ChangeProperty = True
Change_Bye:
Exit Function
Change_Err:
If Err = conPropNotFoundError Then ' Property not found.
Set prp = dbs.CreateProperty(strPropName, _
varPropType, varPropValue)
dbs.Properties.Append prp
Resume Next
Else
' Unknown error.
ChangeProperty = False
Resume Change_Bye
End If
End Function
' *********** Code End ***********
The AllowByPassKey property is explained in detail in the VBA help file that comes with Access. However, the sample code provided in the help files does not use the fourth DDL argument when making a call to CreateProperty. This means that anyone who can open the database can programmatically reset the AllowBypassKey value.
Therefore, in order to restrict the change capabilities to only the Admins, set the fourth argument to True when calling CreateProperty. And don't lock yourself out!
Below is the code that can be used to create this property and some comments on how to use it. Check out the help file for more detail.
'*********** Code Start ***********
'This code was originally written by Michael Kaplan.
'It is not to be altered or distributed,
'except as part of an application.
'You are free to use it in any application,
'provided the copyright notice is left unchanged.
'
'Code Courtesy of
'Michael Kaplan
'
Function ChangePropertyDdl(stPropName As String, _
PropType As DAO.DataTypeEnum, vPropVal As Variant) _
As Boolean
' Uses the DDL argument to create a property
' that only Admins can change.
'
' Current CreateProperty listing in Access help
' is flawed in that anyone who can open the db
' can reset properties, such as AllowBypassKey
'
On Error GoTo ChangePropertyDdl_Err
Dim db As DAO.Database
Dim prp As DAO.Property
Const conPropNotFoundError = 3270
Set db = CurrentDb
' Assuming the current property was created without
' using the DDL argument. Delete it so we can
' recreate it properly
db.Properties.Delete stPropName
Set prp = db.CreateProperty(stPropName, _
PropType, vPropVal, True)
db.Properties.Append prp
' If we made it this far, it worked!
ChangePropertyDdl = True
ChangePropertyDdl_Exit:
Set prp = Nothing
Set db = Nothing
Exit Function
ChangePropertyDdl_Err:
If Err.Number = conPropNotFoundError Then
' We can ignore when the prop does not exist
Resume Next
End If
Resume ChangePropertyDdl_Exit
End Function
Function ChangeProperty(strPropName As String, _
varPropType As Variant, varPropValue As Variant) As Integer
' The current listing in Access help file which will
' let anyone who can open the db delete/reset any
' property created by using this function, since
' the call to CreateProperty doesn't use the DDL
' argument
'
Dim dbs As Database, prp As Property
Const conPropNotFoundError = 3270
Set dbs = CurrentDb
On Error GoTo Change_Err
dbs.Properties(strPropName) = varPropValue
ChangeProperty = True
Change_Bye:
Exit Function
Change_Err:
If Err = conPropNotFoundError Then ' Property not found.
Set prp = dbs.CreateProperty(strPropName, _
varPropType, varPropValue)
dbs.Properties.Append prp
Resume Next
Else
' Unknown error.
ChangeProperty = False
Resume Change_Bye
End If
End Function
' *********** Code End ***********
Monday, October 04, 2004
What's best for DBAs? GUI or T-SQL commands?
What's best for DBAs? GUI or T-SQL commands?
Do SQL Server DBAs really need to know the T-SQL commands for doing day to day administrative tasks? This article is one of the best explanations I've found that discusses the benefits of learning T-SQL to accomplish administrative tasks. More and more DBAs are becoming "lazy" and use the GUI that Microsoft provides, SQL Server Enterprise Manager.
Do SQL Server DBAs really need to know the T-SQL commands for doing day to day administrative tasks? This article is one of the best explanations I've found that discusses the benefits of learning T-SQL to accomplish administrative tasks. More and more DBAs are becoming "lazy" and use the GUI that Microsoft provides, SQL Server Enterprise Manager.
Subscribe to:
Posts (Atom)