Commit 7d0ac1a1 by Patryk Czarnik

Przykładowe bazy danych SQL

parent dee925e5
-- Wykonac jako user postgres (administrator)
-- Pozostale pliki juz jako user alx
DROP DATABASE IF EXISTS hr;
--DROP ROLE IF EXISTS alx;
--CREATE USER alx PASSWORD 'abc123';
CREATE DATABASE hr ENCODING 'utf-8' OWNER alx;
CREATE TABLE regions (
region_id INTEGER CONSTRAINT region_id_nn NOT NULL,
region_name VARCHAR(25),
CONSTRAINT reg_id_pk PRIMARY KEY (region_id)
);
CREATE TABLE countries (
country_id CHAR(2) CONSTRAINT country_id_nn NOT NULL,
country_name VARCHAR(40),
region_id INTEGER,
CONSTRAINT country_c_id_pk PRIMARY KEY (country_id),
CONSTRAINT countr_reg_fk FOREIGN KEY (region_id) REFERENCES regions(region_id)
);
CREATE TABLE locations (
location_id INTEGER,
street_address VARCHAR(40),
postal_code VARCHAR(12),
city VARCHAR(30) CONSTRAINT loc_city_nn NOT NULL,
state_province VARCHAR(25),
country_id CHAR(2),
CONSTRAINT loc_id_pk PRIMARY KEY (location_id),
CONSTRAINT loc_c_id_fk FOREIGN KEY (country_id) REFERENCES countries(country_id)
);
CREATE TABLE departments (
department_id INTEGER,
department_name VARCHAR(100) CONSTRAINT dept_name_nn NOT NULL,
manager_id INTEGER,
location_id INTEGER,
CONSTRAINT dept_id_pk PRIMARY KEY (department_id),
CONSTRAINT dept_loc_fk FOREIGN KEY (location_id) REFERENCES locations (location_id)
) ;
CREATE TABLE jobs (
job_id VARCHAR(10),
job_title VARCHAR(35) CONSTRAINT job_title_nn NOT NULL,
min_salary NUMERIC(8, 2),
max_salary NUMERIC(8, 2),
CONSTRAINT job_id_pk PRIMARY KEY(job_id)
);
CREATE TABLE employees (
employee_id INTEGER,
first_name VARCHAR(25),
last_name VARCHAR(30) CONSTRAINT emp_last_name_nn NOT NULL,
email VARCHAR(30) CONSTRAINT emp_email_nn NOT NULL,
phone_number VARCHAR(20),
hire_date DATE CONSTRAINT emp_hire_date_nn NOT NULL,
job_id VARCHAR(10) CONSTRAINT emp_job_nn NOT NULL,
salary NUMERIC(8, 2),
commission_pct NUMERIC(2, 2),
manager_id INTEGER,
department_id INTEGER,
CONSTRAINT emp_emp_id_pk PRIMARY KEY (employee_id),
CONSTRAINT emp_dept_fk FOREIGN KEY (department_id) REFERENCES departments,
CONSTRAINT emp_job_fk FOREIGN KEY (job_id) REFERENCES jobs (job_id),
CONSTRAINT emp_manager_fk FOREIGN KEY (manager_id) REFERENCES employees,
CONSTRAINT emp_salary_min CHECK (salary > 0),
CONSTRAINT emp_email_uk UNIQUE (email)
);
CREATE TABLE job_history (
employee_id INTEGER CONSTRAINT jhist_employee_nn NOT NULL,
start_date DATE CONSTRAINT jhist_start_date_nn NOT NULL,
end_date DATE CONSTRAINT jhist_end_date_nn NOT NULL,
job_id VARCHAR(10) CONSTRAINT jhist_job_nn NOT NULL,
department_id INTEGER,
CONSTRAINT jhist_date_interval CHECK (end_date > start_date),
CONSTRAINT jhist_emp_id_st_date_pk PRIMARY KEY (employee_id, start_date),
CONSTRAINT jhist_job_fk FOREIGN KEY (job_id) REFERENCES jobs,
CONSTRAINT jhist_emp_fk FOREIGN KEY (employee_id) REFERENCES employees,
CONSTRAINT jhist_dept_fk FOREIGN KEY (department_id) REFERENCES departments
);
COMMENT ON TABLE regions IS 'Regions table that contains region numbers and names. Contains 4 rows; references with the Countries table.';
COMMENT ON COLUMN regions.region_id IS 'Primary key of regions table.';
COMMENT ON COLUMN regions.region_name IS 'Names of regions. Locations are in the countries of these regions.';
COMMENT ON TABLE locations IS
'Locations table that contains specific address of a specific office,
warehouse, and/or production site of a company. Does not store addresses /
locations of customers. Contains 23 rows; references with the
departments and countries tables. ';
COMMENT ON COLUMN locations.location_id IS
'Primary key of locations table';
COMMENT ON COLUMN locations.street_address IS
'Street address of an office, warehouse, or production site of a company.
Contains building number and street name';
COMMENT ON COLUMN locations.postal_code IS
'Postal code of the location of an office, warehouse, or production site
of a company. ';
COMMENT ON COLUMN locations.city IS
'A not null column that shows city where an office, warehouse, or
production site of a company is located. ';
COMMENT ON COLUMN locations.state_province IS
'State or Province where an office, warehouse, or production site of a
company is located.';
COMMENT ON COLUMN locations.country_id IS
'Country where an office, warehouse, or production site of a company is
located. Foreign key to country_id column of the countries table.';
COMMENT ON TABLE departments IS
'Departments table that shows details of departments where employees
work. Contains 27 rows; references with locations, employees, and job_history tables.';
COMMENT ON COLUMN departments.department_id IS
'Primary key column of departments table.';
COMMENT ON COLUMN departments.department_name IS
'A not null column that shows name of a department. Administration,
Marketing, Purchasing, Human Resources, Shipping, IT, Executive, Public
Relations, Sales, Finance, and Accounting. ';
COMMENT ON COLUMN departments.manager_id IS
'Manager_id of a department. Foreign key to employee_id column of employees table. The manager_id column of the employee table references this column.';
COMMENT ON COLUMN departments.location_id IS
'Location id where a department is located. Foreign key to location_id column of locations table.';
COMMENT ON TABLE job_history IS
'Table that stores job history of the employees. If an employee
changes departments within the job or changes jobs within the department,
new rows get inserted into this table with old job information of the
employee. Contains a complex primary key: employee_id+start_date.
Contains 25 rows. References with jobs, employees, and departments tables.';
COMMENT ON COLUMN job_history.employee_id IS
'A not null column in the complex primary key employee_id+start_date.
Foreign key to employee_id column of the employee table';
COMMENT ON COLUMN job_history.start_date IS
'A not null column in the complex primary key employee_id+start_date.
Must be less than the end_date of the job_history table. (enforced by
constraint jhist_date_interval)';
COMMENT ON COLUMN job_history.end_date IS
'Last day of the employee in this job role. A not null column. Must be
greater than the start_date of the job_history table.
(enforced by constraint jhist_date_interval)';
COMMENT ON COLUMN job_history.job_id IS
'Job role in which the employee worked in the past; foreign key to
job_id column in the jobs table. A not null column.';
COMMENT ON COLUMN job_history.department_id IS
'Department id in which the employee worked in the past; foreign key to deparment_id column in the departments table';
COMMENT ON TABLE countries IS
'country table. Contains 25 rows. References with locations table.';
COMMENT ON COLUMN countries.country_id IS
'Primary key of countries table.';
COMMENT ON COLUMN countries.country_name IS
'Country name';
COMMENT ON COLUMN countries.region_id IS
'Region ID for the country. Foreign key to region_id column in the departments table.';
COMMENT ON TABLE jobs IS
'jobs table with job titles and salary ranges. Contains 19 rows.
References with employees and job_history table.';
COMMENT ON COLUMN jobs.job_id IS
'Primary key of jobs table.';
COMMENT ON COLUMN jobs.job_title IS
'A not null column that shows job title, e.g. AD_VP, FI_ACCOUNTANT';
COMMENT ON COLUMN jobs.min_salary IS
'Minimum salary for a job title.';
COMMENT ON COLUMN jobs.max_salary IS
'Maximum salary for a job title';
COMMENT ON TABLE employees IS
'employees table. Contains 107 rows. References with departments,
jobs, job_history tables. Contains a self reference.';
COMMENT ON COLUMN employees.employee_id IS
'Primary key of employees table.';
COMMENT ON COLUMN employees.first_name IS
'First name of the employee. A not null column.';
COMMENT ON COLUMN employees.last_name IS
'Last name of the employee. A not null column.';
COMMENT ON COLUMN employees.email IS
'Email id of the employee';
COMMENT ON COLUMN employees.phone_number IS
'Phone number of the employee; includes country code and area code';
COMMENT ON COLUMN employees.hire_date IS
'Date when the employee started on this job. A not null column.';
COMMENT ON COLUMN employees.job_id IS
'Current job of the employee; foreign key to job_id column of the
jobs table. A not null column.';
COMMENT ON COLUMN employees.salary IS
'Monthly salary of the employee. Must be greater
than zero (enforced by constraint emp_salary_min)';
COMMENT ON COLUMN employees.commission_pct IS
'Commission percentage of the employee; Only employees in sales
department elgible for commission percentage';
COMMENT ON COLUMN employees.manager_id IS
'Manager id of the employee; has same domain as manager_id in
departments table. Foreign key to employee_id column of employees table.
(useful for reflexive joins and CONNECT BY query)';
COMMENT ON COLUMN employees.department_id IS
'Department id where employee works; foreign key to department_id
column of the departments table';
CREATE SEQUENCE locations_seq START WITH 3300 INCREMENT BY 100 MAXVALUE 9900 NO CYCLE;
ALTER TABLE locations ALTER COLUMN location_id SET DEFAULT nextval('locations_seq');
CREATE SEQUENCE departments_seq START WITH 280 INCREMENT BY 10 MAXVALUE 9990 NO CYCLE;
ALTER TABLE departments ALTER COLUMN department_id SET DEFAULT nextval('departments_seq');
CREATE SEQUENCE employees_seq START WITH 207 INCREMENT BY 1 NO CYCLE;
ALTER TABLE employees ALTER COLUMN employee_id SET DEFAULT nextval('employees_seq');
CREATE OR REPLACE VIEW emp_details_view (
employee_id, job_id, manager_id, department_id, location_id, country_id, first_name, last_name, salary, commission_pct, department_name, job_title, city, state_province, country_name, region_name
) AS SELECT e.employee_id,
e.job_id,
e.manager_id,
e.department_id,
d.location_id,
l.country_id,
e.first_name,
e.last_name,
e.salary,
e.commission_pct,
d.department_name,
j.job_title,
l.city,
l.state_province,
c.country_name,
r.region_name
FROM employees e,
departments d,
jobs j,
locations l,
countries c,
regions r
WHERE e.department_id = d.department_id
AND d.location_id = l.location_id
AND l.country_id = c.country_id
AND c.region_id = r.region_id
AND j.job_id = e.job_id;
CREATE INDEX emp_department_ix ON employees(department_id);
CREATE INDEX emp_job_ix ON employees(job_id);
CREATE INDEX emp_manager_ix ON employees(manager_id);
CREATE INDEX emp_name_ix ON employees(last_name, first_name);
CREATE OR REPLACE VIEW szczegoly_pracownika AS
SELECT e.employee_id, e.first_name, e.last_name, e.email, e.phone_number, e.manager_id AS manager_id,
department_id, d.department_name, d.manager_id AS department_manager_id,
location_id, l.street_address, l.city, l.postal_code,
country_id, c.country_name, region_id, r.region_name
FROM employees e
LEFT JOIN jobs j USING (job_id)
LEFT JOIN departments d USING (department_id)
LEFT JOIN locations l USING (location_id)
LEFT JOIN countries c USING (country_id)
LEFT JOIN regions r USING (region_id)
ORDER BY e.employee_id;
CREATE OR REPLACE FUNCTION pensje_dzisiaj() RETURNS void AS $$
DECLARE
pracownik employees%ROWTYPE;
BEGIN
FOR pracownik IN SELECT * FROM employees
LOOP
INSERT INTO salary_history(employee_id, salary, date)
VALUES (pracownik.employee_id, pracownik.salary, current_date);
END LOOP;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE przenies_pracownika(
emp_id employees.employee_id%TYPE,
dep_id employees.department_id%TYPE,
job_id employees.job_id%TYPE
) AS $$
DECLARE
stary employees%ROWTYPE;
data_poczatkowa DATE;
BEGIN
SELECT * FROM employees
WHERE employee_id = emp_id
INTO stary;
IF NOT FOUND THEN
RAISE EXCEPTION 'employee % not found', emp_id;
END IF;
SELECT max(end_date) FROM job_history
WHERE employee_id = emp_id
INTO data_poczatkowa;
IF data_poczatkowa IS NULL
THEN data_poczatkowa := stary.hire_date;
END IF;
INSERT INTO job_history(employee_id, start_date, end_date, job_id, department_id)
VALUES (emp_id, data_poczatkowa, current_date, stary.job_id, stary.department_id);
UPDATE employees SET
department_id = dep_id,
job_id = przenies_pracownika.job_id
WHERE employee_id = emp_id;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION nazwisko_szefa(
emp_id INTEGER
) RETURNS VARCHAR AS $$
DECLARE
wynik VARCHAR;
BEGIN
SELECT first_name || ' ' || last_name
INTO wynik
FROM employees
WHERE employee_id = (SELECT manager_id FROM employees WHERE employee_id = emp_id);
RETURN wynik;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION statystyki_departamentu(
dep_id IN INTEGER,
emp_count OUT INTEGER,
avg_salary OUT NUMERIC,
min_salary OUT NUMERIC,
max_salary OUT NUMERIC
) RETURNS record AS $$
BEGIN
SELECT count(employee_id), avg(salary), min(salary), max(salary)
INTO emp_count, avg_salary, min_salary, max_salary
FROM employees
WHERE department_id = dep_id;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION zarabiajacy_wiecej_niz(emp_id INTEGER)
RETURNS SETOF employees AS $$
DECLARE
the_salary NUMERIC;
BEGIN
SELECT salary INTO the_salary FROM employees WHERE employee_id = emp_id;
RETURN QUERY
SELECT * FROM employees WHERE salary > the_salary ORDER BY salary ASC;
END
$$ LANGUAGE plpgsql;
DROP FUNCTION zarabiajacy_wiecej_niz;
DROP FUNCTION statystyki_departamentu;
DROP FUNCTION nazwisko_szefa;
DROP PROCEDURE przenies_pracownika;
DROP FUNCTION pensje_dzisiaj;
DROP INDEX emp_name_ix;
DROP INDEX emp_manager_ix;
DROP INDEX emp_job_ix;
DROP INDEX emp_department_ix;
DROP VIEW emp_details_view;
DROP VIEW szczegoly_pracownika;
DROP TABLE job_history;
DROP TABLE employees;
DROP TABLE jobs;
DROP TABLE departments;
DROP TABLE locations;
DROP TABLE countries;
DROP TABLE regions;
DROP SEQUENCE employees_seq;
DROP SEQUENCE departments_seq;
DROP SEQUENCE locations_seq;
set PG_HOME=C:\Program Files\PostgreSQL\17
set Path=%Path%;%PG_HOME%\bin
psql -h localhost -p 5432 -d postgres -U postgres -f 0-admin.sql
psql -h localhost -p 5432 -d hr -U alx -f wgraj_hr.sql
pause
SET client_encoding = 'UTF8';
\i 1-hr-create-tabele.sql
\i 2-hr-create-dodatki.sql
\i 3-hr-insert.sql
\i 4-hr-moje-dodatki.sql
-- DROP TABLE sprzedaz;
CREATE TABLE sprzedaz (
lp SERIAL PRIMARY KEY,
data DATE,
miasto VARCHAR(100),
sklep VARCHAR(100),
kategoria VARCHAR(100),
towar VARCHAR(100),
cena NUMERIC(10,2),
sztuk INTEGER
);
-- Importujemy dane z CSV za pomocą import data w pgadmin4
-- To jest graficzny interfejs do polecenia COPY
-- https://www.postgresql.org/docs/current/sql-copy.html
-- Kopiowanie z pliku przez psql zadziałało w takie wersji:
-- \copy sprzedaz(data,miasto,sklep,kategoria,towar,cena,sztuk) from 'sprzedaz.csv' csv delimiter ',' header
-- Lista kolumn nie byłaby konieczna, gdyby tabela nie miała kolumny lp (której nie ma w pliku)
SELECT * FROM sprzedaz;
SELECT count(*) FROM sprzedaz;
This source diff could not be displayed because it is too large. You can view the blob instead.
CREATE TABLE skocznie (
id_skoczni integer primary key,
miasto text,
kraj_s text,
nazwa text,
k integer,
sedz integer
);
CREATE TABLE trenerzy (
kraj char(3) primary key,
imie_t text,
nazwisko_t text,
data_ur_t date
);
CREATE TABLE zawodnicy (
id_skoczka integer primary key,
imie text,
nazwisko text,
kraj char(3),
data_ur date,
wzrost integer,
waga integer
);
CREATE TABLE zawody (
id_zawodow integer primary key,
id_skoczni integer,
data date
);
INSERT INTO skocznie VALUES (1, 'Zakopane', 'POL', 'Wielka Krokiew', 120, 134);
INSERT INTO skocznie VALUES (2, 'Garmisch-Partenkirchen', 'GER', 'Wielka Skocznia Olimpijska', 115, 125);
INSERT INTO skocznie VALUES (4, 'Oberstdorf', 'GER', 'Skocznia Heiniego Klopfera', 185, 211);
INSERT INTO skocznie VALUES (3, 'Oberstdorf', 'GER', 'Grosse Schattenberg', 120, 134);
INSERT INTO skocznie VALUES (5, 'Willingen', 'GER', 'Grosse Muhlenkopfschanze', 130, 145);
INSERT INTO skocznie VALUES (6, 'Kuopio', 'FIN', 'Puijo', 120, 131);
INSERT INTO skocznie VALUES (7, 'Lahti', 'FIN', 'Salpausselka', 116, 128);
INSERT INTO skocznie VALUES (8, 'Trondheim', 'NOR', 'Granasen', 120, 132);
INSERT INTO trenerzy VALUES ('AUT', 'Alexander', 'Pointner', NULL);
INSERT INTO trenerzy VALUES ('FIN', 'Tommi', 'Nikunen', NULL);
INSERT INTO trenerzy VALUES ('NOR', 'Mika', 'Kojonkoski', '1963-04-19');
INSERT INTO trenerzy VALUES ('POL', 'Heinz', 'Kuttin', '1971-01-05');
INSERT INTO trenerzy VALUES ('GER', 'Wolfang', 'Steiert', '1963-04-19');
INSERT INTO trenerzy VALUES ('JPN', 'Hirokazu', 'Yagi', NULL);
INSERT INTO zawodnicy VALUES (1, 'Adam', 'MAŁYSZ', 'POL', '1977-12-03', 169, 60);
INSERT INTO zawodnicy VALUES (2, 'Marcin', 'BACHLEDA', 'POL', '1982-09-04', 166, 56);
INSERT INTO zawodnicy VALUES (3, 'Robert', 'MATEJA', 'POL', '1974-10-05', 180, 63);
INSERT INTO zawodnicy VALUES (4, 'Alexander', 'HERR', 'GER', '1978-10-04', 173, 65);
INSERT INTO zawodnicy VALUES (5, 'Stephan', 'HOCKE', 'GER', '1983-10-20', 178, 59);
INSERT INTO zawodnicy VALUES (6, 'Martin', 'SCHMITT', 'GER', '1978-01-29', 181, 64);
INSERT INTO zawodnicy VALUES (7, 'Michael', 'UHRMANN', 'GER', '1978-09-09', 184, 64);
INSERT INTO zawodnicy VALUES (8, 'Georg', 'SPAETH', 'GER', '1981-02-24', 187, 68);
INSERT INTO zawodnicy VALUES (9, 'Matti', 'HAUTAMAEKI', 'FIN', '1981-07-14', 174, 57);
INSERT INTO zawodnicy VALUES (10, 'Tami', 'KIURU', 'FIN', '1976-09-13', 183, 59);
INSERT INTO zawodnicy VALUES (11, 'Janne', 'AHONEN', 'FIN', '1977-05-11', 184, 67);
INSERT INTO zawodnicy VALUES (12, 'Martin', 'HOELLWARTH', 'AUT', '1974-04-13', 182, 67);
INSERT INTO zawodnicy VALUES (13, 'Thomas', 'MORGENSTERN', 'AUT', '1986-10-30', 174, 57);
INSERT INTO zawodnicy VALUES (15, 'Tommy', 'INGEBRIGTSEN', 'NOR', '1977-08-08', 179, 56);
INSERT INTO zawodnicy VALUES (16, 'Bjoern-Einar', 'ROMOEREN', 'NOR', '1981-04-01', 182, 63);
INSERT INTO zawodnicy VALUES (17, 'Roar', 'LJOEKELSOEY', 'NOR', '1976-05-31', 175, 62);
INSERT INTO zawodnicy VALUES (14, 'Alan', 'ALBORN', 'USA', '1980-12-13', 177, 57);
INSERT INTO zawody VALUES (1, 1, '2007-01-23');
INSERT INTO zawody VALUES (2, 7, '2006-11-15');
INSERT INTO zawody VALUES (3, 3, '2006-12-26');
SELECT * FROM zawodnicy;
SELECT * FROM zawodnicy FULL JOIN trenerzy USING(kraj);
/* To jest komentarz blokowy
czyli taki, który może rozciągać się na wiele linii. */
-- To jest komentarz jednolinijkowy.
-- Zazwyczaj będziemy pisać komentarze przed instrukcją, którą opisujemy
SELECT * FROM employees;
--* Kwestia wielkości liter w SQL *--
-- Wersja dla PostgreSQL, ze wzmiankami na temat innych baz.
-- Jeśli chodzi o słowa kluczowe SQL, np. SELECT, to wielkość liter nie ma znaczenia:
SELECT * FROM employees;
select * from employees;
Select * fRoM employees;
-- Jeśli chodzi o nazwy tabel, kolumn itd, to sprawa jest bardziej skomplikowana...
-- Gdy używamy nazw niecytowanych, to wielkość liter w praktyce nie ma znaczenia:
SELECT * FROM employees; -- OK
SELECT * FROM Employees; -- OK
select * from EMPLOYEES; -- OK
/* Dodatkowy przykład dla tabeli utworzonej przez okno:
-- nie działa:
SELECT * FROM Miasta;
INSERT INTO Miasta(NazwaMiasta) VALUES ('Warszawa');
-- działa:
INSERT INTO "Miasta"("NazwaMiasta") VALUES ('Warszawa');
SELECT * FROM "Miasta";
*/
-- Ale istnieją też nazwy "cytowane" i w takich nazwach wielkość liter ma znaczenie:
SELECT * FROM "Employees"; -- nie działa
select * from "EMPLOYEES"; -- nie działa w PostgreSQL, działa w Oracle
SELECT * FROM "employees"; -- działa w PostgreSQL, nie działa w Oracle
/* Bardziej szczegółowo.
* PostgreSQL zamienia wszystkie nazwy pisane bez cudzysłowów na nazwy pisane małymi literami.
* Oracle zamienia wszystkie nazwy pisane bez cudzysłowów na nazwy pisane WIELKIMI LITERAMI (i to jest zgodne ze standardem SQL).
* Dzieje się to zarówno podczas definiowania obiektów bazodanowych (tabele i ich kolumny, widoki, sekwencje itd.),
* jak i podczas odwoływania się do nich.
* Uwaga! "Nazwa cytowana" to zupełnie coś innego niż 'napis'.
*/
-- Gdy zadajemy zapytanie np.
SELECT First_Name, last_name FROM emPloyees;
-- to Postgres wykona tak naprawdę
SELECT "first_name", "last_name" FROM "employees";
-- Gdy podczas tworzenia tabeli wpisuje nazwy bez cudzysłowów,
-- to wewnętrznie będą użyte małe litery.
CREATE TABLE TEst1(kolumna varchar(10));
INSERT INTO tEST1 VALUES('ABC');
-- Teraz prawdziwa nazwa tej tabeli to test1
-- Jeśli do odczytu używam nazwy w cudzysłowach, to muszę użyć tej wielkości liter.
SELECT * FROM "tEst1"; -- źle
SELECT * FROM "TEST1"; -- źle
SELECT * FROM "test1"; -- OK
-- Gdy używam nazw niecytowanych, to PostgreSQL zamienia je na małe litery i jest OK
SELECT * FROM test1;
SELECT * FROM tEst1;
SELECT * FROM TEST1;
-- Morał: Konsekwentnie używając nazw niecytowanych postępujemy prawidłowo.
-- Gdybym podczas tworzenia tabeli użył nazwy "cytowanej",
-- to utworzona tabela będzie miała dokładnie taką wielkość liter, jakiej użyłem.
CREATE TABLE "TesT2"("KOLUMNA" varchar(10));
INSERT INTO "TesT2" VALUES('XYZ');
-- Teraz jedynym sposobem poprawnego odwołania się do tej tabeli jest użycie nazwy cytowanej "TesT2"
SELECT * FROM "TesT2";
-- Bo gdy użyjemy nazwy niecytowanej, to niezależnie od wielkości liter, zostanie ona zamieniona na DUZE LITERY
SELECT * FROM test2; -- źle
SELECT * FROM TesT2; -- źle bo Postgres szuka tabeli "test2", a tabela nazywa się "TesT2"
-- Nazwy "cytowane" mogą się przydać, jeśli chcemy zachować wielkość liter, użyć nazwy zarezerwowanej jako słowo kluczowe, użyć w nazwie znaków specjalnych, w tym spacji.
CREATE TABLE "Roczne zarobki" ("wartość w złotych" numeric(10,2));
INSERT INTO "Roczne zarobki" VALUES(1234.56);
SELECT * FROM "Roczne zarobki";
DROP TABLE test1;
DROP TABLE "TesT2";
DROP TABLE "Roczne zarobki";
--* ORDER BY - sortowanie wyników *--
SELECT * FROM employees
ORDER BY salary;
-- czym może być kryterium sortowania?
-- można podać nazwę kolumny, można wyrażenie
SELECT * FROM employees
ORDER BY length(last_name);
-- Jeśli w SELECT tworzymy kolumnę i nadaliśmy jej alias, to w OREDER BY można podać ten alias
SELECT first_name || ' ' || last_name AS "Kto"
, 12 * salary AS "Roczne zarobki"
FROM employees
ORDER BY "Roczne zarobki";
SELECT first_name || ' ' || last_name AS "Kto"
, 12 * salary AS roczne
FROM employees
ORDER BY roczne;
-- Można po prostu podać nr kolumny wynikowej, po której sortujemy, licząc od 1
SELECT first_name, last_name, 12 * salary AS "Roczne zarobki", manager_id
FROM employees
ORDER BY 3;
-- Domyślnie kolejność jest rosnąca
-- aby była malejąca, za kryterium sortowania należy dopisać DESC
SELECT * FROM employees
ORDER BY salary DESC;
-- Analogicznie istnieje słowo ASC , ale raczej się go nie używa, bo kolejność rosnąca jest domyślna
SELECT * FROM employees
ORDER BY salary ASC;
-- Może być kilka kryteriów sortowania
-- po pierwsze wg nazwiska, po drugie wg imienia
SELECT * FROM employees
ORDER BY last_name, first_name;
-- W przypadku kilku kryteriów, każde z nich domyślnie jest rosnąco,
-- przy każdym malejącym trzeba osobno pisać DESC
-- Tutaj: rosnąco wg miasta, nast malejąco wg pensji, rosnąco wg nazwisko i imion
-- Zamiast tabeli employees, uzywamy "widoku" emp_detils_view, który łaczy dane z kilku tabel.
SELECT employee_id, first_name, last_name, job_title, salary, department_name, city
FROM emp_details_view
ORDER BY city, salary DESC, last_name, first_name;
SELECT first_name || ' ' || last_name AS "Osoba"
, 12 * salary AS "Roczne zarobki"
, extract(YEAR FROM hire_date) AS "Rok zatrudnienia"
FROM employees
ORDER BY 3, 2 DESC;
-- Zobaczmy gdzie znajdą się NULL-e podczas sortowania.
-- Domyślnie NULLe są traktowane tak, jakby były większe od zwykłych wartości.
-- (tak jest w PostgreSQL i Oracle, zgodnie ze standardem; w MySQL i SQLite jest odwrotnie)
-- Przy ASC pójdą na koniec
SELECT * FROM employees
ORDER BY commission_pct;
-- Przy DESC pójdą na początek
SELECT * FROM employees
ORDER BY commission_pct DESC;
-- Gdy na końcu kryterium sortowania dopiszemy NULLS FIRST lub NULLS LAST, sami określamy ich położenie
SELECT * FROM employees
ORDER BY commission_pct DESC NULLS LAST;
SELECT * FROM employees
ORDER BY commission_pct ASC NULLS FIRST;
SELECT * FROM employees
ORDER BY commission_pct NULLS FIRST;
--* LIMIT / OFFSET *--
-- W różnych bazach danych istnieją różne sposoby, aby zapytanie zwróciło tylko pierwszy rekord / X pierwszych rekordów.
-- W PostgreSQL od dawna służy do tego dodatkowa klauzula LIMIT / OFFSET dopsywana na końcu zapytania (za klauzulą ORDER BY).
-- To zwraca pierwsze 10 rekordów
SELECT * FROM employees
ORDER BY salary DESC
LIMIT 10;
-- Używając OFFSET możemy pobierać następne grupy, np. "trzecią dziesiątkę"
-- OFFSET pomija określoną liczbę rekordów i zaczyna zwracać pewną liczbę następnych
SELECT * FROM employees
ORDER BY salary DESC
LIMIT 10 OFFSET 20;
-- Od rekordu 91 do samego końca:
SELECT * FROM employees
ORDER BY salary DESC
OFFSET 90;
-- Używanie LIMIT / OFFSET bez sortowania jest poprawne technicznie,
-- ale ma mały sens logiczny.
-- Chyba, że w technicznym celu pobrania "próbki danych"...
SELECT * FROM employees
LIMIT 5;
-- Potecjalnym zastosowaniem jest odczyt jednego rekordu o wartości maksymalnej lub minimalnej.
SELECT * FROM employees
ORDER BY salary
LIMIT 1;
-- Alternatywnym rozwiązaniem jest użycie podzapytania:
SELECT * FROM employees
WHERE salary = (SELECT min(salary) FROM employees);
-- Ale w konstrukcji LIMIT kryje się niejednoznaczność w sytuacji, gdy istnieje wiele rekordów o jednakowej wartości.
-- Przykład: istnieją 2 osoby zarabiające 17 tys. Zajmują ex-equo 2 miejsce w firmie.
SELECT * FROM employees
ORDER BY salary DESC
LIMIT 2;
--* Konstrukcja FETCH *--
-- Standard SQL wprowadził rozwiązanie "FETCH", które jest obecnie obsługiwane m.in. przez Oracle i PostgreSQL.
-- W porównaniu do LIMIT/OFFSET dodatkową możliwością jest pobieranie wszystkich rekordów, których wartość
-- znajduje się na granicy - dopisek "WITH TIES".
-- Służy do pobrania tylko określonej liczby rekordów. Umieszcza się ją na samym końcu.
SELECT * FROM employees
FETCH NEXT 10 ROWS ONLY;
-- Jeśli wyniki nie są sortowane, to nie mamy gwarancji, które dokładnie rekordy odczytamy w ten sposób.
-- To podejście może być przydatne, gdy chcemy tylko sprawdzić jak wyglądają dane.
-- Najczęściej jednak FETCH używa się w połączeniu z ORDER BY.
-- To zapytanie zwraca 10 najbogatszych osób w firmie
SELECT * FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
-- Kwestia równych wartości...
-- To zapytanie ma zwrócić 2 najbogatsze osoby w firmie
SELECT * FROM employees
ORDER BY salary DESC
FETCH FIRST 2 ROWS ONLY;
-- Aby odczytać wszystkich, którzy maja tyle samo, można użyc WITH TIES
-- Postgres będzie wiedział, ze ma doczytać kolejne rekordy z taka sama wartością, po której sortowaliśmy
SELECT * FROM employees
ORDER BY salary DESC
FETCH FIRST 2 ROWS WITH TIES;
-- na pewno zwróci jeden rekord
SELECT * FROM employees
ORDER BY salary
FETCH FIRST 1 ROW ONLY;
-- można pominąć liczbę 1, jeśli chcemy odczytać jeden rekord
SELECT * FROM employees
ORDER BY salary
FETCH FIRST ROW ONLY;
-- teoretycznie może być więcej osób, które zarabiają minimum - WITH TIES zwróciłoby wszystkie
SELECT * FROM employees
ORDER BY salary
FETCH FIRST ROW WITH TIES;
-- Na działanie nie wpływa to, czy napiszemy ROW czy ROWS ani FIRST czy NEXT
-- To też działa, chociaż głupio wygląda:
SELECT * FROM employees
ORDER BY salary
FETCH NEXT ROWS ONLY;
-- Jeśli dodamy jeszcze opcję OFFSET, to zaczniemy pobierać rekordy nie od pierwszego, tylko od N-tego
-- Wtedy bardziej czytelne będzie użycie słowa NEXT zamiast FIRST, chociaż nie wpływa to na działanie.
-- Właśnie tego typu konstrukcji używają programiści do realizacji "stronicowania".
-- rekordy od 21 do 30
SELECT * FROM employees
ORDER BY salary DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;
-- W PostgreSQL nie ma FETCH NEXT 20 PERCENT
-- diagramy:
-- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html
--* Polecenia modyfikacji danych (DML) - podstawy *--
/* DML - Data Manipulation Language.
Część standardu SQL odpowiedzialna za pracę z rekordami / z konkretnymi danymi.
Teoretycznie w skład DML wchodzi również polecenie SELECT.
W praktyce, gdy potocznie mówimy "DML", to myślimy o poleceniach INSERT UPDATE DELETE
DDL - Data Definition L. - CREATE, ALTER, DROP
DCL - GRANT / REVOKE
*/
-- INSERT - wstawia zupełnie nowy rekord do tabeli (takie id nie może wcześniej występować)
INSERT INTO countries(country_id, country_name, region_id)
VALUES('PL', 'Poland', 10);
-- Gdy podajemy nazwy kolumn do wstawienia, mamy prawo podać je w zmienionej kolejności lub niektóre pominąć.
-- Dla pominiętych przyjmowana jest wartość domyślna z definicji tabeli lub NULL.
-- (Można też jawnie wpisać DEFAULT lub NULL w miejsce wartości.)
INSERT INTO countries(country_name, country_id)
VALUES('San Escobar', 'Se');
-- Jeśli za tabelą nie podamy nazw kolumn, musimy wstawić wszystkie w tej samej kolejności jak w definicji tabeli.
INSERT INTO countries
VALUES('CZ', 'Czech Republic', 10);
-- Specyfika Postgresa: w jednym INSERT można podać wiele zestawów wartości, rozdzielając przecinkiem
INSERT INTO countries(country_id, country_name, region_id)
VALUES ('CR', 'Croatia', 10)
, ('SR', 'Serbia', 10);
SELECT * FROM countries ORDER BY country_id;
-- UPDATE - modyfikacja istniejących danych
UPDATE countries SET country_name = 'Czechia' WHERE country_id = 'CZ';
-- Przykład zmiany w wielu rekordach: programistom dajemy podwyżkę 10 tys. i ustawiamy prowizję na 10%
-- Nowa wartość może zależeć od dotychczasowej.
UPDATE employees SET salary = salary + 10000, commission_pct = 0.1 WHERE job_id = 'IT_PROG';
SELECT * FROM employees ORDER BY employee_id;
-- Cofnięcie tych zmian.
-- UPDATE employees SET salary = salary - 10000, comission_pct = NULL WHERE job_id = 'IT_PROG';
UPDATE employees SET salary =
CASE
WHEN salary <= 10000
THEN salary + 5000
ELSE salary * 1.25
END;
-- DELETE - usunięcie rekordów wskazanych za pomocą warunku WHERE
DELETE FROM countries WHERE country_id IN ('PL', 'CZ', 'Se', 'CR', 'SR');
-- Uwaga: Brak WHERE spowoduje usunięcie wszystkich rekordów. Uwaga na zaznaczanie.
-- Istnieje też TRUNCATE tabela; -- także usuwa wszystkie rerkody, ale moze działać szybciej
-- Gdyby do tych rekordów odnosiły się rekordy z innych tabel, to TRUCATE tabela CASCADE
-- usunie rekordy także z tych innych tabel.
-- RESTART IDENTITY zrestartuje numerację w kolumnach typu SERIAL
-- Przykład ilustrujący zasadę działania polecenia MERGE.
-- W tabeli rejestr_pracy zbieramy informacje o tym, ile godzin przepracował dany pracownik
-- identyfikowany za pomocą id.
CREATE TABLE rejestr_pracy (
id INTEGER PRIMARY KEY,
suma_godzin NUMERIC(6, 1) NOT NULL
);
TRUNCATE rejestr_pracy;
-- Wstawiam pierwsze rekordy.
-- Pracownik nr 1 i 2 istnieją i mają już jakieś godziny
-- Pracowni nr 3 nie istnieje
INSERT INTO rejestr_pracy VALUES (1, 10);
INSERT INTO rejestr_pracy VALUES (2, 10);
SELECT * FROM rejestr_pracy;
-- Za pomocą MERGE będziemy chcieli teraz zwiększyć liczbę godzin o 5 pracownikom 2 i 3.
-- Przy czym pracownik nr 2 już istnieje, i dla mniego trzeba wykonać UPDATE
-- a pracownik nr 3 nie istnieje, i dla niego INSERT
MERGE INTO rejestr_pracy rp
USING (SELECT 2 AS id, 5 AS zmiana
UNION ALL
SELECT 3 AS id, 5 AS zmiana) src
ON (rp.id = src.id)
WHEN MATCHED THEN UPDATE
SET suma_godzin = rp.suma_godzin + src.zmiana
WHEN NOT MATCHED THEN INSERT
VALUES (src.id, src.zmiana);
SELECT * FROM rejestr_pracy;
-- Przykład podzapytania w UPDATE.
-- Wykonuję w transakcji, którą później anuluję.
UPDATE employees e
SET salary = salary + (SELECT 10 * count(*) FROM employees WHERE salary >= e.salary)
WHERE job_id = (SELECT job_id FROM jobs WHERE job_title = 'Programmer');
SELECT * FROM employees ORDER BY employee_id;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment