Improve Db Performance in a Network Environ

Installing the following code into the first form that opens in a database will increase the database performance in a network environment, especially with Windows 7.

Option Compare Database
 
Public Function PrintISAMStats()
    ‘ Print the values returned by the ISAMStats function
    ‘ to the Debug window.
   
    Debug.Print “Number of disk reads: “, DBEngine.ISAMStats(0)
    Debug.Print “Number of disk writes: “, DBEngine.ISAMStats(1)
    Debug.Print “Number of reads from cache: “, DBEngine.ISAMStats(2)
    Debug.Print “Number of reads from read-ahead cache: “, DBEngine.ISAMStats(3)
    Debug.Print “Number of locks placed: “, DBEngine.ISAMStats(4)
    Debug.Print “Number of release lock calls: “, DBEngine.ISAMStats(5)
    Debug.Print
End Function
 
Public Function ResetISAMStats()
    ‘ This procedure resets the values returned by the
    ‘ ISAMStats function to zero.
   
    Dim intI As Integer
    ‘ Reset each value.
    For intI = 0 To 5
        DBEngine.ISAMStats intI, True
    Next
End Function
 
Public Function UpdateISAM()
    DBEngine.SetOption dbMaxBufferSize, 50000
    DBEngine.SetOption dbMaxLocksPerFile, 500000
    ‘DBEngine.SetOption dbFlushTransactionTimeout, 5000
   
End Function
 
Public Function runtest()
‘UpdateISAM
ResetISAMStats
Dim curtime As Date
curtime = Now
 
DoCmd.OpenForm “frmprc10claims”
PrintISAMStats
 
Debug.Print “Number of seconds:” & DateDiff(“s”, curtime, Now())
 
 
End Function

Posted in Access 2007 problems/solutions, Uncategorized | Tagged , , | Comments Off on Improve Db Performance in a Network Environ

Likesearches in large SQL tables

I’ve had several clients now that have requested a stored procedure that will return records based on key words entered by a user. There can be one or many key words and all have to match in order to match the item or product. I used to do this using a LIKE ‘%keyword1%’ AND LIKE ‘%keyword2%’ type of query using Dynamic SQL.

Using the LIKE operator like this on a large table can make for a long running query since no indexes can be used. A search query on an auto parts table with 1.2 million items took over 30 seconds to run for one of my clients. I developed a technique that would index each word in the item description in another table and use that for a search procedure. The average run time on that procedure is now 1.5 seconds.

In this article I’m going to use the AdventureWorks database to build such a table and show how to use it. Keep in mind that there isn’t much performance gain in this example since AdventureWorks had just over 500 products in it. ( I would never bother with this technique on a table that small.)

There are 2 tables that you have to create: KeyWordItems and KeyWordsToExclude. The first table has the primary key of the product or item table – in this case ProductID from Production.Product. The second table contains words that you want to ignore. This could be ‘a’, ‘an’, ‘the’ and so forth. Another type of word to ignore in most cases would be any single character word, which would exclude such things as ‘&’ or “#” if there are spaces on either side.

Here is the code to create and populate the KeyWordsToExclude table:

CREATE TABLE KeyWordsToExclude
( KeyWord VARCHAR(50) NOT NULL )
GO
ALTER TABLE KeyWordsToExclude
ADD CONSTRAINT PK_KeyWordsToExclude
PRIMARY KEY CLUSTERED (KeyWord)
GO

— Populate the KeyWordsToExclude table with whatever you don’t want to
— use.
INSERT INTO KeyWordsToExclude
( KeyWord )
SELECT ‘A’ UNION ALL
SELECT ‘AN’ UNION ALL
SELECT ‘THE’
GO
Here is the KeyWordItems table:

CREATE TABLE KeyWordItems
( KeyWord VARCHAR(50) NOT NULL
, ProductID INT NOT NULL )
GO

ALTER TABLE KeyWordItems
ADD CONSTRAINT PK_KeyWordItems
PRIMARY KEY CLUSTERED (KeyWord, ProductID)
GO
In order to populate the KeyWordItems table, you should have Jeff Moden’s table valued function – DelimitedSplit8KNEW. It is lightning fast and I’m including it here for simplicity (I hope Jeff doesn’t mind).

