SQL — Structured Query Language Grade 12

SQL (pronounced "S-Q-L" or "sequel") is the language used to communicate with a database. It lets you retrieve, add, update and delete records. In Delphi, SQL is added to a TADOQuery component.

T1Term 1 · SQL Basics, SELECT & WHERE Queries
What is SQL?

Think of a database as a massive spreadsheet with thousands of rows. SQL is how you ask the database a question: "Give me all students in Grade 12 with a mark above 80, sorted by surname." Without SQL, you would have to scroll through every row manually.

Anatomy of a SQL Statement

SELECT Name, Surname, Mark FROM tblStudents WHERE Grade = 12 ORDER BY Surname Which fields Which table Filter condition Sort order

Using SQL in Delphi

SQL goes into the SQL property of a TADOQuery component. Always close the query before changing the SQL, then open it again.

Delphi — two ways to run a query
// Method 1: assign as one string
qryStudents.Close;
qryStudents.SQL.Text := 'SELECT * FROM tblStudents WHERE Grade = 12';
qryStudents.Open;

// Method 2: build line-by-line (easier to read for long queries)
qryStudents.Close;
qryStudents.SQL.Clear;
qryStudents.SQL.Add('SELECT Name, Surname, Mark');
qryStudents.SQL.Add('FROM tblStudents');
qryStudents.SQL.Add('WHERE Grade = 12');
qryStudents.SQL.Add('ORDER BY Surname ASC');
qryStudents.Open;

SELECT — Choosing What to Display

SQL
SELECT * FROM tblCD;                      -- all fields, all records
SELECT CDName, Artist FROM tblCD;          -- only these two fields
SELECT DISTINCT Genre FROM tblCD;          -- each genre shown once only
SELECT TOP 5 * FROM tblCD;               -- first 5 records only

WHERE — Filtering Records

The WHERE clause is how you filter — like asking "show me only the records that match this condition."

SQL — comparison operators
WHERE Genre = "Rock"             -- exact match (text in double quotes)
WHERE Price < 100               -- numbers (no quotes)
WHERE Grade <> 11              -- not equal
WHERE Mark >= 50              -- greater than or equal
WHERE Genre = "Pop" AND Price < 100       -- both must be true
WHERE Genre = "Pop" OR Genre = "Rock"     -- either can be true

Operator Precedence in Compound Conditions

When a WHERE clause mixes AND and OR, SQL doesn't just evaluate left to right — it follows a fixed order of precedence, same as in maths:

OrderOperator(s)
1 (first)Parentheses ( )
2Multiplication, division
3Subtraction, addition
4NOT
5AND
6 (last)OR

AND is evaluated before OR, so a mixed condition may not filter the way you expect unless you add brackets to force the order you want.

SQL — AND evaluates before OR
WHERE Genre = "Pop" OR Genre = "Rock" AND Price < 100
-- reads as: Genre = "Pop"  OR  (Genre = "Rock" AND Price < 100)
-- ALL Pop CDs are included, regardless of price!

-- To make BOTH genres respect the price limit, use brackets:
WHERE (Genre = "Pop" OR Genre = "Rock") AND Price < 100
When in doubt, add brackets

You don't have to memorise the precedence table if you always wrap OR conditions in brackets when combining them with AND. Brackets make your intention explicit and remove any ambiguity — for the reader and for the database.

Special WHERE Operators

OperatorMeaningExample
LIKEPattern match. % = any chars, _ = one charWHERE Name LIKE "S%" — starts with S
INMatch any value in a listWHERE Genre IN ("Rock","Pop","Jazz")
BETWEENWithin a range (inclusive for numbers)WHERE Price BETWEEN 50 AND 150
IS NULLField is empty/blankWHERE CellNo IS NULL
IS NOT NULLField has a valueWHERE CellNo IS NOT NULL
#date#Date values use # symbolsWHERE OrderDate = #2024-05-01#
Dates can be misread — use yyyy/mm/dd

Access SQL can misinterpret a date written as dd/mm/yyyy, reading it as month/day/year instead. Writing the year first removes the ambiguity: prefer #2024/05/01# (yyyy/mm/dd) over #01/05/2024#.

LIKE wildcard examples
WHERE CDName LIKE "S%"        -- starts with S
WHERE CDName LIKE "%love%"    -- contains the word "love"
WHERE CDName LIKE "%s"        -- ends with s
WHERE Code   LIKE "A__"       -- starts with A followed by exactly 2 chars

ORDER BY — Sorting Results

SQL
SELECT * FROM tblCD ORDER BY Artist;             -- ascending (default)
SELECT * FROM tblCD ORDER BY Price DESC;        -- most expensive first
SELECT * FROM tblCD ORDER BY Genre, CDName ASC; -- sort by genre, then name

Calculated Fields & Aliases

You can do maths inside a SELECT and give the result a name using AS.

SQL
SELECT Name, Price, Price * 1.15 AS PriceWithVAT FROM tblProducts;
SELECT Name, Mark, ROUND(Mark / 2, 0) AS HalfMark FROM tblStudents;

Aggregate Functions — Calculating Summaries

FunctionWhat it calculatesExample
COUNT(*)Number of recordsSELECT COUNT(*) FROM tblStudents
SUM(field)Total of numeric fieldSELECT SUM(Mark) FROM tblResults
AVG(field)Average of numeric fieldSELECT AVG(Mark) FROM tblResults
MIN(field)Lowest valueSELECT MIN(Price) FROM tblProducts
MAX(field)Highest valueSELECT MAX(Price) FROM tblProducts
SQL — GROUP BY and HAVING
-- Count students per grade
SELECT Grade, COUNT(*) AS Total
FROM tblStudents
GROUP BY Grade;

