data base question

63
Data Base interview Page 1 of 1 Data Base interview Page 1 of 1 Question: I have many duplicate records in my database table. I want to delete all of them except one. How can I do this? Answer: The situation: you have a database table with a number of duplicate rows (that is, every column each duplicate row matches). You’d like to delete all of these rows save one, essentially removing all duplicates. There are two scenarios we must consider in this feat. The first one is that there is no unique key for each row in the database; the second scenario we’ll examine is that such a key does exist. (The second scenario is "better," since we can perform the DELETE with a single SQL statement, whereas the first scenario requires creating a unique key and then using a cursor... bleh.) NO UNIQUE KEY In this case, we have a difficult problem if we are trying to solve this with a single SQL Statement. In this situation, I recommend one of the following approaches: 1.) Add a unique key to the table This is easy. Add a column called ID as an integer, and make it an identifier column by checking the identity box in the table design window. Set the Identity Seed to 1 and the Identity Increment to 1. The column will automatically be populated with unique values for each row in the table. Proceed to UNIQUE KEY section below. 2.) Write a stored procedure. The strategy here would be to write a query that returns a row for each set of duplicates, using a query such as the following: SELECT Field1, Field2, Count(ID) FROM Foo1 GROUP BY Foo1.Field1, Foo1.Field2 HAVING Count(Foo1.ID) > 1 Use a cursor to loop through the returned rows, then for each set of duplicates, read all rows for that set: SELECT Field1, Field2, ID FROM Foo1 WHERE Field1 = @FIELD1 and Field2 = @FIELD2 Then delete each row except the first one returned, for each set of duplicates. UNIQUE KEY If dealing with a table that does have a unique key, the problem of removing duplicates is much easier, and able to be accomplished in one SQL statement such as the following: DELETE FROM Foo1

Upload: api-3716254

Post on 11-Apr-2015

519 views

Category:

Documents


4 download

DESCRIPTION

faq and coding

TRANSCRIPT

Page 1: Data Base Question

Data Base interview Page 1 of 1

Data Base interview Page 1 of 1

Question: I have many duplicate records in my database table. I want to delete all of them exceptone. How can I do this?

Answer: The situation: you have a database table with a number of duplicate rows (that is, everycolumn each duplicate row matches). You'd like to delete all of these rows save one, essentially removing all duplicates.

There are two scenarios we must consider in this feat. The first one is that there is no unique key for each row in the database; the second scenario we'll examine is that such a key does exist. (The second scenario is "better," since we can perform the DELETE with a single SQL statement, whereas the first scenario requires creating a unique key and then using a cursor... bleh.)

NO UNIQUE KEYIn this case, we have a difficult problem if we are trying to solve this with a single SQL Statement. In this situation, I recommend one of the following approaches:

1.) Add a unique key to the table This is easy. Add a column called ID as an integer, and make it an identifier column by checking the identity box in the table design window. Set the Identity Seed to 1 and the Identity Increment to 1. The column will automatically be populated with unique values for each row in the table. Proceed to UNIQUE KEY section below.

2.) Write a stored procedure. The strategy here would be to write a query that returns a row for each set of duplicates, using a query such as the following: SELECT Field1, Field2, Count(ID)FROM Foo1GROUP BY Foo1.Field1, Foo1.Field2HAVING Count(Foo1.ID) > 1

Use a cursor to loop through the returned rows, then for each set of duplicates, read all rows for that set:

SELECT Field1, Field2, IDFROM Foo1WHERE Field1 = @FIELD1 and Field2 = @FIELD2

Then delete each row except the first one returned, for each set of duplicates.

UNIQUE KEYIf dealing with a table that does have a unique key, the problem of removing duplicates is much easier, and able to be accomplished in one SQL statement such as the following:

DELETEFROM Foo1

Page 2: Data Base Question

Data Base interview Page 2 of 2

Data Base interview Page 2 of 2

WHERE Foo1.ID IN

-- List 1 - all rows that have duplicates(SELECT F.IDFROM Foo1 AS FWHERE Exists (SELECT Field1, Field2, Count(ID)

FROM Foo1WHERE Foo1.Field1 = F.Field1

AND Foo1.Field2 = F.Field2GROUP BY Foo1.Field1, Foo1.Field2HAVING Count(Foo1.ID) > 1))

AND Foo1.ID NOT IN

-- List 2 - one row from each set of duplicate(SELECT Min(ID)FROM Foo1 AS FWHERE Exists (SELECT Field1, Field2, Count(ID)FROM Foo1WHERE Foo1.Field1 = F.Field1AND Foo1.Field2 = F.Field2GROUP BY Foo1.Field1, Foo1.Field2HAVING Count(Foo1.ID) > 1)

GROUP BY Field1, Field2);

Since this may appear complicated, let me explain. My strategy here is to return two lists: The first, List 1, is a list of all rows that have duplicates, and the second, List 2, is a list of one row from each set of duplicates. This query simply deletes all rows that are in List 1 but not in List 2

This FAQ was taken from a previous SQL Guru question...

Alert 4Guys reader Paul Davallou wrote in to share another way...

Another way to remove all duplicate records in a database table save one is to use the following approach:

1.) Capture one instance of the unique rows using a SELECT DISTINCT ..., dumping the results into a temp table.

2.) Delete all of the rows from the original table

3.) Insert the rows from the temp table back into the original table.

And there you have it!Question: How do I populate an HTML dropdown box from the results of a database table?

Answer: The answer to this FAQ comes from Eric Hewitt.

Page 3: Data Base Question

Data Base interview Page 3 of 3

Data Base interview Page 3 of 3

Populating a drop down box from a database isn't as hard as one would think. First, you should have a database — for this FAQ, I'll be using a sample database that I made up that has a DSN created named states and a table called tblStates with two fields: fldAbbr and fldFull. fldAbbr contains the abbreviation name of the state (like CA) while fldFullcontains the state's full name (like California). (For information on creating a DSN, be sure to read: Setting Up a System DSN on Windows 98/NT.)

Here's my example page:<%Option Explicit

'declare our variablesDim conn,sql,rs

'connect to db and open our querySet conn=Server.CreateObject("ADODB.Connection")conn.open "DSN=states"

sql="SELECT * FROM tblStates ORDER BY fldFull"

Set rs=conn.execute(sql)%>

<html><head><title>Database Drop Down Menu

