Advanced Data Access Issues
*Certification Objectives
*Overview of Microsoft SQL Server Security
*SQL Server Database Roles, Database Users, and Logins
*How NT Security Relates to SQL Server
*Windows NT Authentication
*Mixed Mode
*Visual Basic and SQL Server Security
*Using Cursors
*Cursor Libraries
*Cursor Types
*Cursor Type
*ADO CursorType Constant
*Result
*Static
*Forward Only
*Dynamic
*Keyset
*Locking
*Concurrency
*ADO LockType Constant
*Result
*Read-Only Concurrency
*Optimistic and Batch Optimistic Concurrency
*Pessimistic Concurrency
*Implementing Cursors
*Enforcing Data Integrity
*Validation
*Client-Side Validation
*Server-Side Validation
*Referential Integrity
*Design
*Error Handling
*Executing Statements on a Database
*Constant
*Use
*Certification Summary
*Two-Minute Drill
*Self-Test
*
Microsoft has designed SQL Server and Visual Basic to work well together in a happy partnership. VB developers know what to do with their data once they get it. The MCSD tests and courses push the boundaries of VB programmers into the database world of SQL Server. Knowing some of the finer points of database design and administration empowers you, the developer, to use the full potential of SQL Server in your applications and helps you avoid common pitfalls. Microsoft knows how important database knowledge is to good application designand they will test you on it.
In this chapter youll learn a mixed array of very beneficial database information. Well cover the most important elements of SQL Server security. Youll learn what cursors are and how to create and use them in your applications. The discussion of data integrity will help you ensure the long-term success of your work. And finally, youll add some real power to your programming arsenal by executing statements on your database server.
This chapter draws its examples from SQL Server and ADO, Microsofts primary database and data access technologies. These are the technologies youll need to know to pass the MCSD tests.
As a professional developer and test-taker, you will want to learn about database security and use this knowledge in your application design. How does SQL Server recognize the users of applications? What options do we have for handling security? The best way to understand SQL Server security is to start at the table level and work out to the application.
SQL Server assigns permissions on database objects to users and roles. The permissions are select, update, insert, delete, execute, and DRI. Execute permission applies to stored procedures, not tables or views. DRI (Declarative Referential Integrity) applies to tables and allows a user to create a table with a referential integrity relationship to a table where that user has DRI permission . The same objective can be accomplished with select permission, but that also allows the user to view the data in the table. A user with select permission can run a select statement on a table or view, such as SELECT * from Authors, allowing him or her to view the data. Update permission allows changes to the data; insert and delete let the user add and remove rows from a table or view, respectively. For example, take a look at Figure 10-1. GajusR can select from publishers, but not change data. He is also specifically prevented from changing the stores table. Permissions for a role would be assigned in the same way.
Figure 1 SQL Server administrators can be highly selective in assigning permissions
SQL Server administers its own sets of groups (called roles) and users at the database level, distinct from NT domain groups and users . SQL roles can be thought of as groups with a purpose, and contain one or more database users. SQL Server defines its own set of built in database roles, such as db_backupoperator and public. The database administrator (DBA) may create new roles and assign members. Each database has its own set of users and roles. Figure 10-2 shows the users for the Pubs database. Names for users and roles can be the same across different databases and may include the same logical users, or the DBA can create completely different groups for each database.
Figure 2. The list of users for the Pubs database differs from the list for Northwind
Logins relate SQL Server to application users. Databases use database users and roles to handle permissions. A database user corresponds to one login. Roles are sets of database users. The login is a server-level object that provides authentication for a user. Databases have their own sets of users and roles, but there is only one list of logins for a particular SQL Server . Figure 10-3 shows the logins for my server. In this example, NT user RettigP can gain authentication on the server. This, in itself, does not provide access to any data until the DBA makes a database user for the login RettigP and assigns permissions.
Exam Watch: Know how to differentiate Logins from Users.
Figure 3. Logins are available to each database for creating database users
The end user gains authentication with the SQL Server login, but what relationship does this login have with the NT Domain account? The DBA again has a lot of flexibility in setting up security. In short, the SQL Server login may have no relationship to an NT account, it may represent one user account, or it may represent an NT Domain group . SQL 7 has two authentication method options : Windows NT Authentication Mode, and Mixed Mode (see Figure 10-4).
Figure 4. The Security tab of the SQL Server Properties dialog box shows two authentication methods
SQL Server can take advantage of trusted connections by using NT user account names for authentication. In this method, SQL trusts the validity of the NT name as its passed using Windows NT-based facilitiesSQL doesnt check a password, just matches the username against its list of logins . Figure 10-5 shows how a SQL login relates to an NT account. In this example, MetschT is an NT account in the domain CINSTATE. For convenience, Ive given this person the same SQL login name, but I could have entered any valid name. An NT Domain group may also have one SQL login. Entering CINSTATE\Programmers in the Name text box would allow anyone in the Programmers group to have the database permissions granted to the one login.
Figure 5 A new SQL login can belong to an NT domain or remain independent
Windows NT Authentication Mode adds the features of NT security to SQL Server. SQL allows NT to handle account validation, password encryption, auditing, and other administrative tasks, while SQL concentrates on the database-related aspects of security without the extra overhead.
SQL Server also has the capability of authenticating users. SQL can store its own set of accounts, separate from NT. With SQL Server authentication, either the user provides a username and password at the time of initial database access, or the application itself can store a username and password, which it uses to establish a connection. The account is administered by SQL Server, password and all, and has nothing to do with a network logon . Mixed mode combines the functionality of standard security, SQL Server authentication, with NT authentication. SQL server can accept an NT account name, assuming a trusted connection exits, or validate a username and password using the servers login list.
Some cases require SQL Server authentication. SQL 7 can run on Windows 95 or 98, operating systems that do not use NT security . In this case, SQL Server runs in Mixed mode using only SQL Server authentication. Internet clients, as well as any other non-NT client, must provide both a username and password because these clients do not have a trusted connection. Alternately, as the developer you may choose SQL authentication. Security implementation can become almost trouble free if the application connects with a standard account. All users then access the data under the same username and login, with the same permissions. For read-only databases or databases with open access, you can make your life much simpler with this method.
A note on SQL Server changes: SQL 6.5 allowed three security setup configurations: standard, mixed, and integrated. Standard security has fallen away with version 7. With standard security the only method for the user to log on to the server is through SQL Server authentication. This functionality has been retained in Mixed mode. The name "integrated security" has changed to Windows NT Authentication Mode in SQL Server 7.
Once you understand the concepts of SQL Server Security, the implementation becomes easy. Heres an example of a database connection using SQL Server authentication:
Dim cnn As ADODB.Connection
Dim rstTitles As ADODB.Recordset
Dim strCnn As String
strCnn = "Provider=sqloledb;server=pc6742;Initial Catalog=pubs; User
Id=sa;Password=;"
Set cnn = New ADODB.Connection
cnn.Open strCnn
Set rstTitles = New ADODB.Recordset
rstTitles.Open "titles", cnn, adOpenStatic, adLockReadOnly, adCmdTable
rstTitles.MoveFirst
Debug.Print rstTitles!Title
rstTitles.MoveLast
Debug.Print rstTitles!Title
rstTitles.Close
cnn.Close
To implement NT Authentication Mode, change strCnn to
strCnn = "Provider=sqloledb;server=pc6742;Initial Catalog=pubs;Integrated Security=SSPI;"
In this example I use ADO (ActiveX Data Objects) to connect to the SQL Server running on my system. The connection string, strCnn, provides the information ADO uses to find the data . The SQLOLEDB provider has a number of optional parameters beyond those the Connection object exposes. When we set Integrated Security to SSPI (Security Support Provider Interface), the authentication service for Microsoft NT Integrated Security, SQL server uses the trusted connection to obtain a username . If we choose SQL authentication, we provide a username and password with the User Id and Password parameters.
Implementing database security in VB applications is easy, just a matter of a parameter or two. With security, as with other aspects of programming, planning requires more attention and careful thought. SQL Server takes care of the permissions and authentication. VB presents the data to the user, whos unaware of the cooperation between desktop application, NT networking, and SQL Server just in verifying the users right to view or change data.
On the test, you may come across scenarios like the following:
"A user connects through the Internet . . . " | Use Mixed mode, with SQL authentication. |
"My application produces a read-only view." | Give the database users Select, but not Insert, Update, or Delete permissions. |
"My application must give users access to a limited set of columns from several tables," | Create a view for those columns and assign Select permission to the view, but not the underlying tables. |
"New users need access to the application every day," | Use NT Authentication mode and assign a domain group to one SQL login. |
Often in developing applications for Relational Database Management Systems (RDBMS), such as SQL Server, we find that query-type operations fall short of our design goals. In other words, we just cant get it done without looping through records one at a time . VB uses cursors to give us programmatic control over result sets. Once the application makes a connection to a database, it creates a Recordset object to process rows of data, from a table, view, or query statement . This result set is a cursor. Cursors are not as efficient as ANSI SQL statements for operating on a database, so there are numerous options to consider to get the best performance.
Cursors use resources, both memory space and processor time. By selecting a cursor library, you can choose to build your result set on the client or on the server. Server side cursors use the resources of the server to create the cursor. The server sends only the cursor data to the client. With ADO, you set the CursorLocation property of the recordset to determine the cursor library . The following example creates a server side cursor. The SQL server queries the titles table and returns to the client only those rows with the value "business" in the type column.
Set rstTitles = New ADODB.Recordset
rstTitles.CursorLocation = adUseServer
rstTitles.Open "SELECT * FROM titles WHERE type = business", cnn,
adOpenStatic, adLockReadOnly
By changing the constant adUseServer to adUseClient, we turn the record set into a client side cursor. This means that the server will send all the data needed to process the cursor to the client. In this case, all the rows from the titles table travel over the network to the client. The client then allocates space to hold the temporary data and perform the creation of the cursor, finally selecting titles with a type equal to "business."
SQL Server does support server side cursors, but not all databases do, so client side cursors will be necessary in some cases. Server side cursors are more efficient when network traffic is a limiting factor, but the server must be up to handling the load. For large tables client side cursors may be next to impossible. But client side cursors can provide extra features.
Besides establishing where best to run a cursor, you can further improve efficiency by limiting the its capabilities. Some uses dont demand as much flexibility as others. For instance, printing a report from cursor data doesnt require the level of sophistication of an update screen. Table 10-1 shows a list of the primary cursor types.
Cursor Type |
ADO CursorType Constant |
Result |
Static | adOpenStatic | An updateable copy of the data. Changes made to the original tables after the creation of the cursor do not appear. |
Forward only | adOpenForwardOnly | The same as a static cursor, except that it only allows forward scrolling. |
Dynamic | adOpenDynamic | The cursor will change to reflect inserts, updates, and deletes of the underlying data. |
Keyset | adOpenKeyset | The cursor will change to reflect updates and deletes, but not inserts, into the underlying data . |
Table 1 Choose Cursor Types With Speed In Mind
Static cursors give you a snapshot of the data. Use a static cursor when you dont need to see changes to the underlying data and when you might need to move back and forth through the set. Static cursors can perform well because the server doesnt need to keep the data current. A good use for a static cursor would be a search screen, where the user enters a value that the cursor then uses to filter its rows, displaying the matching records.
Forward-only cursors also provide a data snapshot, but limit the record set to forward scrolling. Forward-only cursors are the best choice for reports or any other function that only requires one pass through the data . An identical loop through a static cursor would use more resources and could be noticeably slower. Forward-only cursors may seem very restrictive, but situations where they fit perfectly are in fact quite common.
Dynamic cursors use more resources than any other type, but they also give the most flexibility . Dynamic cursors receive updates to the data from the server whenever another application causes an insert, update, or delete. You can imagine that the overhead needed to maintain this link could be considerable. You might choose a dynamic cursor in cases where not having live data can result in conflicts, such as in an airline reservation system.
Keyset cursors use a saved set of keys to maintain a link to the underlying data. So if another application changes a column that change will bubble through to the keyset cursor. Deletes also appear, but not new records. Keysets work well when you want accurate, but not necessarily the newest data, or if your application gets a lot of new entry.
Exam Watch: Be able to select the most efficient cursor for a given scenario.
Before starting to use a cursor, you have to decide on a locking method, also called concurrency . The problem lies with two or more users trying to access the same data concurrently. Locking conflicts cause headaches for DBAs and frustration for users. Planning appropriate concurrency will help your cursors do their job smoothly. In this section Ill present standard locking options, which are supported by SQL Server, using ADO as the example access technology. Table 10-2 shows the available methods.
Concurrency |
ADO LockType Constant |
Result |
Read Only | AdLockReadOnly | No exclusive locks are made. The result set does not accept edits. |
Optimistic | AdLockOptimistic | The server applies an exclusive lock at the time of update. |
Batch Optimistic | AdLockBatchOptimistic | Exclusive locks are requested when the batch updates are applied. |
Pessimistic | AdLockPessimistic | The server requests exclusive locks at cursor creation. |
Table 2 Choose the Right Locking Method to Reduce Conflicts
Always use read-only concurrency when you know you dont need to edit the data you request from the server. The server will put shared locks on the pages with the records the cursor needs, which will prevent other users from making exclusive locks, but not shared locks. SQL Server does not lock every row in the cursor, just those that the cursor requests at one time.
With optimistic concurrency, the server does not apply any locks to the data until the time of update. When an update occurs an exclusive lock is made and the change applied . The danger here is that after the cursor reads the records, another user will make a change that can then conflict with the update you want to make. For example, you make an optimistic lock on a customers demographic record, then another user locks and updates that same record. When you go to apply your changes, the update will fail. In this case, an error will be produced when the cursor attempts an exclusive lock. Set your locking to batch optimistic when you want to change multiple records in one swoop.
If your application must succeed in its data edits, then pessimistic concurrency is the necessary choice . Pessimistic locking poses the greatest danger of freezing your data. At cursor creation the server locks whatever set of rows it sends to the client. The whole cursor does not receive an exclusive lock, just those records that the cursor processes at one time. Other users cannot obtain either shared or exclusive locks on this data until the cursor moves off the block of records.
Having planned so carefully, what do you do with the cursor once youve created it? Ill give a typical example that uses a cursor to fill a list box. This example also brings together the concepts weve covered and illustrates them with a practical application. Figure 10-6 shows the output. Heres the only code contained in the program, attached to the click event of the command button :
Private Sub cmdFillList_Click()
Dim cnn As ADODB.Connection
Dim rstAuthors As ADODB.Recordset
Dim strCnn As String
'Create Connection
strCnn = "Provider=sqloledb;server=pc6742;Initial Catalog=pubs;Integrated
Security=SSPI;"
Set cnn = New ADODB.Connection
cnn.Open strCnn
'Create Recordset
Set rstAuthors = New ADODB.Recordset
rstAuthors.CursorLocation = adUseServer
rstAuthors.Open "SELECT * FROM Authors", cnn, adOpenForwardOnly, adLockReadOnly
'Fill Listbox
lstNames.Clear
rstAuthors.MoveFirst
While Not rstAuthors.BOF And Not rstAuthors.EOF
lstNames.AddItem Trim(rstAuthors!au_fname) & " " &
Trim(rstAuthors!au_lname)
rstAuthors.MoveNext
Wend
rstAuthors.Close
cnn.Close
End Sub
Figure 6. A cursor can be used to fill a list box programmatically
When the user clicks the button, the application initiates a connection to the pubs database on SQL Server, using a trusted connection. Then the code creates a new record set, called rstAuthors. This record set contains all the fields for all the rows in the authors table. Ive made it a forward scrolling cursor with read-only locking, using the resources on the server.
After the program clears the listbox, a loop moves through each record in the set, adding the first and last names of each author to the box. Note the MoveFirst and MoveNext methods of the recordset. These do exactly as their names indicate. There are a number of cursor movement methods:
The Move method accepts a signed parameter of type Long, which then moves the given number of records forward or back . The EOF (end of file) and BOF (beginning of file) properties work hand in hand with the move methods. Once MoveNext, for example, moves beyond the last record, the EOF property becomes true. Another MoveNext will produce an error. Because the example uses a forward-only cursor, only the Move and MoveNext methods will behave predictably.
By using read-only locks and a forward-scrolling cursor, the example minimizes the impact on the database. One possibility that I didnt account for is the size of the authors table. If I hadnt known that the authors table contained just a few rows, setting the MaxRecords property of the record set would prevent problems due to a very large table. Adding a WHERE clause to the SQL statement is a more efficient way to control record set size, if the design allows.
Exercise 10-1 Demonstrating a dynamic cursor
In this exercise well modify the example from the above section to demonstrate the difference between a dynamic and a static cursor. Between creating the record set and filling the listbox, the program will pause and give us a chance to change the data on the authors table.
'Wait for Update
Msgbox 'Run update now'
UPDATE authors SET au_lname = 'Black' WHERE au_lname = 'White'
UPDATE authors SET au_lname = 'White' WHERE au_lname = 'Black'
rstAuthors.Open "SELECT * FROM Authors", cnn, adOpenDynamic, adLockReadOnly
In my occasional consulting work, Ive often had the pleasure of working with other peoples data. The companies that call me usually do so because theyre in a mess and need help. For various reasons, the data has become corrupted. Organizations that fail to put adequate planning and design into their business logic end up, over time, with aging and increasingly useless data. Applications can be replaced, but data is the lifeblood of a company. Once it gets tired, making it pure again takes a fearful amount of work. You can stop this drift before it starts by building solid validation and referential integrity rules.
Both VB and SQL Server have an array of tools to help with data validation . Your application validates data by checking it as its entered. The tools range from simple to sophisticated. First Ill discuss what can be done with VB to ensure data integrity, then Ill cover the powerful SQL Server methods.
Client-side validation happens through the controls of a form. Controls provide validation because of their basic nature, by the use of their properties, and by using events that the user generates in entry. All of these controls, illustrated in Figure 10-7, can be used with validation logic:
Figure 7. All these controls can help with validation
A CheckBox control forces the user to input a Boolean value. Unlike Jet databases, for instance, SQL Server doesnt have a Yes/No datatype, though the bit datatype works well. Depending on how you intend to store Boolean data, a checkbox can help limit input.
The OptionButton control allows you to give the user a usually small set of fixed choices, which are written on the form as labels. Each option button has a true or false value, which you must then relate to your data through code.
The Masked Edit control lets the developer limit input in a text box type control though the use of an input mask. The classic example of this is the telephone number . The mask requires that three digits be entered between parentheses, then three more, a dash, and the final four numbers. Input masks help you to establish standards among users. Not everyone enters an Social Security number, for instance, in the same way. Some people like to put dashes, others spaces, others just run the numbers together. Sorting or searching this kind of hodgepodge can become very ineffective. The Masked Edit control helps you unify these personal preference issues.
The ListBox control presents the user with an often numerous list of choices, with many, if not all, visible at one time. ListBoxes can be fully controlled by programming, and they are a great way to limit choices and assure uniform, unambiguous input .
The ComboBox control adds to the functionality of a list box by letting the user type. Sometimes appearance governs the choice between the two, but if you want the user to be able to type in new data, rather than choosing from a fixed list, the combo box is what you need.
By themselves, text boxes do not do much for validation, but the KeyPress and Validate events give you total programmatic control over input. When focus shifts from a text box to another control with the CausesValidation property set to true, the Validate event fires, giving you a change to check the input by whatever standards you choose, and if necessary, hold the focus on the text box until the user meets your validation requirements.
Client-side validation works, but server-side validation has real advantages. A database can be used by many applications, with many more programmers having a hand in development. Also, educated users can learn to access data through their own methods, such as forming links to user-level databases. Server-side validation rules reside with the data. No matter how someone connects to the data, the server can apply its validation logic. Server-side validation centralizes control over the integrity of the data. Ill present SQL Servers validation methods in some detail.
SQL Server validates data primarily with constraints . Constraints give you tremendous flexibility in policing data input. SQL Server recognizes five types of ANSI standard constraints: CHECK, DEFAULT, UNIQUE, PRIMARY, FOREIGN KEY. PRIMARY and FOREIGN KEY constraints relate to referential integrity, which well discuss in the next section.
See Figure 10-8 for an example of a CHECK constraint. In this example, the constraint makes sure employees are at least one day old. You can see that a CHECK constraint evaluates a condition that includes the column name. All constraints are saved with the table structure and can be related to multiple columns. Also each column can have more than one constraint.
Figure 8 CHECK constraints help enforce data integrity
DEFAULT constraints differ from defaults, below, in that they apply only to one column of one table . The function, though, is the same. Its not a surprise that defaults supply a value for a column when the user inserts a new record without input for that column. The ANSI standards disallow the use of null, which many RDBMSs nevertheless permit. To avoid null fields, use a default to supply a filler. For example, DBAs often put a default of "Unknown" for a name, or 000-00-0000 for an SSN.
The UNIQUE constraint assures that a column contains no repeating values. Use this constraint for a column that could function as an alternate primary key. You may, for instance, maintain a list of network users and their usernames. This list could have a key that relates to another employee table, a generic employee ID. In addition, you want to make sure that no two employees have the same username in the table, which would be a data entry mistake.
Rules function very similarly to CHECK constraints, except a rule is independent of a particular table. Rules must be created first and then bound to columns. Rules also make sure that data fits within a set or range of values or has a given format.
Defaults, like rules and unlike DEFAULT constraints, are saved separately from a table. They are then applied to any number of columns. Defaults must conform to all other rules or constraints on a field. Of course, a default must also fit the data type.
Triggers and stored procedures both use more system resources than constraints, and so should be used only when more basic validation methods cannot get the job done. Think of a trigger as the SQL Server equivalent of an AfterUpdate or Change event . Triggers fire when an update, insert, or delete happens on a table. Triggers consist of Transact-SQL and can perform any number of tasks in response to a data change. A trigger may, for instance, keep a sales totals table up to date by adding new sales to the totals after each insert. Triggers can help with validation by performing a more complex check of new values than is possible with constraints.
Stored procedures are the code modules of SQL Server. Also consisting of Transact-SQL, stored procedures give the database designer full programmatic control over data. Stored procedures can create tables, add users, generate output, copy data, perform row-by-row operations, and whatever else you can dream up. (Ill show you how to run a stored procedure from your VB application and describe their usefulness in more detail later.) They assist in data integrity by providing a single centralized set of logic to perform a task. Instead of writing complex data operations in your application, you can simply call a stored procedure. Anyone else who wants to accomplish the same goal can run the same procedure, instead of coming up with new code.
Referential integrity is a fundamental part of RDBMSs. Instead of storing information in one huge table, the process of normalization splits data out into elemental pieces. The smaller tables then share relationships through their keys. The potential exists for this relationship to break down if an entity does not correspond to a row in another table. Through referential integrity an RDBMS ensures that the proper references exist between key values, thus maintaining the validity of relationships .
Figure 10-9 shows one improvement of SQL Server 7 over earlier versions, a database diagram utility. Look at the relationship between the Suppliers and Products tables. The key for Suppliers is SupplierID, for Products, ProductID. Notice that the SupplierID field also appears in Products. SQL Server represents the relationship between the two tables with a gray bar connecting the two, a key on the left end, and the
¥ (infinity symbol) on the right. The SupplierID column in Suppliers and the SupplierID column in Products share a one to many relationship. A single supplier can provide more than one product, hence the SupplierID column in Products will have repeating values. For each record in Suppliers there can be many corresponding records in Products.Figure 9. A database diagram visually represents referential integrity relationships
The PRIMARY KEY constraint defines the keys in each table . In the products table, SupplierID is the foreign key. When SQL Server enforces referential integrity, it makes sure that every SupplierID in Products has a matching SupplierID in Suppliers. The FOREIGN KEY constraint, created at the same time as the rest of the table, assures that SQL Server performs the referential integrity checks . This script creates the Products table; notice the FOREIGN KEY constraints:
CREATE TABLE [dbo].[Products] (
[ProductID] [int] IDENTITY (1, 1) NOT NULL ,
[ProductName] [nvarchar] (40) NOT NULL ,
[SupplierID] [int] NULL ,
[CategoryID] [int] NULL ,
[QuantityPerUnit] [nvarchar] (20) NULL ,
[UnitPrice] [money] NULL CONSTRAINT [DF_Products_UnitPrice] DEFAULT (0),
[UnitsInStock] [smallint] NULL CONSTRAINT [DF_Products_UnitsInStock] DEFAULT (0),
[UnitsOnOrder] [smallint] NULL CONSTRAINT [DF_Products_UnitsOnOrder] DEFAULT (0),
[ReorderLevel] [smallint] NULL CONSTRAINT [DF_Products_ReorderLevel] DEFAULT (0),
[Discontinued] [bit] NOT NULL CONSTRAINT [DF_Products_Discontinued] DEFAULT (0),
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[ProductID]
) ON [PRIMARY] ,
CONSTRAINT [FK_Products_Categories] FOREIGN KEY
(
[CategoryID]
) REFERENCES [dbo].[Categories] (
[CategoryID]
),
CONSTRAINT [FK_Products_Suppliers] FOREIGN KEY
(
[SupplierID]
) REFERENCES [dbo].[Suppliers] (
[SupplierID]
),
CONSTRAINT [CK_Products_UnitPrice] CHECK ([UnitPrice] >= 0),
CONSTRAINT [CK_ReorderLevel] CHECK ([ReorderLevel] >= 0),
CONSTRAINT [CK_UnitsInStock] CHECK ([UnitsInStock] >= 0),
CONSTRAINT [CK_UnitsOnOrder] CHECK ([UnitsOnOrder] >= 0)
)
A number of operations could cause a trappable error because of the relationship between Products and Suppliers. An insert into the Products table of a row without a SupplierID from Suppliers will produce an error. A delete from Suppliers that tries to remove a SupplierID thats present in Products will violate referential integrity. Finally, your application will produce an error if it tries to change a SupplierID in either table if the result would be an orphan record.
Exam Watch: Remember the operations that can result in referential integrity violations
The goal of this Question and Answer section is to help you understand how referential integrity works to preserve data consistency. Refer to Figure 10-9 and consider the following questions and answers.
What kind of relationship exists between the Customers and the Orders tables: one to one, one to many, or many to many? | Each Customer can have many orders; hence the relationship is one to many. Notice how the diagram visually represents this logical relationship |
Can adding a row to Customers cause a referential integrity violation? Adding a row to Orders? | Adding new customers will not cause a problem, but adding an order that doesnt have a corresponding customer will violate referential integrity. |
Under what circumstances will deleting a row from Customers cause an error? | If you delete a row from Customers that has corresponding entries in Orders, you will create orphan records, producing an error. |
Can changing the ReportsTo column in the Employees table violate referential integrity? | This question is a little less obvious. The Employees table has a one to many relationship with itself, through the ReportsTo column. The ReportsTo column contains the EmployeeId of each employees supervisor. The designers of this database decided to enforce the accuracy of this column by relating it to the EmployeeId column of the same table. So, if you change a value in ReportsTo to something that doesnt match an entry in EmployeeId, youll cause a violation. |
So what do you do when your application tries to break the referential integrity relationship on your server? You trap the error, of course, and deal with it. Like any other VB error, SQL Server errors can be trapped with the On Error statement. In addition to the VB Err object, the ADO Connection object has an Errors collection . This collection can be looped through with a For Each, Next construction. Your application can respond not to only referential integrity errors with the Errors collection but to many other SQL Server errors as well, such as locking and connecting errors. This code sample illustrates how to use the Errors collection of the Connection object:
Private Sub IntegrityErrors()
Dim cnn As ADODB.Connection
Dim er As ADODB.Error
On Error GoTo OnError
'Code to Create Connection and violate referential integrity
'
'
'
Exit Sub
OnError:
Debug.Print Err.Number, Err.Description
For Each er In cnn.Errors
Debug.Print er.Number, er.Description
Next
Resume Next
End Sub
Once youve trapped the error, you can automate a response, or let the user give input in deciding a course of action. In the case of referential integrity errors, youll want to communicate to the user an understandable explanation for why the operation failed. Error handling sophistication is up to you, the developer. SQL Server will never let your application violate referential integrity, but you need to make sure that a failure to insert, update, or delete doesnt introduce problems into a larger segment of logic.
The Execute method of the ADO Connection object opens up a world of possibilities to you, the developer. With this one method you can :
Microsoft has optimized SQL Server for running stored procedures and executing statements. As a programmer, you have the choice of performing logic on the client VB application or on the server. Executing statements on the server takes advantage of server resources and reduces network traffic . Let the server do database work whenever possible.
Lets start with an example. This procedure updates the authors table, changing a last name for one record, then prints the names in the authors table. Both operations use the Execute method:
Private Sub ExecutingStatements()
Dim cnn As ADODB.Connection
Dim rstAuthors As ADODB.Recordset
Dim strCnn As String
Dim sqlUpdate As String
'Create Connection
strCnn = "Provider=sqloledb;server=pc6742;Initial Catalog=pubs;Integrated
Security=SSPI;"
Set cnn = New ADODB.Connection
cnn.Open strCnn
'Execute Update Statement
sqlUpdate = "UPDATE authors SET au_lname = 'Kornau' WHERE au_id = 3"
cnn.Execute sqlUpdate, , adCmdText
'Fill Recordset with stored procedure output
Set rstAuthors = cnn.Execute("AuthorList", , adCmdStoredProc)
'Print results
rstAuthors.MoveFirst
While Not rstAuthors.BOF And Not rstAuthors.EOF
Debug.Print rstAuthors!au_fname, rstAuthors!au_lname
rstAuthors.MoveNext
Wend
rstAuthors.Close
cnn.Close
End Sub
The Execute method takes three parameters. The first is a string variable containing an SQL statement or command text. In the above example, sqlUpdate contains the text of an UPDATE action query. Any valid SQL statement will work, including INSERT, UPDATE, and DELETE statements. If your goal is to return a result set from a SELECT statement, use the Open method of the Recordset object. Ive omitted the second parameter, RecordsAffected, from both Executes, because its not useful in this case. This parameter returns the number of records affected by a query or command. Those familiar with ISQL will immediately recognize this standard output from query statements. The third parameter takes a constant that tells SQL Server how to handle the command text. Table 10-3 lists the applicable constants.
Constant |
Use |
AdCmdText | The string is a standard command, such as an action query. |
AdCmdTable | ADO builds a query to return all rows from a table. |
AdCmdTableDirect | SQL Server returns all rows from a table. |
AdCmdStoredProc | The string contains the name of a stored procedure . |
AdCmdUnknown | The application does not know the type of command. |
AdExecuteAsync | The application continues execution without waiting for the command to complete. The ExecuteComplete signals completion . |
AdFetchAsync | The application waits for the number of rows specified with the CacheSize property and then moves on while the command completes. |
Table 3 The Execute Method Accepts a Variety of Statement Types
In addition to running an action query, the example populates a record set with the results of a stored procedure. The stored procedure AuthorList is nothing more than a SELECT statement, but take a look at Figure 10-10, a more complicated stored procedure. Stored procedures accept parameters and can perform any task possible with Transact-SQL. Using the Execute method to run stored procedures gives your application the power to run resource intensive operations without causing a drag on the client machine. Learning Transact-SQL may take some work, but it adds a lot of potential to your VB applications.
Figure 10. A VB application can call SQL Server stored procedures
Exercise 10-2 Executing INSERT statements on the server
Using SQL statements to do your inserts on a table will often be the most efficient method. In this exercise youll send an INSERT statement to SQL Server to add a record to the authors table of the pubs database.
INSERT stores (stor_id, stor_name) VALUES (1111, "The New Bookstore")
SQL Server uses permissions to enforce security on database objects. The DBA grants permissions to users and roles: select, update, insert, delete, execute, DRI. Roles are groups of database users. Both users and roles are specific to the database. SQL Server uses logins for authentication. Logins are specific to the server and are mapped to database users. SQL Server can perform its own authentication of a username and password, or it use a trusted connection to authenticate an NT domain username. A login can represent one user or an NT domain group. With ADO, the connection string either supplies a username and password, or instructs the provider to use a trusted connection.
Cursors let the application loop through a set of records, one row at time. The cursor library determines if the cursor will run on the server or workstation. Cursors can be static, forward only, dynamic, or keyset. There are four locking options in creating a cursor: read only, optimistic, batch optimistic, and pessimistic. Choosing cursor options carefully makes a positive impact on overall performance. The Recordset object in Visual Basic represents a cursor.
Enforcing data integrity includes both validation and referential integrity. VB allows client-side validation through the use of controls: TextBox, CheckBox, ComboBox, Masked Edit, OptionButton, ListBox. SQL Server implements server-side validation with constraints, rules, defaults, triggers, and stored procedures. Referential integrity insures that normalized data maintains the proper relationships. SQL Server uses the PRIMARY and FOREIGN KEY constraints to enforce referential integrity. A foreign key is a column that can only include values present in the primary key of another table. The Errors collection of the ADO Connection objects lets the application handle referential integrity errors.
The Execute method of the ADO Connection objects gives the programmer the ability to execute statements and stored procedures on the database server. Any stored procedure can be run from a VB application. A VB record set can hold a result set returned by a stored procedure. The application can also run action queries against data on the server. Using the Execute method takes advantage of server resources and SQL Servers performance capabilities.