info sql

59
SQL

Upload: evyx

Post on 19-Jun-2015

469 views

Category:

Documents


1 download

DESCRIPTION

Lessius Handelswetenschappen Informatiemanagement II Pdf SQL

TRANSCRIPT

Page 1: Info SQL

SQL

Page 2: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 2

SQL is een relationele databasetaal. De taal bevat niet alleen instructies voor het invoeren, wijzigen, raadplegen van gegevens maar ook instructies voor het creëren van tabellen en relaties en het beveiligen van gegevens. SQL was, is en blijft de databasetaal voor alle relationele database systemen als Oracle, Sybase, Informix, Microsoft SQL Server, Microsoft Access, ….

SQL is een min of meer levende taal. Succesvolle ontwikkelaars van RDBMS’en hebben bijgedragen aan de ontwikkeling. In 1970 publiceerde dr. E.F. Codd (werkzaam aan de IBM Research Laboratory, California) zijn artikel “A Relational Model of Data for Large Shared Data Banks” in Communications of het Association for Computing Machinery. Hij wordt sindsdien de “vader” van het relationele model genoemd. Onderzoek aan relationele vraaagtalen gaf het leven aan onder meer de taal SEQUEL (=Structured English Query Language). Later werd deze naam gewijzigd in SQL (Strucured Query Language) wat soms nog steeds als “sequel” wordt uitgesproken.

Niet IBM zelf, maar een bedrijf “Relational Software, Inc.” Bouwde het eerste commerciële RDBMS gebaseerd op SQL. Dit product, genaamd Oracle, kwam uit in 1979. Het bedrijf noemt inmiddels al lang Oracle Corporation en is marktleider voor RDBMS’en voor minicomputers en client/server systemen.

Verschillende organisaties hebben gepoogd de ontwikkeling van SQL te sturen door het formuleren van standaarden. De standaard waarnaar de meeste SQL handboeken naar verwijzen, is de ANSI/ISO standaard SQL3. Alhoewel we in deze cursus ons baseren op de standaard SQL, zullen toch enkele afwijkingen kunnen geconstateerd worden. Dit heeft te maken met het feit dat de oefeningen zullen uitgetest worden in Access XP, die wel standaard SQL ondersteunt maar ook een eigen SQL-dialect bevat.

Structured Query Language

� SQL is a relational database language

– DDL: data definition languagedefining tables, specifying integrity rules, ...

– DML: data manipulation languagenot only for retrieving data but also for inserting, updating and removing data

– DCL: data control languagegranting and revoking authorization privilges (security) and for defining transaction boundaries

� SQL consists of only a few keywords

Page 3: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 3

SQL kent een kleine maar zeer krachtige verzameling instructies gericht op het bewerken, beheren en beveiligen van database gegevens. Volgende aspecten zullen worden behandeld in de cursus:

• Opzetten van tabellen

• Specificeren van primaire-, refererende- sleutels sleutels en andere integriteitsregels

• Raadplegen van gegevens (joins, functies en subqueries)

• Muteren van gegevens

In deze cursus gaan we ons dus niet verdiepen in het creëren van views en het beveiligen van de gegevens.

Page 4: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 4

Vermits SQL een relationele databasetaal is, moet je vertrouwd zijn met het relationele database model. We zetten hier voor alle duidelijkheid nogmaals enkele sleutelwoorden op een rij:

• Tabel (of table) De gegevens worden opgeslagen in één of meerdere tabellen. De officiële naam is eigenlijk relatie (waarvan ook de term relationele database afkomstig is) maar we kiezen hier voor de term tabel vermits dit zowel in SQL als in Access wordt gebruikt. De term relatie gebruiken we meer om het verband tussen de tabellen aan te duiden.

• Kolom (column) en Rij (row) In een tabel worden de gegevens gegroepeerd in rijen en kolommen. Een rij bevat alle gegevens die een object (dit kan een persoon, een artikel, een boek, ... zijn) beschrijven. De rijen hebben geen specifieke volgorde, via de juiste SQL instructies kan men immers de rijen in eender welke volgorde opvragen. De kolommen bevatten gegevens die gelijkaardig zijn voor alle rijen (bijvoorbeeld alle namen, alle adressen, alle geboortedata). Men gebruikt ook de term veld i.p.v. kolom.

• Integriteitsregel (integrity rule of constraint) Integriteitsregels worden door het RDBMS gebruikt telkens gegevens worden ingevoerd, verwijderd of gewijzigd om te zorgen dat er geen “corrupt systeem” ontstaat. Integriteitsregels zorgen er bijvoorbeeld voor dat de waarden van de primaire sleutel klantID uniek zijn in de tabel KLANT. Of nog: er kunnen geen aankopen worden geregistreerd in de AANKOPEN tabel tenzij de klantID reeds bestaat in de tabel KLANT.

Structured Query Language

� Some keywords from the relational database model:

– Table

– Column

– Row

– Integrity rule (or constraint)

– Primary key

– Foreign key (or referential key)

Page 5: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 5

• Primaire sleutel (primary key) De primaire sleutel is het veld of de combinatie van velden die een unieke waarde identificatie waarde vormen voor elke rij uit de tabel.

• Refererende sleutel (foreign key) Een refererende sleutel (ook wel vreemde sleutel genoemd) van een tabel is het veld waar de waarden van een primary key (van dezelfde tabel of van een andere tabel) in kunnen voorkomen, maar waarin geen andere waarden in te vinden zijn. Tussen beide tabellen kan dan een relatie worden gecreëerd waarop de integriteitsregels van toepassing zijn.

De voorbeelden en oefeningen zijn gebaseerd op de tabellen van de Northwind database, de voorbeeld database van Access XP. Access XP biedt de tool QBE (query by example) met grafische interface zodat de gebruiker afgeschermd wordt van de SQL instructies. De gebruiker kan echter de SQL instructie die Access genereert, opvragen via de SQL view in het menu View. Alhoewel Access XP een “SQL dialect” hanteert, kan de gebruiker in het SQL window zelf standaard SQL instructies laten uitvoeren.

Om in Access een SQL commando zelf in te typen, start je met het creëren van een query in design view. Zodra Access de lijst toont met files waaruit je informatie kunt halen, klik je op Close zonder een file te selecteren. Vervolgens kies je meteen de optie SQL view uit het menu View

De meeste pakketten die SQL ondersteunen, bieden windows/editors aan waarin je meerdere SQL commando’s kunt intypen en laten uitvoeren. In dit geval moet je elke SQL commando afsluiten met een “;”. In Access kan je echter maar één SQL commando per query ingeven, daarom is het niet echt noodzakelijk om in Access een SQL commando te beëindigen met een “;”. Het commando mag je zelfs ingeven over meerdere regels om de leesbaarheid te verhogen.

SQL: Northwind Example

Page 6: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 6

In deze cursus zullen we woorden die eigen zijn aan de SQL taal steeds in hoofdletters weergeven.

De Data Definition Language commando’s worden gebruikt om tabellen te creëren, een tabelstructuur te wijzigen of om tabellen te vernietigen, alsook om de relaties tussen de tabellen vast te leggen.

SQL: DDL

Data Definition Language

� CREATE TABLE

� CONSTRAINT

– PRIMARY KEY

– FOREIGN KEY

� ALTER TABLE

� DROP TABLE

Page 7: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 7

Naast de bovenstaande commando’s zijn er ook commando’s voor het creëren en verwijderen van databases, views en indexes. Deze commando’s zullen we echter niet bespreken in deze cursus.

Page 8: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 8

Met het commando CREATE TABLE creëer je een nieuwe tabel conform de structuur zoals die bepaald wordt vanuit het ERD en de normalisatie. De relaties tussen de verschillende tabellen zijn ook vastgelegd via het ERD. Het commando beschrijft de verschillende mogelijke componenten die moeten/kunnen worden gebruikt. De SQL codewoorden staan in hoofdletters. De namen die de gebruiker zelf mag kiezen, staan in kleine letters. Optionele componenten staan tussen vierkante haakjes ([ ]). In een opsomming van mogelijke waarden wordt een verticale streep (|) gebruikt als scheidingsteken.

Noch het ERD, noch de normalisatie geven informatie over het data type van een veld. Dit vind je wel terug in de Data Dictionary. Het data type bepaalt welke waarden een veld kan bevatten. De data types die we in deze cursus gaan gebruiken, zijn:

• Tekstvelden: CHAR(n) met n het maximale aantal karakters dat het veld kan bevatten (in het Access table design window komt dit overeen met data type “Text” met onderaan de property “field size”)

• Numerieke velden: BYTE voor velden met kleine, numerieke waarden (tussen 0 en 255) INTEGER voor gehele getallen tussen –32768 en +32767 LONG voor grote gehele getallen (van –2,147,483,648 tot 2,147,483,647) SINGLE voor reële getallen (tot 3.402823E38) DOUBLE voor zeer grote reële getallen (tot 4.94065645841247E–324) (in het Access table design window komt dit overeen met data type

CREATE TABLE

� CREATE TABLE tablename( fieldname1 datatype1 [NULL | NOT NULL][DEFAULT value1] [field1 constraint claus][, fieldname2 datatype2 [NULL | NOT NULL][DEFAULT value1] [field1 constraint claus]][, ...][, CONSTRAINT pkname PRIMARY KEY

(fieldname [, fieldname, ...])][, CONSTRAINT fkname FOREIGN KEY(fieldname) REFERENCE

tablename(fieldname)]);

Page 9: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 9

“Number” met onderaan de property “field size” Byte, Integer, Long Integer, Single precision of Double precision)

• Datumvelden DATE met een standaard datumformaat zoals bepaald bij de Regional Settings (in het Access table design window komt dit overeen met data type

“Date/Time”)

• Boolean waarden BOOLEAN kan enkel True of False bevatten (in het Access table design window komt dit overeen met data type “Boolean”)

De naamgeving van de verschillende componenten is zeer belangrijk. De namen van de tabellen moeten uniek zijn binnen eenzelfde database. De veldnamen zijn uniek binnen dezelfde tabel. Kies steeds korte, maar wel duidelijke namen. Laat de namen het liefst met een letter starten en gebruik ook liever geen spaties in de namen. Indien je toch spaties in tabelnamen of veldnamen wil gebruiken, moet je wel telkens de volledige naam tussen rechte haken ([ ]) typen, bv. [Order details].

Per veld kan worden aangegeven of het een waarde moet bevatten (d.w.z. de gebruiker is verplicht om een waarde in te geven) of eventueel leeg (of onbekend) mag blijven. Gebruik hiervoor resp. de codewoorden NOT NULL en NULL.

Bij sommige velden is het nuttig om een validatieregel vast te leggen via een field constraint clause. Een dergelijke validatieregel start met het codewoord CHECK waarna tussen haakjes de regel wordt vermeld. Bijvoorbeeld: op het veld geboortedatum kan een validatieregel

CHECK (year(geboortedatum)<year(date())-18))

worden gebruikt om te vermijden dat de klanten jonger dan 18 zijn.

De table constraint clause bepaalt de primary key en legt via de foreign key de relatie vast tussen deze tabel en de andere tabellen.

Op de volgende bladzijden wordt dit commando uitvoerig toegelicht.

Om de tabel Shippers uit de Northwind database te creëren, zonder de primary key of foreign keys, gebruik je het volgende commando:

CREATE TABLE Shippers (ShipperID LONG, CompanyName CHAR(40), Phone CHAR(24) );Dit is de meest elementaire vorm waarin je het Create table commando

kunt gebruiken. Het voorziet enkel de naam van de tabel (Shippers) en de veldenlijst (ShipperID, CompanyName en Phone) tesamen met data type en veldlengte voor elk veld. De tabel bevat echter een primary key, nl. ShipperID. Om deze te creëren tesamen met de tabel, moet je het volgende commando gebruiken:

Page 10: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 10

CREATE TABLE Shippers (ShipperID LONG, CompanyName CHAR(40), ContactName CHAR(40), Address CHAR(60), City CHAR(15), Region CHAR(15), PostalCode CHAR(10), Country CHAR(15), Phone CHAR(24), Fax CHAR(24), CONSTRAINT pkShippers PRIMARY KEY (ShipperID) );De naam pkShippers is de naam van de primary key constraint.

Gewoonlijk starten we dergelijke namen met de afkorting pk (van primary key) gevolgd door de naam van de tabel. Elke tabel kan immers slechts over één primary key beschikken. Merk op dat de velden die deel uitmaken van de primary key, worden opgesomd tussen haakjes. In dit voorbeeld bevat de tabel echter een enkelvoudige sleutel. Het volgende voorbeeld creëert de tabel Order Details met een samengestelde sleutel op OrderID en ProductID. Vermits de tabelnaam Order Details uit twee woorden bestaat, moeten we de naam tussen rechte haken plaatsen om aan te geven dat deze twee woorden één tabelnaam vormen.

CREATE TABLE [Order Details] (OrderID LONG, ProductID LONG, UnitPrice SINGLE, Quantity INTEGER, Discount SINGLE, CONSTRAINT pkOrderDetails PRIMARY KEY (OrderID, ProductID) );In het volgende voorbeeld creëren we de tabel Products tesamen met de

primary key en de foreign keys. Het veld ProductID is de enkelvoudige primary key. De tabel bevat wel twee foreign keys: SupplierID en CategoryID. Dit betekent dat de waarde van deze velden niet uniek moet zijn binnen de tabel Products, maar dat de waarde verwijst naar een primary key waarde in een andere tabel. Door het creëren van de foreign key constraint, creëren we meteen ook de relatie tussen deze tabellen. Je kan dan bv. geen SupplierID invullen in Products tenzij deze waarde reeds bestaat in de tabel Suppliers.

Page 11: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 11

CREATE TABLE Products (ProductID LONG, ProductName CHAR(40), SupplierID LONG, CategoryID LONG, QuantityPerUnit CHAR(20), UnitPrice SINGLE, UnitsInStock INTEGER, UnitsOnOrder INTEGER, ReorderLevel INTEGER, Discontinued YESNO, CONSTRAINT pkProducts PRIMARY KEY (ProductID), CONSTRAINT fkSuppliers FOREIGN KEY (SupplierID) REFERENCES Suppliers (SupplierID), CONSTRAINT fkCategories FOREIGN KEY (CategoryID) REFERENCES Categories (CategoryID) );Merk op dat in het commando de velden die worden opgesomd na het

codewoord Foreign key de veldnamen zijn in de tabel die je creëert, terwijl de veldnamen bij de References de veldnamen zijn uit de gerelateerde tabel. Het volgende voorbeeld zal dit meteen verduidelijken. De relatie tussen de tabellen Orders en Shippers loopt immers via de velden ShipperID in Shippers en ShipVia in Orders.

CREATE TABLE Orders (OrderID LONG, CustomerID CHAR(5), OrderDate DATE, RequiredDate DATE, ShippedDate DATE, ShipVia LONG, Freight SINGLE, ShipName CHAR(40), ShipAddress CHAR(60), ShipCity CHAR(15), ShipRegion CHAR(15), ShiPostalCode CHAR(10), ShipCountry CHAR(15), CONSTRAINT pkOrders PRIMARY KEY (OrderID), CONSTRAINT fkCustomers FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID), CONSTRAINT fkShippers FOREIGN KEY (ShipVia) REFERENCES Shippers (ShipperID) );Uiteraard kan je alleen maar een foreign key constraint creëren op

voorwaarde dat de gerelateerde tabel reeds bestaat. In het bovenstaande voorbeeld moeten dus de tabellen Customers en Shippers reeds bestaan vooraleer je de tabel Orders kunt creëren met deze foreign keys.

Je kan ook invoerregels vastleggen op gewone velden. Stel dat je de tabel Customers wil creëren, waarbij je de invoer van het veld Country wil beperken tot de waarden France, UK en Belgium, dan gebruik je het volgende commando:

Page 12: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 12

CREATE TABLE Customers (CustomerID Long, CompanyName CHAR(40), Address CHAR(60), City CHAR(15), PostalCode CHAR(10), Country CHAR(15) NOT NULL CHECK (Country IN (“UK”, “France”, “Belgium”), CONSTRAINT pkCustomers PRIMARY KEY (CustomerID) );In het bovenstaande voorbeeld geven we niet alleen aan dat de invoer

van het veld Country beperkt wordt tot de drie opgegeven waarden, maar ook nog dat de gebruiker verplichts is om een waarde in te voeren via de NOT NULL optie.

Je kan ook een default waarde opgeven, dit is een standaard waarde die het veld krijgt bij het creëren van een nieuwe record op voorwaarde dat de gebruiker geen waarde opgeeft voor dit veld. In het onderstaande voorbeeld krijgt het veldje Discount een default waarde 0:

CREATE TABLE [Order Details] (OrderID LONG, ProductID LONG, UnitPrice SINGLE, Quantity INTEGER, Discount SINGLE DEFAULT 0, CONSTRAINT pkOrderDetails PRIMARY KEY (OrderID, ProductID) );Een standaard waarde kan natuurlijk niet beletten dat een gebruiker de

inhoud van een veld leeg maakt, zodat het veldje de waarde NULL bevat. Als een veld steeds een waarde moet bevatten, gebruik je de optie NOT NULL. En natuurlijk kan je deze optie gebruiken tesamen met de default optie:

CREATE TABLE [Order Details] (OrderID LONG, ProductID LONG, UnitPrice SINGLE, Quantity INTEGER, Discount SINGLE NOT NULL DEFAULT 0, CONSTRAINT pkOrderDetails PRIMARY KEY (OrderID, ProductID) );Met het bovenstaande commando geven we aan dat een nieuwe record

steeds discount 0 heeft, tenzij de gebruiker bij de creatie van de nieuwe record een andere waarde opgeeft. Maar na het creëren van de record, kan de gebruiker het veld niet meer leegmaken.

Page 13: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 13

Geef de SQL instructies om volgende tabellen en relaties te creëren. De hieronder weergegeven tabelstructuren komen uit de Access XP Documenter. Vertaal de Access data types dus eerst naar SQL data types. De primary keys, foreign keys en relaties kan je aflezen uit de bovenstaande slide.

Table: gemeenten

Columns

Name Type Size

PCode Long Integer 4 Woonplaats Text 30

Table: klanten

Columns

Name Type Size

KlantID Long Integer 4 Naam Text 30 Voornaam Text 15 Adres Text 30 PCode Long Integer 4 GebDatum Date/Time 8

Exercises

Page 14: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 14

Table: aankopen

Columns

Name Type Size

KlantID Long Integer 4 Aankoopdatum Date/Time 8 Bedrag Long Integer 4

Page 15: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 15

SQL biedt ook de mogelijkheid om de structuur van een tabel te wijzigen via het ALTER commando. Alhoewel de structuur van een database vast moet liggen na het ontwikkelen van het ERD en de normalisatie, kan een wijziging in de bedrijfsstrategie ertoe leiden dat de databasestructuur moeten gewijzigd. Het ALTER commando met de ADD component laat ook toe om eerst de tabellen met hun velden te creëren, nadien de primary keys op de verschillende velden vast te leggen en ten slotte dan de foreigns keys en dus de relaties in te stellen.

In één van de vorige voorbeelden werd de tabel Customers gecreëerd, echter zonder de velden ContactName en ContactTitle. Deze twee velden kunnen we nu toevoegen aan de tabelstructuur in twee afzonderlijke commando’s:

ALTER TABLE Customers ADD ContactName CHAR(30); ALTER TABLE Customers ADD ContactTitle CHAR(30);Ook al heb je bij het opstellen van de

data dictionary de nodige aandacht besteed aan data type en lengte van de verschillende velden, blijkt soms na een tijdje dat bepaalde velden beter een ander type of lengte kunnen krijgen. In het volgende voorbeeldje krijgt het veld ShipAddress uit Orders een veldlengte 40 i.p.v. 60.

ALTER TABLE Orders MODIFY ShipAddress CHAR(40);

Uiteraard moet je bij dergelijke wijzigingen steeds de nodige voorzichtigheid aan de dag leggen. Een veldlengte verminderen terwijlde tabel reeds gegevens bevat, kan ervoor zorgen dat in sommige records de laatste n karakters van de inhoud verloren gaat. Een veldlengte vergroten is natuurlijk nooit een probleem. Numerieke velden kunnen probleemloos worden omgevormd tot tekstvelden,

ALTER TABLE

� ALTER TABLE tablenameADD fieldname datatype |DROP fieldname |RENAME fieldname newfieldname |MODIFY fieldname datatype ;

Page 16: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 16

omgekeerd kan opnieuw een probleem vormen. Een zorgvuldige studie vooraf van de eisen van het informatiesysteem tezamen met een goede data dictionary vermijdt dergelijke ingrijpende veranderingen.

Het ALTER TABLE commando laat ook toe om eerst de tabel te creëren met de velden en het juiste type en lengte. Achteraf kan je dan de primary key en foreign keys toevoegen aan de tabel, zoals in het volgende voorbeeld. Hierin wordt de tabel Orders opnieuw aangemaakt: eerst worden enkel de velden gecreëerd, daarna pas worden de primary key en foreigns keys toegevoegd.

CREATE TABLE Orders (OrderID LONG, CustomerID CHAR(5), OrderDate DATE, RequiredDate DATE, ShippedDate DATE, ShipVia LONG, Freight SINGLE, ShipName CHAR(40), ShipAddress CHAR(60), ShipCity CHAR(15), ShipRegion CHAR(15), ShiPostalCode CHAR(10), ShipCountry CHAR(15) );

ALTER TABLE Orders ADD CONSTRAINT pkOrders PRIMARY KEY (OrderID);

ALTER TABLE Orders ADD CONSTRAINT fkCustomers FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID);