</title></head><body><form action="form2.asp" method=post><select name="mystates"><%Do While Not rs.eofResponse.Write("<option value=""" & rs("fldAbbr") & """>" & _

rs("fldFull") & "</option>")rs.movenext

Loop

conn.closeSet conn=NothingSet rs=Nothing%></select>

</form></body></html>

As you can see, this doesn't require a lot of code! To get the value of the selected drop down box item, just have the form2.asp page include the code: <% myvariable = Request.Form("mystates") %>, and it's that easy!

Page 4: Data Base Question

Data Base interview Page 4 of 4

Data Base interview Page 4 of 4

Another method that you can use to populate an HTML dropdown list is through the ADO method GetString. This provides faster performance and more compact code than the example above. (The example above was provided to show a simple, basic way to place database results in an HTML dropdown list.) To learn how to do this, be sure to read: Displaying Listboxes and Hyperlinks with GetString.

Question: How can I take the result of a SELECT...MULTIPLE or a group of same-named checkboxes and turn it into a query? That is, if the user selects 3 answers, how can I construct a query that looks for all 3?

Answer: To illustrate the request, let's assume that the form you will use looks like this:

<FORM Method="POST" ...>

Select one or more makes:<SELECT Name="CarMake" MULTIPLE><OPTION>Chevrolet<OPTION>Ford<OPTION>Honda<OPTION>Isuzu<OPTION>Jeep<OPTION>Lexus<OPTION>Plymouth<OPTION>Volkwagen</SELECT><P>Check the years you want to see reports for:<INPUT Type=CheckBox Name="ReportYear" Value="1997"> 1997<INPUT Type=CheckBox Name="ReportYear" Value="1998"> 1998<INPUT Type=CheckBox Name="ReportYear" Value="1999"> 1999<INPUT Type=CheckBox Name="ReportYear" Value="2000"> 2000<INPUT Type=CheckBox Name="ReportYear" Value="2001"> 2001<P><INPUT Type=Submit Value="Show reports..."></FORM>

See that? In our next page, when we do

<%makes = Request("CarMake")years = Request("ReportYear")%>

we will get multiple makes and/or years if the user selected more than one (makes) or checked more that one box (years).

Page 5: Data Base Question

Data Base interview Page 5 of 5

Data Base interview Page 5 of 5

But what is the form in which we get those multiple values?

Why not find out for yourself:

<%Response.Write "Makes requested are: " & Request("CarMake") & "<P>"Response.Write "Years requested are: " & Request("ReportYear") & "<P>"%>

Did you do that? No? Well, I won't keep you in suspense. You would have seen something like

Ford, Honda, Plymouth1997, 2000

depending, of course, on what the actual selections were.

Do you understand the format? You get a STRING that contains the individual items, separated from each other by COMMA-SPACE (a comma followed by a space...and please don't ask me why the silly space...some MS hotshot decided that one years ago!).

Now, the first thing that most people think of when they see a delimited string in VBScript is the SPLIT function. Nope. Not this time!

It turns out that SQL has a wonderful built-in ability to do exactly what we need! It is the WHERE fieldname IN ( item, item, item ) clause of SQL. And if you are not familiar with it, see the bottom of this page.

Actually, the list of ReportYears is already in the form we need for that clause! We could do this:

<%years = Request("ReportYear") ' gets list: 1997,2000 etc....SQL = "SELECT make, reportYear, rating, comments " _

& "FROM carReportsTable " _& "WHERE reportYear IN (" & years & ")"

Set RS = yourConnection.Execute( SQL )...%>

Simple as that! We are done!

But that only works for numeric fields! For text and date fields, you need to surround each item in the list with the appropriate delimiters, such as WHERE ... IN ( 'item','item','item').

Page 6: Data Base Question

Data Base interview Page 6 of 6

Data Base interview Page 6 of 6

But good old Replace makes this easy!

<%makes = Request("CarMake") ' gets list: Ford,Honda, ... etc.makes = Replace( makes, ", ", "','" ) ' see below!...SQL = "SELECT make, reportYear, rating, comments " _

& "FROM carReportsTable " _& "WHERE make IN ('" & makes & "')"

Set RS = yourConnection.Execute( SQL )...%>

See how that works? The Replace changes each COMMA-SPACE into APOSTROPHE-COMMA-APOSTROPHE!! And then we simply put an apostrophe on the front and back of the whole thing, and we have the proper form of list needed for the WHERE...IN... clause!

So the final form, combining both of those form fields is:

<%years = Request("ReportYear") ' gets list: 1997,2000 etc.makes = Request("CarMake") ' gets list: Ford,Honda, ... etc.makes = Replace( makes, ", ", "','" ) ' see below!...SQL = "SELECT make, reportYear, rating, comments " _

& "FROM carReportsTable " _& "WHERE make IN ('" & makes & "') "& "AND reportYear IN (" & years & ")"

Set RS = yourConnection.Execute( SQL )...%>

Question: How can I pick a random record from a table, using a stored procedure?

Answer: Bill Wilkinson's FAQ describes how to return a random record from a table. However, when you use a SQL Server database it's more efficient to use a stored procedure to return the records. For more information on using stored procedures read this article.

In this article Nathan Pond describes the use of a stored procedure to return a random record, but his method has some overhead because it creates a temporary table. This can be very ineffective when you have a large table.

My examples assume you have a table with an autoincrement ID field. It also covers the fact that there might be gaps in the ID field. (for example because some records were deleted)If your table does not have an autoincrement ID field you might want to use a second table which has an autoincrement ID field and holds a pointer to the actual table.

Page 7: Data Base Question

Data Base interview Page 7 of 7

Data Base interview Page 7 of 7

Now I’ll show you the code, and try to explain every step:

CREATE PROCEDURE spGetRandomRecord

AS

--Declare local variablesDECLARE @counter int, @randno int, @uBound int, @lBound int

The above solution is suitable when you have minor gaps in the ID field. If the gaps are fairly large it might take too long to find a record. To avoid this you could try to find a record in a range of id's and select one of them.

Page 8: Data Base Question

Data Base interview Page 8 of 8

Data Base interview Page 8 of 8

--There were some records found in the specified --range, so select one of them SET NOCOUNT OFFSELECT TOP 1 * FROM Table WHERE id BETWEEN @randno and @randno + 25--Set the @counter variable to 1 to leave the WHILE loopSET @counter = 1END

Depending on the gaps in your id field you might want to change the 25 in something more appropriate for your situation, just test what works best in your situation.

Happy Programming!

Question: How can I find out if a record already exists in a database? If it doesn't, I want to add it.

Answer: I've seen this question posed many times in the ASP Q&A Messageboard... Typically this is done to determine if a user name already exists, common to many login required sites. Many people query the database and then check the values against the returned values through a loop of some sort. If they don't find it, they then add the record. To me, this seems like unnecessary overhead. I've also seen it done by selecting the record, and then checking for EOF, like so:<%Dim strSQLstrSQL = "SELECT * FROM MyTable WHERE username = '" & _

Request.Form("username") & "'"Set rs = db.Execute()

If rs.EOF Then'Now do the insert

Else

Each of these methods require at least two trips to the database. Why not let SQL do it all for you? You can check if the user exists and add him if he doesn't in ONE call! You can either do this with a stored procedure or from ASP.

The stored procedure:

CREATE PROCEDURE InsertName

Page 9: Data Base Question

Data Base interview Page 9 of 9

Data Base interview Page 9 of 9

ENDELSEBEGIN--This means the record isn't in there already, let's go ahead and add

itSELECT 'Record Added'INSERT into MyTable(username, userpassword) VALUES(@username,

@userpassword)END

First, we check if the record exists with the EXISTS keyword. EXISTS executes the query we tell it to (the SELECT) and returns a boolean value. If it finds the record, we return 'This record already exists!' to our recordset and do nothing else. If it doesn't exist, we execute our INSERT statement, and then return 'Record Added' to our recordset. The -- is a comment in SQL, and is equivalent to VBScript's ' or REM.

With the sotred procedure solution, our ASP code would look like:

<%Dim db, rsSet db = Server.CreateObject("ADODB.Connection")db.Open myTest 'use your connection here'And call our Stored procedure passing the username and userpasswordSet rs = db.Execute("InsertName '" & _

Request.Form("username") & "','" & _Request.Form("password") & "'")

'Now let's check what happenedIf rs(0) = "This record already exists!" Then'We can either redirect back to the original page and tell'the user to try again or write something out to this page

ElseResponse.Write "Your user name and password has been accepted."

End If%>

Simple! And only one call. If you prefer not to use a stored procedure, you can easily do the same right from ASP.

<%Dim db, rs, sSQL

username = "Steve"password = "1234"

sSQL = "IF EXISTS(SELECT 'True' FROM MyTable WHERE username = '" & _username & "') "

sSQL = sSQL & "BEGIN "sSQL = sSQL & "SELECT 'This record already exists!' "sSQL = sSQL & "END ELSE BEGIN "sSQL = sSQL & "SELECT 'Record Added' "sSQL = sSQL & "INSERT INTO MyTable(username, userpassword) VALUES('" &

Page 10: Data Base Question

Data Base interview Page 10 of 10

Data Base interview Page 10 of 10

sSQL = sSQL & "INSERT INTO MyTable(username, userpassword) VALUES('" & _

username & "','" & password & "') "sSQL = sSQL & "END"

Set db = Server.CreateObject("ADODB.Connection")db.Open myTest 'use your connection here

'And execute our statementSet rs = db.Execute(sSQL)

If rs(0) = "This record already exists!" Then'We can either redirect back to the page and 'tell the user to try again or write something out to the page

ElseResponse.Write "Your user name and password has been accepted."

End If%>

You can also get more creative in your return values so that you could Response.Write the return value right out to the page. Either way, you're using your resources more efficiently by executing only one database call.

Marco De Luca shares a another clever way to add a record to a table if it doesn't already exist...

I was reading one of your recent articles on 'Adding a record to adatabase table if it doesn't exist' and I figured out another way to do it. I don't know if this will benefit you, but here is the SQL statement for you.

It checks the USERS table to see if a user name and password exist for a user, then it inserts the user name and password if the user doesn't exist. This might be useful as it works in MS Access. I believe your solution works in all other relational database systems other then MS Access.

----'Name to insert' = the user name you want to insert'pword' = the password to insert----

INSERT INTO USERS (UserName, Password)SELECT DISTINCT 'name to insert' as theName, 'pword' as pwordFROM USERSWHERE 'name to insert' & 'pword' NOT In

(select UserName & Password from USERS);

Happy Programming!

Page 11: Data Base Question

Data Base interview Page 11 of 11

Data Base interview Page 11 of 11

Yet another way! (from Bill Wilkinson)

I think this may be the easiest way yet, since you don't really have to change your SQL INSERT code, at all!

<%' I assume you have a connection open...objConn.Errors.Clear ' just to be safe, clear out any existing errors

On Error Resume Next ' then IGNORE errors!catConn.Execute("INSERT INTO whateverTable (field1,field2,field3) Values(777,'whatever','and more')")On Error GoTo 0 ' turn off the ignoring of errors!

' now see if we got any errors from the insert!For Each oops In catConn.Errors

If oops.number = -2147217900 ThenResponse.Write "That item already exists in that table!<BR>"

ElseResponse.Write "Unexpected error: " & oops.number & " -- " &

oops.description End If

Next%>

We use the wonderful error-catching ability of ADODB and VBS to both ignore (from the VBS perspective) and catch (from the ADODB view) a possible error on the INSERT of the possibly duplicate name/value.

The error number -2147217900 equates to &H80040E14 (0x80040E14 for you Java/C/C++/JavaScript people), which is of course the error that is thrown (at least by Access!) when you attempt such a duplicate insertion. That number may or may not be the same for other databases, but it's easy to check which error number you are getting (heck, the above code will tell you, giving an "Unexpected error" message) and modify the code to match.

And, as noted, you don't even have to change your INSERT query one little iota!

Question: What is Transact-SQL (T-SQL)?

Answer: Transact SQL, also called T-SQL, is Microsoft's extension to the ANSI SQL language. It is the driving force of Microsoft's SQL Server and is a dynamic database programming language.There have been several extensions added to the ANSI SQL language which have become their own SQL language. Oracles PL/SQL is another.So if you were using an Oracle database, you would do database programming in PL/SQL. Just like you use T-SQL with SQL server.

Page 12: Data Base Question

Data Base interview Page 12 of 12

Data Base interview Page 12 of 12

How is T-SQL Used?T-SQL is written inside of a stored procedure. A stored procedure is a stored set of SQL commands that sit on the physical server. In this case the SQL server. They are compiled after their first use and take heavy burden off the server. Often with ASP development you run in to situations where interaction between the database and the application are rapidly in succession. Like this.

A new user comes in. Lets put him in the users table. Return the identity. Now lets update the member count for his company in the Comapany table. Add him to the company members table with his new ID. He was sponsored by another company member so lets track all that as well.

In a normal ASP application we would be doing ALL of the above from the application. We would execute 1 SQL statement, come back and do the next, come back and do the next, etc.... With no valid reason for doing so. The above scenario could all be done dynamically with T-SQL in 1 stored procudure call. Thus several SQL statements execute with only 1 trip to the database as opposed to several.

Why T-SQL?DynamicStatic SQL, like you write in your ASP pages, has several draw backs.The biggest being that it is static. With dynamic SQL you can dynamically build your queries and get a high amount of reuse out your objects. Much like you would use IF statements and Select CASE statements in ASP program, you can do the same with T-SQL. The following is an example of a TSQL statement that selects a different field in the SQL Server Database based on the parameters passed to the Stored Procedure (Stored Proc).CREATE PROCEDURE SP_Products@cat int,@Price nvarchar(10)AS

Select CODE,TITLE,Version,Status,

Case @PriceWhen 'Price' THEN PriceWhen 'PriceA' Then PriceAWhen 'PriceB' Then PriceBWhen 'PriceC' Then PriceC

End,Lots,LotsOf,Description,Pic

From ProductsWhere Category = @cat ORDER BY CODE

In the above example I am selecting a different Price field based on the user level of the buyer.

Page 13: Data Base Question

Data Base interview Page 13 of 13

Data Base interview Page 13 of 13

Extended FunctionalityWithout the ability to dynamically change your SQL statements in your Active Server Pages, you have no functionality. Look at the example above. Sure I could have used ASP to generate the same SQL query but at an expensive of extra data processing, combining technologies, and complexity to update.

MaintainabilityStored Procedures are essentially functions. Just like you build a function for maintenance, a stored procedure works the same way. It is an object oriented approach to database programming at the database level. Not the application level (which is not an option regardless).

SecurityStored Procedure calls say nothing important to the observer. If you are concerned about who knows what about your database,you hide practically everything with stored procedures.

The Editor ItselfEditing stored procedures on the SQL server is also far more practical and user friendly than opening ASP files and re-writing static SQL.The SQL server also checks your syntax and will not let you write an invalid query. You do not have to keep hitting the refresh button in the browser until everything checks out.

So that's an overview of T-SQL. Any serious database programmer should be learning how to use it. Especially for large complex applications. Look for specific T-SQL examples soon. This is also a great site for SQL information -http://www.sqlteam.com

Question: How do I use CASE in Transact SQL?

Answer: Case Statements in Transact SQLSelection criteria in your Stored Procedures will need to change at times if you really want high reuse out of your Stored Procs. You can use CASE to do this. CASE is very similar to SELECT CASE in any othr data processing language.

The following stored procedure will select a different Price from the database based on the login level of the user. CREATE PROCEDURE SP_Products@cat int,@Price nvarchar(10)AS

Page 14: Data Base Question

Data Base interview Page 14 of 14

Data Base interview Page 14 of 14

When 'PriceC' Then PriceCEnd,Lots,LotsOf,Description,PicFrom ProductsWhere Category = @cat ORDER BY CODE

Based on what you have just seen you may be thinking CASE will work everywhere. It would be only practical to assume this will work with the ORDER BY clause.

CREATE PROCEDURE SP_Products@cat int,@Price nvarchar(10)AS

Select CODE,TITLE,Version,Status,Lots,LotsOf,Description,PicFrom Products Where Category = @cat ORDER BY

Case @PriceWhen 'Price' THEN TitleWhen 'PriceA' Then VersionWhen 'PriceB' Then StatusWhen 'PriceC' Then Lots

End

Well guess what? It will compile file. The syntax checker in SQL Server will not throw an error but IT DOES NOT WORK. SQL server is very fussy about where CASE is used. And in most cases unlogically.

UPDATE (Feb. 6th, 2002)The above comments are in regard to Microsoft SQL Server 6.5. The above code works in Microsoft SQL Server 7.0 and Microsoft SQL Server 2000.

Visit SQLteam.com for more great information on Transact SQL!

Question: I let my users put in search strings for any number of fields. How do I convert their inputs into a usable SQL search string?

Answer: We will assume that the first page is a form something like this:<FORM Action="page2.asp" Method=Post>Company name: <INPUT Name="Company" Size=60>City name: <INPUT Name="City" Size=60>Type of product:

<SELECT Name="Product">

Page 15: Data Base Question

Data Base interview Page 15 of 15

Data Base interview Page 15 of 15

<OPTION>Hardware<OPTION>Software

</SELECT>...</FORM>

Note that we purposely show a mixture of text and SELECT specifications that the user can make.

So now, on the next page (Page2.asp), we can do this:

<%' put the list of searchable fields into an array, thus:formFields = Array("Company", "City", "Product")' and the list of TABLE field names into a parallel array, thus:dbFields = Array("CompanyName", "CityName", "ProductType")

where = "" ' we will build the WHERE clause...delimiter = " WHERE "

For fnum = 0 To UBound( formFields )fval = Request.Form( formFields(fnum) )If ("X" & fval) <> "X" Then

' user gave us a value for this field!' ...build one LIKE clause, for this field...' (of course, this could be an = test instead of LIKE)where = where & delimiter & dbFields(fnum) & " LIKE '%" & fval & "%'"' change delimiter to the conjunction now!delimiter = " OR " ' this might be " AND " instead!

End IfNext' the WHERE clause is built...or is it?If Len(where) = 0 Then

' ??? the user did not give *any* values ???' ??? YOU must decide what to do ???

End If' so build the full query:SQL = "SELECT * FROM table " & where%>

Do you see that? By putting the field names into arrays, the whole thing is wondefully flexible. Add more fields or remove some at any time by just changing the array contents. No need to change any actual logic in the code.

NOTE: If the DB fields and the form fields have the same name, then of course you can use only one array. We show it with a pair of arrays to emphasize the flexibility.

Question: How can I eliminate duplicates from a recordset?

Page 16: Data Base Question

Data Base interview Page 16 of 16

Data Base interview Page 16 of 16

Answer: This question typically appears in the context of producing a <SELECT> list from a database table, where the user wants to show a list of all the unique items in the table. But the same problem can appear in different guises.

The answer is the Distinct keyword of SQL.

Consider this table:

Name Group StatusJones Admin ActiveSmith Admin PassiveWilson Sales ActiveHanson Sales PassiveBarney Systems ActiveHalley Systems Active

Let's look at the different kinds of queries we can make on that table and the results we will get.

SQL = "SELECT DISTINCT Group FROM table ORDER BY Group"results:

AdminSales Systems

Makes sense! We asked for all the possible values of Group but then used Distinct to eliminate the duplicates!

SQL = "SELECT DISTINCT Group, Status FROM table ORDER BY Group"results:

Admin, ActiveAdmin, PassiveSales, Active Sales, Passive Systems, Active

Aha! The Distinct keyword applies to all fields of the returned recordset! If two records are different in any field, then they are different. Period. And using Distinct will not magically collapse them into one entry.

Do note the Systems example! Only one record is returned here, because all (okay, both) the Systems entries have the same value for Status so indeed there is only one truly distinct record.

Okay?

Page 17: Data Base Question

Data Base interview Page 17 of 17

Data Base interview Page 17 of 17

Question: How can I pick random records from a table?

Answer: The answer posted here was prompted by this particular question:

>I want to get some non-repeated distinct records from a records pool>which, of course, contains only distinct records. For example, suppose>I have 100 distinct records in an array, I want to randomly pick 5>records from these 100 records. These records should not be repeated. I>know there are lots of algorithm, but I don't want to use loops, unless>I really have to, to check if a record has been picked already. I want an>efficient method since it is a database problem.

There are two types of simple solutions. (And there are more complicated ones, for more complicated situations.)

They apply to these three situations:

In general, you should prefer the solutions in *numerical* order, though there are surely cases where 3 is better than 2. 1 is always the best, if it is applicable to your DB info.

(1) When you have a table that has an autoincrement or identity field that starts at a known number and goes to a known end number with no gaps. In the case of relatively static data (e.g., picking 5 ad links from a set of 100), you should be able to establish this kind of table. If so, then use solution number 1 (below).

(2) For tables where such a gap-less numbering system is impossible or impractical (e.g., a "live" data system) but where the total number of records is a reasonable number (certainly 100 is viable, perhaps even 1000 if you are desperate), see solution number 2 (below).

(3) When you have a very small table and need only a few fields from each record to build your HTML page. (As an arbitrary figure, lets say the product of record count times fields needed is no more than 200 to 2000 for this scheme, depending upon how much memory your system has and how heavily loaded it is.) If this matches your situation, use solution # 3, way below.

If none of those situations obtain, then the best solution is probably to build ANOTHER table with the characteristics described in situation 1, where the sequentially numbered records simply contain references to the records in the main table. If the main table is dynamically updated, then you will want to rebuild the auxiliary table from time to time.

Anyway....

Solution #1:

Page 18: Data Base Question

Data Base interview Page 18 of 18

Data Base interview Page 18 of 18

If possible, keep the count of the number of record someplace handy, outside the DB. If not, find the count via something likeSet RS = conn.Execute("Select Count(*) AS RecCount From table")recCount = RS("RecCount")

and then in either case do:

CONST choiceCount = 5 ' or however many you wantCONST firstRecNum = 1 ' adjust if not true for your tablechoices = ""chosen = 0Do While chosen < choiceCountchoose = Int( Rnd * recCount ) + firstRecNumchTest = "#" & choose & "#"If InStr( choices, chTest ) = 0 Thenchoices = choices & chTestchosen = chosen + 1

End IfLoopchoices = Replace( choices, "##", "," )choices = Replace( choices, "#", "" )SQL = "SELECT fld1,fld2 FROM table " _

"WHERE sequenceField IN (" & choices & ")"Set RS = conn.Execute( SQL )

Do you see the tricks? I insist on finding the record number surrounded by #...# in the

Page 19: Data Base Question

Data Base interview Page 19 of 19

Data Base interview Page 19 of 19

Set RS = conn.Execute("SELECT primaryKeyField FROM table")allKeys = RS.GetRows ' convert RS to an array!recCount = UBound( allKeys, 2 ) + 1' a lot of similarity to solution #1...CONST choiceCount = 5 ' or however many you wantchoices = ""keys = ""chosen = 0Do While chosen < choiceCount

choose = Int( Rnd * recCount )chTest = "#" & choose & "#"If InStr( choices, chTest ) = 0 Then

choices = choices & chTestkeys = keys & DELIM & allKeys(0,choose)chosen = chosen + 1

End IfLoop

' if the primary key is not numeric!keys = Mid( keys, 3 ) & Chr(39)

*or **' if the primary key *is* numerickeys = Mid( keys, 2 )

SQL = "SELECT fld1,fld2 FROM table " _"WHERE primaryKeyField IN (" & keys & ")"

Set RS = conn.Execute( SQL )

As you can see, this is really the same solution as #1 except that instead of using the simple sequential numbers we use the record number in the array to pick the primary key and *then* pick the entirety of the needed records.

Solution #3:

But if you have relatively small records and relatively few of them, then why not eliminate the two-step process of solution #2? Okay, I give up...why not:RANDOMIZE ' don't forget this one!' select *only* the fields you must have, for efficiencySet RS = conn.Execute("SELECT fld1,fld2,fld3 FROM table")allRecords = RS.GetRows ' convert RS to an array!recCount = UBound( allRecords, 2 ) + 1' still similarity to solution #1...CONST choiceCount = 5 ' or however many you wantchoices = ""chosen = 0Do While chosen < choiceCountchoose = Int( Rnd * recCount ) chTest = "#" & choose & "#"

Page 20: Data Base Question

Data Base interview Page 20 of 20

Data Base interview Page 20 of 20

chTest = "#" & choose & "#"If InStr( choices, chTest ) = 0 Then

choices = choices & chTestchosen = chosen + 1

' Then RIGHT HERE put out the HTML that' is appropriate for the chosen record!info1 = allRecords( 0, choose )info2 = allRecords( 1, choose )info3 = allRecords( 2, choose )Response.Write "<SOMEHTMLTAG>" & info1 ........

End IfLoop

So the common trick to all of these is the use of a string to "remember" the records already chosen and then the search within that string via the InStr function to check for already chosen records. And then we just make sure that we use delimiters that will ensure we don't get any "false matches" and the rest is easy.

For more information on selecting random records from a database table, check out these 4Guys articles:Returning a Random Number of Database RecordsChoosing a Random Record from a RecordsetGetting a Random Record Using a Stored ProcedureReturning Rows in Random Order

Question: (1) How can I have two dates and select all records that have a date field with a value between those two dates? (2) How can I select all records in a given month?

Answer: (1) For the purposes of answering the first question, we will assume that both dates are entered into a form on the prior page by the user:

<%' note: if either date entered by the user is invalid, ' the call to CDate will fail with an error.firstDate = CDate( Request.Form("startDate") )

That code works for an Access database. Only Access surrounds literal dates with #...#. For any other database use

Page 21: Data Base Question

Data Base interview Page 21 of 21

Data Base interview Page 21 of 21

<%SQL = "SELECT * FROM table WHERE theDateField BETWEEN '" & firstDate & "' AND '" & lastDate & "'"%>

See CAUTION below!

(2) There are probably several ways to find all records in a given month, but this way should work universally for all databases. It relies on a quirk in the DateSerial function, but it is a quirk that is well described in the Microsoft VBScript documentation.

Again, for ease of demonstration, we will assume that the year and month to be used come from form fields on the prior page:

<%' what year and month did the user choose?' (again, if the numbers are not valid CInt will give an error)theYear = CInt( Request.Form("chosenYear") )theMonth = CInt( Request.Form("chosenMonth") )

' find first day of the given month...firstDate = DateSerial( theYear, theMonth, 1 ) ' day 1 of theMonth in theYear' now comes the "quirk" from the MS documentation:lastDate = DateSerial( theYear, theMonth + 1, 0 )

SQL = "SELECT * FROM table WHERE theDateField BETWEEN #" & firstDate & "# AND #" & lastDate & "#"...%>

The quirk in DateSerial is this: If you ask for a month or day out of range, then the out of range value is converted by quite logical means to the best corresponding day. So if you give 13 as the month, DateSerial figures out you want January of the year after the given year. And if you give 0 as the day, then DateSerial figures out you want the day before the first day of the given month, which is of course the last day of the prior month!

Again, the form shown is for an Access DB. Change each # character to a ' for any other DB.

CAUTION! If the field you are checking, in either of these sets of code, actually contains a DateTIME value (that is, if there is a time component to the value of the field), then the code will not work in all cases! Reason: A date alone is considered to be the same as that date at midnight of that day!

Page 22: Data Base Question

Data Base interview Page 22 of 22

Data Base interview Page 22 of 22

So if the field in the DB is (say) April 2, 2002 9:30:10 AM and you are looking for fields with values between March 17, 2002 AND April 2, 2002 ... well, you won't find that record! Because 9:30:10 AM is *after* midnight and so is not between the two given dates.

In Access, this is easy to fix: Just use DateValue(theDateField) in place of the bare date field! This strips off the time part of the field and now the comparisons all make sense! For other DBs, check the documentation to see if they have a functionality equivalent to DateValue. (Incidentally, VBScript as used in ASP also has the DateValue function, so you could check the VBS manual to see how it works, if need be.)

Question: How do I get SQL to accept an apostrophe in various queries, such as when I try to add a user named "O'Brien"?

Answer: Just as VBScript uses a pair of " marks inside a literal string to represent a single embedded ", so does SQL use a pair of ' marks within a literal to mean a single embedded '.

That is, with VBScript you can do:<%demoText = "He said, ""This should work!"" "Response.Write demoText

and the lineHe said, "This should work"

will appear in the browser.

So with SQL, you do something similar:That will insert the name O'Brien into the database.

What do you do when you get a value from a FORM input and need to check for an embedded apostrophe? Use the VBScript REPLACE function.

Thus:

Page 23: Data Base Question

Data Base interview Page 23 of 23

Data Base interview Page 23 of 23

SQL = "INSERT INTO table (userName) VALUES(" & valueForSQL & ")"someConnection.Execute SQL

%>

If the strings used in that REPLACE are hard to read, you could do this, instead:

<%SQLQT = Chr(39) ' this is the apostrophe character!...valueForSQL = Replace( Request.Form("userName"), SQLQT, SQLQT &

SQLQT )...

%>

And that makes it pretty clear that you are replacing a single apostrophe with a pair of them.

Zach Mattson offers a nifty function to automatically replace all instances of single apostrophes with double apostrophes in the Request.Form collection!

I use a function that takes the entire form collection and stuffs it into another collection (a Dictionary object) but, as it moves the contents from the Request.Form collection over to the Dictionary object, it replaces all single apostrophes with double apostrophes.

<%'*********************************************************Function RemoveCharacters() dim frm,item Set frm = Server.CreateObject("Scripting.Dictionary") frm.CompareMode=1 For each Item in Request.Form frm.Add Cstr(Item), Replace(Request.Form(Item),"'","''")

Next Set RemoveCharacters = frm

End Function '*************************************************%>

Calling and using this function is a breeze:

<% dim myformSet myform = RemoveCharacters()

myform("formfieldname") ' refer to a specific form field%>

Big Thanks To Bill Wilkinson for his help on that one!

Page 24: Data Base Question

Data Base interview Page 24 of 24

Data Base interview Page 24 of 24

Question: How can I order the results from a database query?

Answer: This FAQ answer comes from Rob Taylor:

Ordering things on your page from your database is very easy - simply specify the columns you wish to order your query by using an ORDER BY clause. When using the ORDER BY clause, ordering happens by default in Ascending order (lowest to highest). To order in reverse (highest to lowest), use the keyword DESC.

Some examples of ordering using the ORDER BY clause can be seen below:SELECT * from tblRockers ORDER BY ID

This will produce all the data in Ascending order on the ID column.

ID Band Category First Album1 Jackson Browne Country/Rock 19691 Bonnie Raitt Country Rock 19741 Steppenwolf Classic Rock 19642 Ozzy Heavy Metal 19802 Rush Hard Rock 1969

SELECT * from tblRockers ORDER BY ID DESC

This will produce all the data in Descending order by ID.

ID Band Category First Album2 Ozzy Heavy Metal 19802 Rush Hard Rock 19691 Jackson Browne Country/Rock 19691 Bonnie Raitt Country Rock 19741 Steppenwolf Classic Rock 1964

Now lets try to organize the data even more by adding other column names to the ORDER BY clause.

SELECT * from tblRockers ORDER BY ID,[First Album]

will return:

ID Band Category First Album1 Steppenwolf Classic Rock 19641 Jackson Browne Country/Rock 19691 Bonnie Raitt Country Rock 19742 Rush Hard Rock 19692 Ozzy Heavy Metal 1980

Notice the data is ordered by ID and by First Album in Ascending order.

Page 25: Data Base Question

Data Base interview Page 25 of 25

Data Base interview Page 25 of 25

Lets look at Descending

SELECT * from tblRockers ORDER BY ID,[First Album] DESC

Gives us this:

ID Band Category First Album2 Ozzy Heavy Metal 19802 Rush Hard Rock 19691 Bonnie Raitt Country Rock 19741 Jackson Browne Country/Rock 19691 Steppenwolf Classic Rock 1964

Happy Programming!

Question: Why do I get a closed recordset from my stored procedure? Why don't I get the expectedfields from the stored procedure?

Answer: Let's take a look at the following stored procedure. This procedure needs to get a value from the settingstable to get the record we want.

CREATE PROCEDURE spGetRecord (@param int)AS

Declare @varID int

SELECT @varID = field2 FROM SettingsTable WHERE ID = @paramSELECT * FROM DataTable where ID = @varID

(of course this could be done with a nested SELECT statement, but this example should give you the general idea)

Now when you try to request a field in the recordset in your ASP page you can get the following error:

"Item cannot be found in the collection corresponding to the requested name or ordinal"

Now most people take another look at their table to verify that the field does exist and start to pull their hairs out because the field really exists in the table.

A similar thing can happen when you use statements like DELETE, INSERT, UPDATE, CREATE, etc. in your stored procedure. You get back a recordset, but it's not the correct one! When you execute the query in the query analyzer it does return the correct values, but your ASP page seems to have incorrect values, or no values at all.

Page 26: Data Base Question

Data Base interview Page 26 of 26

Data Base interview Page 26 of 26

This is because every SQL statement in a stored procedure, by default, sends back a special message informing that it has completed the statement. This special message, among other things, includes the "nn rows affected" message you've probably seen if you've executed SQL statements through SQL Server's Query Analyzer. When ADO gets back these messages, it has trouble differentiating between what SQL statement's values it should populate in the recordset. In the stored procedure in the example the results of the first SELECT @varID= statement are passed to the recordset. The results of the second select (the results we actually want!) are passed as well, as the next recordset, but not as the default recordset. (Of course, you can get to these results using the ADO Recordset's NextRecordSet method - read Using Multiple Recordsets for more information on the NextRecordset method).

Now that we know why this happens, let's see what we can do about it. Essentially, you have to tell SQL server not to return a message to the client for every completed SQL statement in the stored procedure. This can be accomplished via the SET NOCOUNT ON and SET NOCOUNT OFF statements like so:

CREATE PROCEDURE spTest

AS

SET NOCOUNT ON SELECT @nextID = nextID FROM SettingsTableSET NOCOUNT OFF

SELECT * FROM DataTable where ID = @nextID

The SET NOCOUNT [ON | OFF] option tells SQL Server to return no information for the statements between the two lines. So always use SET NOCOUNT ON for statements that aren’t supposed to return any records!

Question: Why does the .RecordCount property return a value of -1?

Answer: Many times developers new to ADO or ASP poke through the documentation and quickly come upon the .RecordCount property of the ADO Recordset object. "Great!" they may think, "I can use this to get the total number of records in a query." So they hammer out a quick ASP page, something like:Dim objRSSet objRS = Server.CreateObject("ADODB.Recordset") objRS.Open "SELECT * FROM Table1", "DSN=MyDSN"Response.Write "There are " & objRS.RecordCount & " records in Table1"

When they run, this however, the output they get is:There are -1 records in Table1

Page 27: Data Base Question

Data Base interview Page 27 of 27

Data Base interview Page 27 of 27

Egad! How can a database table have -1 records? It can't; it doesn't. The .RecordCountproperty requires a non-forward-only cursor to return sensible results. This is because all of the records in the Recordset have to be stepped through and, then, the cursor has to be returned to the beginning of the Recordset. This requires a dynamic cursor. When opening a Recordset a forward-only cursor is used by default; a forward-only cursor, as its name implies, does not allow the cursor to step back through the records of a Recordset. (To learn more about Recordset cursors be sure to read: Recordset Cursors: Choose the Right Cursor for the Right Job.)

So how do we get the .RecordCount property to work? In this ASPMessageboard postBill Wilkinson shares:

<%

Why does that work? Because of the way we opened the RecordSet. If you don't specify the open modes, you end up getting a forward-only, read-only "cursor." And such cursors can NOT give you a valid RecordCount, hence you always get -1. Using 3,3 (or using the constants adOpenStatic, adLockOptimistic if you have included a valid adovbs.incfile) gives you a cursor that *can* count records for you. (For more information on using adovbs.inc to avoid "magic numbers" (the 3, 3, for example), see: FAQ: What is adovbs.inc? Why should I use it? What benefits does it have?)

Oh...and one more comment: If you learn to use the RecordSet.GetRows method, to convert your recordset into an array, then you gain efficiency and you don't need to use RecordCount (because the size of the array tells you how many records there are).

Oh, what the heck:

Page 28: Data Base Question

Data Base interview Page 28 of 28

Data Base interview Page 28 of 28

%>

(For more information on GetRows be sure to check out: Use GetRows to Speed Up Displaying "Skinny" Tables)

Happy Programming!

Question: Why do I get the error "The application is using arguments that are of the wrong type, areout of acceptable range, or are in conflict with one another" when I try to open a recordset?

Answer: A typical example code might be:' after creating and opening a connection...Set RS = Server.CreateObject("ADODB.RecordSet")RS.Open "someTableName", theConnection, adOpenForwardOnly, asLockOptimistic, adCmdTable

and the resulting error would be:

ADODB.Recordset error '800a0bb9' The application is using arguments that are of the wrong type, are

The problem: You have not defined the constants adOpenForwardOnly, asLockOptimistic, and adCmdTable. And VBScript converts any undefined variables into *zero*. So you *really* just did:And that is simply not legal, as VBS so carefully and succinctly tells you.

You need to include the file adovbs.inc in your page. For more on this topic, see ASPFAQ number 123.

Akhilesh correctly points out that you can also get this error if you try to use an uninitialized variable for the connection argument to ADODB.RecordSet.Open. This simple page illustrates that:

Page 29: Data Base Question

Data Base interview Page 29 of 29

Data Base interview Page 29 of 29

Set RS = Server.CreateObject("ADODB.RecordSet")conn.Open "someDSNperhaps"RS.Open "Select * From test", con ' ### misspelled variable name! ###%></BODY></HTML>

This is a good argument for using <% OPTION EXPLICIT %> at the top of your ASP pages, as ASP would then tell you that con is not defined.

Question: ** UPDATED 24 June 2003 **: Why am I getting "Operation must use an updateable query" errors?

Answer: The answer to this question comes from a post by Derek Branch in the Databases Forumon the ASPMessageboard.

Here's what Microsoft has to say about the error message... It's usually a problem with permissions:[http://support.microsoft.com/support/kb/articles/Q175/1/68.ASP]

SYMPTOMSThe following is a common error encountered when using ActiveX Data Objects (ADO) with Active Server Pages:

Microsoft OLE DB Provider for ODBC Drivers error ' 80004005' [Microsoft][ODBC Microsoft Access 97 Driver] Operation must use an updateable query.

CAUSEThis article explains the three primary causes of this error, and the workarounds. Although this article refers to Microsoft Access databases, the information provided here also applies to other types of databases.

RESOLUTION This error is typically encountered when your script attempts to perform an UPDATE or some other action that alters the information in the database. This error occurs because ADO is unable to write to the database for one of the following reasons:

The most common reason is that the Internet Guest account (IUSR_MACHINE) does not have Write permissions on the database file (.mdb). To fix this problem, use the Security tab in Explorer (see below if the Security tab does not appear!)) to adjust the properties for this file so that the Internet Guest account has the correct permissions. NOTE: When using Microsoft Access databases with ADO, it is also necessary to give the Internet Guest account Write permissions on the directory containing the .mdb file. This is because

Page 30: Data Base Question

Data Base interview Page 30 of 30

Data Base interview Page 30 of 30

Jet creates an .ldb file to handle database locking. You may also need to give read/write permission on the Temp folder because Jet may create temporary files in this directory.

A second cause of this error is that the database was not opened with the correct MODE for writing. If you perform the Open on the Connection object, you use the Mode property to indicate the permissions on the connection as shown here: SQL = "UPDATE Products Set UnitPrice = 2;" Set Conn = Server.CreateObject("ADODB.Connection") Conn.Mode = 3 ' 3 = adModeReadWrite Conn.Open "myDSN" Conn.Execute(SQL) Conn.Close

NOTE: By default, the MODE is set to 0 (adModeUnknown), which generally allows updates.

Another cause of this error is that the "Read Only" setting may be checked in the Options page for this DSN in the ODBC Manager.

The last issue and work around pertains to any SQL data source. The error can be caused by SQL statements that violate referential integrity of the database. Here are a few of the most common queries that fail:

The simplest group to deal with are those you cannot change: crosstab, SQL pass-through, union, or update (or make-table) action queries that have UniqueValue properties set to Yes.

Another very common cause is when the join includes linked ODBC tables that do not have unique indexes. In this case, there is no way for SQL to guarantee that records are unique in a table that has fields whose value will change with the query.

One cause does have a robust workaround. If you try to update a join field on the "one" side of a "one-to-many" query it will fail unless you turn on cascading updates. This way, you delegate referential integrity to the JET engine.

Alert reader Matt Smith wrote in with the following situation and solution...

"Our situation: ASP and Access DB worked fine locally - did not work when moved to the host server. 'Write' permissions were the obvious culprit, so we modified the permissions on the directory and also thru the IIS Admin interface. Still did not work. Contacted the host and they said "You need to set write permissions before you put the db in it. Try deleting the database, and re-uploading it, and it should work." Had not heard that before, but it worked."

WHAT IF THE SECURITY TAB DOESN'T APPEAR IN MY FILE/FOLDER

Page 31: Data Base Question

Data Base interview Page 31 of 31

Data Base interview Page 31 of 31

PROPERTIES?

If you are not using NTFS--if you are still using Fat16 or Fat32--then it should not appear. But by the same token, you shouldn't have a need for it on such file systems.

If you are using NTFS--and especially if you are using WindowsXP--then try the following steps:

(1) Bring up "Windows Explorer" or "My Computer" in a window.(2) Click on the "Tools" menu in that window.(3) Click on the "Folder Options" menu item. (4) Click on the "View" tab. (5) Find the checkbox labelled "Use simple file sharing (Recommended)" (6) UNCHECK that checkbox!(7) Say OK. Close the dialog. Close the window. (8) You might have to log off and back on again. I didn't, but others have reported they had to. (9) Now go view the properties of a folder or file with Windows Explorer/My Computer and see if the Security tab isn't there!

Happy Programming!

Question: Why do I get a type mismatch error from my query? Why do I get the message Syntax error (missing operator) from my query? Why do I get a Too few parameters expected 1 error from my query?

Answer: All these errors can occur in a SELECT, UPDATE, INSERT or other SQL query. Usually, each simply means that you are trying to query, change, or add to a given field but the data you are using does not match the type of that field or that the field name you are using doesn't exist.

For example, if you do<%

and the system gives you one of these errors, it's a pretty safe bet that the reason is because the field userName either doesn't exist in the table (the field name is misspelled?) or it is a text field, but you did not enclose the text value you are trying to compare it to in apostrophes. The correct form is

Page 32: Data Base Question

Data Base interview Page 32 of 32

Data Base interview Page 32 of 32

%>

The errors can occur in the other direction, as well:

<%SQL = "SELECT * FROM table WHERE accountNumber = '" & Request.Form("acctNo") & "'"%>

Here, a type mismatch (or, more likely with Access, a missing operator message) almost surely indicates that the field accountNumber in the table is a numeric field, and so you should not use the apostrophes around the test value.

If you are using Access, then you have the further possibility that you may be using the wrong format for a Date/Time field:

<%SQL = "SELECT * FROM table WHERE startDate > '" & Request.Form("when") & "'"%>

One of these errors from that line, assuming you are using an Access Database, presumably means that you should have surrounded the date with #...#, thus:

<%SQL = "SELECT * FROM table WHERE startDate > #" & Request.Form("when") & "#"%>

Note that Access is the only database that uses #...# for dates instead of simply '...'.

Summary: Go over your query carefully. Check for field names and types in the original database. Make sure all your usages are correct. Then try again.

Question: How can I display images that reside in my database through an ASP page?

Answer: This is a fairly commonly asked question on ASPMessageboard.com. Essentially, you can have images stored in a database in one of two formats: as a text field specifying a URL from where the image can be found; or as a binary field stored within the database.

Page 33: Data Base Question

Data Base interview Page 33 of 33

Data Base interview Page 33 of 33

There are times when each approach makes sense. Since we are dealing with the Web, oftentimes it makes more sense to use the former approach, to simply store the URLs to the images as opposed to actually storing the binary contents of the image in the database. Such practices will keep your overall database size small and make displaying images a breeze (just need a simple IMG tag whose SRC attribute uses the database value). The disadvantage with this approach is that is decouples the images from the database. This could be a problem if your database is used on multiple servers scattered throughout the country or world, and the images are on a computer that is, perhaps, only reachable through a certain location, perhaps an intranet of some kind.

The other option is to store the image contents into the database as a binary field. As I summarized before, this oftentimes greatly increases the overall size of your database, but is nice because all of the data for your database exists in one location, as opposed to having the images residing separately.

So how does one display an image from a database via an ASP page? Using the first technique, assume that your table had a column called ImageUrl, that contained a fully qualified URL to the image file, such as: http://www.yourserver.com/images/hat.gif, or whatever. Then, to display the image via an ASP page, you'd simply need to display an IMG tag like so:Response.Write("<img src="" & Recordset("ImageUrl") & """>")

This simply outputs an IMG tag with the SRC set to the ImageUrl column in the database table.

Displaying an image whose binary contents resides in the database itself is a triffle more difficult. For this scenario it is assumed that there is a BLOB column named Picture that stored the actual bits for the image. In your ASP page you need to, essentially, do three things:

1.) Read in the image column into a Recordset2.) Set the ContentType property of the Response object to the image's filetype.3.) Use Response.BinaryWrite to output the contents of the image.