CREATE FUNCTION dbo.DelimitedSplit8KNEW
–===== Created by Jeff Moden (Prototype: Testing Still in Progress)
–===== Define I/O parameters
(
@pString VARCHAR(8000),
@pDelimiter CHAR(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
WITH
E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), –10
E2(N) AS (SELECT 1 FROM E1 a, E1 b), –100
E4(N) AS (SELECT 1 FROM E2 a, E2 b), –10,000
cteTally(N) AS (
SELECT 0 UNION ALL
SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E4
)
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY t.N),
ItemValue = SUBSTRING(@pString,t.N+1,ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,t.N+1),0),DATALENGTH(@pString)+1)-t.N-1)
FROM cteTally t
WHERE t.N BETWEEN 0 AND DATALENGTH(@pString)
AND (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0)
;
GO
Now we come to actually populating the KeyWordItems table. There are a few quirks in the AdventureWorks product names. Slashes are used to separate some words – e.g. ‘LL Mountain Seat/Saddle’. Also some product names have commas after a word – e.g. ‘Mountain Bike Socks, M’. This type of punctuation is not likely to be entered by a user searching by product name and so needs to be ignored. In the code below there are 2 REPLACE functions that will replace a ‘/’ with a space (which makes it a separate word) and strip out commas.

SELECT
RecID = IDENTITY (INT, 1, 1)
, CAST(P.ProductID AS INT) AS ProductID, Split.ItemValue AS KeyWord
INTO #PW — ProductWords
FROM Production.Product P
CROSS APPLY (SELECT ItemValue FROM dbo.DelimitedSplit8KNEW(REPLACE(REPLACE(P.Name, ‘/’, ‘ ‘), ‘,’, ”), ‘ ‘)) AS Split
WHERE LEN(ItemValue) > 1