ALTER TABLE Orders ADD CONSTRAINT fkShippers FOREIGN KEY (ShipVia) REFERENCES Shippers (ShipperID);Als je velden en constraints

achteraf kunt toevoegen, kan je ze natuurlijk ook weer wegvegen. Dit gebeurt met de DROP component van het ALTER TABLE comando:

ALTER TABLE [Order Details] DROP CONSTRAINT pkOrderDetails;

ALTER TABLE [Order Details] DROP Discount; En ten slotte kan je ook volledige tabellen vernietigen via het DROP commando. Het commando verwijdert de tabellen en dus ook alle records van die tabel uit de database. In de meeste systemen is er geen manier om dit commando ongedaan te maken. Wel zal er in de meeste gevallen niet worden toegelaten om een tabel te verwijderen indien de primary key van de te verwijderen tabel gebruikt wordt in een relatie met een andere tabel. M.a.w. als er warden van de primary key van de te verwijderen table nog steeds als foreign key voorkomen in een gerelateerde tabel.

DROP TABLE Categories;

Page 17: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 17

Page 18: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 18

De Data Manipulation Language commando’s van SQL worden gebruikt voor het opvragen (retrieval) van informatie en het onderhoud (modification = toevoegen, wijzigen, verwijderen) van gegevens.

In de volgende slides kijken we eerst naar de commando’s voor gegevensonderhoud. Daarna kunnen we alle aandacht toespitsen op het opvragen van informatie.

Data Manipulation Language

� INSERT INTO

� SELECT

� UPDATE

� DELETE

Page 19: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 19

Het commando INSERT INTO laat toe om één record per keer toe te voegen aan een tabel, of om een reeks records te copieren vanuit een andere tabel. In beide gevallen moet je steeds letten op de integriteitsregels:

• De waarde van een primary key moet uniek blijven in de tabel

• Er kunnen geen records worden toegevoegd indien de waarde van een foreign key niet bestaat in de gerelateerde tabelOm record per record toe

te voegen, gebruik je de VALUES component van het INSERT INTO commando. Tussen haakjes som je de waarden op van de velden in de volgorde zoals bepaald via het CRATE TABLE commando. Mag een veld geen waarde krijgen, gebruik dan de waarde NULL.

INSERT INTO [Order Details] VALUES (10250, 15, 150, 10, 0.02);INSERT INTO [Order Details] VALUES (10250, 15, NULL, 10, 0.02);Indien je slechts enkele velden een

waarde wil geven, of je weet niet de juiste volgorde van de velden, kan je ook gebruik van de veldenlijst waarin je dan de velden opsomt in een bepaalde volgorde, die een waarde moeten krijgen:

INSERT INTO Customers (CompanyName, ContactName, Phone, CustomerID) VALUES ("Alfreds Futterkiste", "Maria Anders", "030-0074321", 1);Tekstwaarden worden steeds tussen

aanhalingstekens geplaatst. Dus als je over een veldje postcode zou beschikken van het type CHAR(4) voor Belgische postcodes, dan moet je de waarde opgeven als tekstwaarde (bv. “2000”) ook al bestaan Belgische postcode uitsluitend uit

INSERT INTO

� INSERT INTO tablename[(fieldname1, fieldname2,...)]VALUES (value1, value2, ...)];

� INSERT INTO tablenamesubquery

Page 20: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 20

cijfers. We maken echter geen berekeningen met een postcode en vermits het data type CHAR(4) is, moet je de postcodes tussen aanhalingstekens plaatsen.

Het INSERT INTO commando laat via een subquery toe om een hele reeks records vanuit een andere tabel in te laden. Indien je bv. een klantentabel zou krijgen van een collega, dan kan je via een subquery alle klanten uit regio “CA” selecteren en deze aan je tabel toevoegen.

INSERT INTO Customers SELECT * FROM MyCustomers WHERE Region = "CA";Subqueries worden later in de cursus

uitgebreid behandeld en we zullen dan hier niet verder over dit voorbeeld uitweiden. Het commando INSERT INTO komt in Access 2000 overeen met de actiequery append to. Ook deze actiequery laat toe om een geselecteerde reeks records toe te voegen aan een bestaande tabel.

Het commando

DELETE FROM Shippers;verwijdert alle records uit de tabel Shippers. Het DELETE commando wordt dan ook gebruikt na het uittesten van de database structuur om alle testgegevens uit de tabellen te verwijderen. In een levende database komt het niet zo dikwijls voor dat je een tabel wil leegmaken tenzij het om tijdelijke of transactie tabellen gaat. De WHERE component laat toe om slechts een beperkte set records uit een tabel weg te vegen:

DELETE FROM Orders WHERE Year(OrderDate) = 1996;

In het bovenstaande voorbeeld worden alle bestellingen uit 1996 uit de tabel verwijderd. Op die manier kunnen we ervoor zorgen dat een database niet overbelast wordt met te oude gegevens. Wil je toch nog een backup hebben van

DELETE

� DELETE FROM tablename[WHERE condition];

Page 21: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 21

deze gegevens, dan zou je ze eerst in een backup tabel kunnen overbrengen via het INSERT INTO commando vooraleer je ze uit de tabel Orders verwijdert.

De WHERE clause is een conditie of voorwaarde die per record angeeft of de record al dan niet mag worden verwijderd. Het kan een enkelvoudige conditie zijn zoals

WHERE Region = "CA"

of een samengestelde conditie waarbij enkelvoudige condities worden samengevoegd via de logische operatoren AND en OR. Vb:

WHERE Region = "CA" AND Phone IS NULL

De uitdrukking Phone IS NULL geeft hierbij de waarde True indien het veld Phone niet is ingevuld.

Let op met de integriteitsregels bij het DELETE commando. Records waarvan de primary key in gebruik is in gerelateerde tabellen, kunnen niet worden verwijderd.

Het commando DELETE komt in Access XP overeen met de actiequery Delete.

Het UPDATE commando laat toe om veldwaarden in verschillende records te wijzigen. Het volgende voorbeeld voert een prijsstijging uit van 5% voor alle producten:

UPDATE Products SET UnitPrice = UnitPrice * 1.05;In veel gevallen zal je echter een

dergelijke wijzigingen willen doorvoeren in een beperkt aantal records. De records moeten dan opnieuw worden geselecteerd via de WHERE component. Bv. je wenst een prijsstijging van 5% door te voeren op alle producten van de

UPDATE

� UPDATE tablenameSET fieldname1 = value1[, fieldname2 = value2, ....][WHERE condition];

Page 22: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 22

categorie “Beverages” (met categoryId = 2). I.p.v. de verkoopprijs in elke record uit deze categorie manueel aan te passen, kan je een UPDATE commando uitvoeren:

UPDATE Products SET UnitPrice = UnitPrice * 1.05 WHERE CategoryId = 2;

Het volgende voorbeeldje wijzigt de vervoerskosten (veldje Freight) voor de orders van het jaar 1992

UPDATE Orders SET Freight = 0 WHERE year(OrderDate) = 1992;

Ook bij het UPDATE commando kan de WHERE component enkelvoudig of samengesteld zijn.

Het UPDATE commando komt overeen met de actiequery UPDATE uit Access XP.

Page 23: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 23

Structured Query Language

� Five questions to create a select query:

– What is the output (columns) you want to see ?

– What are the constraints on the data ?

– Which tables are involved ?

– Do you need to summarize information ?

– Do you need selections on summarized data ?

Het SQL commando waarmee je wellicht het meest wordt geconfronteerd, is het SELECT commando. Hiermee kan je alle informatie uit je database halen – van de meest eenvoudige (hoeveel bestellingen hebben we dit jaar) tot complexere (wat is de omzet per kwartaal) tot de meest ingewikkelde bevragingen (welke klanten hebben dit jaar niets meer besteld).

SELECT

� SELECT field1 [, field 2, ...]FROM table1[, table2, ...][WHERE condition][GROUP BY GroupField1, GroupField2, ....][HAVING condition][ORDER BY SortField1, SortField2, ...];

Page 24: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 24

Een SELECT instructie is samengesteld uit een aantal componenten. De SELECT en FROM component zijn verplicht, de overige zijn optioneel en worden dus enkel gebruikt als de vraag naar informatie dit vereist. Als de optionele componenten worden gebruikt, moeten ze wel in de bovenstaande volgorde voorkomen.

Hieronder worden eerst de verschillende componenten en hun specifieke betekenis in het algemeen beschreven. Nadien worden de verschillende componenten stelselmatig ingeoefend aan de hand van verschillende voorbeelden.

• De SELECT component somt de output kolommen op. Dit kunnen velden zijn die

o rechtstreeks uit een tabel komen,

o of een berekening op basis van velden uit een tabel,

o of groepstotaal waarbij de records eerst worden gegroepeerd (bv. de maximum verkoopprijs per catergorie).

• De FROM component geeft aan uit welke tabellen de gegevens komen nodig om de gevraagde output te genereren. Dit zijn dus zeker de tabellen van de velden die je in de SELECT component gebruikt. Deze lijst kan nog worden uitgebreid met tabellen die je nodig hebt om de relaties tussen deze eerste groep tabellen te beschrijven. Ten slotte moet je eventueel tabellen opnemen waarvan je velden gebruikt nodig om de nodige informatie te selecteren (zie ook WHERE component)