The Response.ContentType property sends a bit of information to the browser informing the browser how to interpret the binary information it will be receiving. So, if you are storing a gif image in the database, you should set Response.ContentType to image/gif.

The BinaryWrite method allows the Response object to send binary information as opposed to textual information. You'll need to use this to blast the image's bits to the browser. A high-level look at the code needed to display an image whose bits reside in the database itself can be seen below:

Page 34: Data Base Question

Data Base interview Page 34 of 34

Data Base interview Page 34 of 34

'Read in the image from the databaseSet objRS = objConn.Execute("SELECT Picture FROM TableName WHERE ID = 1")

'Set the ContentType to image/gifResponse.ContentType = "image/gif"

'Send the binary bits to the browserResponse.BinaryWrite(objRS("Picture"))

That's all there is to it! Realize that you cannot display both binary information and textual information from this ASP page, you can only send the image down the wire. If you want to include this on an ASP page with textual information, you will need to reference the ASP page that displays the image via an IMG tag in the ASP page that you wish to display textual information alongside the picture. For example:

This is a picture:<img src="ShowPicture.asp">

For more information on both of these processes, be sure to read: Displaying Images that Reside in a Database

Happy Programming!

There are oftentimes when images or other binary information needs to be stored along with textual information in a database. For example, imagine a database that contains Employee information. Some non-textual information that one may wish to store in this database could be a picture of the employee and the employee's current resume in Word format. This article focuses on how to save and retrieve image information.