-- Only show groups with more than 10 students
SELECT Grade, COUNT(*) AS Total
FROM tblStudents
GROUP BY Grade
HAVING COUNT(*) > 10;
WHERE vs HAVING

WHERE filters individual records before grouping. HAVING filters groups after GROUP BY. If you have both, WHERE runs first.

SQL String & Math Functions

FunctionReturnsExample
LEN(field)Character countSELECT LEN(Name) FROM tblStudents
LEFT(field, n)First n charactersLEFT(Surname, 3) → first 3 letters
RIGHT(field, n)Last n charactersRIGHT(IDNo, 4)
MID(field, start, n)Substring from positionMID(IDNo, 3, 2) → month of birth (position 1–2 is the year)
ROUND(field, dec)Rounded valueROUND(Avg, 2) → 2 decimal places
INT(field)Integer part (no decimal)INT(75.8) = 75
FORMAT(field, "0.00")Formatted stringFORMAT(Price, "R0.00")

Date Functions

SQL — date examples
SELECT Name, YEAR(BirthDate) AS BirthYear FROM tblStudents;
SELECT * FROM tblOrders WHERE MONTH(OrderDate) = 12;     -- December orders
SELECT * FROM tblStudents WHERE YEAR(BirthDate) = 2008;

INSERT, UPDATE, DELETE

SQL — modifying data
-- Add a new record
INSERT INTO tblStudents (Name, Surname, Grade)
VALUES ("Alice", "Smith", 12);

-- Change existing data
UPDATE tblStudents
SET Grade = 12
WHERE StudentID = 45;

-- Remove a record
DELETE FROM tblStudents
WHERE StudentID = 45;
Always use WHERE with UPDATE and DELETE

Without a WHERE clause, UPDATE changes every record and DELETE removes every record in the table. Always double-check your condition.

Building an INSERT String with QuotedStr

Instead of a parameterised query, you will sometimes build the whole SQL statement as one string by joining Delphi variables onto it directly. Text values need quotes around them in the final SQL string — the QuotedStr function wraps a string value in quotes for you, so you don't have to type them by hand.

Delphi — INSERT built with string concatenation + QuotedStr
qryStudents.Close;
qryStudents.SQL.Text := 'INSERT INTO tblStudents (Name, Surname) VALUES (' +
  QuotedStr(edtName.Text) + ', ' + QuotedStr(edtSurname.Text) + ')';
qryStudents.ExecSQL;   // use ExecSQL (not Open) for INSERT/UPDATE/DELETE — they don't return records
QuotedStr vs parameters

QuotedStr is a quick way to build a query from a string variable, but a parameterised query (below) is the safer, preferred technique — it avoids manually matching quotes and is more resistant to SQL injection.

Parameterised Queries

Instead of hardcoding a value, use a parameter that is filled in by the user at runtime.

Delphi — parameterised query (by name method)
qryStudents.Close;
qryStudents.SQL.Clear;
qryStudents.SQL.Add('SELECT * FROM tblStudents');
qryStudents.SQL.Add('WHERE Grade = :pGrade');   // :pGrade is the parameter
qryStudents.Parameters.ParamByName('pGrade').Value := sedGrade.Value;
qryStudents.Open;

SQL in Delphi — Reading Field Values

Delphi
// Read a field value from the current record
sName   := qryStudents['Name'];
iGrade  := qryStudents['Grade'];
rMark   := qryStudents['Average'];

// Loop through all records
qryStudents.First;
while not qryStudents.Eof do
begin
  memOut.Lines.Add(qryStudents['Name'] + ' ' + qryStudents['Surname']);
  qryStudents.Next;
end;

Worked Example — A Search Screen

A classic Paper 1 task: the user types (part of) a surname into edtSearch and clicks a button. The program must build a parameterised query from that input, run it, list every match in memOut, and report how many were found. This combines user input, SQL, looping a dataset and string output.

Delphi — search button click
procedure TfrmMain.btnSearchClick(Sender: TObject);
var
  iFound : integer;
begin
  memOut.Clear;
  iFound := 0;

  // 1. Build a parameterised query from the user's input
  qryStudents.Close;
  qryStudents.SQL.Clear;
  qryStudents.SQL.Add('SELECT Name, Surname, Grade, Average');
  qryStudents.SQL.Add('FROM tblStudents');
  qryStudents.SQL.Add('WHERE Surname LIKE :pName');
  qryStudents.SQL.Add('ORDER BY Surname ASC');
  // add a wildcard so 'coe' matches 'Coetzee'
  qryStudents.Parameters.ParamByName('pName').Value := edtSearch.Text + '%';
  qryStudents.Open;

  // 2. Loop through the result set and display each record
  qryStudents.First;
  while not qryStudents.Eof do
  begin
    memOut.Lines.Add(qryStudents['Name'] + ' ' + qryStudents['Surname']
                     + '  (Gr ' + IntToStr(qryStudents['Grade'])
                     + ', ' + FloatToStrF(qryStudents['Average'], ffFixed, 4, 1) + '%)');
    Inc(iFound);
    qryStudents.Next;
  end;

  // 3. Report how many were found
  if iFound = 0 then
    ShowMessage('No learner found starting with "' + edtSearch.Text + '".')
  else
    lblCount.Caption := IntToStr(iFound) + ' learner(s) found';
end;
Why a parameter, not string-joining?

Using :pName and ParamByName is safer and cleaner than gluing the user's text straight into the SQL string — it avoids quote/type errors and blocks SQL injection (see Cybercrime). The wildcard '%' is added to the value, so a partial surname still matches.