• De WHERE component voert een selectie op record niveau uit. Het is bv. immers niet nodig om alle records van de tabel Orders te verwerken als je alleen maar geïnteresseerd bent in de bestellingen van het jaar 1998.

• De GROUP BY component creëert groepjes records waarvan de waarde van het veld of velden, opgesomd door de GROUP BY component, gelijk zijn. Hierdoor is het bv. mogelijk om de maximum verkoopprijs per catergorie te berekenen. De GROUP BY component zal dus de records groeperen tot groepjes met gelijke categorie zodat per groepje dan het maximum kan worden berekend.

• De HAVING component voert ook een selectie uit maar deze keer op het niveau van de groepstotalen. Deze component laat bv. toe in de lijst met maximum verkoopprijs per catergorie die maxima te selecteren die groter zijn dan een bepaalde waarde.

• De ORDER BY component sorteert alleen maar de de output records.

De volgende regels zijn van belang bij een SELECT-instructie:

• een SELECT-instructie bevat steeds minimaal de SELECT en FROM componenten

• de volgorde van de componenten staat vast zoals hierboven beschreven

• de HAVING component mag alleen worden gebruikt in combinatie met de GROUP BY-component

Page 25: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 25

Telkens je een query maakt die informatie moet leveren (dus een SELECT instructie), moet je de volgende zes vragen – al dan niet in deze volgorde – beantwoorden. Bij eenvoudige queries is het soms niet nodig om al de vragen te beantwoorden. De vragen vormen wel steeds een leidraad om de gevraagde SQL instructie op te bouwen.

Welke output is nodig ?

Met het antwoord op deze vraag geef je aan welke output kolommen je wenst te zien. Alhoewel bij de oefeningen in deze cursus dikwijls reeds de gevraagde output wordt getoond, zal je later in werksituaties alleen maar een vraag in gesproken of geschreven taal doorkrijgen. Het is dan ook belangrijk om deze algemene vraag om te vormen tot een correcte SQL instructie. Maak daarom een schematisch overzicht van de gevraagde output m.b.v. voorbeeld gegevens. Noteer bij elk gegeven via welk veld of via welke berekening je dit gegeven bekomt. Dit geeft je al meteen een beeld welke velden en berekeningen je nodig hebt in de SELECT component, alsook welke tabellen nodig zijn in de FROM component. Het antwoord op deze vraag levert je de SELECT component van de SQL instructie.

Welke selecties zijn nodig ?

In vele gevallen moeten niet alle gegevens (d.w.z. alle records) worden verwerkt maar slechts een klein groepje records. Deze recordselecties worden steeds opgebouwd met behulp van velden uit de tabellen. Een eerste voorbeeldje: we moeten enkel de bestellingen van 1999 verwerken, dan is het jaartal van de bestellingsdatum 1999 (Year(OrderDate) = 1999). Een tweede voorbeeld: we wensen de bestellingen van de klanten uit Frankrijk te verwerken, dan moet Country = "France". Het antwoord op deze vraag levert je de WHERE component van de SQL instructie.

Welke tabellen zijn nodig ?

In de query moet je aangeven uit welke tabellen de informatie moet worden gehaald. Deze tabellen vind je door de tabellen op te sommen van elk veld dat je gebruikt om de eerste twee vragen te beantwoorden. Neem vervolgens alle tabellen op die zorgen voor het beschrijven van de relaties tussen deze tabellen. Bv. We zouden een lijst willen van alle klanten tesamen met de producten die zij ooit hebben besteld. Eigenlijk willen we dus enkel de namen van de klanten zien, en de betelde productnamen. De klantnaam komt natuurlijk uit de tabel Customers, de productnaam uit de tabel Products. Tussen deze twee tabellen bestaat geen rechtstreeks verband (er is geen enkel veld uit klanten dat wordt gebruikt in de tabel Products, en omgekeerd). Maar je kan het verband wel beschrijven via de tabellen Orders en Order Details. Je moet dan ook deze twee tabellen opnemen in je query ook al gebruik je eigenlijk geen enkel veld uit deze tabellen. Het antwoord op deze vraag levert je de FROM component van de SQL instructie.

Page 26: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 26

Moeten er groepstotalen worden gemaakt ?

Aan de vraagstelling kan je dikwijls zien of er groepstotalen moeten worden gemaakt. Meestal komt dan het woordje “per” in de vraag voor. Bv. de omzet per kwartaal, het aantal bestellingen per klant, het aantal producten per categorie. Het creëren van groepstotalen houdt in dat je via de GROUP BY component aangeeft welke groepjes records er moeten worden gecreëerd, terwijl je in de SELECT component aangeeft welk groeptotaal je wil zien in de output. Merk op dat je via een SQL instructie in de output alleen maar ofwel de groepstotalen kunt tonen ofwel de detail informatie, maar nooit beide tegelijkerijd.

Zijn er selecties nodig op de groepstotalen ?

Het antwoord op deze vraag geeft aan of je eventueel een HAVING component moet gebruiken.

Moeten de gegevens worden gesorteerd ?

Normaal zullen de gegevens automatisch worden gesorteerd naargelang de primary keys die je in de output toont. Maar natuurlijk kan je deze volgorde steeds zelf wijzigen via de ORDER BY component.

Heel belangrijk om te weten, is dat je enkel in de SELECT component een naam aan een berekening kunt geven, maar deze naam kan je nergens anders in je SELECT statement gebruiken. Dus ook al geef je je berekende waarde een naam, dan nog moet je telkens de berekening zelf gebruiken indien je op deze berekende waarde selecties uitvoert ongeacht of je deze selecties nu in de WHERE component dan wel in de HAVING component moet uitvoeren.

Page 27: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 27

Eenvoudige SELECT queries op basis van één tabel

De meest eenvudige SELECT query is het tonen van alle records en velden van een tabel, bv. GEEF EEN VOLLEDIG OVERZICHT VAN DE TABEL EMPLOYEES:

SELECT * FROM Employees;

De asterisk in de SELECT component is de “verzamelnaam” voor alle velden uit een tabel. De FROM component geeft duidelijk aan dat het hier gaat om alle velden uit de tabel Employees. Mocht er – om de een of andere reden – twijfel bestaan over de tabel waaruit je alle velden nodig hebt, gebruik dan de schrijfwijze Employees.*. Het resultaat van deze query zie je in de volgende afdruk:

In de meeste gevallen zal je natuurlijk een geselecteerd aantal velden van een tabel willen tonen. De voorgaande vraag kan dus worden herwerkt tot: GEEF

PERSONEELSNUMMER, NAAM EN VOORNAAM VAN ELK PERSONEELSLID.

SELECT LastName, FirstName FROM Employees;

Wil je de namen van de personeelsleden in één kolom tonen (dus als één lange string), dan moet je deze tekst berekenen. De berekening is in dit geval het aaneenrijgen van teksten tot één lange string. Hiervoor gebruiken we de & operator. Let wel op: deze operator creëert niet automatisch een balnco tussen de achternaam en voornaam. Daarom moet je er zelf voor zorgen dat je een blanco als tekstwaarde (=" ") toevoegt:

SELECT EmployeeID, LastName & " " & FirstName AS FullName FROM Employees;

Het resultaat van berekeningen wordt getoond in een apart kolom alsof de waarde rechtstreeks uit een tabel zou komen. Boven elke kolom wordt steeds de naam van het veld getoond. Bij berekeningen stelt Access zelf een unieke naam voor. Deze namen zijn gewoonlijk niet betekenisvol. Het is dan ook beter om zelf namen te geven aan dergelijke berekeningen. Dit gebeurt via de AS operator.

Page 28: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 28

Dus AS FullName geeft de naam FullName aan de berekening en die naam zal je dan ook zien verschijnen in de output. Access zal trouwens deze naam ook gebruiken in de rapporten en formulieren die je met deze quey zou maken.

De volgende SQL instructie geeft ons DE PRIJZEN DIE EEN KLANT MOET BETALEN PER

BESTELD PRODUCT.

SELECT OrderID, ProductID, Quantity * UnitPrice * (1 - Discount) AS ProductPrice FROM [Order Details];

De bovenstaande voorbeeldjes selecteerden wel op veldniveau, maar verwerkten wel records uit de gevraagde tabel. Bij de volgende vraag zijn we geïnteresseerd in een beperkt aantal records uit een tabel: GEEF ALLE KLANTEN UIT LONDEN. Een dergelijke recordselectie wordt uitgevoerd door de WHERE component.

SELECT CompanyName, ContactName, Address, PostalCode, City FROM Customers WHERE City = "London";

De WHERE component in dit voorbeeld is een enkelvoudige voorwaarde: alleen de records die voldoen aan deze ene voorwaarden worden getoond in de output. Uiteraard kan je complexe samengestelde voorwaarden in een WHERE component gebruiken, zoals in de volgende vraag: GEEF ALLE KLANTEN UIT LONDEN

EN PARIJS. Let hier wel op de vertaling van het woordje "en" uit de gesproken of

Page 29: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 29

geschreven taal naar de SQL taal. Wat we eigenlijk in de output willen zien, zijn alle records die de waarde "London" hebben voor het veldje Country plus alle records die voor het zelfde veld de waarde "Paris" hebben. Vermits een veld geen twee waarden tegelijkertijd kan hebben, moeten we het woordje "en" uit de vraagstelling vertalen naar de logische operator "OR".

SELECT CompanyName, ContactName, Address, PostalCode, City FROM Customers WHERE City = "London" or City = "Paris";

De selecties uit de voorgaande voorbeelden selecteerden steeds op basis van de veldwaarden. Je kan echter ook selecteren op basis van berekende waarden, zoals in het volgende voorbeeld: GEEF VAN ALLE ORDERS UIT 1997 DE MEEST

NOODZAKELIJKE GEGEVENS. De aanduiding van de output kolommen is in dit geval nogal vaag, in realiteit zal je dus moeten nagaan wat de opdrachtgever nu eigenlijk met de output wil doen, maar laten we hier veronderstellen dat het ordernummer, de orderdatum en het uitvoerland voldoende zijn.

SELECT OrderID, OrderDate, ShipCountry FROM Orders WHERE Year(OrderDate) = 1997;