re are two approachs to saving images in a database. One is to use textual information to save the URL of the image in the path. For example, our Employee table may have the following structure:

Column DatatypeEmployeeID int (Primary Key)FirstName varchar(50)LastName varchar(50)Picture varchar(100)

The Picture column would then contain the path of the respective Employee's personal photo, perhaps /employee/images/Bob.Smith.jpg or http://EmployeeServer/pictures/Bob.Smith.jpg. This method is more efficient than storing the actual binary image in the database, but suffers from scalability issues. In such a scenario, the Employee's photos must be saved on the Web server or on a machine the Web server can access. If this database was to be replicated to several database servers, the physical files

Page 35: Data Base Question

Data Base interview Page 35 of 35

Data Base interview Page 35 of 35

would also need to be replicated and their filenames and locations preserved. This would be a major headache!

Fortunately modern database systems allow you to have non-textual columns. In Microsoft SQL Server, the image datatype can be used to save a binary object (such as an Employee's picutre). In Access, use the OLE Object datatype. By placing the actual image in the database, however, there can be some efficiency concerns, especially if the images are large and/or the database resides on a different server than the Web server.

Displaying an Image From a Database Using ASPFor this example, image that our Employee table (defined above) had the Picture column datatype changed to an image datatype. It is essential that we know the type of image being stored in the Picturecolumn, whether it is a GIF, a JPG, a PNG, etc. For this example we'll assume all pictures are JPG. (In a scenario where there may be multiple file formats, a look-up table should be used to specify the filetype for each row.)

Now we need to create an ASP page whose sole responsibility is to display a specific Employee's picture. This ASP page will be called ShowEmployeePicture.asp, and will expect a command through the QueryString, the Employee's EmployeeID. Furthermore, this ASP page will be called through an IMG tag on an ASP or HTML page that wishes to display a particular Employee's picture. For example, on a page that wished to show Employee #007's picture, the following IMG tag should be used:

<IMG SRC="ShowEmployeePicture.asp?EmployeeID=007">

(Note that this method of displaying ASP pages is strikingly similar to the technique discussed in a previous 4Guys article: Serving Dynamic Images from Static Web Pages. If you have not yet read that article, I suggest you do so now.)

The code for ShowEmployeePicture.asp needs to perform a number of tasks:

1.) Read in the EmployeeID passed through the QueryString2.) Grab the picture from the database corresponding to the passed-in EmployeeID3.) Set the ContentType to image/jpeg, since all Employee photos are JPG files (If you are unfamiliar with Step 3, using the Response.ContentType property, take a moment to read Serving Dynamic Images from Static Web Pages)4.) Use Response.BinaryWrite to send the picture to the client

