Programming Best Practices

Monday, October 23, 2006

SQL Detectives

SQL Detectives
Data Centre Times, Oct 23, 2006
Special Correspondent, Yours Truly, Reporting from the Scene


This episode attempts to trace the track of a mysterious Data Leak that managed to escape the sophisticated dragnet prepared by SD and MP. Special detective HK of TRAIT was drafted to assist SD in recapturing the felon at large. A first-person account of the thrilling chase is provided by our friendly neighbourhood HK.

[Over to HK].

I was steamed out from back-breaking work from the HP-UX terminal when I was approached with this alarming problem. Realising the important nature of the situation, and the untold misery it was causing my colleague, I started out with the following queries:~

SQL> SELECT tname
FROM tab
WHERE tname LIKE '%CHEQUE%';

SQL> SELECT tname
FROM tab
WHERE tname LIKE '%LOG%';

And I got the names of the tables I wanted:~

MM_FT_TA_CHEQUE_TAID /* dummy table */
LOG_TEST2 /* history table */

SQL> DESC LOG_TEST2
Name Null? Type
--------------------- -------- -----------------
USER_NAME NOT NULL VARCHAR2(20)
ACTIVITY NOT NULL VARCHAR2(1000)
DATE_TIME NOT NULL DATE
IP_ADDRESS VARCHAR2(20)
ACTUAL_NAME VARCHAR2(50)

Okay, I'm in business. I suspect that you have already updated the master tables, but I do a sanity check (I have to find the master tables). So I play around, and find a likely candidate.

SQL> SELECT COUNT (*), COUNT (cheque_dt), COUNT (entry_date), COUNT (dd_dt)
FROM mm_ft_ta_cheque;

COUNT(*) COUNT(CHEQUE_DT) COUNT(ENTRY_DATE) COUNT(DD_DT)
---------- ---------------- ----------------- ------------
1101 829 865 0


Okay, we have some deficiencies. RV said that the entry_date was made mandatory only recently. OK. We still need to find out the other tables and the other stats.

I recall a value of 157 (as the result of some count) on SD's SQL*Plus session. So I do this:~

SQL> SELECT COUNT (*)
FROM mm_ft_ta_cheque_taid
WHERE taapp_id IN (SELECT taapp_id
FROM mm_ft_ta_cheque
WHERE entry_date IS NULL);

And I get:

COUNT(*)
----------
157

Voila! So this is what SD's been trying to do:~
  1. Some of the entries in the master table (mm_ft_ta_cheque) do not have entry_date.
  2. All the transactions are logged in the history table, which has the date_time value logged as well. It has all the necessary fields.
  3. The problem becomes one of identifying the records with null entry_dates (in mm_ft_ta_cheque), and getting matching date_time values from the history table (log_test2 - unusual name for a log table)
  4. The solution is now trivial: you only have to count the position and issue the appropriate SUBSTR command to extract the embedded taapp_id from activity.
  5. Even then RV asks: "Why do you need a GROUP BY?" The reason became self-evident in a matter of seconds.
  6. If some values are missng then it is clear that no contraints are placed against that field (and none ever could).
  7. So, if some values are missing, some values (records) can be duplicated as well.And indeed, that's what we find.

SQL>SELECT DISTINCT SUBSTR (activity, 55, 17), COUNT (*)
FROM log_test2
GROUP BY SUBSTR (activity, 55, 17)
HAVING COUNT (*) > 1;

