Gegevenstypen
NOTICE: Dit document is een hoofdstuk uit het boek “The InterBase World”, geschreven door Alexey Kovyazin en Serg Vostrikov.
Ondanks het feit dat datatypes in detail zijn beschreven in de documentatie (zie [1, hoofdstuk 4.), is het noodzakelijk om een reeks begrippen te behandelen die vaak zullen worden gebruikt in de volgende hoofdstukken van dit boek. Naast algemene informatie zullen ook verschillende voorbeelden van het gebruik van datatypes in InterBase-databases en aanbevelingen over het gebruik en de conversie ervan worden besproken. We zullen in detail de verschillen in datatypes bekijken die bestaan in de 1e en 3e InterBase-database-dialect.
Over gegevens
Datatypes zijn basiselementen van elke programmeertaal of elke DBMS-server. InterBase is hierop geen uitzondering. Wanneer we zeggen dat een database bepaalde informatie opslaat, moeten we ons realiseren dat deze informatie niet op één plaats kan worden opgeslagen. Integendeel; gegevens moeten worden gesorteerd en op hun “plank” worden geplaatst. Datatypes bepalen wat er op een bepaalde “plank” kan worden geplaatst en wat niet. Wanneer we over de “plank” spreken, bedoelen we in de eerste plaats velden van databasetabellen (zie hoofdstuk “Tabellen. Primaire sleutels en generatoren” (deel 1)), evenals variabelen binnen triggers en opgeslagen procedures, enz. Elk datatype heeft een reeks bewerkingen die kunnen worden uitgevoerd op de waarden van dit type. Daarom is het belangrijk om het juiste datatype te kiezen bij het ontwerpen van een database. Dit helpt veel problemen te voorkomen bij het ontwikkelen van clientprogramma’s. In InterBase zijn er 12 datatypes die kunnen voldoen aan de vereisten van de ontwikkelaar voor het opslaan van gegevens. Deze typen worden voorwaardelijk onderverdeeld in de volgende 6 groepen:
- voor het opslaan van gehele getallen - INTEGER en SMALLINT;
- voor het opslaan van reële getallen - FLOAT en DOUBLE PRECISION;
- voor het opslaan van getallen met vaste precisie - NUMERIC en DECIMAL;
- voor het opslaan van datum, tijd en tijdstempel - DATE, TIME en TIMESTAMP;
- voor het opslaan van symbolen - CHARACTER (in verkorte vorm - CHAR) en VARYING CHARACTER (VARCHAR);
- voor het opslaan van dynamisch uitbreidende gegevens - BLOB
Daarnaast is het mogelijk om arrays van waarden van primitieve typen te definiëren, d.w.z. alle genoemde typen behalve BLOB.
De meeste InterBase-datatypes komen overeen met de typen die zijn gedefinieerd in de SQL92-standaard, maar daarnaast zijn er hun eigen bijzonderheden - arrays van primitieve datatypes en BLOB. Arrays in InterBase kunnen een reeks gegevens van hetzelfde type in één veld bevatten. We kunnen bijvoorbeeld een array van waarden van het type INTEGER definiëren. Arrays kunnen meerdere dimensies hebben. Het BLOB-datatype is een dynamisch uitbreidend datatype, waarvan de naam vaak wordt ontcijferd als Binary Large OBject. Er moet worden gezegd dat BLOB een uitvinding is van InterBase-ontwikkelaars die later werd verspreid en zich vestigde in alle hedendaagse SQL-servers.
Syntax voor het definiëren van datatypes
Datatypes worden gebruikt voor het beschrijven van velden in tabellen, variabelen in triggers en opgeslagen procedures. De algemene syntax voor het definiëren van alle mogelijke datatypes in InterBase wordt hieronder gegeven.
< datatype> =
{SMALLINT | INTEGER | FLOAT | DOUBLE PRECISION}[ ]
| {DATE | TIME | TIMESTAMP} [ ]
| {DECIMAL | NUMERIC} [( precision [, scale])] [ ]
| {CHAR | CHARACTER | CHARACTER VARYING | VARCHAR} [( int)
[ ] [CHARACTER SET charname]
| {NCHAR | NATIONAL CHARACTER | NATIONAL CHAR}
[VARYING] [( int)] [ ]
| BLOB [SUB_TYPE { int | subtype_name}] [SEGMENT SIZE int]
[CHARACTER SET charname]
| BLOB [(seglen [, subtype])]
Kenmerken van datatypes zoals grootte, precisie en een bereik van mogelijke waarden worden in detail beschreven in tabel 4.1 in [1., daarom zullen we onszelf hier niet herhalen. Laten we nu kort de belangrijkste bijzonderheden van datatypes bekijken en ons richten op hun mogelijke toepassing.
Gehele typen
SMALLINT en INTEGER verwijzen naar gehele typen. Ik moet zeggen dat SMALLINT een verminkte versie van INTEGER vertegenwoordigt. De lengte is 2 bytes in tegenstelling tot 4 die bedoeld zijn voor het opslaan van INTEGER. Meestal moet u niet besparen op schijfruimte. Op deze grond is het beter om het type INTEGER te gebruiken voor het opslaan van gehele waarden.
Het toepassingsgebied van gehele typen is duidelijk: ze zijn nodig voor velden die alleen gehele getallen bevatten - voor het opslaan van tellers, nummers, enz. Meestal hebben velden die primaire sleutels bevatten ook het type INTEGER.
Reële datatypes
De typen FLOAT en DOUBLE PRECISION behoren tot de reële typen (ze worden ook getaltypen met een zwevend punt genoemd). Ten eerste wil ik de lezer waarschuwen tegen het gebruik van FLOAT - de precisie ervan is niet voldoende voor het opslaan van de meeste fractionele waarden. Het wordt vooral afgeraden om geldwaarden erin op te slaan - in FLOAT-variabelen verschijnen snel afrondingsfouten en dit kan een accountant verrassen bij het maken van berekeningen. De beste manier om getallen met een zwevend punt op te slaan (bijvoorbeeld in boekhoudsystemen en systemen voor wetenschappelijke berekeningen) in een database is om ze op te slaan in het type DOUBLE PRECISION.
U moet er rekening mee houden dat er in het 3e dialect van InterBase een mechanisme is voor het opslaan van typen met een vast punt van 64 bytes. Dit mechanisme wordt gebruikt voor het opslaan van geldwaarden. Het gebruik van deze typen biedt de beste precisie.
Datatypes met een vast punt
NUMERIC en DECIMAL behoren tot deze datatypes. De vraag over het verschil tussen NUMERIC en DECIMAL wordt zeer vaak gesteld. Beide typen hebben een identieke cijfercapaciteit - van 1 tot 18 tekens, identieke precisie - van nul tot een cijfercapaciteit.
Laten we eraan herinneren dat: de cijfercapaciteit een totaal aantal cijfers is, en precisie - een aantal tekens na de komma.
Het grappigste is dat deze typen volgens de documentatie verschillen in de maximale cijfercapaciteit, maar in werkelijkheid zijn ze bijna gelijk geïmplementeerd en er is geen verschil tussen hen.
U kunt dit gemakkelijk controleren door de utility isql te starten en de hieronder genoemde reeks acties uit te voeren. We maken de tabel van de volgende vorm:
SQL> CREATE TABLE test (
CON> Num_field NUMERIC(15,2),
CON> Dec_field DECIMAL(15,2));
Vervolgens geven we een opdracht om de structuur van de tabel te tonen:
SQL> show tables test;
En we zien het volgende
NUM_FIELD NUMERIC(15, 2) Nullable
DEC_FIELD NUMERIC(15, 2) Nullable
Zoals u kunt zien, meldt InterBase dat beide gegeven kolommen het type NUMERIC hebben. De redenen voor dergelijk gedrag liggen in de realisatie van datatypes met een vast punt. De zaak is dat InterBase slechts drie mechanismen heeft voor het opslaan van elke integer-expressie, en alle typen, hoe ze ook worden genoemd, worden gerealiseerd volgens deze varianten.
Hier is de tabel uit [1., die illustreert hoe verschillende gehele typen (tabel 1.1) worden opgeslagen. Zoals u kunt zien, is het opslaan van de gegevens in het 3e dialect anders voor getallen met een grote cijfercapaciteit:
Tabel 1.1. Opslag van getallen met een vast punt
| Cijfercapaciteit | Dialect 1 | Dialect 3 |
|---|---|---|
| van 1 tot 4 | SMALLINT voor NUMERIC INTEGER voor DECIMAL |
SMALLINT |
| van 5 tot 9 | INTEGER | INTEGER |
| van 10 tot 18 | DOUBLE PRECISION | INT64 |
Nu kunnen we met zekerheid zeggen wat de belangrijkste verschillen tussen NUMERIC- en DECIMAL-typen zijn: in het geval van het definiëren van een veld (of variabele) met een kleine cijfercapaciteit (tot vier) wordt de eerste opgeslagen als een 2-byte integer SMALLINT, en de tweede - als een 4-byte INTEGER. Dus, als een cijfercapaciteit meer dan vier is, zullen DECIMAL- en NUMERIC-typen equivalent zijn.
Let op het verschil in realisatie van typen met een grote cijfercapaciteit in het eerste en derde dialect. In het eerste dialect werd het getal met een vast punt omgezet van integer naar reëel, waar de mechanismen van afronding werden toegepast. In het derde dialect is deze bijzonderheid geëlimineerd - de grote integers worden echt als integers opgeslagen - met behulp van het INT64-mechanisme dat 64-bit getallen kan opslaan in een bereik +/-2^32. Daarom zou het beter zijn om de gegevens over geldmiddelen op te slaan in databases die zijn gemaakt met het gebruik van het 3e dialect. Alleen het gebruik van het INT64-mechanisme zal de veiligheid van de kleine geldresten garanderen.
Typen voor het opslaan van datum en tijd
Typen voor het opslaan van datum en tijd zijn veranderd in versie InterBase 6.x en zijn klonen in vergelijking met 4.x en 5.x. Om niet in historische webwerken met deze typen te verwarren, zullen we een situatie in InterBase 6e versie bekijken. Vervolgens zullen we op basis daarvan kort vermelden wat eerder was. Dit wordt gedaan voor die gebruikers die nog steeds met vroege versies van InterBase werken. Dus, er zijn 3 typen in InterBase 6.x voor het opslaan van datum en tijd - DATE, TIME en TIMESTAMP.
- Type DATE slaat datums op tot op een dag nauwkeurig. Een bereik van mogelijke waarden - van 1 januari 100 n.Chr. tot 29 februari 32768.
- Type TIME slaat de gegevens over tijd op tot op een tienduizendste van een seconde nauwkeurig. Een bereik van mogelijke waarden - van 00:00 uur tot 23:59.9999 uur.
- Type TIMESTAMP vertegenwoordigt een combinatie van DATE- en TIME-typen.
Hoe werkt u met datums? Als de vraag gaat over het werk op serverniveau in opgeslagen procedures of triggers, is alles vrij eenvoudig - we kunnen altijd een variabele van het vereiste type declareren en deze instellen vanuit de tabellen en vice versa. Het is echter noodzakelijk om de gegevens van een database naar de applicatie en terug over te dragen. In dit geval zijn er twee benaderingen: bibliotheken gebruiken die een origineel formaat van InterBase-datums toepassen voor toegang tot objecten van deze typen en dit formaat converteren naar gewone intralanguage datum-/tijdtypen (FIBPlus is een voorbeeld van zo’n bibliotheek), of het mechanisme van het transformeren van datums naar regels, ingebouwd in InterBase, gebruiken.
Wat doet u als u alleen het jaar of de maand uit de volledige datum moet halen? U zult hiervoor een groep functies EXTRACT moeten gebruiken (beschikbaar in alle klonen van InterBase 6.x) waarmee u alleen het vereiste deel uit de datum kunt halen. Deze functies worden op de volgende manier gebruikt:
EXTRACT (MONTH FROM DATE_FIELD)
EXTRACT (YEAR FROM DATE_FIELD)
De volledige lijst van parameters in functie EXTRACT is de volgende: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, WEEKDAY, YEARDAY. Hun functie volgt uit hun naam, daarom zullen we ze hier niet interpreteren.
Datatypes voor het opslaan van tekst
Er zijn twee typen in InterBase bedoeld voor het opslaan van tekstinformatie - CHAR en VARCHAR. Hun volledige namen - CHARACTER en CHARACTER VARYING, maar er is geen reden om lange namen te gebruiken - zelfs de opdracht Show tables in de utility isql geeft korte namen van typen.
Om een veld of variabele van symbolisch type te definiëren, is het noodzakelijk om tussen haakjes na een naam van het type een aantal symbolen op te geven die in een gedefinieerd object zullen worden gebruikt, of een aantal symbolen weg te laten - zo wordt een veld met een lengte van 1 symbool gemaakt.
CREATE TABLE testCHARLen(
Field1 CHAR(255),
Field2 CHAR);
Wanneer deze tabel wordt gemaakt, zal Field1 een lengte van 255 symbolen hebben, en Field2 - 1 symbool.
Typen CHAR en VARCHAR zijn in veel opzichten vergelijkbaar - beide kunnen tot 32768 symbolen bevatten, maar er zijn enkele verschillen. Hoewel beide typen op dezelfde manier in een database worden opgeslagen, werkt InterBase er op verschillende manieren mee. Het volgende voorbeeld demonstreert dit:
SQL> create table testCHAR ( c1 char(10), c2 varchar(10));
SQL> insert into testCHAR(c1,c2) values(‘Test’,‘Test’);
SQL> SELECT ‘(’||c1.|’)’, ‘(’||c2.|’)’ from testCHAR;
Als resultaat krijgen we het volgende:
(Test ) (Test)
Zoals u kunt zien, waren er na de waarde ‘Test’, gekozen uit een veld c1, spaties. Dit betekent dat bij het selecteren van de gegevens uit een veld van het type CHAR de geretourneerde waarde wordt aangevuld met spaties tot de volledige lengte van een veld. Het is moeilijk aan te nemen waarom dergelijk gedrag dat leidt tot een aanzienlijke groei van het netwerkverkeer (belasting van een netwerk) noodzakelijk is. In elk geval is VARCHAR een symbolisch type dat wordt aanbevolen om te gebruiken.
Een van de belangrijkste kenmerken van het symbolische type is de tekenset - CHARACTER SET. De tekenset wordt gedefinieerd voor de hele database en standaard gebruikt voor alle symbolische velden als deze niet expliciet opnieuw wordt gedefinieerd bij het maken van een veld. Om een symbolisch veld te maken met de expliciete aanduiding van een tekenset, is het noodzakelijk om de beschrijving van een tekenset toe te voegen in de beschrijving van een kolom (in zinnen CREATE TABLE of ALTER TABLE). Tekenset WIN1251 wordt meestal gebruikt voor de ondersteuning van het Russisch (als u in detail wilt weten over het gebruik van het Russisch in InterBase, zie het hoofdstuk “InterBase Russification” (deel 1)). Hier is een voorbeeld van de tabel met een symbolisch veld met de expliciet beschreven tekenset WIN1251:
CREATE TABLE TestCHARSET(
Field1 VARCHAR(255),
Field2 VARCHAR(255) CHARACTER SET win1251);
Hier is Field1 het veld zonder expliciete aanduiding van een tekenset, daarom wordt de tekenset die bij het aanmaken van de database is opgegeven hiervoor gebruikt. Het is duidelijk bepaald voor veld Field2 dat het symbolen in WIN1251-codering zal opslaan.
Naast het aangeven van een tekenset voor symbolische velden, kunt u ook een collatievolgorde specificeren die bepaalt hoe de symbolen van deze gegevensset worden gesorteerd. Voor het Russisch zijn er twee varianten van collatie - WIN1251 en PXW_CYRL. Voor meer details over het gebruik van COLLATION ORDER, zie het hoofdstuk “InterBase-russificatie”.
U kunt een volledige lijst van tekensets en COLLATION ORDER die daarop worden toegepast vinden in de documentatie [1, hoofdstuk 13..
Let op! Volgens de documentatie over InterBase 6 zijn er 4 symbolische typen: naast de hierboven genoemde gegevenstypen bestaan er nog 2 - NCHAR en NCHAR VARYING, maar dezelfde documentatie legt hieronder uit dat de laatste twee typen dezelfde typen zijn als CHAR en VARCHAR, alleen waar de tekenset ISO8859.1 standaard wordt gebruikt. Dit betekent dat het gebruik van pseudo-type NCHAR feitelijk gelijk is aan het toepassen van CHAR DEFAULT CHARACTER SET ISO8859.1. Evenzo voor NCHAR VARYING, alleen wordt daar in plaats van CHAR VARCHAR gebruikt. Het is duidelijk dat de toepassing van deze pseudo-typen bedoeld is voor gebruikers in West-Europa en de VS, waar een tekenset ISO8859.1 is gemaakt voor de ondersteuning van talen.
BLOB-gegevenstype
Het BLOB-gegevenstype is bedoeld voor het opslaan van een grote hoeveelheid gegevens van variabele grootte. Het BLOB-type maakt het mogelijk om gegevens op te slaan die niet in velden van andere typen kunnen worden geplaatst - bijvoorbeeld een afbeelding, muziekbestanden, videofragmenten, enz. De vereisten voor het definiëren van het eenvoudigste veld van het BLOB-type in de tabel zijn dezelfde als voor het definiëren van een veld van elk elementair type:
CREATE TABLE testBLOB(
myBlobField BLOB);
Als resultaat wordt het veld myBlobField gemaakt waarin aanzienlijke gegevens kunnen worden opgeslagen. Ondanks het feit dat BLOB-velden niet verschillen van andere in de manier van definiëren, verschilt hun realisatie binnen een database sterk. Niet-BLOB-velden bevinden zich op de gegevenspagina (zie het hoofdstuk “InterBase-databasestructuur” (deel 4)) dicht bij elkaar, en terwijl alleen de BLOB-identifier op de gegevenspagina wordt opgeslagen, wordt de BLOB zelf op een speciale pagina toegewezen. Een dergelijke gegevensstructuur maakt het mogelijk om gegevens van een niet-vaste grootte op te slaan.
Het BLOB-type heeft de mogelijkheid om een set van verschillende subtypen en speciale procedures, filters genoemd (BLOB-filters), te definiëren voor het werken met deze subtypen. Er zijn enkele vooraf bepaalde BLOB-subtypen ingebouwd in InterBase. Al deze subtypen hebben niet-negatieve nummers, bijvoorbeeld subtype 0 - gegevens van ongedefinieerd type, subtype 1 - de tekst, subtype 2 - BLR (Binary Language Representation, zie de woordenlijst en het hoofdstuk “InterBase-databasestructuur”), enz. Een gebruiker kan ook BLOB-subtypen definiëren die negatieve waarden kunnen hebben. Het filter kan op elk type worden toegepast. Het zal een veld van het ene subtype naar het andere converteren.
Opgemerkt moet worden dat het gebruik van BLOB-velden meestal een alternatief is voor het opslaan van externe bestanden met betrekking tot een database. Wat BLOB-filters betreft, deze worden vrij zelden gebruikt vanwege de gerichtheid op een smalle categorie van taken.
Arrays
DBMS InterBase was een van de eerste waarin arrays verschenen. Ondersteuning van arrays in een database is de uitbreiding van het traditionele relationele model. De aanwezigheid van arrays maakt het mogelijk om het werken met sets van één gegevenstype te vereenvoudigen.
De array is een verzameling van waarden van één type, met een gemeenschappelijke naam en die het mogelijk maakt om elk array-element aan te spreken op basis van zijn nummer. Arrays in InterBase kunnen eendimensionaal en multidimensionaal zijn.
Om een veld van een array van getallen INTEGER in de tabel te maken, moet iets als het volgende worden geschreven
CREATE TABLE test(
myOneDimArray INTEGER[12.,
myTwoDimArray INTEGER[5,4.,
myThreeDimArray INTEGER[2,10,8.);
Zo worden drie velden van het array-type gemaakt: myOneDimArray - een veld dat een eendimensionale array van 12 getallen bevat, myTwoDimArray, dat een tweedimensionale array (matrix) bevat - 5х4 Integers, en myThreeDimArray - een veld dat een driedimensionale array 2х10х8 bevat. Opgemerkt moet worden dat bij een dergelijke definitie array-elementen worden genummerd vanaf “één”, d.w.z. het eerste element heeft nummer 1, het tweede - nummer 2, enz. Als iemand de grenzen van de array zelf wil instellen, bijvoorbeeld van 0 tot 5, moet hij een velddefinitie op de volgende manier specificeren:
myArray INTEGER[0:5.
Arrays zijn gerealiseerd op basis van velden van het BLOB-type, dus u hoeft niet bang te zijn dat de multidimensionale array uw tabel “vervuilt” met een groot aantal gegevens: InterBase zal de gegevens van de array netjes op afzonderlijke pagina’s toewijzen om invoer-uitvoerbewerkingen in deze velden te optimaliseren. Hoe arrays te gebruiken? Ze bieden een handig mechanisme voor het opslaan van objecten van hetzelfde type. In 80% van de gevallen geven ontwikkelaars er echter de voorkeur aan om meerdere gegevens in onderliggende (detail)tabellen op te slaan in plaats van arrays, daarom worden arrays niet zo vaak gebruikt in clienttoepassingen van InterBase DBMS. Dit gebeurt omdat de toegangsbibliotheken die bij Delphi en C++ Builder worden geleverd, zoals BDE en IBX, niet met arrays kunnen werken. Volgens de documentatie over InterBase is het mogelijk om met arrays te werken met behulp van een preprocessor gpre, maar dit is niet de meest handige manier voor een Delphi/C++ Builder-ontwikkelaar. Gelukkig is er in de FIBPlus-bibliotheek ondersteuning voor veld-arrays in InterBase. U kunt er in detail over lezen in het hoofdstuk “FIBPlus speciale mogelijkheden”. De clientbibliotheek IBProvider die het mogelijk maakt om clienttoepassingen voor InterBase te maken met behulp van ontwikkeltools van Microsoft ondersteunt ook het werken met arrays (zie het hoofdstuk “Ontwikkeling van clienttoepassingen van InterBase DBMS met behulp van de technologie van Microsoft OLE DB” (deel 3)).
Conclusie
Opgemerkt moet worden dat het onmogelijk is om over gegevenstypen te vertellen zonder vooruit te lopen, omdat ze doordringen in alle belangrijke gebieden die verband houden met de ontwikkeling van database-toepassingen. Daarom is het bij het lezen van dit boek beter om het gegeven hoofdstuk te gebruiken als een snelle referentie waarnaar u elke keer kunt verwijzen wanneer u de basis van InterBase wilt opfrissen.