Here is the code for ShowEmployeePicture.asp:

<%'Step 1: Read in the employee ID from the querystringDim iEmployeeIDiEmployeeID = Request.QueryString("EmployeeID")

'Step 2: grab the picture from the databaseDim objConn, objRS, strSQLstrSQL = "SELECT Picture FROM Employee WHERE EmployeeID = " & iEmployeeID

Set objConn = Server.CreateObject("ADODB.Connection")objConn.Open "DSN=EmployeeDatabase"

Page 36: Data Base Question

Data Base interview Page 36 of 36

Data Base interview Page 36 of 36

Set objRS = Server.CreateObject("ADODB.Recordset")objRS.Open strSQL, objConn

'Step 3: Set the ContentType to image/jpegResponse.ContentType = "image/jpeg"

'Step 4: Use Response.BinaryWrite to output the imageResponse.BinaryWrite objRS("Picture")

'Clean up...objRS.CloseSet objRS = Nothing

objConn.CloseSet objConn = Nothing

%>

Well, that's all there is to it! Not too difficult with ADO and ASP after all! Happy Programming!

Question: I have a database with a table of categories and a table of subcategories. How can I easily make a display that will show each category with its related subcategories grouped under it?

Answer: This question actually takes many forms, and I've tried to phrase it in as general terms as possible, so that hopefully you will recognize it as being the same as your question. Sample versions of the way this question might be asked include:-- How can I show all the authors of each book in my catalog?-- How can I show all the students in each classroom?-- How can I show all the players on each team?

Before we go further, we need to establish what our database looks like. At a minimum, it contains two tables. The first "outer" or "main" table has a primary key. And the second "inner" or "dependent" table has a field that acts as a foreign key to the primary key in the main table. For example:Table: Teams

TeamID -- autonumber field, primary keyTeamName -- text fieldWins -- numeric fieldLosses -- numeric field...

Table: PlayersPlayerID -- autonumber field, primary keyTeamID -- numeric, foreign key to TeamsPlayerName -- text...

Page 37: Data Base Question

Data Base interview Page 37 of 37

Data Base interview Page 37 of 37

Each of those tables might have many more fields, of course, but those are enough to illustrate the concepts herein. And if you do not have your data arranged in this way--if you have some clumsier organization that doesn't meet the rules of database normalization--then you need to reorganize the data before you start, more than likely. (And do a search with google for "Database Normalization Techniques" if you need help on this topic. There are tons of material on it and it would be redundant to touch on it here!)

Anyway, given those two tables, what can you do with them?

Almost the biggest mistake you can make--and yet the method that we tend to most often see beginners attempting--is to open up the first table and then, as you move to each new record in that table, open up another recordset where you select only the subcategory records that match the given category. Something like this kind of code:

<%' BAD CODE! DO NOT DO THIS!Set teamRS = conn.Execute("SELECT * FROM Teams ORDER BY TeamName")Response.Write "TEAM " & teamRS("TeamName") Do Until teamRS.EOF