Het veldje OrderDate bevat de volledige datum (dagnummer, maandnummer, jaartal). We hoeven echter alleen maar te selecteren op basis van het jaartal dat we berekenen met de functie Year. Access (en ook andere databases die SQL ondersteunen) bevat verschillende Date-functies:

• YEAR(datum) geeft het jaartal van een datum, bv. Year(#19/2/69#) geeft 1969 als resultaat;

• MONTH(datum) geeft het maandnummer, bv. Month(#19/2/69#) geeft 2 als resultaat;

• DAY(datum) geeft het dagnummer, bv. Day(#19/2/69#) geeft 19 als resultaat;

Naast deze veel voorkomende functies heeft Access nog een speciale functie Format die een waarde kan tonen volgens een opgegeven outputformaat: FORMAT(waarde, outputformaat). Deze functie kan ook worden gebruikt voor waarden van een ander type dan het type Date. Bij datumwaarden kan het outputformaat een van de volgende mogelijkheden zijn:

• d dagnummer (bv format(#9/2/69#, "d") geeft "9")

• dd dagnummer met leidende nul (bv format(#9/2/69#, "dd") geeft "09")

• ddd afkorting van de weekdag (bv. format(#9/2/69#, "ddd") geeft "di")

• ddddd weekdag (bv. format(#9/2/69#, "dddd") geeft "dinsdag")

• m dagnummer (bv format(#9/2/69#, "m") geeft "2")

Page 30: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 30

• mm dagnummer met leidende nul (bv format(#9/2/69#, "mm") geeft "02")

• mmm afkorting van de weekdag (bv. format(#9/2/69#, "mmm") geeft "feb")

• mmmm weekdag (bv. format(#9/2/69#, "mmmm") geeft "februari")

• yy laatste twee cijfers van het jaartal (bv. format(#9/2/69#, "yy") geeft "69")

• yyyy jaartal (bv. format(#9/2/69#, "yyyy") geeft "1969")

• q geeft het kwartaalnummer (format(#9/2/69#, "q") geeft "1")

Natuurlijk kun je net zoals in Excel ook je eigen datumformaat samenstelling door een combinatie van deze symbolen te gebruiken. Bv. format(#9/2/69#,"dddd d mmmm yyyy") zal "dinsdag 9 februari 1969" als resultaat geven.

Al deze functies kan je dus gebruiken in elke component van de SELECT instructie.

Willen we nu in het laatste voorbeeld de gegevens ook nog eens sorteren, eerst per uitvoerland en vervolgens per uitvoerland in chronologische volgorde, dan moeten we de instructie als volgt aanpassen:

SELECT OrderID, OrderDate, ShipCountry FROM Orders WHERE Year(OrderDate) = 1997 ORDER BY ShipCountry, OrderDate;

Som je meerdere velden op in de ORDER BY component, dan is het eerste veld in de opsomming de hoofdsortering. Binnen de groep records met gelijke waarde voor dit veld, worden de records dan op analoge manier bijkomend gesorteerd op het volgende velden uit de opsomming, totdat de records in de gevraagde volgorde kunnen worden getoond.

Page 31: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 31

Standaard zal SQL de gegevens in stijgende volgorde tonen. Om de volgorde om te keren, volstaat het om de afkorting DESC (=descending) te gebruiken na de veldnaam. Vb: GEEF ALLE ORDERS UIT MAART 1998 IN DALENDE VOLGORDE OP

BESTELLINGSDATUM.

SELECT OrderID, OrderDate, FROM Orders WHERE Year(OrderDate) = 1998 AND Month(OrderDate) = 3 ORDER BY OrderDate DESC;

Access beschikt ook nog over de functie Date() die de systeemdatum van de PC geeft (= huidige datum). Ook deze functie kan je gebruiken in combinatie met de hiervoor besproken Date/Time functies: Month(Date()) geeft je bijvoorbeeld het nummer van de huidige maand. Een combinatie van al deze functies laat toe om alle personeelsleden te selecteren die in de huidige maand zijn geboren en deze te ordenen op stijgend dagnummer van hun verjaardag.

SELECT LastName & " " & FirstName AS FullName, BirthDate FROM Employees WHERE Month(BirthDate) = Month(Date()) ORDER BY Day(BirthDate);

Selecteren op bepaalde periodes met datumvelden is het eenvoudigste via de Year, Month en Day functies. Bv ALLE ORDERS VAN HET DERDE KWARTAAL VAN 1998:

SELECT OrderID, OrderDate, FROM Orders WHERE Year(OrderDate) = 1998 AND (Month(OrderDate) = 7 OR Month(OrderDate) = 8 OR Month(OrderDate) = 9);

Maar je ka nook gebruik maken van de BETWEEN operator. Met deze operator geef je de start- en einddatum van een periode op:

SELECT OrderID, OrderDate, FROM Orders WHERE OrderDate BETWEEN #1/7/98# AND #30/9/98#;

Een nieuw probleem dat we moeten bekijken, is het selecteren van records waarvan een veld geen waarde heeft. Bv. GEEF ALLE KLANTEN WAARVAN WE HET

FAXNUMMER NIET KENNEN. In dit geval kunnen we geen WHERE component maken zoals "WHERE Fax = ?????" vermits we geen waarde hebben waarmee we de faxnummer kunnen vergelijken. We kunnen wel gebruiken maken van de waarde NULL. Deze waarde geeft aan dat een veld geen waarde heeft. De oplossing van het probleem is dan ook:

SELECT CompanyName, ContactName, Address, PostalCode, City FROM Customers WHERE Fax Is Null;

De voorwaarde "Fax Is Null" is standaard SQL. In Access kan je deze voorwaarde herschrijven door gebruik te maken van de functie IsNull. IsNull(Fax) geeft de waarde True indien het veldje Fax geen waarde heeft, in het andere geval geeft de functie de waarde False. Het volstaat in dit geval de WHERE component uit te

Page 32: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 32

werken als volgt: WHERE IsNull(Fax) en niet WHERE IsNull(Fax) = True vermits de functie zelf al True of False geeft als resultaat.

Dergelijke voorwaarden kan je natuurlijk probleemloos combineren in samengestelde voorwaarden: GEEF ALLE LONDENSE KLANTEN WAARVAN WE HET

FAXNUMMER NIET KENNEN.

SELECT CompanyName, ContactName, Address, PostalCode, City FROM Customers WHERE City = “London” AND Fax Is Null;

Bij selectievoorwaarden kan je ook gebruik maken van de zogenaamde wildcarts, symbolen die een groepje onbekende karakters voorstellen:

• *: groep karakters (letters, cijfers, leestekens, …), dit kunnen 0, 1, 2 of meer karakters zijn

• ?: 1 karakter (letter, cijfer, leesteken, …)

• @: 1 letter

• #: 1 cijfer

Met deze wildcarts kan je records selecteren waarvan je slechts een stukje van een veldwaarde kent. Bv. WE WENSEN EEN LIJST VAN ALLE PERSONEELSLEDEN DIE EEN

MBA DIPLOMA BEHAALDEN. In het veldje Notes uit de Employees tabel staat bij deze werknemers ergens de afkorting MBA. Vermits er voor en na deze afkorting nog een reeks karakters kan staan, zoeken we naar informatie van de vorm *MBA*. Selecteren op zo'n informatie gebeurt via de Like operator:

SELECT LastName & " " & FirstName AS FullName FROM Employees WHERE Notes Like '*MBA*';

Een ander voorbeeld: GEEF ALLE KLANTEN WAARVAN DE NAAM START MET DE LETTER A:

SELECT CompanyName FROM Customers WHERE CompanyName Like 'A*';

Of nog: GEEF ALLE KLANTEN WAARVAN DE NAAM EINDIGT MET EEN D:

SELECT CompanyName FROM Customers WHERE CompanyName Like '*d';

Page 33: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 33

Oefeningen

De onderstaande oefeningen zijn gebaseerd op de Access database Northwind. De tabellen bij sommige opgaven zijn een gedeelte van de gevraagde output.

• Geef een overzicht van alle producten die nog steeds kunnen worden verkocht (veldje Discontinued moet False zijn) maar die moeten worden bijbesteld. Een product moet worden bijbesteld indien UnitsInStock + UnitsOnOrder (= aantal reeds bijbestelde producten) onder het ReOrderLevel zakt.

• Geef alle personeelsleden (volledige naam in één kolom) die in 1992 bij de firma zijn gestart, in alfabetische volgorde.

• Van welke leveranciers kennen we de Home Page niet ?

• Welke orders (geef bestellingsnummer, klantnummer en besteldatum) hebben we deze maand ?

• Geef alle Franse klanten in alfabetische volgorde

• Geef alle personeelsleden in dalende volgorde van hun geboortedatum.

• Geef alle orders in chronologische volgorde waarvan de vrachtkosten (veldje Freight) meer dan $100 bedraagt.

• Geef alle leveranciers alfabetisch per land.

Page 34: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 34

Eenvoudige SELECT queries op basis van meerdere tabellen

De meeste SELECT queries moeten gegevens halen uit meerdere tabellen. Hoewel de relaties gecreëerd en gekend zijn in de database, moeten de "verbanden" tussen de tabellen in een SELECT query opnieuw worden beschreven. In een SQL instructie spreekt men gewoonlijk niet meteen over de relaties tussen de tabellen maar wel over de joins tussen tabellen. Een join combineert gegevens uit verschillende tabellen ook al bestaan er geen relaties tussen de tabellen. Een join bestaat alleen maar in een SQL instructie. Een relatie zit permanent in de databasestructuur en zorgt voor de integrity rules (de waarde van de foreign key moet reeds bestaan in de gerelateerde tabel, zie CREATE TABLE instructie). Hoe een join moet worden gelegd, leggen we hierna in kleine stapjes uit. Stel dat je een volgende lijst wil produceren: GEEF ALLLE

ORDERS (BESTELLINGSNUMMER, BESTELDATUM, KLANTNAAM EN CONTACTPERSOON) VAN ALLE

ORDERS UIT 1998. Bestellingsnummer (=OrderID) en besteldatum (=OrderDate) komen uit de tabel Orders, klantnaam (=CompanyName) en contactpersoon (=ContactName) komen uit de tabel Customers. Je start dan ook met de volgende onvolledige SQL instructie:

SELECT OrderID, OrderDate, CompanyName, ContactName FROM Orders, Customers WHERE Year(OrderDate) = 1998;

Als je deze SQL instructie zou uitvoeren, krijg je 830 * 91 = 75 530 rijen in de output. Vanwaar dit grote aantal rijen ? Met de bovenstaande SQL instructie wordt elke record uit Orders (830 in totaal) verbonden met elke record uit Customers (91 in totaal). De output is dan ook de productverzameling van beide tabellen. Uit deze productverzameling zijn we enkel geïnteresseerd in die rijen die voldoen aan de voorwaarden van de relatie tussen Orders en Customers. M.a.w. uit de productverzameling moeten we die records selecteren waarvan de CustomerID uit Orders overeenkomt met de CustomerID uit Customers. Het heeft immers enkel zin om een Orders record met CustomerID = "ALFKI" te gaan verbinden met een record uit Customers met dezelfde CustomerID "ALFKI" want het is juist die klant die de bestelling heeft geplaatst. Om deze set records te selecteren,gebruikte men vroeger de WHERE component:

SELECT OrderID, OrderDate, CompanyName, ContactName FROM Orders, Customers WHERE Orders.CustomerID = Customers.CustomerID AND Year(OrderDate) = 1998;

Deze manier van selecteren is nog steeds correct, maar wel verouderd. Ze wordt dan ook niet meer in deze cursus gebruikt en ook niet meer aanvaard op het examen ! Een betere manier om een join te beschrijven, is deze impliciet op te nemen in de FROM component:

SELECT OrderID, OrderDate, CompanyName, ContactName FROM Orders INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID WHERE Year(OrderDate) = 1998;

Page 35: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 35

Deze laatste methode zorgt trouwens ook voor een snellere uitvoering van de instructie.

Natuurlijk kan je ook meer dan twee tabellen in je SQL instructie opnemen: GEEF

EEN OVERZICHT VAN ALLE ORDERS, TEZAMEN MET PRODUCTINFORMATIE VAN DE BESTELDE

PRODUCTEN.

SELECT Orders.OrderID, OrderDate, ProductName, Quantity, [Order Details].UnitPrice, Quantity * [Order Details].UnitPrice * (1 - Discount) AS TeBetalen FROM (Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID) INNER JOIN Products ON [Order Details].ProductID = Products.ProductID ORDER BY Orders.OrderID, ProductName;

Merk op dat eerst de join tussen Orders en Order Details wordt beschreven. Deze join wordt tussen haakjes gezet en levert een soort "tijdelijke tabel", waarna de join tussen deze tijdelijke tabel en Products wordt beschreven. Merk op dat het aantal rijen in de output 2155 bedraagt en dat dit het aantal records van de tabel Order Details is. Logisch vermits we van elk order alle detail informatie willen zien.

Merk ook op dat we in deze SQL instructie steeds Orders.OrderID en [Order Details].UnitPrice schrijven. Dit komt omdat de veldnamen OrderID en UnitPrice niet meer uniek zijn in deze SQL instructie. OrderID kan uit zowel Orders als [Order Details] komen, UnitPrice kan uit Products of [Order Details] komen. We moeten dus bij deze velden telkens aanduiden uit welke tabel het systeem de veldwaarden moet halen. In he geval van OrderID kiezen we Orders omdat in deze tabel OrderID de primary key is. In het geval van UnitPrice moeten we [Order Details] kiezen omdat we willen berekenen hoeveel de klant moet betalen

Page 36: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 36

voor het bestelde product. We moeten dus teruggrijpen naar de prijs overeengekomen op de bestelbon, en niet de huidige verkoopprijs die inmiddels misschien al gestegen is.

Een volgend voorbeeldje kan deze problematiek nog verder verduidelijken: GEEF

EEN OVERZICHT VAN ALLE BESTELDE PRODUCTEN, TEZAMEN MET HUN BESTELDATUM, TOENMALIG VERKOOPPRIJS EN HUIDIGE VERKOOPPRIJS.

SELECT ProductName, OrderDate, [Order Details].UnitPrice, Products.UnitPrice AS HuidigeVerkoopprijs FROM (Orders INNER JOIN [Order Details] ON Orders.OrderID=[Order Details].OrderID) INNER JOIN Products ON [Order Details].ProductID=Products.ProductID ORDER BY ProductName, OrderDate;

Het gebruik van synoniemen zoals in het voorgaande voorbeeld (Products.UnitPrice AS HuidigeVerkoopprijs) kan je ook gebruiken voor tabelnamen. Vooral nodig als je eenzelfde tabel tweemaal moet gebruiken in dezelfde SQL insructie, zoals in het volgende voorbeeld: GEEF ALLE

PERSONEELSLEDEN TEZAMEN MET DE NAAM VAN HUN DIRECTE CHEF. Het veldje ReportsTo in de tabel Employees bevat het personeelsnummer van de directe chef:

SELECT Employees.lastName & " " & Employees.FirstName As EmployeeName, Chiefs.LastName & " " & Chiefs.FirstName AS

Page 37: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 37

ChiefName FROM Employees INNER JOIN Employees As Chiefs ON Employees.ReportsTo =Chiefs.EmployeeID;

Natuurlijk ontbreekt in deze output één werknemer: de chef die aan niemand anders moet rapporteren. Deze werknemer wordt er immers uit gefilterd via de join.

Page 38: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 38

Oefeningen

• Geef alle klanten met hun facturatie adres (adresgegevens uit Customers), hun orders uit 1998 en het leveringsadres van het order (adresgegevens uit Orders).

• Geef alle orders (tezamen met de klantgegevens en de naam van de verkoper) die te laat gaan arriveren bij de klant. Wanneer is een order te laat:

o Wanneer de ShippedDate groter is dan de RequiredDate want dan is het order vertrokken na de uiterste verzenddatum

o Wanneer de ShippedDate niet is ingevuld en de RequiredDate reeds in het verleden ligt.

• Geef een overzicht van alle producten per category en de naam van de leverancier

Page 39: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 39

Queries met unieke rijen

De volgende SQL instructie toont de verschillende waarden van ShipCountry voor alle records uit Orders:

SELECT ShipCountry FROM Orders ORDER BY ShipCountry

Het geeft ons dan ook het antwoord op de vraag: GEEF EEN OVERZICHT VAN ALLE

UITVOERLANDEN. Probleem is wel de landen waarnaar verschillende orders zijn gestuurd, ook verschillende keren in de lijst voorkomen. Wijzigen we de instructie in:

SELECT DISTINCT ShipCountry FROM Orders;

Dan verschijnt elk uitvoerland slechts één keer in de lijst. De DISTINCT operator zorgt ervoor dat de volledige rij uniek is in de lijst, ongeacht het aantal kolommen in de SELECT component. In het volgende voorbeeld is de combinatie van ShipCountry en ShipCity uniek.

SELECT DISTINCT ShipCountry, ShipCity FROM Orders;

DISTINCT zal er ook voor zorgen dat de output alfabetisch of chronologisch gesorteerd is.

Page 40: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 40

Queries met groeperingen

Bedrijven zijn niet alleen geïnteresseerd in overzichtslijsten, ook al gaat het om geselecteerde of berekende gegevens. Veel informatie die opgevraagd wordt, vereist het berekenen van groepstotalen. De groep records kan zowel de volledige tabel zijn, als subgroepjes gecreëerd op basis van veldwaarden. De groepstotalen worden berekend aan de hand van de volgende functies (de aggregate functions):

• COUNT

• MIN

• MAX

• SUM

• AVG

• STDEV

• VARIANCE

De namen van de functies zijn duidelijk en zijn trouwens dezelfde als de overeenkomstige functies in Excel bij het berekenen van subtotalen in excel lijsten.

De meest eenvoudige voorbeelden zijn het tellen van de records in een tabel, of het aantal records in een geselecteerde set records:

• HOEVEEL PERSONEELSLEDEN HEBBEN WE ?

SELECT COUNT(*) FROM Employees;

• HOEVEEL PERSONEELSLEDEN HEBBEN WE IN FRANKRIJK ?

SELECT COUNT(*) FROM Employees WHERE Country = "France";

• GEEF DE MINIMUM EN MAXIMUM VERKOOPPRIJS VAN DE PRODUCTEN DIE NOG STEEDS

VERKOCHT WORDEN.

SELECT Min(UnitPrice) AS Minimum, Max(UnitPrice) AS Maximum FROM Products WHERE Not Discontinued;

We kunnen ook groepstotalen berekenen op berekende waarden: GEEF EEN

OVERZICHT VAN ALLE FACTUURTOTALEN. Een factuurtotaal kan je enkel berekenen door eerst te berekenen hoeveel de klant moet betalen per besteld product en dit dan te sommeren voor alle bestelde producten van dat bepaalde order.

Page 41: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 41

SELECT Orders.OrderID, CompanyName, SUM(Quantity * UnitPrice * (1 – Discount)) As InvoiceTotal FROM (Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID) INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID GROUP BY Orders.orderID, CompanyName;

De berekening Quantity * UnitPrice * (1 – Discount) geeft het te betalen bedrag per besteld product. Dit moeten we sommeren per bestelling, m.a.w. we sommeren deze bedragen in het groepje records waar de OrderID dezelfde waarde heeft. Het creëren van dergelijke groepjes records gebeurt in de GROUP BY component. Merk op dat de GROUP BY component steeds alle kolommen uit de SELECT component bevat, waar geen groepstotaal in wordt berekend. En andersom kan je stellen dat als je in de SELECT component een mengeling hebt van kolomwaarden uit records en groepstotalen, dan moet je ook een GROUP BY component in de SQL instructie gebruiken.

Zoals reeds gezegd bij de inleiding van SQL, kan een SQL instructie ofwel de detail informatie tonen, ofwel de groepstotalen, maar niet beide tezamen.

Andere voorbeelden zijn: GEEF HET AANTAL BESTELLINGEN PER KLANT IN 1997:

SELECT CompanyName, COUNT(OrderID) As NoOfOrders FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID WHERE YEAR(OrderDate) = 1997 GROUP BY CompanyName;

Of nog: WAT IS HET AANTAL BESTELLINGEN PER MAAND PER UITVOERLAND IN 1998?

SELECT ShipCountry, Month(OrderDate) AS BestelMaand, COUNT(OrderID) As NoOfOrders FROM Orders WHERE YEAR(OrderDate) = 1998 GROUP BY ShipCountry, Month(OrderDate);

WAT IS DE GEREALISEERDE OMZET PER VERKOPER PER KWARTAAL IN 1998 ?

SELECT LastName & " " & FirstName AS Verkoper, Format(OrderDate, "q") AS Kwartaal, SUM(Quantity * UnitPrice * (1 – Discount)) As InvoiceTotal FROM (Employees INNER JOIN Orders ON Employees.EmployeeID = Orders. EmployeeID) INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID WHERE Year(OrderDate) = 1998 GROUP BY LastName & " " & FirstName, Format(OrderDate, "q");

Page 42: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 42

Stel dat we in bovenstaande output slechts geïnteresseerd zijn in de verkopers en kwartalen van meer dan €30 000 omzet. Selecties op groepstotalen worden steeds aangegeven in de HAVING component:

SELECT LastName & " " & FirstName AS Verkoper, Format(OrderDate, "q") AS Kwartaal, SUM(Quantity * UnitPrice * (1 – Discount)) As InvoiceTotal FROM (Employees INNER JOIN Orders ON Employees.EmployeeID = Orders. EmployeeID) INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID WHERE Year(OrderDate) = 1998 GROUP BY LastName & " " & FirstName, Format(OrderDate, "q") HAVING SUM(Quantity * UnitPrice * (1 – Discount)) >= 30000;

In dit voorbeeld zie je zeer duidelijk dat de WHERE component selecteert op recordniveau (alle orders van 1998) terwijl de HAVING component selecteert op de groepstotalen.

In een SQL instructie met GROUP BY component worden de gegevens steeds gesorteerd op de kolommen waarop je groepeert. Deze volgorde kan je natuurlijk altijd wijzigen via de ORDER BY component. STEL DAT JE DE VOORGAANDE

OUTPUT IN DALENDE VOLGORDE VAN DE OMZET WENST TE ZIEN:

SELECT LastName & " " & FirstName AS Verkoper, Format(OrderDate, "q") AS Kwartaal,

Page 43: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 43

SUM(Quantity * UnitPrice * (1 – Discount)) As InvoiceTotal FROM (Employees INNER JOIN Orders ON Employees.EmployeeID = Orders. EmployeeID) INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID WHERE Year(OrderDate) = 1998 GROUP BY LastName & " " & FirstName, Format(OrderDate, "q") HAVING SUM(Quantity * UnitPrice * (1 – Discount)) >= 30000 ORDER BY SUM(Quantity * UnitPrice * (1 – Discount)) DESC;

Een ander eenvoudig voorbeeld: WELKE KLANTEN HEBBEN IN 1997 MEER DAN 10

ORDERS GEPLAATST BIJ DE FIRMA ? GEEF ZE IN DALENDE VOLGORDE VAN HET AANTAL.

SELECT CompanyName, COUNT(OrderID) As NumberOfOrders FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID WHERE Year(OrderDate) = 1997 GROUP BY CompanyName HAVING COUNT(OrderID) > 10 ORDER BY Count(OrderID) DESC;

IN WELKE ORDERS WARDEN MEER DAN 3 PRODUCTEN TEGELIJKERTIJD BESTELD ?

SELECT OrderID, COUNT(ProductID) As NumOfProducts FROM [Order Details] GROUP BY OrderID HAVING COUNT(ProductID) > 3;

Merk ten slotte op dat in al deze voorbeelden de namen van de berekeningen nooit gebruikt worden in de GROUP BY en HAVING componenten.

Page 44: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 44

Oefeningen

• Geef per maand het aantal personeelsleden die verjaren.

• Geef per categorie het aantal producten.

• Wat is de omzet per klant per kwartaal per jaar?

• Hoeveel orders verschepen de vervoerders per jaar ?

• Wat is het totaal aan vervoerskosten dat moet worden betaald per vervoerder in 1997 ?

• Selecteer de vervoerders waaraan de firma per jaar meer dan €5000 vervoerskosten moet betalen per jaar.

Page 45: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 45

Subqueries

Gebruik je in een gewone SELECT query de gegevens van de tabellen Customers en Orders met een join op Customers.CustomerID = Orders.CustomerID, dan worden automatisch alle klanten eruit gefilterd die geen enkel order meer hebben besteld. Met een normale join worden immers volgende recordcombinaties weerhouden:

Het schema toont duidelijk dat de klanten met code BOLID en WILMK niet getoond worden in een gewone SELECT instructie. Voor een bedrijf is het nochtans belangrijk om dergelijke informatie uit een database te kunnen halen. Dit wordt in SQL opgelost met behulp van subqueries.

De vraag die we dus met behulp van een subquery willen oplossen, luidt: GEEF

ALLE KLANTEN DIE GEEN BESTELLINGEN HEBBEN GEPLATST BIJ DE FIRMA NORTHWIND. Hoe zouden we dit zelf manueel oplossen ? Door eerst de lijst te nemen van alle klantencodes uit de klantentabel, vervolgens de lijst van alle klantencodes uit de Orders tabel (dit zijn immers de klanten die iets hebben besteld) en tenslotte in de eerste lijst elke code schrappen die ook in de tweede lijst voorkomt. De overblijvende codes zijn dan het antwoord op de vraag. We combineren dus eigenlijk volgende twee queries:

SELECT CustomerID FROM Customers;

SELECT CustomerID FROM Orders;

En deze twee queries moeten we nu combineren waarbij we aangeven dat de CustomerID uit de eerste query niet mag voorkomen in de outputset van de tweede query:

SELECT CustomerID FROM Customers WHERE CustomerID NOT IN (SELECT CustomerID FROM Orders);

CUSTOMERS

CustomerID

ANTON

AROUT

BLONP

BOLID

CHOPS

DRAGD

WILMK

ORDERS

OrderID CustomerID

10478 AROUT

10479 DRAGD

10480 CHOPS

10481 DRAGD

10482 AROUT

10483 ANTON

10484 BLONP

10485 CHOPS

10486 AROUT

10487 DRAGD

Page 46: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 46

De output zal in dit geval enkel de klantencodes bevatten, dus volgende oplossing is eigenlijk beter:

SELECT CompanyName FROM Customers WHERE CustomerID NOT IN (SELECT CustomerID FROM Orders);

De output toont ons dan de twee klanten die niets meer hebben besteld:

We zouden de vraag ook als volgt kunnen aanpassen: WELKE KLANTEN HEBBEN IN

1998 NIETS MEER BESTELD ? In dit geval moeten we in de subquery de juiste orders-records selecteren:

SELECT CompanyName FROM Customers WHERE CustomerID NOT IN (SELECT CustomerID FROM Orders WHERE Year(OrderDate) = 1998);

De output bevat uiteraard ook de twee klanten van het voorgaande voorbeeld:

Een ander voorbeeld: Geef alle producten die in 1996 niet werden verkocht. Dit lossen we weer op door de lijst te nemen van alle producten met daarnaast de lijst van alle producten die in 1996 werden verkocht en opnieuw alle producten uit de eerste lijst te schrappen die ook voorkomen in de tweede lijst. De queries waarmee we starten, zijn dus:

Page 47: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 47

SELECT ProductName FROM Products

SELECT ProductID FROM Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID WHERE Year(OrderDate) = 1996;

Deze twee queries combineren we dan tot:

SELECT ProductName FROM Products WHERE ProductID NOT IN (SELECT ProductID FROM Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID WHERE Year(OrderDate) = 1996);

En de output ziet er als volgt uit:

Naast het gebruik van de IN operator, kan je ook volgende operatoren gebruiken:

• <= ALL, >= ALL

• < ANY, > ANY

Al deze operatoren kan je enkel gebruiken als de subquery verschillende rijen output levert. We zullen hierover nog enkele voorbeelden bespreken.

WELKE WERKNEMER IS DE OUDSTE ? Dit kunnen we niet oplossen als volgt:

SELECT LastName & " " & FirstName, Min(Birthdate) FROM Employees GROUP BY LastName & " " & FirstName;

Want dit lever tons per werknemer de minimale geboortedatum. Vermits iedereen maar één geboortedatum heeft, is de output hetzelfde als van volgende instructie:

SELECT LastName & " " & FirstName, Birthdate FROM Employees;

Page 48: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 48

Het probleem kunnen we wel oplossen met een subquery: de oudste werknemer is diegene waarvan de geboortedatum kleiner of gelijk is aan alle geboortedata in de tabel (deze oplossing grijpt terug naar de definitie van het minimum: het minimum is de waarde die kleiner of gelijk is aan elke waarde uit de set). De oplossing is dan ook:

SELECT LastName & " " & FirstName, Birthdate FROM Employees WHERE BirthDate <= ALL (SELECT BirthDate FROM Employees);

WELKE KLANTEN HEBBEN HET EERSTE ORDER DOORGEGEVEN? Ook dit probleem kunnen we oplossen met een subquery en de <= ALL operator:

SELECT CompanyName, OrderID, OrderDate FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID WHERE OrderDate <= ALL (SELECT OrderDate FROM Orders);

Een andere oplossing voor beide voorgaande problemen, is als volgt:

SELECT LastName & " " & FirstName, Birthdate FROM Employees WHERE BirthDate < ANY (SELECT BirthDate FROM Employees);

SELECT CompanyName, OrderID, OrderDate FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID WHERE OrderDate < ANY (SELECT OrderDate FROM Orders);

In beide voorbeelden leverde de subquery een reeks output records op. Vandaar het gebruik van de <= ALL operator. Kenden we echter de juiste minimum waarde, dan hadden we ook gewoon de = operator kunnen gebruiken:

SELECT LastName & " " & FirstName, Birthdate FROM Employees WHERE BirthDate = (SELECT Min(BirthDate) FROM Employees);

SELECT CompanyName, OrderID, OrderDate FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID WHERE OrderDate = (SELECT Min(OrderDate) FROM Orders);

Page 49: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 49

Ook het volgende problem kan je op analoge manier oplossen: WELKE

WERKNEMERS ZIJN IN HETZELFDE JAAR BIJ DE FIRMA GESTART ALS SUYAMA MICHAEL ?

SELECT LastName & " " & FirstName AS FullName, HireDate FROM Employees WHERE LastName & " " & FirstName <> "Suyama Michael" AND Year(HireDate) = (SELECT Year(HireDate) FROM Employees WHERE LastName & " " & FirstName = "Suyama Michael");

De voorwaarde LastName & " " & FirstName <> "Suyama Michael" is enkel op genomen om te vermijden dat Suyama Michael mee in de output wordt getoond.

Het laatste voorbeeld toont dat subqueries ook kunnen worden gebruikt in andere componenten dan enkel de WHERE component. Het voorbeeld geeft antwoord op de vraag: WELKE KLANT HEEFT DE BESTELLING DOORGEGEVEN MET HET

HOOGSTE FACTUURTOTAAL.

SELECT CompanyName, Orders.OrderID, Sum(Quantity*UnitPrice*(1-Discount)) As InvoiceTotal FROM (Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID)) INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID GROUP BY CompanyName, Orders.OrderID HAVING Sum(Quantity*UnitPrice*(1-Discount)) >= ALL (SELECT Sum(Quantity*UnitPrice*(1-Discount)) FROM [Order Details] GROUP BY OrderID);

De subquery wordt in de HAVING component gebruikt om juist die facturen te kunnen selecteren met de maximum waarde. Het speciale van de subquery is dat deze gebruik maakt van de GROUP BY component en dus groepeert op OrderID, zonder OrderID zelf te tonen via de SELECT component.

Page 50: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 50

UNION queries worden gebruikt om twee of meerdere sets records samen te voegen tot één set records. Indien we bv. een algemene mailing willen versturen naar zowel de klanten als de leveranciers, dan kunnen de adressen uit de twee verschillende tabellen worden verzameld in een set.

SELECT CompanyName, Address, PostalCode, City, Country, "Customer" AS Description FROM Customers UNION SELECT CompanyName, Address, PostalCode, City, Country, "Supplier" AS Description FROM Suppliers ORDER BY Country, CompanyName;

UNION queries

� The UNION operator combines two sets of rows

� Both rows must contain the same set of columns (data type !)

� UNION eliminates duplicate rows of data

� UNION ALL keeps all the rows

� INTERSECT only keeps the rows that exist in bothsets of rows

� EXCEPT removes all rows from the second set

Page 51: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 51

Bij het gebruik van de UNION operator gelden de volgende regels:

• De SELECT componenten van alle SELECT componenten moeten een gelijk aantal kolommen bezitten en bovendien moeten overeenkomstige kolommen hetzelfde type hebben.

• Allen achter de laatste SELECT component mag een ORDER BY component worden uitgewerkt. Het sorteren wordt op het gehele eindresultaat uitgevoerd.

• De SELECT componenten mogen geen DISTINCT bevatten. De UNION operator verwijdert automatisch alle dubbels tenzij met UNION ALL gebruikt.

Page 52: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 52

Herhalingsoefeningen

• Geef een overzicht van de leveranciers met de producten die zij leveren, gesorteerd per land, per leverancier en per product.

Country Company Name Product Name Australia G'day, Mate Guaraná Fantástica Australia Pavlova, Ltd. Uncle Bob's Organic Dried

Pears Brazil Refrescos Americanas LTDA Ikura Canada Forêts d'érables Thüringer Rostbratwurst Canada Ma Maison NuNuCa Nuß-Nougat-Creme Denmark Lyngbysild Sir Rodney's Scones Finland Karkki Oy Tunnbröd France Aux joyeux ecclésiastiques Carnarvon Tigers France Escargots Nouveaux Schoggi Schokolade France Gai pâturage Rössle Sauerkraut Germany Heli Süßwaren GmbH & Co. KG Queso Cabrales Germany Nord-Ost-Fisch Handelsgesellschaft

mbH Konbu Germany Plutzer Lebensmittelgroßmärkte AG Queso Manchego La Pastora …

• Geef een overzicht van alle orders die in 1996 naar Amerika werden verscheept .

Order ID Shipped Date Company Name 10756 02-Jan-96 Split Rail Beer & Ale 10761 08-Jan-96 Rattlesnake Canyon Grocery 10757 15-Jan-96 Save-a-lot Markets 10775 26-Jan-96 The Cracker Box 10805 09-Feb-96 The Big Cheese 10808 09-Feb-96 Old World Delicatessen 10820 13-Feb-96 Rattlesnake Canyon Grocery 10815 14-Feb-96 Save-a-lot Markets 10821 15-Feb-96 Split Rail Beer & Ale 10822 16-Feb-96 Trail's Head Gourmet Provisioners …

Page 53: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 53

• Geef een overzicht van alle orders die te laat bij de klant zullen arriveren, zodat de verkoper de klant kan informeren over het probleem.

Company Name Contact Name Phone Order

ID Order Date Required

Date Shipped Date FullName

Folk och fä HB Maria Larsson 0695-

34 67 21 10264 24-

Aug-94 21-Sep-

94 23-Sep-94 Suyama

Michael Split Rail Beer & Ale Art Braunschweiger (307) 555-

4680 10271 01-Sep-94 29-Sep-

94 30-Sep-94 Suyama

Michael Berglunds snabbköp Christina

Berglund 0921-12 34 65 10280 14-

Sep-94 12-Oct-

94 13-Oct-94 Fuller

Andrew Suprêmes délices Pascale Cartrain (071)

23 67 22 20 10302 11-

Oct-94 08-Nov-

94 09-Nov-94 Peacock

Margaret …

• Zijn er produkten die nog niet geleverd zijn bij de klanten maar die nu reeds niet meer worden geproduceerd? Indien ja, moeten we die klanten op de hoogte stellen.

• Geef van de orders van 1996 het bedrag dat de klant moet betalen per besteld product .

• Geef een lijst van alle maanden van het jaar 1996 waarin verkoper ’Davolio Nancy’ iets verkocht heeft.

• Welke kortingen werden er aan klanten toegekend ? (Geef een opsomming van de percentages).

• Hoeveel orders werden reeds verscheept ?

Page 54: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 54

• Wat is de totale omzet in 1995 ?

TotalIncome 590926.404388681

• Wat is de minimale en maximale eenheidsprijs in de productentabel ? Hou enkel rekening met producten die nog worden geproduceerd !

MinPrice MaxPrice $2.50 $263.50

• Wat is de gemiddelde leeftijd van de werknemers ?

AverageAge 44.2222222222222

• Hoeveel producten verkopen we per categorie ?

• Bereken de gerealiseerde omzet per verkoper per maand voor de orders van 1996.

• Wat is de minimale en maximale eenheidsprijs per categorie ?

Category MinUnitPrice MaxUnitPrice Beverages $7.75 $263.50 Condiments $10.00 $43.90 Confections $9.20 $81.00 Dairy Products $2.50 $55.00 Grains/Cereals $7.00 $38.00 Meat/Poultry $7.45 $24.00 Produce $10.00 $53.00 Seafood $6.00 $62.50

Page 55: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 55

• Welke vrachtkosten (totalen !) hebben we per land betaald aan de verschillende vervoerders voor de orders uit 1996?

• Welke producten werden reeds meer dan 10 keer verkocht (ongeacht het aantal stuks per bestelling) ?

• Hoeveel orders werkt een verkoper per maand af en hoe evolueert dit per jaar ?

• Welke werknemers zijn verkopers ?

• Welke facturen hebben een totaal bedrag van meer dan 2000$ ?

• Welke amerikaanse klanten hebben niets meer besteld in 1996 ?

• Bestaan er leveranciers die momenteel geen enkel product meer leveren ? (kijk enkel naar producten die nog geproduceerd worden)

• Geef alle werknemers die geen mexicaanse klanten hebben

• Welke klant(en) heeft(hebben) de eerste orders doorgegeven ?

• Welke producten die nog geproduceerd worden hebben een verkoopprijs die hoger is dan de gemiddelde prijs ?

• Geef het gemiddelde van factuurtotalen uit de tabel orders

Page 56: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 56

• Geef alle klanten tezamen met orderinformatie (OrderID, OrderDate) waarvan het factuurtotaal van de orders uit bv 1997 groter is dan het gemiddelde factuurtotaal van dat jaar.

• Bestaan er leveranciers die momenteel geen enkel product meer leveren ? (kijk enkel naar producten die nog geproduceerd worden) Antwoord: één leverancier: Refrescos Americanas LTDA

• Welke producten werden niet verkocht in September 1996 ? (42 in totaal)

• Geef alle werknemers die geen mexicaanse klanten hebben

• Welke producten die nog geproduceerd worden hebben een verkoopprijs die hoger is dan de gemiddelde prijs ? (21 in totaal)

• Welke facturen hebben een factuurtotaal van meer dan 10000 € ?

• Geef het gemiddelde van factuurtotalen uit de tabel orders

• Geef alle klanten tesamen met orderinformatie (OrderID, OrderDate) waarvan het factuurtotaal van de orders uit bv 1997 groter is dan het gemiddelde factuurtotaal van dat jaar.

Page 57: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 57

Function Overview

� Current Date

Date() : system date

� Current Time

Time() : system time

� Current Time Stamp

Now() : system date + time

Function Overview (suite)

� Date functions

Year (myDate)

Ex: Year (# 15/2/00 #) equals 2000

Month (myDate)

Ex: Month (# 15/2/00 #) equals 2

Day (myDate)

Ex: Day (# 15/2/00 #) equals 15

Format (myDate, myFormatDate)

Ex: Format (#15/2/00#, ‘dddddd, mmmm dd, yyyy’)

equals Tuesday, February 02, 2000

Page 58: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 58

Interval codes:

• “ww” = aantal weken

• “y” = aantal dagen !!!!!

• “yyyy” = aantal jaren

• “m” = aantal maanden

Voor verdere uitleg: zie help functie van Access.

Function Overview (suite)

� Date functions

DateDiff (interval, date1, date2)

Ex: DateDiff (“ww”, #1-Feb-1995#, Now()) equals 264

Page 59: Info SQL

Information Management II, Academiejaar 2009-2010 Pag. 59

Function Overview (suite)

� String Functions

UCase (myString)

converts to upper case

LCase (myString)

converts to lower case

Left (myString, N)

takes the N lefmost characters

Ex: Left (‘abcdef’, 3) equals ‘abc’

Right (myString, N)

takes the N rightmost characters

Ex: Right (‘abcdef’, 2) equals ‘ef’

Function Overview (suite)

� String Functions

LTrim (myString)

removes any blanks at the beginning of a string

Ex: LTrim (‘ abcdef ‘) equals ‘abcdef ‘

RTrim (myString)

removes any blanks at the end of a string

Ex: RTrim (‘ abcdef ‘) equals ‘ abcdef’

Len (myString)

counts the number of characters

Ex: Len (‘ abcde’) equals 7