SQL (Part 1) SQL (Structured Query Language) is the database query language that is used by Access. It was developed by IBM, and the current standard is SQL92. However, Access does not adhere completely to the standard, and in fact adds some features of its own. For the most part it’s not necessary for the Access developer/user to know SQL in depth, because Access creates SQL automatically when you use the query design window to create a query. However, you can view the code in the SQL view window and type standard SQL statements yourself if you wish. SQL becomes more immediately relevant in two ways: some types of queries cannot be created in query design view, notably subqueries and union queries SQL can be embedded in VBA code DML (Data Manipulation Language) At its simplest, DML is basic querying. Essentially, the result of a query is itself a table (even if the query result is a single cell you can think of it as a table with one row and one column). To write an SQL query: - start a new query in Design view - close the Show Table window without adding a table. The view button will show SQL. If you click it you will see SELECT; the word SELECT begins the statement and the semicolon ends it – write your query in between. By convention SQL words are capitalized, but they do not have to be. The general form of a statement is: SELECT Column(s) FROM TableName [WHERE Clause] [GROUP BY clause] [HAVING clause] [ORDER BY clause]; The basic query is a Select query, and the simplest statement is as follows: SELECT * FROM Employees; The * stands for all columns. There must be a FROM clause, which refers to the table(s) being queried. One semicolon (;) ends the statement Essentially an SQL statement is a single statement, so line breaks are irrelevant to the logic of the query; however you should use them to make the statement clear to read. The view the result of the query, go to Datasheet view; the result is all columns and all records in the employee table; to modify the query return to SQL SELECT FirstName, LastName, Dept FROM Employees; Specify fields explicitly to restrict the number of columns in the result. A comma separates the field names. Order By clause SELECT FirstName, LastName, Dept FROM Employees ORDER BY LastName; You can use an ORDER BY clause to order the results. The default is ASC, (for ascending), so can be left out. Use ORDER BY field DESC if you want a descending sort. SELECT FirstName, LastName, Dept FROM Employees ORDER BY Dept, LastName; You can ORDER BY more than one column; the first-named takes priority The Where Clause SELECT FirstName, LastName, Dept FROM Employees WHERE Dept = "MK" The WHERE clause is used to restrict the records returned to those that comply with one or more criteria. 1 ORDER BY LastName; SELECT FirstName,LastName,Dept, PayRate FROM Employees WHERE Dept="MK" AND PayRate>=15 ORDER BY PayRate; You can use comparison operators together with the WHERE clause SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE Dept="MK" AND PayRate>=15 AND PayRate<=25 ORDER BY PayRate DESC; You can specify multiple AND conditions when querying data. SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE Dept="MK" AND PayRate BETWEEN 15 AND 25 ORDER BY PayRate DESC; SQL provides a BETWEEN operator; it is more efficient than using comparison operators, so the query should run more quickly. SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE Dept="MK" OR Dept = "CE" ORDER BY Dept, PayRate DESC; This example uses an OR condition SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE (Dept= "MK" OR Dept = "CE") AND PayRate BETWEEN 15 AND 25 ORDER BY Dept, PayRate DESC; Brackets must be used to establish priority; without brackets this example the AND condition would only apply to records with CE in the Dept field (AND is evaluated before OR) Arithmetic Expressions Arithmetic operators are the same as in VBA (including Mod, Integer Division and Exponentiation) SELECT FirstName, LastName, Dept, Hours * PayRate FROM Employees WHERE Dept="MK" ORDER BY LastName; Aliases An alias is a column heading (caption). The SQL key word AS is used. SELECT FirstName, LastName, Dept, Hours * PayRate AS GrossPay FROM Employees WHERE Dept="MK" ORDER BY LastName; Concatenation operator Use concatenation to ‘glue together’ fields, expressions or literals. SELECT FirstName & " " & LastName AS FullName, The concatenation operator is & Dept, Hours*PayRate AS GrossPay Two are used here; the first is to concatenate the space, the FROM Employees second to concatenate the LastName field WHERE Employees.Dept="MK" ORDER BY LastName; Like Operator The Like operator is used when you use a wild card in a query SELECT FirstName & " " & LastName AS FullName, Dept, Hours*PayRate AS GrossPay FROM Employees WHERE LastName LIKE "B*" ORDER BY LastName; 2 NULL Operator Null means there is no value in the field, the value is unknown SELECT FirstName & " " & LastName AS FullName, Dept, Hours*PayRate AS GrossPay FROM Employees WHERE Dept Is Null ORDER BY LastName; IN Operator IN can be used to specify that the value of a field is in a set of values. SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE Dept IN(“MK”, “CE”, “EE”) ORDER BY Dept, PayRate DESC; NOT Operator Not is used to reverse the logic of a query. SELECT LastName, Dept FROM Employees WHERE Not (Dept="MK" Or Dept="CE"); SELECT FirstName, LastName, Dept FROM Employees WHERE LastName NOT LIKE "B*" ORDER BY LastName; SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE Dept NOT IN(“MK”, “CE”, “EE”) ORDER BY Dept; SELECT FirstName, LastName, Dept, Hired FROM Employees WHERE Hired NOT BETWEEN #1/1/2002# AND #1/1/2003# ORDER BY Hired DESC; Note that dates must be enclosed within # symbols and are in North American format mm/dd/yy Number Functions Round() is used to round numbers. Int() is used to discard the decimal part. SELECT LastName, Round(PayRate*1.1) As If Round() is supplied one argument then it returns an IncreasedPayRate integer FROM Employees; SELECT LastName, Round(PayRate*1.1,2) As IncreasedPayRate FROM Employees; The second optional argument to Round() is the required number of decimal places. SELECT LastName, Int(PayRate*1.1) As IncreasedPayRate FROM Employees; There are no Trunc, Ceil, Floor, Power or Sqrt functions in Access SQL 3 Comparison Operators Reference Operator = > Meaning Equal to Greater than < <> >= <= IN LIKE Less than Not equal to Greater than or Equal to Less than or Equal to Equal to a value within a collection of values Similar to BETWEEN …AND IS NULL Within a range of values including the two values which define the limits Field does not contain a value Notes e.g., >15 all records with a value greater than 15 >M all values beginning with M through to the end of the alphabet e.g, IN(“Redhill”,”Epsom”,”Egham”) e.g, LIKE “Sp*” finds Spanner and Sprockett; uses wildcards e.g, BETWEEN #1/1/96# AND #1/1/97# Logical Operators Reference Operator AND OR NOT Meaning Both expressions must be true in order for the entire expression to be judged true If either or both expressions are true the entire expression is judged to be true Inverts Truth Notes AND is evaluated before OR AND is evaluated before OR e.g, NOT IN(“Redhil”l,”Epsom”,”Egham”) Wildcards You use wildcards with the Like operator to select data Wildcard Symbol * Meaning Any number of characters Notes Like “M* “ all values beginning with M followed by none, one or more characters Like “ 08/*/92“ any day in August 1992; the zero in the month part of the date is necessary ? Any one character Like “M??” all values beginning with M and exactly two additional characters The key word DISTINCT You can use the word DISTINCT to restrict the resulting recordset to unique values. For example the following query will return 12 rows (because there are 12 records in the CompanyCars table. SELECT Make, Model FROM CompanyCars; But if you inspect the resulting recordset, you will see there are duplicates. There are several Ford Mondeos for example. If you want just unique values add the word DISTINCT to the statement as follows. SELECT DISTINCT Make, Model FROM CompanyCars; 4 There are now 7 records, each combination of Make and Model appears just once. There is no longer a one-to-one mapping of the query result to records in the table. (In interactive query design view this is accomplished by going to Query properties and setting the Unique Values property to Yes). Totals Queries When you use the Totals button in query design view you are able to group records on one or more fields and use one of the inbuilt SQL functions like Avg. SELECT Count(SalesOrders.OrderNo) AS CountOfOrderNo FROM SalesOrders; or alternatively, SELECT Count(*) AS CountOfOrderNo FROM SalesOrders; Count (*) to count all records in recordset SELECT Dept, Avg(PayRate) AS AvgOfPayRate FROM Employees GROUP BY Dept; SELECT Dept, Count(*) AS EmployeeCount FROM Employees GROUP BY Dept; SELECT Dept, Sum(Hours*Payrate) AS [Gross Pay] FROM Employees GROUP BY Dept; SELECT Dept, Min(PayRate) AS MinOfPayRate FROM Employees GROUP BY Dept; SELECT Dept, Avg(PayRate) AS AvgOfPayRate FROM Employees WHERE Hired >=#1/1/2003# GROUP BY Dept; You can also use Where clause 5 SQL (part 2) Action Queries queries select data from your tables, but have no effect on that data. Action queries make permanent changes to the data in your tables. There are four types of Action query: Update, Append, Delete, MakeTable. To run an Action query from interactive Access you must use the Run button. Select Update query You can use an Update query when you want to change the value of data in your tables. The example effects a 5% increase in the PayRate field of the Employees table where the value in the Dept field is MK. UPDATE Employees SET Employees.PayRate = [PayRate]*1.05 WHERE Employees.Dept ="MK" ; Append query An Append query can take some data or complete rows from one table and move them to another. For example, you could transfer one month’s orders to another table at the end of each month. The tables involved must have at least one field in common. The example shows how to transfer all the data in the CustomerID field in the Customers table to the newly created (week 8 handout) Credit table. INSERT INTO Credit (CustID) SELECT Customers.CustomerID FROM Customers ; Delete query queries can delete entire rows from a table, usually according to one or more criteria. They are very useful where you have to delete thousands of records. Here is a simple example that deletes all rows from the equipment table where the value in the DeptCode field is MK. Delete DELETE Equipment.* FROM Equipment WHERE Equipment.DeptCode = "MK" ; MakeTable query queries enable you to make a table from one or more existing tables. You may want to make a table in order to create a snapshot of your data at a particular moment in time, or you could make a table with columns from other tables in order to run queries against it (thereby improving performance). Another use would be if you needed to create a temporary table as part of wider process. Note that you would not usually expect the resulting table of a MakeTable query to be used for data entry. MakeTable Here’s an example which is useful – it would make a table called AnalysisOfSales against which you could run queries and create charts. Inevitably it involves multiple Join clauses, so you are better off creating the query in design view. 6 The code created by Access is reproduced, with the relevant key words highlighted. SELECT PurchaseItem.Quantity, Products.ProductID, Products.ProductName, Products.UnitPrice, Customers.CustomerID, Customers.City INTO AnalysisOfSales FROM (Customers INNER JOIN SalesOrders ON Customers.CustomerID = SalesOrders.CustomerID) INNER JOIN (Products INNER JOIN PurchaseItem ON Products.ProductID = PurchaseItem.ProductID) ON SalesOrders.OrderNo = PurchaseItem.OrderNo; Remaining DML topics Parameter query Parameters allow the user to supply data to query criteria when the query is run; the parameter is enclosed in square brackets, and user input replaces it and completes the WHERE clause. SELECT * FROM Products WHERE UnitPrice > [Enter minimum cost] ; The HAVING clause The HAVING clause can be quite tricky to understand at first. It used in queries where GROUP BY is used, rather like the WHERE clause, but the difference is that the HAVING applies to groups and WHERE applies to rows. To illustrate the difference the following SQL statement uses WHERE to get the department and average pay rate where the Dept field begins with M. SELECT Dept, Avg(PayRate) AS AvgOfPayRate FROM Employees WHERE Dept Like "M*" GROUP BY Dept ; This statement gets the department and average pay rate of departments ‘having’ more than 3 rows (i.e. more than 3 employees). SELECT Dept, Avg(PayRate) AS AvgOfPayRate FROM Employees GROUP BY Dept HAVING Count(*) >3 ; Using WHERE COUNT(*) > 3 is wrong The next example lists departments and average pay rates of departments with an average pay rate of more than 20. SELECT Dept, Avg(PayRate) AS AvgOfPayRate FROM Employees GROUP BY Dept HAVING Avg(PayRate)>20 ; Subqueries A subquery is a select statement that is nested in the WHERE or HAVING clause of another select statement. For example you may want the names of all employees who earn the lowest pay rate in the company. Think of the problem as having two steps: 1. find the lowest pay rate: SELECT MIN(PayRate) FROM Employees; which yields 6.00 2. then list the employees whose pay rate = 6 SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE PayRate = 6; You can combine the two statements if you use a subquery 7 SELECT FirstName, LastName, Dept, PayRate FROM Employees WHERE PayRate = (SELECT MIN(PayRate) FROM Employees); The inner statement is processed first, then the outer one. Another example: find all employees in the same department as WILSON SELECT FirstName, LastName, Dept FROM Employees WHERE Dept =(SELECT Dept FROM Employees WHERE LastName = 'Wilson'); Note there can only be one LastName of Wilson in the table. This is how a subquery looks in interactive query design view In Design view you can create a subquery by typing the inner SQL statement into the Criteria cell Subqueries can make use of the IN and EXISTS operators: IN is used if you want to look up the value in a column of a table or another query. The following statement returns records where there is a customer in a city where there isn’t an employee in that city. SELECT CustomerID, City FROM Customers WHERE City NOT IN (SELECT City FROM EmployeeAddresses) ; If using IN the subquery can only return one column. EXISTS is used to check whether an item exists in the subquery. The following statement returns all values of CustomerID who have never placed a Sales Order SELECT CustomerID FROM Customers WHERE NOT EXISTS (SELECT * FROM SalesOrders WHERE SalesOrders.CustomerID = Customers.CustomerID) ; The inner SQL statement could be extended with a WHERE clause, for example since a certain date. SELECT CustomerID FROM Customers WHERE NOT EXISTS (SELECT * FROM SalesOrders WHERE SalesOrders.CustomerID = Customers.CustomerID AND SalesOrders.OrderDate >= Date()-90) ; 8 SQL (part 3) This part continues the look at the DML component of SQL, specifically queries that are based on more than one table. Querying more than one table When more than one table is being queried, there is usually a join. Most of the time the join is supplied automatically, and doesn’t present a problem. To simplify: A join is supplied automatically in the query if there is a relationship between the tables. By default the join is an Inner Join. Inner Join The Inner Join is the most common join employed in general use; it returns a recordset where there is value in one table that matches a value in the other table on the join field. To illustrate, observe the query design below. Note the tables are in a one-to-many relationship, and the fields on which the tables are related are Deptcode and Dept. This is the field the two tables have in common. The Deptcode field is the key field in the Departments table, but Dept is not the key field in the Employees table (ID is). Note that neither of the key fields needs to appear in the query. The query is asking for a list of employees’ last names and the name of the department they are in. This query design produces the recordset of 38 records, shown on the right. The SQL for the illustrated query uses an Inner Join: SELECT LastName, Deptname FROM Departments INNER JOIN Employees ON Departments.Deptcode = Employees.Dept ORDER BY LastName; In a join clause specify the table name before the column name using a dot. This is mandatory. In the file Company2004.mdb there are 39 employees in the Employees table, but only 38 are in the query result. This is because one of the employees does not have a value in the Dept field, i.e. isn’t assigned to a department. The effect of the Inner Join is that the query returns records where the value in the Deptcode field in the Departments table matches the value of the Dept field in the Employees table (assuming no other criteria, for simplicity’s sake). Therefore the record with the Null value in the Dept field is not included. Outer Joins The characteristic of an Outer Join is that it returns the complete set of records from one table or other. There are two types of Outer Join; Left Join and Right Join. SELECT LastName, DeptName FROM Departments RIGHT JOIN Employees ON Departments.Deptcode = Employees.Dept ORDER BY LastName; 9 This is how a Right Join appears in Design view. An arrow points from the Employees field list to the field list of the Departments table. The result of the query includes all employees. The Left Join reverses the logic it includes all departments, whether or not they have employees. As there are no departments without employees in Company2004.mdb the Left Join produces the same result as the Inner Join. SELECT LastName, DeptName FROM Departments LEFT JOIN Employees ON Departments.Deptcode = Employees.Dept ORDER BY LastName; Try the following SQL statements. There are 12 records in the CompanyCars table, 39 in the Employees table. SELECT FirstName, LastName, RegNo, Make, Model FROM Employees INNER JOIN CompanyCars ON Employees.ID = CompanyCars.EmpID ; SELECT FirstName, LastName, RegNo, Make, Model FROM Employees RIGHT JOIN CompanyCars ON Employees.ID = CompanyCars.EmpID ; SELECT FirstName, LastName, RegNo, Make, Model FROM Employees LEFT JOIN CompanyCars ON Employees.ID = CompanyCars.EmpID ; The Inner Join produces 9 rows in the result – this implies 3 cars are not allocated to an employee. The Right Join produces 12 rows in the result – all the records in CompanyCars are included. The Left Join produces 39 records in the result – all the employees, but only 9 of the cars. Now do the equivalent of deleting the Join. Create the following statement: SELECT FirstName, LastName, RegNo, Make, Model FROM Employees, CompanyCars ; If there is no join between the tables you get 12 x 39 = 468 rows. The result is meaningless. This might lead to the question “is there a query that shows all the employees and all the cars in the same table?” The answer is yes, it’s called a Union query. But first we’ll look at a basic example. The Union query A Union query can combine the sets of records from two tables in one query result. As a starting point recreate the statement with the Left Join from above. Union queries can be used where there is no way to join two tables. The next example shows a list of the values of the City fields from the Employee Addresses table and the Customers table. SELECT EmployeeAddresses.City FROM EmployeeAddresses UNION SELECT Customers.City FROM Customers ; None of the values are duplicated in the query result. Save this query as Cities. Try the following variation, no need to save the changes. 10 SELECT EmployeeAddresses.City FROM EmployeeAddresses UNION ALL SELECT Customers.City FROM Customers ; In this variation the addition of the SQL key word ALL results in a result of 41 rows; there are 30 rows in EmployeeAddresses and 11 in Customers. A more complex union query can view all the cars and all the employees: SELECT FirstName, LastName, RegNo, Make, Model FROM Employees LEFT JOIN CompanyCars ON Employees.ID = CompanyCars.EmpID UNION SELECT FirstName, LastName, RegNo, Make, Model FROM Employees RIGHT JOIN CompanyCars ON Employees.ID = CompanyCars.EmpID ; The result of this query has 42 rows: 39 employees plus 3 cars that are not allocated to an employee. 11