Set playerRS = conn.Execute("SELECT * FROM Players WHERE TeamID=" & teamRS("TeamID")

Do Until playerRS.EOFResponse.Write " -- PLAYER " & playersRS("PlayerName")playersRS.MoveNext

This works, but it puts a pretty big strain on the performance of your database server! If you have 30 teams, you have to open up 31 recordsets.

Let's see if we can do it all with one recordset!

<%SQL = "SELECT * FROM Teams, Players " _

Page 38: Data Base Question

Data Base interview Page 38 of 38

Data Base interview Page 38 of 38

Response.Write "</UL>" End IfResponse.Write "TEAM: " & curTeam & "<UL>"priorTeam = curTeam

End IfResponse.Write "<LI>" & RS("PlayerName") RS.MoveNext

LoopResponse.Write "</UL>" ' clean up the tags!%>

Let's look at how that code works.

First, we use a JOIN to get the information from both tables at once. (Yes, that is a JOIN. If you are an Access user who is used to seeing the keywords INNER JOIN, be assured that this is simply another way of expressing the same thing.) The important thing here is the ORDER BYclause that we put there! By ordering first by team name and then by player name, we ensure that all the players in the "Angels" will appear before all the players in the "Mariners". And, further, all the players will appear in alphabetical order within the team grouping.

We get an ADODB.RecordSet with all the players and all the teams and we are ready to process it. First, though, we initialize our priorTeam variable to a blank string.

We start with the first record. We get the name of the team from the record. Does it match the name of the prior team? (Well, of course not! There isn't any prior team!) A special test: Is the name of the prior team blank? Yes? Then do nothing. In any case, since we are processing a new team, output the team name along with an opening list delimiter. And then "remember" that this is the prior team! And we're done with the change-of-team processing.

So we output the name of the player that this record refers to and go to the next record.

Back at the top of our loop: We get the name of the team from the record. Does it match the name of the prior team? Let's assume it does. So we skip the change-of-team code and go to the next record. And this paragraph repeats until we finish the first team.

Finally, we get to a record where the team is not the same as the one we remembered in priorTeam. Is the name of the prior team blank? Not any more, so output a closing list delimiter tag. And, again, we output the new team name and an opening list delimiter and proceed to work with the players of this next team.

And we continue all this until we run out of records.

See that? It's not hard! You only had to keep track of one extra thing: the name of the team the prior player in the recordset was a member of. This is clean, simple, and fast code. In almost all cases, there is no reason not to go this way.

Page 39: Data Base Question

Data Base interview Page 39 of 39

Data Base interview Page 39 of 39

"He said 'almost all'! What is he hiding!"

Suppose that you have huge amount of information stored in each record of the "outer" (or "main") table. Perhaps a lengthy history of the team. Or maybe a ".gif" image of the team picture. With the technique given above, you'll be duplicating that huge chunk of data in each record of the recordset! This is not a good thing. What can we do?

The simple answer is that we can do part of the JOIN operation ourself, in VBScript coding!

<%' add this recordset!teamSQL = "SELECT * FROM Teams ORDER BY Teams.TeamName"Set teamRS = conn.Execute( teamSQL )

' same as before except we only get one field from Team...playerSQL = "SELECT Teams.TeamName, Players.* " _

& "FROM Teams, Players " _& "WHERE Players.TeamID = Teams.TeamID " _& "ORDER BY Teams.TeamName, Players.PlayerName"

Set RS = conn.Execute( playerSQL )

priorTeam = ""Do Until RS.EOF

curTeam = RS("TeamName")If curTeam <> priorTeam Then

If priorTeam <> "" ThenResponse.Write "</UL>"

End IfResponse.Write "TEAM: " & curTeam & "<UL>"priorTeam = curTeam' AND HERE IS THE NEW CODE!If curTeam <> teamRS("TeamName") Then

Response.Write "Something went badly wrong!"Response.End

End If' (field names are just for demo purposes)Response.Write "This team was established in " _

& teamRS("startYear") _& " by " & teamRS("founder") _& " and its history is " _& teamRS("history") & "<P>"

teamRS.MoveNext' end of added code

End IfResponse.Write "<LI>" & RS("PlayerName") RS.MoveNext

LoopResponse.Write "</UL>" ' clean up the tags!%>

See what we've done? We've ensured that the primary sort in both recordsets (the name of the

Page 40: Data Base Question

Data Base interview Page 40 of 40

Data Base interview Page 40 of 40

team, in this example) is the same. And then we have limited the data we get from the "outer" table to just the one field (again, the name of the team). Then, as we traipse through the RSrecordset in exactly the same manner as before, we pull the needed extra information from the main table only as we change teams! And we only do the teamRS.MoveNext at that time, so (if we did everything right!) we will never get that error message and end prematurely.

Whew! If it looks complicated to you on first glance, study it a while longer. It really isn't that hard to adapt to whatever situation you might need it for.

If you'd like to see an example of the first and simpler form of this code, go to http://www.ClearviewDesign.com/NEWBIE and look for the "Special Category and Subcategory Display Demo" on that page.

Question: My memo fields show up as blank! OR When I test my memo field for a value and then try to show it only if it is not blank [or only if it contains some string or or or], then when I display it I get nothing!

Answer: To use Memo fields in Access (and longer VARCHAR fields in various other DBs), you must "follow the rules." Which are:

(1) The memo field should be the last field in your list of fields.(2) You can only retrieve the value of a memo field from the recordset ONE TIME.

Or you can follow the other set of rules, below...

So...

Often this means that you must specifically call out the fields you are reading, viaSELECT fld1, fld2, ... memoFld FROM table ...

instead of simply

SELECT * FROM table ...

And then you *must* copy the memo field to a VBS variable and thereafter use the variable, thus:

theText = RS("memoFld")If Trim(theText) = "" Then

...Else

Response.Write theText...

End If

Page 41: Data Base Question

Data Base interview Page 41 of 41

Data Base interview Page 41 of 41

Do you see it? If you had coded Response.Write("memoFld") in both of the places I used the theText variable there, the second time you would always get a null value!!!

Another set of rules

Thanks to user "Stacy" for these rules!

Stacy has verified that they work in all the following combinations. We invite you to try them on your system and report the results:Access - using a System DSN Access - using ODBC Microsoft Access Driver (*.mdb) and a DSN-less connectionAccess - using Microsoft.Jet.OLEDB.4.0 and a DSN-less connectionSQL Server 2000 - using a System DSN SQL Server 2000 - using ODBC Driver={SQL Server} and a DSN-less connectionSQL Server 2000 - using OLE DB Provider=SQLOLEDB and a DSN-less connection

And the technique is simple:Rule 1: Be sure to use ADODB.RecordSet.Open to create the recordset object. (That is, do not use ADODB.Connection.Execute or ADODB.Command.Execute!)Rule 2: Set the cursor location to the client.Rule 3: Use adOpenKeyset as the open mode for the recordset.

So, in code:

Set RS = Server.CreateObject("ADODB.RecordSet")RS.CursorLocation = adUseClientRS.Open table_name_or_sql_query, your_connection_object, adOpenKeyset

Stacy reports that if you do this, you no longer have to worry about the order of the fields or the other restrictions noted above.

Question: How can I output the records from my recordset in columns in a table? That is, instead of just one record per row in a table, how can I have multiple records per row?

Answer: In other words, what you want to see is code something like this:

<TR><TD>info from record 1</TD><TD>and then record 2</TD><TD>and record 3</TD>

Page 42: Data Base Question

Data Base interview Page 42 of 42

Data Base interview Page 42 of 42

<TD>record 4 starts a new row</TD><TD>and record 5</TD><TD> </TD>

</TR>

And the answer is pretty easy: Only put in the <TR> tag at the beginning of every group of N records. Only put in the </TR> tag at the end of such a group.

"And how," you ask, "does one do that?"

Several ways, but this is conceptually a simple way to do it:

<% Const COLUMN_COUNT = 3 ' or 2 or 4 or ... ... ... we assume you know how to get the recordset RS from a query ......column = 0 ' initialize counter Do While Not rs.EOF

' as describe in the text, put in ' <TR> at beginning of group:If column = 0 Then Response.Write "<TR>" ... output your <TD> through </TD> stuff... ... such as:... Response.Write "<TD>" & _

RS("somethingOrOther") & "</TD>"...column = column + 1 If column = COLUMN_COUNT Then

Response.Write "</TR>" & vbNewLine column = 0 ' start over!

End If RS.MoveNext

Loop ' clean up last row, if needed! If column <> 0 Then

For c = column To COLUMN_COUNT Response.Write "<TD>???</TD>"

Next Response.Write "</TR>" & vbNewLine

End If ... %>

In the line that reads Response.Write "<TD>???</TD>" replace the ??? there with nothing at all if you don't want the table cell boundaries to show. Replace the ??? with &NBSP; if you want the boundaries to show up with no content. And you can omit the loop entirely (and just output the </TR> tag) if you aren't showing borders on the table.

Page 43: Data Base Question

Data Base interview Page 43 of 43

Data Base interview Page 43 of 43

That was the simple answer!

It works fine if you are happy seeing output on the screen in the orderrecord1 -- record2 -- record3 record4 -- record5 -- record6

But what if you *must* have the data displayed in the formrecord1 -- record5 -- record9 record2 -- record6 -- record10record3 -- record7 -- record11record4 -- record8 -- record12Then what do you do?

There are several ways to accomplish this:(1) Get the count of records. Divide by the number of columns. Loop through the recordset as many times as needed to get all rows, but instead of using MoveNext use Move to move to the next logically numbered record for the given row.(2) Get the count of records. Divide by the number of columns. Then output code of the form:

<TR><TD>

<TABLE><TR><TD>..rec1..</TD></TR><TR><TD>..rec2..</TD></TR><TR><TD>..rec3..</TD></TR><TR><TD>..rec4..</TD></TR></TABLE>

</TD><TD>

<TABLE><TR><TD>..rec5..</TD></TR><TR><TD>..rec6..</TD></TR><TR><TD>..rec7..</TD></TR><TR><TD>..rec8..</TD></TR></TABLE>

</TD>...</TR>

But both those solutions have problems. The first, because moving randomly through recordsets is quite inefficient. The second, because getting the "inner" tables to line up nice and pretty may not be as easy as you wish (depending on the cell contents).

So...

An alternative to the first solution: Use ADODB.RecordSet.GetRows( ).

It has all the advantages of being able to use a single table and none of the drawbacks of doing

Page 44: Data Base Question

Data Base interview Page 44 of 44

Data Base interview Page 44 of 44

random cursor positiong.

Thus:

<%Const COLUMN_COUNT = 3...... get the recordset from a query ...allRecords = RS.GetRows ' convert it to an array!RS.Close ' no further need of it

' how many records?recMax = UBound( allRecords, 2 )' then how many rows from those records?rowCount = (recMax + 1) / COLUMN_COUNT' round to next number of rows if not even divide:If rowCount <> Int(rowCount) Then

rowCount = Int(rowCount) + 1End If

For row = 0 To rowCount-1Response.Write "<TR>" & vbNewLine' for this row, we start on this same record:startRec = row ' we do COLUMN_COUNT columns per row:For col = 0 To COLUMN_COUNT-1

' and we use this record number:rec = startRec + col * rowCount' caution! we might go past end!If rec > recMax Then

' past end...output blank cellResponse.Write "<TD>???</TD>"

Else' output info about this record:' (one fld of record shown...but use' as many fields as needed, of course!)Response.Write "<TD>" & _allRecords(0,rec) & "</TD>"

End IfNext ' next column

' done with one row...Response.Write "</TR>" & vbNewLine

Next ' next row...%>

Again, replace the ??? with the appropriate blank cell contents for your table.

<form name="Login" method="post" action="check_user.asp"><table width="273" border="0" cellspacing="0" cellpadding="1" bgcolor="#000000"

align="center">

Page 45: Data Base Question

Data Base interview Page 45 of 45

Data Base interview Page 45 of 45

<tr> <td>

<table width="273" border="0" align="center" cellspacing="0" cellpadding="0" bgcolor="#CCCCCC">

<tr> <td align="right" height="46" valign="bottom" width="94">User name: </td><td height="46" valign="bottom" width="172">

<input type="text" name="txtUserName"></td>

</tr><tr>

<td align="right" width="94">Password: </td><td width="172">

<input type="password" name="txtUserPass"></td>

</tr><tr>

<td align="right" height="44" width="94">&nbsp;</td><td height="44" width="172"> &nbsp;

<input type="submit" name="Submit" value="Enter">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <input type="reset" name="Submit2" value="Reset">

</td></tr>

</table></td>

</tr></table></form>

<br><center>

Session Cookies must be enabled on your web browser<br><br>

</center></body>

check user.asp

<% Option Explicit %><!--#include file="common.asp" --><!--#include file="functions/functions_hash1way.asp" --><%

Page 46: Data Base Question

Data Base interview Page 46 of 46

Data Base interview Page 46 of 46

Dim strUserName 'Holds the user nameDim strUserPassword 'Holds the user passwordDim strPassword 'Holds the users passwordDim strEncyptedPassword 'Holds the encrypted password

'Initalise the strUserName variablestrUserName = Request.Form("txtUserName")strPassword = Request.Form("txtUserPass")

'Check form inputstrUserName = Replace(strUserName, "'", "''", 1, -1, 1)

'Initalise the strSQL variable with an SQL statement to query the databasestrSQL = "SELECT " & strDbTable & "Configuration.Password, " & strDbTable & "Configuration.Username "strSQL = strSQL & "FROM " & strDbTable & "Configuration "strSQL = strSQL & "WHERE " & strDbTable & "Configuration.Username ='" & strUserName & "'"

'Query the databasersCommon.Open strSQL, adoCon

'If the recordset finds a record for the username entered then read in the password for the userIf NOT rsCommon.EOF Then

'Encrpt password'Concatenate salt value to the password

strEncyptedPassword = strPassword & strSalt

'Re-Genreate encypted password with new salt valuestrEncyptedPassword = HashEncode(strEncyptedPassword)

'Read in the password for the user from the databaseIf strEncyptedPassword = rsCommon("Password") Then

'If the password is correct then set the session variable to TrueSession("blnIsUserGood") = True

'Reset Server VariablesrsCommon.CloseSet rsCommon = NothingadoCon.CloseSet adoCon = Nothing

'Redirect to the admin menu page

Page 47: Data Base Question

Data Base interview Page 47 of 47

Data Base interview Page 47 of 47

Response.Redirect"admin_menu.asp"End If

End If

'Reset Server VariablesrsCommon.CloseSet rsCommon = NothingadoCon.CloseSet adoCon = Nothing

'If the script is still running then the user must not be authorisedSession("blnIsUserGood") = False

'Redirect to the unautorised user pageResponse.Redirect"unauthorised_user_page.htm"%>

change user password.asp

<% Option Explicit %><!--#include file="common.asp" --><!--#include file="functions/functions_hash1way.asp" --><%

'Set the response buffer to trueResponse.Buffer = True

'If the session variable is False or does not exsist then redirect the user to the unauthorised user pageIf Session("blnIsUserGood") = False or IsNull(Session("blnIsUserGood")) = True then

'Reset Server VariablesSet rsCommon = NothingadoCon.CloseSet adoCon = Nothing

'Redirect to unathorised user pageResponse.Redirect"unauthorised_user_page.htm"

End If

'Dimension variablesDim strMode 'holds the mode of the page, set to true if changes are to be made to the databaseDim strNewUsername 'Holds the new username

Page 48: Data Base Question

Data Base interview Page 48 of 48

Data Base interview Page 48 of 48

Dim strNewPassword 'Holds the new passwordDim strUsernameDim strPasswordDim strEncyptedPassword

'Read in the users details from the formstrNewUsername = Request.Form("name2")strNewPassword = Request.Form("password2")strMode = Request.Form("mode")

'Initalise the strSQL variable with an SQL statement to query the databasestrSQL = "SELECT " & strDbTable & "Configuration.Username, " & strDbTable & "Configuration.Password From " & strDbTable & "Configuration;"

'Set the cursor type property of the record set to Dynamic so we can navigate through the record setrsCommon.CursorType = 2

'Set the Lock Type for the records so that the record set is only locked when it is updatedrsCommon.LockType = 3

'Query the databasersCommon.Open strSQL, adoCon

'If there is a record returned read in the details or update itIf NOT rsCommon.EOF Then

'Read in the Username and password from the recordsetstrUsername = rsCommon("Username")strPassword = rsCommon("Password")

'If the user is changing there username and password then update the databaseIf strMode = "change" Then

'Concatenate salt value to the passwordstrEncyptedPassword = strNewPassword & strSalt

'Re-Genreate encypted password with new salt valuestrEncyptedPassword = HashEncode(strEncyptedPassword)

'Update the recordsetrsCommon.Fields("Username") = strNewUsername

Page 49: Data Base Question

Data Base interview Page 49 of 49

Data Base interview Page 49 of 49

rsCommon.Fields("Password") = strEncyptedPassword

'Update the database with the new user's detailsrsCommon.Update

'Re-run the NewUser query to read in the updated recordset from the databasersCommon.Requery

strUsername = rsCommon("Username")

End IfEnd If

'Reset Server VariablesrsCommon.CloseSet rsCommon = NothingadoCon.CloseSet adoCon = Nothing

%> <html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"><title>Change Admin Username &amp; Password</title>

<!-- The Web Wiz Guestbook v. <% = strVersion %> is written by Bruce Corkhill 2001-2004If you want your Guestbook then goto http://www.webwizguestbook.com -->

<!-- Check the from is filled in correctly before submitting --><script language="JavaScript"><!-- Hide from older browsers...

//Function to check form is filled in correctly before submittingfunction CheckForm () {

//Check for a Usernameif (document.frmChangePassword.name2.value==""){

alert("Please enter your a Username");document.frmChangePassword.name2.focus();return false;

}

//Check for a Password

Page 50: Data Base Question

Data Base interview Page 50 of 50

Data Base interview Page 50 of 50

if (document.frmChangePassword.password2.value==""){alert("Please enter your a Password");document.frmChangePassword.password2.focus();return false;

}

//Check both passwords are the sameif ((document.frmChangePassword.password.value) !=

(document.frmChangePassword.password2.value)){alert("The passwords entered do not match");

document.frmChangePassword.password2.focus();return false;

}

return true}// --></script>

