Język SQL Język zapytań SQL- język relacyjnych baz danych SQL (Structured Query Language),czyli Strukturalny Język Zapytań. SQL jest standardowym językiem do kierowania poleceń do relacyjnej bazy danych i komunikacji z bazą. Za pomocą języka SQL można wprowadzać dane do bazy, odczytywać modyfikować usuwać. Ponieważ SQL jest standardowym językiem wykorzystywanym we wszystkich relacyjnych bazach danych, użytkownik znający ten język może pracować z dowolną relacyjną bazą danych. 1
Język zapytań SQL- język relacyjnych baz danych Wariacje SQL w zależności od producenta bazy danych - Oracle, IBM, Sybase, Microsoft, mysql PDQ (Parallel Data Query), czyli SQL na wiele procesorów Relacyjne bazy danych - standardy SQL Stworzono standardowy język zapytań (SQL), służący do operowania danymi w bazach relacyjnych. Standardy dotyczące relacyjnych baz danych są dobrze zdefiniowane przez takie organizacje, jak Międzynarodowa Organizacja Normalizacyjna (ISO) i Narodowy Amerykański Instytut Standaryzacyjny (ang. American National Standards Institute - ANSI). SQL - 89 SQL - 92 - SQL 2 SQL - 92 - rozszerzony o ODBC w 1995 roku SQL - 99 - SQL 3 wzbogacony o rozszerzenia obiektowe 2
Język definicji i manipulacji danymi Język SQL składa się z trzech języków podrzędnych, pozwalających wykonywać praktycznie dowolne operacje w relacyjnej bazie danych Do projektowania, definiowania logicznej struktury danych używany jest język definicji danych (ang. DDL - Data Definition Language). do tworzenia tabel, usuwania ich, definiowania perspektyw, indeksów, ograniczeń itd Do modyfikowania danych w systemu służy język manipulacji danymi (ang. DML - Data Manipulation Language). Język zapytań o dane (odczytywać dane) (ang. DQL - Data Query Language) Informacje o widokach w słowniku Oracle Możemy uzyskać listę tabel w słowniku Oracle stosując zapytanie do następującego widoku. SELECT * FROM dictionary ORDER BY table_name; 3
Tworzenie tabeli poprzez polecenia CREATE TABLE CREATE TABLE user.student ( ID_STUDENT INTEGER NOT NULL, IMIE VARCHAR2(25) NOT NULL, NAZWISKO VARCHAR2(30) NOT NULL, PESEL INTEGER NOT NULL, ADRES_E_MAIL VARCHAR2(100), CONSTRAINT PK_STUDENT PRIMARY KEY (ID_STUDENT )) Stawianie komentarzy do tabeli COMMENT ON COLUMN user.student.id_student IS 'IDENTYFIKATOR STUDENTA'; COMMENT ON COLUMN user.student.imie IS 'IMIE STUDENTA'; COMMENT ON COLUMN user.student.nazwisko IS 'NAZWISKO STUDENTA'; COMMENT ON COLUMN user.student.pesel IS 'NUMER PESELU'; COMMENT ON COLUMN user.student.adres_e_mail IS 'ADRES E_MAIL STUDENTA'; 4
Sposoby wprowadzania danych do bazy danych Oracle - Wprowadzić za pomocą formularza (Oracle Forms) - Pobrać dane z innej bazy Oracle poprzez db_link - Wprowadzić za pomocą narzędzi typu TOAD - Wprowadzić z pliku tekstowego używając opcję External Tables - Wprowadzić z Arkusza Kalkulacyjnego za pomocą HTML_DB - Wprowadzić za pomocą ODBC (Open Data Base Connection) na przykład dane przechowywane w Ms Acces lub bazę danych Open Office, DB2, SQL Server, mysql - Wprowadzić za pomocą HTML_DB - Wprowadzić dane z pliku eksportowego używając narzędzi IMPORT - Wprowadzić dane z pliku używając narzędzi LOADER - Wprowadzić dane z użyciem narzędzi Oracle DATA POMP - Wprowadzić dane z użyciem funkcji TABLE FUNCTION (PL/SQL) - Wprowadzić bezpośrednio do tabeli z użyciem instrukcji INSERT Instrukcja INSERT Instrukcja INSERT jest bardzo prosta. INSERT INTO table_name[(field_names)] VALUES(field_values); gdzie (field_names) parametr nie jest obowiązkowy ale jest bardzo ważny, pomocny, należy go używać w celu czytelności kodu. INSERT INTO table_name VALUES (list_of_values); 5
Instrukcja INSERT Tabela do której chcemy wprowadzać, dodawać dane musi istnieć! Jeżeli tabela nie istnieje Oracle wyświeli nam błąd : ORA-00942: tabela lub perspektywa nie istnieje W takiej sytuacji należy najpierw utworzyć tabelę i następne wprowadzić dane. Wprowadzanie danych do tabeli za pomocą INSERT INSERT INTO user.student (ID_STUDENT, IMIE, NAZWISKO, PESEL, ADRES_E_MAIL) VALUES (1, 'PIOTR', 'ILCZEW', 62092211111, 'pilczew@yahoo.com'); INSERT INTO user.student (ID_STUDENT, IMIE, NAZWISKO, PESEL, ADRES_E_MAIL) VALUES (2, 'ADAM', 'GALEWSKI', 710209211111, 'adamg@yahoo.com'); select * from user.student; 6
Tworzenie tabeli poprzez polecenia CREATE TABLE CREATE TABLE user.piosenki (WYKONAWCA_ID INTEGER NOT NULL, PIOSENKA VARCHAR2(100), CONSTRAINT PK_PIOSENKI PRIMARY KEY (WYKONAWCA_ID )); COMMENT ON COLUMN user.piosenki.wykonawca_id IS 'IDENTYFIKATOR WYKONAWCY ; COMMENT ON COLUMN user.piosenki.piosenka IS 'TYTUL PIOSENKI ; Tworzenie tabeli poprzez polecenia CREATE TABLE CREATE TABLE user.wykonawca ( WYKOANWCA_ID INTEGER NOT NULL, IMIE_NAZWISKO VARCHAR2(80), CONSTRAINT PK_WYKONAWCA PRIMARY KEY (WYKOANWCA_ID )); COMMENT ON COLUMN user.wykonawca.wykoanwca_id IS 'IDENTYFIKATOR WYKONAWCY ; COMMENT ON COLUMN user.wykonawca.imie_nazwisko IS 'IMIE i NAZWISKO WYKONAWCY ; 7
Zmiana nazwy kolumn po utworzeniu tabeli Jeżeli po utworzeniu tabeli chcemy zmienić nazwy kolumn możemy usunąć tabelę i ponownie ją utworzyć. W celu nie utracenia wprowadzonych do tabeli danych należy użyć tymczasowej tabeli. Druga metoda jest użycie polecenia ALTER TABLE RENAME COLUMN ALTER TABLE PIL.WYKONAWCA RENAME COLUMN WYKOANWCA_ID to WYKONAWCA_ID Usuwanie klucza z kolumny tabeli ALTER TABLE user.piosenki DROP (WYKONAWCA_ID); ALTER TABLE user.piosenki ADD WYKONAWCA_ID INTEGER NOT NULL; ORA-01758: aby dodać obowiązkową kolumnę (NOT NULL) tabela musi być pusta DELETE from user.piosenki; 8
Wprowadzanie danych do tabeli za pomocą INSERT INSERT INTO PIOSENKI ('LATO',1); ORA-00928:brak słowa kluczowego SELECT Brakuje słowa kluczowego VALUES INSERT INTO PIOSENKI VALUES('LATO',1); ORA-01722 niepoprawna liczba To oznacza, że dane, które wprowadzamy są w niewłaściwym, nieodpowiednim porządku. INSERT INTO PIOSENKI VALUES(2,'ZIMA'); 1 row inserted Wprowadzanie danych do tabeli za pomocą INSERT INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(3,'ZIMA'); 1 row inserted INSERT INTO PIOSENKI (PIOSENKA, WYKONAWCA_ID,) VALUES('ZIMA', 4); ORA-01747 niepoprawna specyfikacja użytkownik.tablica.kolumna, tablica.kolumna lub kolumna Należy usunąć, po WYKONAWCA_ID INSERT INTO PIOSENKI (PIOSENKA, WYKONAWCA_ID) VALUES('ZIMA', 4); ORA-00904: "WYKOANWCA_ID": niepoprawny identyfikator 9
Wprowadzanie danych do tabeli za pomocą INSERT INSERT INTO WYKONAWCA ( WYKONAWCA_ID, IMIE_NAZWISKO) VALUES (1, 'Jean Michel Jarre'); INSERT INTO WYKONAWCA ( WYKONAWCA_ID, IMIE_NAZWISKO) VALUES (2, 'Salvatore Adamo'); INSERT INTO WYKONAWCA ( WYKONAWCA_ID, IMIE_NAZWISKO) VALUES (3, 'Celine Dion'); INSERT INTO WYKONAWCA ( WYKONAWCA_ID, IMIE_NAZWISKO) VALUES (4, 'Celine Dion'); Jeżeli zrobimy próby i chcemy wprowadzić po raz kolejny tego samego wykonawcę to wystąpi błąd ORA-00001:naruszono więzy unikatowe PK_WYKONAWCA INSERT INTO WYKONAWCA ( WYKONAWCA_ID, IMIE_NAZWISKO) VALUES (4, 'Andrea Bocceli'); Usuwanie wierszy z tabeli delete from WYKONAWCA where WYKONAWCA_ID=4; Usuwanie wszystkich wierszy z tabeli DELETE FROM PIOSENKI; lub TRUNCATE TABLE PIOSENKI; 10
Wprowadzanie danych do tabeli za pomocą INSERT -- usuwanie danych z tabeli DELETE FROM PIOSENKI; -- wprowadzanie danych do tabeli INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(1,'Equinoxe'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(1, 'Oxygene'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(1, 'Oxygene'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(1, 'C est la vie'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Tombe La Neige'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Vous Permettez Monsieur'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Nathalie'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Plus fort que le temps'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Que Sera'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Nuestra novela'); INSERT INTO PIOSENKI (WYKONAWCA_ID, PIOSENKA) VALUES(2, 'Comme toujours'); SELECT * FROM WYKONAWCA; DESC WYKONAWCA; SELECT DISTINCT WYKONAWCA_ID, IMIE_NAZWISKO FROM WYKONAWCA; SELECT * from PIOSENKI SELECT w.wykonawca_id, w.imie_nazwisko, p.piosenka FROM PIOSENKI p, WYKONAWCA w WHERE p.wykonawca_id = w.wykonawca_id; 11
Tworzenie tabeli poprzez polecenia CREATE TABLE CREATE TABLE PIL.PIOSENKI_N ( NUMER_ID INTEGER NOT NULL, WYKONAWCA VARCHAR2(80), PIOSENKA VARCHAR2(80), CONSTRAINT PK_PIOSENKI_N PRIMARY KEY (NUMER_ID )); COMMENT ON COLUMN PIL.PIOSENKI_N.NUMER_ID IS 'NUMER PIOSENKI w KOLEKCJI ; COMMENT ON COLUMN PIL.PIOSENKI_N.WYKONAWCA IS 'WYKOANWCA PIOSENKI ; COMMENT ON COLUMN PIL.PIOSENKI_N.PIOSENKA IS 'TYTUL PIOSENKI ; Wprowadzanie danych do tabeli za pomocą INSERT INSERT INTO PIOSENKI_N VALUES(1,'Jean Michel Jarre', 'Equinoxe'); INSERT INTO PIOSENKI_N VALUES(2,'Jean Michel Jarre', 'Oxygene'); INSERT INTO PIOSENKI_N VALUES(3,'Jean Michel Jarre', 'Oxygene'); INSERT INTO PIOSENKI_N VALUES(4,'Jean Michel Jarre', 'C est la vie'); INSERT INTO PIOSENKI_N VALUES(5,'Salvatore Adamo', 'Tombe La Neige'); INSERT INTO PIOSENKI_N VALUES(6,'Salvatore Adamo', 'Vous Permettez Monsieur'); INSERT INTO PIOSENKI_N VALUES(7,'Salvatore Adamo', 'Nathalie'); INSERT INTO PIOSENKI_N VALUES(8,'Salvatore Adamo', 'Plus fort que le temps'); INSERT INTO PIOSENKI_N VALUES(9,'Salvatore Adamo', 'Que Sera'); INSERT INTO PIOSENKI_N VALUES(10,'Salvatore Adamo', 'Nuestra novela'); INSERT INTO PIOSENKI_N VALUES(11,'Salvatore Adamo', 'Comme toujours'); INSERT INTO PIOSENKI_N VALUES(12,'Edit Piaf', 'La vie en rose'); INSERT INTO PIOSENKI_N VALUES(13,'Dalida', 'Salma ya salama arabic'); 12
Normalizacja danych - tworzenie słownika SELECT * FROM PIOSENKI_N; Wybieramy listy wykonawców bez powtórzenia (DISTINCT) SELECT DISTINCT WYKONAWCA FROM PIOSENKI_N; Normalizacja danych - tworzenie słownika CREATE TABLE PIL.WYKONAWCA_T (WYKONAWCA_ID INTEGER, WYKONAWCA_IMIE VARCHAR2(80), CONSTRAINT PK_WYKONAWCA_T PRIMARY KEY (WYKONAWCA_ID )); INSERT INTO WYKONAWCA_T (WYKONAWCA_IMIE ) SELECT DISTINCT WYKONAWCA WYKONAWCA_IMIE FROM PIOSENKI_N; ORA-01400: Nie można wstawić wartości NULL do user.wykonawca_t.wykonawca_id 13
Normalizacja danych - tworzenie słownika Wyłączenie sprawdzenia ograniczenia ALTER TABLE WYKONAWCA_T DISABLE CONSTRAINT PK_WYKONAWCA_T; INSERT INTO WYKONAWCA_T (WYKONAWCA_IMIE ) SELECT DISTINCT WYKONAWCA WYKONAWCA_IMIE FROM PIOSENKI_N; 2 rows inserted Normalizacja danych - tworzenie słownika SELECT * FROM WYKONAWCA_T; UPDATE WYKONAWCA_T SET WYKONAWCA_ID=1 WHERE WYKONAWCA_IMIE LIKE '%Jarre%'; UPDATE WYKONAWCA_T SET WYKONAWCA_ID=2 WHERE WYKONAWCA_IMIE LIKE '%Adamo%'; Włączenie sprawdzenia ograniczenia ALTER TABLE WYKONAWCA_T ENABLE CONSTRAINT PK_WYKONAWCA_T; 14
Normalizacja danych - tworzenie słownika - drugie rozwiązanie Stosujemy trigger (wyzwalacz) Tworzymy sekwencję create sequence SEQ_WYKONAWCA_T Tworzymy wyzwalacz, który używa tej sekwencji create trigger WYK_T_trigger before insert on WYKONAWCA_T for each row begin select SEQ_WYKONAWCA_T.nextval into :new.wykonawca_id from dual; end; Wyzwalacz został utworzony. show error Nie ma błędów. Tom Kyte, How to auto increment a column - like a sqlserver Identity or Informix serial, version 8.0.5, http://asktom.oracle.com/pls/ask/f?p=4950:8:888505161037443817::no::f4950 _P8_DISPLAYID,F4950_P8_CRITERIA:256815210563 Normalizacja danych - tworzenie słownika - drugie rozwiązanie Usuwamy wiersze z tabeli WYKONAWCA_T delete from WYKONAWCA_T; Wstawiamy dane do słownikowej tabeli INSERT INTO WYKONAWCA_T (WYKONAWCA_IMIE ) SELECT DISTINCT WYKONAWCA WYKONAWCA_IMIE FROM PIOSENKI_N; Wyświetlanie wprowadzonych danych select WYKONAWCA_ID, WYKONAWCA_IMIE from WYKONAWCA_T; 15
Normalizacja danych Mamy listy wykonawców w tabeli WYKONAWCA_T Teraz chcemy stworzyć listę piosenek w tabeli PIOSENKI_T, pobierając z tabeli WYKONAWCA_T identyfikator WYKONAWCA z kolumny WYKONAWCA_ID, a z tabeli PIOSENKI_N lista PIOSENEK. CREATE TABLE PIOSENKI_T as SELECT w.wykonawca_id, p.piosenka FROM PIOSENKI_N p, WYKONAWCA_T w WHERE p.wykonawca=w.wykonawca_imie; Normalizacja danych Teraz chcemy dodawać piosenki do tabeli PIOSENKI_T i jednocześnie uaktualniać, dodawać dane do tabeli WYKONAWCY_T. Teraz musimy połączyć więżą referencyjną kolumny WYKONAWCA_ID z tabeli PIOSENKI_T i WYKONAWCA_ID z tabeli WYKONAWCY_T. ALTER TABLE PIL.PIOSENKI_T ADD CONSTRAINT WYKONAWCA_ID FOREIGN KEY (WYKONAWCA_ID) REFERENCES PIL.WYKONAWCA_T (WYKONAWCA_ID) ENABLE VALIDATE; 16
Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Teraz przed dodaniem danych do tabeli PIOSENKI_T sprawdzamy czy WYKONAWCA figuruje w tabeli WYKONAWCA_T. Jeżeli nie istnieje wpis dodajemy wpis do tabeli WYKONAWCA_T. Jeżeli spróbujemy dodać wpis z nieistniejącym numerem wykonawcy WYKONAWCA_ID wystąpi błąd INSERT INTO PIOSENKI_T ( WYKONAWCA_ID, PIOSENKA) VALUES (11,'TEST'); ORA-02291: naruszono więzy integralności (user.wykonawca_id) - nie znaleziono klucza nadrzędnego Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Teraz przed dodaniem danych do tabeli PIOSENKI_T sprawdzamy czy WYKONAWCA figuruje w tabeli WYKONAWCA_T. Jeżeli nie istnieje wpis dodajemy wpis do tabeli WYKONAWCA_T. Jeżeli spróbujemy dodać wpis z nieistniejącym numerem wykonawcy WYKONAWCA_ID otrzymamy błąd INSERT INTO PIOSENKI_T ( WYKONAWCA_ID, PIOSENKA) VALUES (11,'TEST'); ORA-02291: naruszono więzy integralności (user.wykonawca_id) - nie znaleziono klucza nadrzędnego 17
Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Wprowadzamy do tabeli WYKONAWCA_T imię i nazwisko wykonawcy w kolumny WYKONAWCA_IMIE. INSERT INTO WYKONAWCA_T (WYKONAWCA_IMIE) VALUES ('Goran Bregovic'); Następne wybieramy identyfikator WYKONAWCY - WYKONAWCA_ID z tabeli WYKONAWCA_T. SELECT * FROM WYKONAWCA_T; W tabeli WYKONAWCA_T artysta Goran Bregovic posiada identyfikator WYKONAWCA_ID 7 INSERT INTO PIOSENKI_T ( WYKONAWCA_ID, PIOSENKA) VALUES (7,'Djurdjevdan'); 1 rows inserted Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Innym możliwym rozwiązaniem jest stosowanie multi-table insert, który jest dostępny w Oracle9i oraz Oracle 10g. INSERT ALL INTO WYKONAWCA_T ( WYKONAWCA_IMIE) VALUES (WYKONAWCA_IMIE) INTO PIOSENKI_T (PIOSENKA) VALUES (PIOSENKA) SELECT 'Enrique Iglesias' WYKONAWCA_IMIE, 'Be With You' PIOSENKA FROM dual; W tabeli PIOSENKI_T pole WYKONAWCA_ID jest puste. 18
Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Widzimy, że do tabeli wykonawców możemy wprowadzać ile razy chcemy imię i nazwisko tego samego wykonawcy. W celu eliminacji tego zjawiska i przejmując, że - nie istnieje dwóch wykonawców z tym samym imieniem i nazwiskiem - wprowadzamy zawsze w jednakowy sposób imię i nazwisko wykonawcy możemy wprowadzić następujące zmiany. Wyłączamy kontrolę referencyjnej integralności w celu usunięcia wierszy ALTER TABLE WYKONAWCA_T DROP PRIMARY KEY CASCADE; Usuwamy danych o wykonawcy 'Enrique Iglesias z tabeli WYKONAWCY_T. DELETE FROM WYKONAWCA_T WHERE WYKONAWCA_IMIE='Enrique Iglesias ; Wstawianie danych do tabeli, zależnej od tabeli referencyjnej W celu zmiany klucza głównego z WYKONAWCA_ID na WYKONAWCA_IMIE należy ponownie utworzyć tabelę WYKONAWCA_T. Używamy tabeli tymczasowej WYKONAWCA_1. CREATE TABLE WYKONAWCA_1 as SELECT * FROM WYKONAWCA_T; DROP TABLE WYKONAWCA_T; CREATE TABLE PIL.WYKONAWCA_T (WYKONAWCA_ID INTEGER, WYKONAWCA_IMIE VARCHAR2(80), CONSTRAINT PK_WYKONAWCA_T PRIMARY KEY (WYKONAWCA_IMIE )); 19
Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Należy ponownie utworzyć wyzwalacza CREATE OR REPLACE TRIGGER WYK_T_trigger before insert on WYKONAWCA_T for each row begin select SEQ_WYKONAWCA_T.nextval into :new.wykonawca_id from dual; end; Wstawianie danych do tabeli, zależnej od tabeli referencyjnej Wprowadzamy nie powtarzające się dane do tabeli WYKONAWCA_T, które pobieramy z tabeli WYKONAWCA_1 INSERT INTO WYKONAWCA_T (WYKONAWCA_IMIE) SELECT DISTINCT WYKONAWCA_IMIE FROM WYKONAWCA_1; 5 rows inserted Przy ponownej próbie wprowadzania danych z tabeli do tabeli WYKONAWCA_T, które pobieramy z tabeli WYKONAWCA_1 otrzymujemy komunikat BŁĄD w linii 1: ORA-00001: naruszono więzy unikatowe (PIL.PK_WYKONAWCA_T) 20
Wstawianie danych do tabeli, zależnej od tabeli referencyjnej ALTER TABLE PIOSENKI_T ADD ( CONSTRAINT WYKONAWCA_ID FOREIGN KEY (WYKONAWCA_ID) REFERENCES WYKONAWCA_T (WYKONAWCA_ID) DISABLE); DELETE FROM WYKONAWCA_T WHERE WYKONAWCA_IMIE='Enrique Iglesias' DELETE FROM PIOSENKI_T WHERE PIOSENKA='Be With You'; Ćwiczenie Proszę zrobić model tabel dotyczących lotów. - samoloty; - rejsy - samolot - rejs Tabele samoloty i rejsy będą tabelami słownikowymi, a tabela samolot - rejs będzie powiązana referencyjne z tabelami słownikowymi. Proszę zaprojektować tabele i wprowadzić do nich przykładowe dane oraz zbudować powiązania referencyjne. 21
Dane dotyczące floty samolotów Lp Samolot Model Liczba miejsc 1 Boeing 737-400 170 2 Boeing B777-200 358 3 Boeing B757-200 180 4 Airbus A330-300 305 5 Airbus A300-600 247 6 Aero Alenia ATR-72 66 7 Boeing B777-300 388 8 Boeing B747-300 405 9 FOKKER 100 100 10 Boeing B737-800 189 Na początku lotnisko jest obsługiwane przez pierwszych 5 typów samolotów. Później następne 5 typy samolotów zaczynają latać do danego portu. http://www.747sp.com/explained.asp http://www.thaiair.com/travel_destination_information/our_aircraft http://www.jetonly.com/en/tub.htm 22