— Delete the words that are in KeyWordsToExclude (This could have been
— included
— above but done as a separate step to keep the SQL simple. In
— AdventureWorks there aren’t any product names that will be excluded.
DELETE PW
FROM #PW PW
INNER JOIN KeyWordsToExclude KWE ON
PW.KeyWord = KWE.KeyWord

— Delete any product key words with the same word twice. Again
— AdventureWorks product names don’t have the same word twice in them.
— Your data may differ.
DELETE PW
FROM #PW PW
INNER JOIN
(SELECT MIN(RecID) AS MinRecID, ProductID, KeyWord
FROM #PW
GROUP BY ProductID, KeyWord
HAVING COUNT(*) > 1
) AS X ON
PW.ProductID = X.ProductID
AND PW.KeyWord = X.KeyWord
AND PW.RecID X.MinRecID

— Populate the KeyWordItems table.
INSERT INTO KeyWordItems
( ProductID, KeyWord )
SELECT
ProductID, KeyWord
FROM #PW
It is VERY important to note that the SAME logic that is used to populate the KeyWordItems table must be used when handling the user input – i.e. the same punctuation handling, the same ignored words, the same restriction on no single letter words, etc. Otherwise you may end up where something that should match that won’t match.

Once the key words table has been created it will have to be maintined either in a procedure or trigger when an update occurs to the column you are using for the words (item description, product name, etc.). If any change is made to that column the procedure or trigger should delete and re-add the key words using the same logic that was used to initially populate it.

Now for some actual queries.

Here is a typical query for 2 key words – usually created in Dynamic SQL – that would search the Products table.

SELECT
ProductID, Name, Color, Size, ListPrice
FROM Production.Product
WHERE
Name LIKE ‘%nut%’
AND Name LIKE ‘%hex%’This would return something like:

Here are the actual stats from SET STATISTICS TIME ON and SET STATISTICS TIME ON which was set before running the query above:

Table ‘Product’. Scan count 1, logical reads 23, physical reads 3
, read-ahead reads 0, lob logical reads 0, lob physical reads 0
, lob read-ahead reads 0.

SQL Server parse and compile time:
CPU time = 15 ms, elapsed time = 270 ms.

The important point above is the table scan on the Product table. This is what the query plan for the above query looks like:

Here is the procedure that will do the search using the new key word tables:

CREATE PROCEDURE ItemSearch
@KeyWords VARCHAR(256)
AS

SET NOCOUNT ON;

— We need to know how many words the user entered for the search.
DECLARE
@KWCount INT

— Temp table of passed in key words
CREATE TABLE #KW
( RecID INT IDENTITY(1, 1)
, KeyWord VARCHAR(50))

— Same function and same logic used to populate KeyWordItems table.
INSERT INTO #KW
( KeyWord )
SELECT ItemValue
FROM dbo.DelimitedSplit8KNEW(REPLACE(REPLACE(@KeyWords,’/’,’ ‘),’,’,”),’ ‘) AS Split
WHERE NOT EXISTS
(SELECT 1 FROM KeyWordsToExclude KWE
WHERE KWE.KeyWord = Split.ItemValue)

— Delete any duplicate words. Same as was done for the KeyWordItems
— table.
DELETE KW
FROM #KW KW
INNER JOIN
(SELECT MIN(RecID) AS MinRecID, KeyWord
FROM #KW
GROUP BY KeyWord
HAVING COUNT(*) > 1
) AS X ON
KW.KeyWord = X.KeyWord
AND KW.RecID X.MinRecID

— Number of words in the temp table.
SELECT @KWCount = X.RecCount
FROM
(SELECT COUNT(*) AS RecCount FROM #KW
) AS X

— Create a little stub table from the temp table joined to the
— KeyWordItems table. Using this stub table avoids
— joining to the larger Product table multiple times and then
— aggregating the results. The GROUP BY also does a sort on ProductID
— so that this stub table and the main Product table are eligible
— for a MERGE JOIN. You may have to index it in order to get a
— MERGE in the query plan.

— The HAVING part means that the product name must match the #KW table
— the same number of times as the count of words. This produces the
— same effect as the AND operator in the original LIKE query.

SELECT
KWI.ProductID
INTO #KWP — Stub table of Products with the matching key words.
FROM #KW
INNER JOIN KeyWordItems KWI ON
#KW.KeyWord = KWI.KeyWord
GROUP BY KWI.ProductID
HAVING COUNT(*) = @KWCount

— Return the requested Products
SELECT
P.ProductID, P.Name, P.Color, P.Size, P.ListPrice
FROM #KWP KWP
INNER JOIN Production.Product P ON
KWP.ProductID = P.ProductID
GO
Now we run the stored procedure for the same key words:

EXEC ItemSearch
@KeyWords = ‘nut hex’
This produces the same results as the original LIKE query. Here are the stats for the procedure:

Table ‘Product’. Scan count 0, logical reads 78, physical reads 2
, read-ahead reads 0, lob logical reads 0, lob physical reads 0
, lob read-ahead reads 0.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 175 ms.

The important part of this is that there is no Scan count on the Product table. Here is what the actual query plan looks like:

Note that we get a Clustered Index Seek on the Product table instead of a scan.

As I mentioned before, since AdventureWorks only has just over 500 products, this technique doesn’t do much. When I used it against an item table of auto parts of over 1.2 million the performance differences was astounding.

This technique is most useful for searches where all criteria (and AND condition) must be met in order to qualify for a match. I hope someone finds this useful.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on Likesearches in large SQL tables

SQL Server and Visual Basic

Remember when casting values that there are some cases where a null value will result in an error and no conversion. Check your properties for your variables in VB and you fields in SQL.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on SQL Server and Visual Basic

SQL or Access Adding Records

Suggestions on how to let users enter new data into SQL or even Access.

1) Build them a real data-entry app (could be web-based, but doesn’t have to
be) that will check all the inputs before submitting the data to the tables
OR
2) Have them input whatever they want (Excel – that could be
an option).
Import into a staging table with the same field names, but all data types
set to varchar(), so that there is no validation. This way you gather
everything the users send you.
Then write an app that goes through the staging table and does all the
validations that you want, imports the good records and spits out a report
of rejected records that can be dealt with manually

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on SQL or Access Adding Records

Referencing values on subforms

Remember the syntax for the way to recall information from a subform to a form is [subFormName].[Form]![FieldName]

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on Referencing values on subforms

VPN

Take advantage of free resources to create a VPN for remote access. LogMeIn is one such resource. Use it to create a VPN between your base location and any remote users you might have. That way you can remote into a database anytime with Internet access.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on VPN

Security

The first and most important step in creating a security process is to establish and maintain a solid backup process for ALL files! Whether they are only in Access or in Access and SQL Server.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on Security

DLookup

Doing an If clause with DLookup genereally means usin a comparison of the lookup result to a value using the equals function. Most other functions do not tend to work well with DLookup and especially not the IF ISNULL structure.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on DLookup

File System Objects

Remember to look to use FSO’s such as FileCopy when you are trying to do things outside of Acces from within Access. File System Objects are readily available and require few skills to learn.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on File System Objects

List boxes not populating

If you are using list box(es) and they will not populate with values, check your references in the VBA Editor. Odds are that there is a missing reference to a DLL or two.

Posted in Access 2007 problems/solutions, Uncategorized | Tagged | Comments Off on List boxes not populating