</head><body bgcolor="#FFFFFF" text="#000000"><h1 align="center">Change Admin Username &amp; Password</h1><div align="center"><p><a href="admin_menu.asp" target="_self">Return to the the Administration Menu</a><br><br>Make sure you <b>remember</b> the new<b> username</b> and <b>password</b> <br>as you <b>will not</b> be able to Login or <b>Administer the Guestbook without them</b>!!!</p>

<p>Passwords are 160bit one way encrypted and so can not be recovered if you do forget your password!!<br>

<br></p>

</div><form method="post" name="frmChangePassword" action="change_admin_username.asp" onSubmit="return CheckForm();">

<table width="483" border="0" cellspacing="0" cellpadding="0" align="center" bgcolor="#000000" height="30">

<tr> <td height="2" width="483" align="center">

<table width="100%" border="0" cellspacing="1" cellpadding="2"><tr>

<td bgcolor="#FFFFFF"> <table width="100%" border="0" cellspacing="0" cellpadding="2">

<tr> <td align="right" width="29%">Username:&nbsp;&nbsp;</td><td width="71%">

Page 51: Data Base Question

Data Base interview Page 51 of 51

Data Base interview Page 51 of 51

<input type="text" name="name2" size="15" maxlength="15" value="<% = strUsername %>" >

</td></tr><tr>

<td width="40%" align="right" class="text">Password:&nbsp; </td><td width="60%"> <input type="password" Name="password" size="15" maxlength="15">

</td></tr><tr><td width="40%" align="right" class="text">Confirm Password:&nbsp; </td><td width="60%"> <input type="password" Name="password2" size="15" maxlength="15">

</td></tr><tr>

<tr> <td align="right" width="29%">

<input type="hidden" name="mode" value="change"></td><td width="71%">

<input type="submit" name="Submit" value="Update Details"><input type="reset" name="Reset" value="Clear">

</td></tr>

</table></td>

</tr></table>

</td></tr>

</table></form><br></body></html>index.asp

<% Option Explicit %><!--#include file="common.asp" --><%

'Buffer the responseResponse.Buffer = True

'Dimension variablesDim strName 'Holds the Users name

Page 52: Data Base Question

Data Base interview Page 52 of 52

Data Base interview Page 52 of 52

Dim strEmailAddress 'Holds the Users e-mail addressDim strCountry 'Holds the users countryDim strHomepage 'Holds the Users homepageDim strComments 'Holds the Users commentsDim dtmEntryDate 'Holds the date the commnets where madeDim intRecordPositionPageNum 'Holds the record positionDim intRecordLoopCounter 'Loop counter for displaying the guestbook recordsDim intTotalNumGuestbookEntries 'Holds the total number of records in the databaseDim intTotalNumGuestbookPages 'Holds the total number of pages in the databaseDim intLinkPageNum 'Holds the page number to be linked to

'If this is the first time the page is displayed then the guestbook record position is set to page 1If Request.QueryString("PagePosition") = "" Then

intRecordPositionPageNum = 1

'Else the page has been displayed before so the guestbook record postion is set to the Record Position numberElse

intRecordPositionPageNum = CInt(Request.QueryString("PagePosition"))End If

'Initalise the strSQL variable with an SQL statement to query the database by selecting all tables ordered by the decending datestrSQL = "SELECT " & strDbTable & "Comments.* " & _"FROM " & strDbTable & "Comments "If strDatabaseType = "SQLServer" Then

strSQL = strSQL & "WHERE " & strDbTable & "Comments.Authorised = 1 "Else

strSQL = strSQL & "WHERE " & strDbTable & "Comments.Authorised = true "End IfstrSQL = strSQL & "ORDER BY Date_stamp DESC;"

'Set the cursor type property of the record set to dynamic so we can naviagate through the record setrsCommon.CursorType = 3

'Query the databasersCommon.Open strSQL, adoCon

'Set the number of records to display on each page by the constant set at the top of the scriptrsCommon.PageSize = intRecordsPerPage

'Get the guestbook record poistion to display from

Page 53: Data Base Question

Data Base interview Page 53 of 53

Data Base interview Page 53 of 53

If NOT rsCommon.EOF Then rsCommon.AbsolutePage = intRecordPositionPageNum

%><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"><title>Guest Book of ITM Group</title>

<table width="90%" border="0" align="center" cellpadding="0" cellspacing="0" height="8"><tr valign="middle"> <td height="14"> <table width="98%" border="0" cellspacing="0" cellpadding="0" align="center">

<tr> <td width="70%"align="center" class="text">ITMGOC guestbook. <BR>If you would like to sign

the guest book then click on the link below.<br> <HR> <a href="sign.asp?PagePosition=<% = intRecordPositionPageNum %>" target="_self">Sign the guest book</a><HR></td>

</tr></table></td>

</tr></table><table width="90%" border="0" align="center" cellspacing="0" cellpadding="0"><tr> <td align="center" class="text"> <%

'If there are no rcords in the database display an error messageIf rsCommon.EOF Then

'Tell the user there are no records to showResponse.Write "<br>There are no records in the guestbook database"Response.Write "<br>Please check back later"Response.End

'Display the guestbookElse

'Count the number of enties in the guestbook databaseintTotalNumGuestbookEntries = rsCommon.RecordCount

'Count the number of pages there are in the guestbook database calculated by the PageSize attribute set above

intTotalNumGuestbookPages = rsCommon.PageCount

Page 54: Data Base Question

Data Base interview Page 54 of 54

Data Base interview Page 54 of 54

'Display the HTML number number the total number of pages and total number of records in the guestbook database

%> <table width="100%" border="0" cellspacing="0" cellpadding="0" align="center"><tr> <td align="center" class="text"><br>There are <% = intTotalNumGuestbookEntries %> guestbook

entries in <% = intTotalNumGuestbookPages %> pages and you are on page number <% = intRecordPositionPageNum %></td>

</tr></table><br> <%

'For....Next Loop to display records from the guestbook databaseFor intRecordLoopCounter = 1 to intRecordsPerPage

'If there are no guestbook records left to display then exit loopIf rsCommon.EOF Then Exit For

'Read in the values form the databasestrName = rsCommon("Name")strCountry = rsCommon("Country")strEmailAddress = rsCommon("EMail")dtmEntryDate = CDate(rsCommon("Date_stamp"))strHomepage = rsCommon("Homepage")strComments = rsCommon("Comments")

'If URL homepgae entry is diisabled don't show URLIf blnURL = false Then

strHomepage = ""'If there is no homepage entry to display the display no URL givenElseIf strHomepage = "http://" or strHomepage = "" then

strHomepage = "no URL given"

'Else turn the URL stored in the strHomepage variable into a hyperlinkElse

strHomepage = "<a href=""" & strHomepage & """ target=""_blank"">" & strHomepage & "</a>"

End If

'Write the HTML to the web browser to display the guestbook entries%> <table width="100%" border="0" cellspacing="0" cellpadding="1" align="center"

bgcolor="<% = strTableBorderColour %>"><tr> <td> <table width="100%" border="0" cellpadding="2" cellspacing="0">

<tr bgcolor="<% = strTableTitleColour %>">

Page 55: Data Base Question

Data Base interview Page 55 of 55

Data Base interview Page 55 of 55

<td colspan="2" class="text"> <table width="100%" border="0" cellspacing="0" cellpadding="0">

<tr> <td width="93%" class="text">Comments by <%

'Display the email address if there is oneIf blnEmailAddress AND strEmailAddress <> "" Then Response.Write("<a href=""mailto:" &

strEmailAddress & """>" & strName & "</a>") Else Response.Write(strName)

%> on <% = FormatDateTime(dtmEntryDate, VbLongDate) %> at <% = FormatDateTime(dtmEntryDate, VbShortTime) %> </td>

<td width="7%" align="right" class="text">IP&nbsp;Logged</td></tr>

</table></td></tr><tr> <td colspan="2" bgcolor="<% = strTableColour %>" class="text"><% = strComments %> </td>

</tr><tr bgcolor="<% = strTableTitleColour %>"> <td width="50%" align="left" class="text"><% = strCountry %></td><td width="50%" align="right" class="text"><% = strHomepage %></td>

</tr></table></td>

</tr></table><br> <%

'Move to the next record in the databasersCommon.MoveNext

'Loop back round Next

End If

'Display an HTML table with links to the other entries in the guestbook%> <table width="100%" border="0" cellspacing="0" cellpadding="0" align="center">

<tr> <td> <table width="100%" border="0" cellpadding="0" cellspacing="0">

<tr><td width="50%" align="center" class="text"> <%

'If there are more pages to display then add a title to the other pagesIf intRecordPositionPageNum > 1 or NOT rsCommon.EOF Then

Response.Write vbCrLf & " Page:&nbsp;&nbsp;"

Page 56: Data Base Question

Data Base interview Page 56 of 56

Data Base interview Page 56 of 56