SUBSTR(ACTIVITY,5 COUNT(*)
----------------- ----------
TAKRKMYR200600029 4
TAKRKMYR200600037 8
TAKRKMYR200600159 3
TAKRKMYR200600164 2
TAKRKMYR200600172 2
TAKRLCNN200600119 2
TAKRLCNN200600121 2
TAKRLCNN200600401 2
TAKRLCNN200600412 2
TAKRLCNN200600499 2
TAKRLCNN200600669 2
TAKRLCNN200600759 2
TAKRLCNN200600895 2

13 rows selected.

Hmm...duplication. We now need to check if it is duplication or constraint violation (multiple entries with different values). So we finally have the Query to check it:~

SQL>SELECT user_name, SUBSTR (activity, 1, 150), date_time, ip_address,
actual_name
FROM log_test2
WHERE SUBSTR (activity, 55, 17) IN
('TAKRKMYR200600029',
'TAKRKMYR200600037',
'TAKRKMYR200600159',
'TAKRKMYR200600164',
'TAKRKMYR200600172',
'TAKRLCNN200600119',
'TAKRLCNN200600121',
'TAKRLCNN200600401',
'TAKRLCNN200600412',
'TAKRLCNN200600499',
'TAKRLCNN200600669',
'TAKRLCNN200600759',
'TAKRLCNN200600895'
)
/

Rest assured: from the output we can see that the table has merely replicated values. (If you're not convinced, just cut and paste the Query given previously in SQL*Plus. It will produce a cluttered, but truncated, report from which you can verify that only duplication has taken place, nothing more serious. For want of space I am not including the screendump of the above Query. I have included a much cleaner report at the end, one that fully leverages the power of the STRING functions in PL/SQL):

It is merely repetition. But your ASP may not work correctly if multiple rows are returned.

What it needs is a good (unique) index on ACTIVITY. I think that's the only problem here.

The biggest problem, of course, is "hairy" SQL. Don't EVER embed SQL, especially UPDATEs, in table, no matter what you are going to do. Never store SQL on tables, it's terrible programming. At times it's deadly.

Of course, it's a trivial matter to remove duplication. But the problem is, programmers who are smart enough to embed DML in tables should be able to figure out the way themselves! We have been crying wolf over the programming practices at IT Cell, and it seems to be a lost cause, with the widespread adoption of trick programming (it is too easy to handle application complexity - something that arises from a bad design - by embedding hairy SQL in tables)

Here's the SQL you needed:~

SELECT SUBSTR (l.activity, 55, 17) log_taapp, l.date_time log_dt,
f.taapp_id f_taapp
FROM log_test2 l, mm_ft_ta_cheque_taid f
WHERE SUBSTR (l.activity, 55, 17) = f.taapp_id

And of course, you should have written the UPDATE SQL yourself, since I see that the table has been updated.

May I ask a few simple questions?
  1. From the data in the ACTIVITY field, I infer that it is simply a listing of the Querystring generated by ASP.
  2. I cannot make out how this could actually be useful. I can only assume that the table LOG_TEST2 might be used to roll back some of the changes made to the master (data) tables. I presume this is how the new applications at IT Cell make certain at what stage the ball is at the present moment. What a crude technique! Instead of setting a flag, use the Querystring! How symbolically foolish!
  3. If this is indeed the case, all I can say is that a database application should not have this functionality. Once something is done - whether it is in error is another question - there should not be provision to roll it back. This is not the same as advice note flow, where giving a connection might not be feasible and the outdoor section might have to return the advice note to the commercial officer. This is simply not the same case. I can understand it if Medallist wrote this logic, because he doesn't know the difference between a cat and a mouse.
  4. I'm assuming that the value in the ACTIVITY field is meant to do something meaningful. Even then, embedding DML in a table is dangerous. I've been crying myself hoarse that bulk updates are dangerous (ust run a bulk update at the SQL*Plus prompt - whether the data changed or not, it will say 'xx rows updated').
  5. Programming is not a trick. Like any other means of livelihood, it has its own dignity and rules. Don't think that there is an easy way to learn programming. There is no easy way.
  6. I think you have fundamentally demonstrated to yourself what actually went wrong at Eranakulam, and what is actually wrong with the Cash Management module: a database application that thinks it is a Web application, with a primary duty of smoothly displaying 'pages'. A lot of loose ends, incomplete transactions ("failed final submission" in Anil sir's terminology) means the application itself is "swaaha".
  7. Please do not subvert the meaning of "table". Table is used to hold data, not hairy SQL to be embedded in lame ASP. That is not the way things have been designed. Do not suppose that ASP programmers in different parts of the world are fools. There are some things you cannot do using ASP. NONE of the fancy things Medallist tried to do with SSANet 4 can be done in ASP. Unfortunately, there are no workarounds.
Still, if you use all the features of the language, and use the database the way it should be used, almost any problem can be solved. Believe me.

I do not believe that Manoj, Anil, and myself are lacking ideas or capacity. I think we're all perfectly capable of doing things competently. (RV will immediately go off, saying, "We need training... we need help..." and when she does that I feel awful) When it comes to designing an application and programming, I don't think we lack anything. Understand the reason why we (we=TRA Integrators) are silent. Understand why we should work in such a way as to add to our knowledge, our skills, and above all, help us further our careers as programmers. I think, for you and for me and for everyone at IT Cell, it's probably too late to change tack. And hence, try to reason for yourself, and understand why our silence carries a lot of meaning.

Back to Shalla Bal: The Final Report



SQL> COLUMN TAAPP_ID FORMAT A20
SQL> COLUMN OFF_CODE FORMAT A15
SQL> COLUMN CIR_CODE FORMAT A10
SQL> COLUMN UNIT_CODE FORMAT A12

SQL> SELECT RTRIM (SUBSTR (activity, 55, 17)) taapp_id,
RTRIM (SUBSTR (activity,
INSTR (activity, '#', -1, 6) + 1,
(INSTR (activity, '#', -1, 5) - 1
)
- (INSTR (activity, '#', -1, 6))
)
) off_code,
RTRIM (SUBSTR (activity,
INSTR (activity, '#', -1, 4) + 1,
(INSTR (activity, '#', -1, 3) - 1
)
- (INSTR (activity, '#', -1, 4))
)
) cir_code,
RTRIM (SUBSTR (activity,
INSTR (activity, '#', -1, 2) + 1,
(INSTR (activity, '#', -1, 1) - 1
)
- (INSTR (activity, '#', -1, 2))
)
) unit_code
FROM log_test2
WHERE SUBSTR (activity, 55, 17) IN
('TAKRKMYR200600029',
'TAKRKMYR200600037',
'TAKRKMYR200600159',
'TAKRKMYR200600164',
'TAKRKMYR200600172',
'TAKRLCNN200600119',
'TAKRLCNN200600121',
'TAKRLCNN200600401',
'TAKRLCNN200600412',
'TAKRLCNN200600499',
'TAKRLCNN200600669',
'TAKRLCNN200600759',
'TAKRLCNN200600895'
)
ORDER BY SUBSTR (activity, 45, 100)
/

And running this Query will give you the following Report: ~

TAAPP_ID OFF_CODE CIR_CODE UNIT_CODE
-------------------- --------------- ---------- ------------
TAKRKMYR200600029 SDEIDCNR KRK MYR
TAKRKMYR200600029 SDEIDCNR KRK MYR
TAKRKMYR200600029 SDEIDCNR KRK MYR
TAKRKMYR200600029 SDEIDCNR KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600037 SDENAJGROUPS KRK MYR
TAKRKMYR200600159 SDENAJPHONES KRK MYR
TAKRKMYR200600159 SDENAJPHONES KRK MYR
TAKRKMYR200600159 SDENAJPHONES KRK MYR
TAKRKMYR200600164 SDESNGLR KRK MYR
TAKRKMYR200600164 SDESNGLR KRK MYR
TAKRKMYR200600172 SDEEXTLMR KRK MYR
TAKRKMYR200600172 SDEEXTLMR KRK MYR
TAKRLCNN200600119 SDEESSIIKNG KRL CNN
TAKRLCNN200600119 SDEESSIIKNG KRL CNN
TAKRLCNN200600121 SDENSCNN KRL CNN
TAKRLCNN200600121 SDENSCNN KRL CNN
TAKRLCNN200600401 SDEKOD KRL CNN
TAKRLCNN200600401 SDEKOD KRL CNN
TAKRLCNN200600412 AOTRICNN KRL CNN
TAKRLCNN200600412 AOTRICNN KRL CNN
TAKRLCNN200600499 SDEGPNR KRL CNN
TAKRLCNN200600499 SDEGPNR KRL CNN
TAKRLCNN200600669 SDOPKSZ KRL CNN
TAKRLCNN200600669 SDOPKSZ KRL CNN
TAKRLCNN200600759 SDEIIITMB KRL CNN
TAKRLCNN200600759 SDEIIITMB KRL CNN
TAKRLCNN200600895 AOTRCCIICNN KRL CNN
TAKRLCNN200600895 AOTRCCIICNN KRL CNN

35 rows selected.

Imagine the lengths I went to! Imagine the power! The glory! All this just to make sure that only duplication has taken place! All this to prove that 1+1+1+1=1 if the table doesn't have a constraint!

So, in the end, science triumphs and time flies. The following things have been achieved by a combination of persistence, serendipity, and confidence in SQL:~

  1. HK gets a chance to brush up his string manipulation (and a chance to open Feuerstein again).
  2. SD can breathe a sigh of relief; the only problem is data duplication. Probably all that she needs are a few indexes/constraints that would prevent spurious data entry in future. (But the query to remove duplicate entries is a secret until and when she steps down from her fool's paradise).
  3. A nicely formatted PL/SQL report has been generated (one that is perfectly useless and serves to feed the vanity).
  4. A case against "hairy programming" has been built.
  5. A live demonstration has been made of the common ills of programming practices being followed at Data Centre (as exemplified by Eranakulam and Cash Management).

I cringed when SD showed me what she wanted to do with the dynamic SQL. It all sounded INCREDIBLY difficult! I was thinking all the while how SQL could become so tough so suddenly... From SD's description, it was clear to me that she was not thinking straight... was just imagining that things were too complicated! Actually, at TRA we're doing just this sort of work all the time (we used to). I think we (SM and HK) are both excellent 'SQL Detectives'. What SD wanted to do was, simply stated, back-end data manipulation to cover up for a leaky database application (SSANet also has leaks). All ASP applications that do not use SQL procedures to check the integrity of data suffer from data leaks at some point or another. (As far as I know, all the Web applications made at IT Cell have data leaks. A case in point is the celebrated data leak that caused a terrific smashup between Trivandrum Computer Cell and IT Cell). At best, data leaks are a pain in the neck (like the present case). At worst, they destroy data. In some cases, if there are no redundant mechanisms to collect data, it may even be impossible to rebuild the data, no matter how clever you are with SQL. (I think, if there is data in some form somewhere, we can use it to rebuild the broken data; if it is theoretically possible then we'll do it). One way of ensuring that data is not corrupted is to enforce constraints (primary/foreign/unique/check). I think such precautions need to be taken at the back-end for every database application. And this is where the database rules the game.

I know SD is having a stiff load at the present moment, but I think she has only herself to blame. She took all this upon herself. She used to complain of having no work to do, and now she's got a full plate! I think her former mentor might have found her services invaluable at this stage. WTMS might become a busy application sometime in the future. Whatever his faults, he makes robust, dependable programs. I don't think the same can be said about Medallist. (Forgive my ignorance, but I can't seem to remember one decent page he has written.)

I hope that the author of the mischief would seriously consider "straight" programming as a viable alternative in the future.

Friday, September 15, 2006

Finding Your Way With Oracle
A Guide to Database Programming With Oracle on the Windows Platform
©Zeinab, 2006

You might have noticed the "Copyright" symbol attributing the work to Zeinab (Me). However, that merely serves to indicate that we're discussing Windows, not necessarily that the article was born out of thin air.

First of all, we shall see what aspects of database programming we're going to touch upon this time.
  • We shall see the structure of a Web-based database application using ASP.NET. It might as well be applied to ASP, as "Best Practices" remain the same throughout.
  • We shall make use of core Oracle functionality that has existed since Oracle 7.3.4. We shall not make use of the latest functionality.
  • We shall see the uneasy relationship between the Web developer, database developer, and the sysadmin. We shall also see the case when you are fulfilling all three roles. Of course, we shall discuss why you have to be the three-in-one IT employee.
  • Wherever possible, I'll use images. A picture is better than nonsense.
  • Emphasis, as in all my programming articles, will be laid on the command line. GUI is a complicated wrapper built around a command line. Those who rely too much on the GUI are either ignorant of what happens behind, or in too much hurry; in either case they bring the application to its knees and the organization disrepute. I hope you are not one of those, or if you are, that you want to change.
  • Lots of history and advice.
  • Some UNIX commands and a little about scripts (little bit of bass, little bit of rap).
  • Job scheduling, procedures, functions, and the other advanced aspects of the Oracle database you're expected to use, all the time.
  • It does not guarantee 100% of the results that you expect out of it. More than anything, it expects you to work on the aspects you're weak in, and build yourself up (bootstrapping).
  • Since you've elected to be in the job that requires a lot of alacrity of mind, capacity for design, analysis, and the ability to think from the point of view of the customer (user), I expect that a decent enough explanation would suffice, instead of a strict proof. After all, faith is the basis of all study.
  • The article contains very few citations; most you would have to take on faith, but I try to demonstrate my methods.
  • As usual, this article is dedicated to Netscape (Mozilla), Adobe, WinRAR, Crimson Editor, WordWeb, iTunes & iPod, BSPlayer, and all the nicer virtual things the world has to offer.
  • And to Richard Stevens, who taught me how to write a computer book. If it were not for him, you would be reading something like "Mary had a little lamb..."
Here we go.

ASP Technology and What's So Wrong With It

ASP is a primitive scripting technology introduced by Microsoft to supplement its Internet Information Server (IIS). It provided bare bones functionality, with support for the following scripting languages:
  • JavaScript
  • Microsoft JScript (now obsolete)
  • VBScript
It also provides data access through ADO and ODBC connectivity. Besides these features, you can also embed Java applets, which can communicate with a database using its own (JDBC) technology. ASP was introduced by Microsoft around 1996.

From the preceding discussion the following things are clear:
  • ASP is quite an old technology quite unsuitable for the new Web scenario which is based entirely on style sheets, client side script, modularized data access code, and high volumes of data requiring an explicit memory management model.
  • It does not support a full-fledged language (no, not even VB). Hence,
    • It does not support objects
    • Does not have any error handling mechanism as such
    • Results in buggy code that is nearly impossible to detect
  • ASP is not compiled code. The asp tags are merely "interpreted" by an ISAPI filter (asp.dll) in the Web Server, which passes these script elements for processing to the database engine (or whatever designed to handle such code).
  • An ASP "application" cannot be debugged at design time. (It is not compiled code).
  • It results in lots of spaghetti code and memory leaks (if you forget to put an rs.MoveNext in a loop, the program would freeze, and there's no way out except to restart IIS).
More than anyone else, the folks at Microsoft knew that their scripting platform was quite a lame one, and they have been "plugging the holes" over the years. A significantly new product was the .NET platform, which Microsoft never really expected to take off as well as it has. This was allied with ASP.NET as the Web technology, and a new language - C# - also renowned as Java's evil cousin - took centre stage as the language of choice. ASP.NET supported full-fledged object oriented languages such as
  • C#
  • VB.NET
among others. This new platform was extensible, and it is actually the first Microsoft technology that has been ported to other architectures (other than Intel), the most notable being the Mono Project, which is a port of ASP.NET to the Apache Web Server (and thus could be run from Linux or UNIX systems).

Microsoft has finally given its long-suffering customers their due. The .NET platform has its share of admirers, but mostly it will be appreciated for making the IIS Web Server platform competitive in the face of stiff challenge from Apache (Apache, in its various versions, still has more than 60 per cent market share, down 10 percent from their highest ever share of around 70 per cent in October 2005). Whatever the reasons, Microsoft .NET resulted in the revival of a company that was previously almost written off for its lack of a programming platform (Windows is still largely coded in C, using the native Windows API).

A new Windows based database application (oh, needless to say, it is a "Web" application - which is what we say when we should actually say, "distributed application") should have these components.
  • Web technology - ASP.NET
  • Database - SQL Server or Oracle (or, quite recently added, MySQL)
  • An IDE for development, debugging, and testing
  • The programming technology - .NET
In our particular scenario, we have
  • Oracle database
  • ASP technology on IIS Server
  • Visual InterDev for application development (debugging and testing being meaningless for ASP)
Since this is the kind of work we're doing now, we shall see what we can do with it to develop somewhat better ASP "applications".


Best Practices

For distributed applications, best practices remain the same regardless of the technology used. These take into account the fact that the lonesome developer - more appropriately called a lonely devil - will do more harm than good, and mostly harm nearly all the time. The first thing to guard against in a team set-up is clever programming. Do NOT appreciate or encourage clever programming.

Cryptic shortcuts never good a program make.

A good program is a readable program. A program will always be changing, as it responds to new challenges and requirements. Once written, it will constantly be in the maintenance mode until finally it is scrapped in favour of a new version. A program is never a monolith; it is organic. Unless a program changes, its usefulness will soon be lost. Being in the telecom sector, we are acutely aware of this fact.

With this cautioning, we shall see some points and discuss each in turn.
  • The most important phase is the design phase (developing/collecting a robust specification after system study), with lots of discussion among the programmers and the project leader (who should remain silent most of the time - his job being to act as the resource person on the desired functionality of the application as well as the subject expert in the house). The design phase should take around 30-35 percent of the time.
  • Coding should never take more than 20 per cent of application development time. Once the specifications have been developed, coding can begin after agreeing upon the technologies/programming techniques to be used.
  • In coding a database application, there should be both front-end code as well as back-end code. Standalone queries passed from Web pages and executing on the database is NOT back-end code. Back-end code is secure code stored as procedures and packages. Back-end code is cached and optimized code that won't break. Unfortunately, our applications make no use of back-end code at all (the only exception being the TRA Transfer Application, which uses 100% backend code to achieve its objective - but it is not a "Web" application).
  • Modularize the code. Do not reinvent the wheel. Write everything as functions, preferably back-end functions. If your page makes a call to the Server, better make it call on the database server, to a function. This is best for everyone.
  • Spend some time isolating common functionality in every page that you do, and write modules for it, and compile it (even is ASP you can package it as a dll). This way you can sure that code doesn't get fragmented. And a change to one page will not affect another page without breaking something - in which case you will have to recompile the entire application to get it running.
  • Do not continuously modify a production system. If you get requests for modification, stack it up, and when the pile gets tall enough, release a new version of the application. This is the way to go.
  • A database application's back-end should never be modified. This portion contains the business logic, and once it is decided, it stays. SQL manipulation of a database will kill the application.
  • Never pass ordinals as such from the front-end (Web page or Form) to the back-end. Use bind variables. Use bind variables always. Each query should be of the form
    INSERT INTO sometable (field1) VALUES (:field1); -- using bind variables
    and not
    INSERT INTO sometable (field1) VALUES (xyz); -- schoolboy programming
  • Better still, have the database accept values only after performing some sort of checking.
  • There should be some client-side validation to see if the user has failed to enter a required field; but never entrust serious data checking to JavaScript or any other client-side scripting technology. To do that you have to hack the pages, and doing that is harmful. A database knows its data requirements best, so entrust those tasks to a database. Don't even suppose that client-side script can stand-in for heavy duty database functionality such as constraints, etc.
  • Client-side scripts are meant to ease the load on the Web Server, not the data server. (Very important point: it means, do not use client-side scripting to check data integrity or validation). Browser version and resolution check OK; range validation, definitely not.
  • A database is expected to deal only with data. And data means the normal types such as VARCHAR, NUMBER etc. Not other symbols or tags. Do not try to redefine common uses that have been tried and tested for over 15 years.
  • Document the functionality of each page (assuming it is an ASP), such as
    • What the page does
    • The fields (local variables)
    • Bind variables (the values it receives from the Form or front-end)
    • A revision history, if possible
  • Have a good idea of what code you are expected to write - do not waste time writing code everyone else will write in his/her own way. Let the Project Leader worry about the co-ordination issues!
  • It is a crime to have the Web Server deal with an error. Have the database raise errors, rather than letting the Web Server deal with errors. I am talking about developing a package, which will have the SQL procedures raise errors, which will be handled by the database, and the resulting message logged. It is not easy in a "Web" application, but it is certainly the way to go.
  • If you modularize the code, then you have a good chance of being able to compile your application, or at least your module of the application. You can then actually unit test the application!
  • The idea of unit testing is simple: your unit will be treated as a black box. It will receive some input (fields), and it will perform some manipulation and output some fields (or modify a database, etc.). In case you perform the transaction explicitly, you can have Oracle return a code saying the transaction was successful - and thus you can make your code fully modular.
  • If you have got the flow thus far, then you will have made a pretty scalable and useful application ruled by the business logic, and with few security holes. Testing and debugging will be very easy, and fruitful.
Next we shall look at some database aspects (from the developer's point of view).


Database Best Practices

A Web developer is expected to have expertise in html, scripting, and of course, Web technologies such as .NET or PHP or Java. However, we often find ourselves writing database applications accessed through the network, and such applications often contain nearly all the database side code. That is, it will contain nearly all the code that accesses or modifies the database! As such, it is clear that we should understand the database a little better - than just treating it as a black box.


We shall deal with the nuances of database programming in a subsequent post. Until then, cheerio!