End If

'If the guestbook page number is higher than page 1 then display a back link If intRecordPositionPageNum > 1 Then

Response.Write vbCrLf & ("<a href=""default.asp?PagePosition=" & intRecordPositionPageNum - 1 & """ target=""_self"">&lt;&lt;&nbsp;Prev</a>")End If

'If there are more pages to display then display links to all the pagesIf intRecordPositionPageNum > 1 or NOT rsCommon.EOF Then

'Display a link for each page in the guestbook For intLinkPageNum = 1 to intTotalNumGuestbookPages

'If there is more than 7 pages display ... last page and exit the loopIf intLinkPageNum > 15 Then

If intTotalNumGuestbookPages = intRecordPositionPageNum Then Response.Write(" ..." & intTotalNumGuestbookPages) Else Response.Write(" ...<a href=""default.asp?PagePosition=" & intTotalNumGuestbookPages & """ target=""_self"">" & intTotalNumGuestbookPages & "</a>")

'Exit LoopExit For

'Else display the normal link codeElse

'If the page to be linked to is the page displayed then don't make it a hyper-linkIf intLinkPageNum = intRecordPositionPageNum Then Response.Write(" " &

intLinkPageNum) Else Response.Write("&nbsp;<a href=""default.asp?PagePosition=" & intLinkPageNum & """ target=""_self"">" & intLinkPageNum & "</a>")

End IfNext

End If

'If it is Not the End of the guestbook entries then display a next link for the next guestbook page

If NOT rsCommon.EOF then Response.Write ("&nbsp;<a href=""default.asp?PagePosition=" & intRecordPositionPageNum

+ 1 & """ target=""_self"">Next&nbsp;&gt;&gt;</a>") End If

'Finsh HTML the table %> </td>

</tr></table></td>

</tr>

Page 57: Data Base Question

Data Base interview Page 57 of 57

Data Base interview Page 57 of 57

</table><%

'Reset Server VariablesrsCommon.CloseSet rsCommon = NothingadoCon.CloseSet adoCon = Nothing

%> </td></tr>

</table><div align="center"><br>

<br></div><!--#include file="includes/footer.asp" -->

Question: How do I display data on a web page using arrays instead of Do...While...MoveNext...???...

Answer: This FAQ describes the process of displaying data on a Web page through arrays instead of the traditional Recordset method. The way we've all likely used to display arrays involves opening a Recordset object and then using a Do While Not objRS.EOF ... Loop, iterating through each row in the Recordset. While this approach is fine and good, there are advantages to using a special Recordset method called GetRows(), which reads the contents of the Recordset into a two-dimensional array; then, to display the Recordset data, you can simply iterate through this array.

While the syntax involved in using the array-based approach is a bit more confusing that simply looping through the Recordset via a Do While Not objRS.EOF ... Loop, the main motivation behind using GetRows() is performance. For a good read on the performance advantages be sure to read Why GetRows() Is Best to Fetch Data.

This builds on previous FAQ's in treating Recordsets as arrays, and vice versa. Read the FAQ below as well as the documentation on GetRows if you need to brush up.

FAQ: How can I convert a Recordset into an array? Also, how can I convert an array into a Recordset?http://www.aspfaqs.com/aspfaqs/ShowFAQ.asp?FAQID=161

Before we get started there are three important things to remember when working with GetRows; remembering these three things will save you many headaches and hours

Page 58: Data Base Question

Data Base interview Page 58 of 58

Data Base interview Page 58 of 58

debugging, trust me! They are:

1. VBScript arrays start at 0 and go to n-1, where n is the count of the records (or count of columns) in the recordset. If you have 30 records, you will have rows numbered from 0 to 29; if you have 5 columns, you will have columns numbered from 0 to 4. In other words, just as if you had done Dim rows(4,29) to declare the array size yourself.2. VBScript arrays are arranged by MyArray(ColumnElement, RowNumber) instead of MyArray(RowNumber, ColumnElement)3. The order of field selection in your SQL statement determines the column subscript used to access that field. If you have 5 fields you will have 0 to 4 column elements.

Imagine that you have the following SELECT SQL statement:Select fName, lName, Address, City, State, Zip FROM SomeTableORDER BY lName, fName

and that you have already created and opened both Connection and Recordset objects. To read the contents of the Recordset into an array, use:

MyArray = rsMyRecordSet.GetRows()

VBScript sets up MyArray(ColumnCount, RowCount) with ColumnCount being the number of fields selected - 1 (0 based) and RowCount being the number of records returned - 1 (0 based)

(NOTE: If you want the array in any specific order, you can use the ORDER BY clause in your select. We can't recommend trying to sort the two-dimensional arrays that GetRows produces, as most any process you use to do so will be, at best, slow and tedious. The method described in this FAQ, for example, is completely inadequate and unusable with two-dimensional arrays.)

In the above select statement, the data will be in the order of the columns in the SELECT clause - that is, the 0th column will contain the value of the current row's fName value; the 1st column will contain the value of the current row's lName value; etc.

To access the elements of an array we need to use integer index values. To get the first column of the first row, we'd use: MyArray(0,0). This approach, however, is very unreadable. To make your code more readable (and hence more maintainable), considercreating constants whose values represent the position in the array for the column and whose name is similar to the column's name. For example, it would be prudent to add:

Const MyFirstNameOrdinal = 0Const MyLastNameOrdinal = 1Const MyAddressOrdinal = 2Const MyCityOrdinal = 3Const MyZipOrdinal = 4

Page 59: Data Base Question

Data Base interview Page 59 of 59

Data Base interview Page 59 of 59

In this instance you will have five columns in the array numbered 0 through 4.

Now that we know how our array is going to be laid out we can display the array's contents using a For...Next loop construct. In order to use this construct we must know what the loop bounds are. Clearly we want to start from the 1st row and 1st column (array indexes 0,0). But how high do we have to go? That depends on how many columns and rows our SQL statement returns. Since we do not know the number of rows returned when writing our script, we'll have to use the VBScript UBound function, which returns the total number of elements in an array. (An example of using UBound can be seen in this FAQ.)

Of course, UBound is a trifle trickier to use when dealing with multi-dimensional arrays (recall that GetRows() returns a two-dimensional array). When using UBound with multi-dimensional arrays, you must specify the dimension you wish to get the upperbound for. So, if we want to get the total number of columns or rows, we have to use different UBoundstatements, as shown below:

Ubound(MyArray,1) 'Returns the Number of ColumnsUbound(MyArray,2) 'Returns the Number of Rows

For our first example I will show how to display the data in some predetermined format, like:The code to do this would be a simple loop through each of the rows in the array; then, in the loop body, Response.Write statements would output the proper array values:

If you were wanting to display the fields in the exact order as they were presented in your SELECT clause you could add an inner loop for the columns. I usually just manually output the columns (as shown above) for simplicity, but either approach will work:

Page 60: Data Base Question

Data Base interview Page 60 of 60

Data Base interview Page 60 of 60

NextNext

Happy Programming !!!

Question:

How can I convert a Recordset into an array? Also, how can I convert an array into a Recordset?

Answer: Conceptually, a Recordset is nothing more than a glorified two-dimensional array. A Recordset consists of zero to many rows, each row having zero to many columns, just like a two-dimensional array. Specifically, each cell of a Recordset is a Field object, storing information like the name of the column, the value of the cell, the data type, the maxlength, the precision, whether or not the cell can contain a NULL value, etc.

Using the GetRows() method of the Recordset object we can turn the contents of a Recordset into a two-dimensional array. Understand that this array contains less information that the Recordset. Only the values of each of the cells in the Recordset are stored in the array, information like the column's name, the data type, etc., are not stored in this array. You may be wondering why, exactly, one would want to use GetRows() as opposed to just looping through a Recordset. For a good read on the performance advantages of using GetRows(), be sure to read Why GetRows() is Best to Fetch Data.

The complete technical syntax for GetRows() can be seen here. In its simplest form, all you need to do is open a Recordset object and then call GetRows():'Assumes there is an open Connection object, objConn

'Create a RecordsetDim objRSSet objRS = Server.CreateObject("ADODB.Recordset")objRS.Open "SELECT * FROM Table1", objConn

'Now, read the Recordset into a 2d arrayDim aTable1ValuesaTable1Values = objRS.GetRows()

That's all there is to it! At this point, aTable1Values contains a row for each row in the Recordset objRS and a column for each of the Recordset's columns. At this point, we can close our Recordset and Connection objects and still work with the data in the array.

To display this array data, we need to use two nested For loops. The first loop needs to loop through each row. The total number of rows can be found by examining the upper bound of the second dimension of the array (UBound(ArrayName, 2)). Next, an inner loop needs to step through each column of the current row. This can be done via a Forloop as well; to find the total number of columns, retrieve the upper-bound of the first

Page 61: Data Base Question

Data Base interview Page 61 of 61

Data Base interview Page 61 of 61

dimension of the array (UBound(ArrayName, 1)).

So, our code to loop through the two-dimensional array would look like:

Dim iRowLoop, iColLoopFor iRowLoop = 0 to UBound(aTable1Values, 2)For iColLoop = 0 to UBound(aTable1Values, 1)Response.Write(aTable1Values(iColLoop, iRowLoop) & "<br>")

Next 'iColLoop

Response.Write("<p>")Next 'iRowLoop

A live demo of using GetRows() can be found here. Be sure to check it out!

GetRows() also can accept up to three optional parameters. Let's take a close look at the third optional parameter, which allows us to only bring back certain columns of the Recordset into the array. (For a discussion on all three of the optional parameters, be sure to read the technical docs.)

This third, optional parameter can specifies what columns to bring back. If you don't provide this parameter (as we did not provide it in our earlier example, all of the columns will be returned. If, however, you only want a subset of columns back, you can specify those column names you'd want back using an array. For example:

ArrayName = objRS.GetRows(, , Array("ColumnName1", "ColumnName2", ...))

An example of using this third parameter can be seen in the live demo.

For an examination of turning a two-dimensional array into a Recordset, be sure to read: Inserting the Contents of a Two-Dimensional Array into a Database.

Happy Programming!

Source Code

<%Const adOpenStatic = 3Const adLockReadOnly = 1

'Establish database connectionDim objRS, objConn

Set objConn = Server.CreateObject("ADODB.Connection")objConn.Open "DSN=MyDSN"

Page 62: Data Base Question

Data Base interview Page 62 of 62

Data Base interview Page 62 of 62

'Retrieve the results of the sp_popularity stored procedure, which'returns the 10 most popular ASP FAQs'(We use a Static cursor so we can use MoveFirst... only required if you plan on'using multiple calls to GetRows())Set objRS = Server.CreateObject("ADODB.Recordset")objRS.Open "sp_Popularity", objConn, adOpenStatic, adLockReadOnly

'Create our arraysDim aPopularFAQs, aFAQIDs

'Convert the array into a two-dimensional arrayaPopularFAQs = objRS.GetRows()

'Move to the beginning of the Recordset, since we want to'call GetRows() againobjRS.MoveFirst

'Get a 2d array of the Recordset, but only for the'Rows FAQID and ViewCountaFAQIDs = objRS.GetRows(,,Array("FAQID", "ViewCount"))

'Close our Recordset and database connection...objRS.CloseSet objRS = Nothing

objConn.CloseSet objConn = Nothing

'Display our two arrays...Response.Write("<h2>A Display of the Recordset as an Array</h2>")Dim iRowLoop, iColLoopFor iRowLoop = 0 to UBound(aPopularFAQs, 2)

For iColLoop = 0 to UBound(aPopularFAQs, 1)

Response.Write(aPopularFAQs(iColLoop, iRowLoop) & "<br>")

Next 'iColLoop

Response.Write("<p><hr><p>")

Next 'iRowLoop

Response.Write("<h2>A Display of the <code>aFAQIDs</code> " & _"Array</h2><i>This shows a listing of the FAQIDs and " & _"their ViewCounts</i><p>")

'Display the FAQIDs arrayFor iRowLoop = 0 to UBound(aFAQIDs, 2)

For iColLoop = 0 to UBound(aFAQIDs, 1)

Response.Write(aFAQIDs(iColLoop, iRowLoop) & "<br>")

Next 'iColLoop

Page 63: Data Base Question

Data Base interview Page 63 of 63

Data Base interview Page 63 of 63

Response.Write("<p><hr><p>")

Next 'iRowLoop%>