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);
SET client_encoding = 'UTF8';
BEGIN TRANSACTION;
INSERT INTO regions VALUES
( 1 , 'Europe'
);
INSERT INTO regions VALUES
( 2 , 'Americas'
);
INSERT INTO regions VALUES
( 3 , 'Asia'
);
INSERT INTO regions VALUES
( 4 , 'Middle East and Africa'
);
INSERT INTO countries VALUES
( 'IT' , 'Italy' , 1
);
INSERT INTO countries VALUES
( 'JP' , 'Japan' , 3
);
INSERT INTO countries VALUES
( 'US' , 'United States of America' , 2
);
INSERT INTO countries VALUES
( 'CA' , 'Canada' , 2
);
INSERT INTO countries VALUES
( 'CN' , 'China' , 3
);
INSERT INTO countries VALUES
( 'IN' , 'India' , 3
);
INSERT INTO countries VALUES
( 'AU' , 'Australia' , 3
);
INSERT INTO countries VALUES
( 'ZW' , 'Zimbabwe' , 4
);
INSERT INTO countries VALUES
( 'SG' , 'Singapore' , 3
);
INSERT INTO countries VALUES
( 'UK' , 'United Kingdom' , 1
);
INSERT INTO countries VALUES
( 'FR' , 'France' , 1
);
INSERT INTO countries VALUES
( 'DE' , 'Germany' , 1
);
INSERT INTO countries VALUES
( 'ZM' , 'Zambia' , 4
);
INSERT INTO countries VALUES
( 'EG' , 'Egypt' , 4
);
INSERT INTO countries VALUES
( 'BR' , 'Brazil' , 2
);
INSERT INTO countries VALUES
( 'CH' , 'Switzerland' , 1
);
INSERT INTO countries VALUES
( 'NL' , 'Netherlands' , 1
);
INSERT INTO countries VALUES
( 'MX' , 'Mexico' , 2
);
INSERT INTO countries VALUES
( 'KW' , 'Kuwait' , 4
);
INSERT INTO countries VALUES
( 'IL' , 'Israel' , 4
);
INSERT INTO countries VALUES
( 'DK' , 'Denmark' , 1
);
INSERT INTO countries VALUES
( 'HK' , 'HongKong' , 3
);
INSERT INTO countries VALUES
( 'NG' , 'Nigeria' , 4
);
INSERT INTO countries VALUES
( 'AR' , 'Argentina' , 2
);
INSERT INTO countries VALUES
( 'BE' , 'Belgium' , 1
);
INSERT
INTO locations VALUES
(
1000 ,
'1297 Via Cola di Rie' ,
'00989' ,
'Roma' ,
NULL ,
'IT'
);
INSERT
INTO locations VALUES
(
1100 ,
'93091 Calle della Testa' ,
'10934' ,
'Venice' ,
NULL ,
'IT'
);
INSERT
INTO locations VALUES
(
1200 ,
'2017 Shinjuku-ku' ,
'1689' ,
'Tokyo' ,
'Tokyo Prefecture' ,
'JP'
);
INSERT
INTO locations VALUES
(
1300 ,
'9450 Kamiya-cho' ,
'6823' ,
'Hiroshima' ,
NULL ,
'JP'
);
INSERT
INTO locations VALUES
(
1400 ,
'2014 Jabberwocky Rd' ,
'26192' ,
'Southlake' ,
'Texas' ,
'US'
);
INSERT
INTO locations VALUES
(
1500 ,
'2011 Interiors Blvd' ,
'99236' ,
'South San Francisco' ,
'California' ,
'US'
);
INSERT
INTO locations VALUES
(
1600 ,
'2007 Zagora St' ,
'50090' ,
'South Brunswick' ,
'New Jersey' ,
'US'
);
INSERT
INTO locations VALUES
(
1700 ,
'2004 Charade Rd' ,
'98199' ,
'Seattle' ,
'Washington' ,
'US'
);
INSERT
INTO locations VALUES
(
1800 ,
'147 Spadina Ave' ,
'M5V 2L7' ,
'Toronto' ,
'Ontario' ,
'CA'
);
INSERT
INTO locations VALUES
(
1900 ,
'6092 Boxwood St' ,
'YSW 9T2' ,
'Whitehorse' ,
'Yukon' ,
'CA'
);
INSERT
INTO locations VALUES
(
2000 ,
'40-5-12 Laogianggen' ,
'190518' ,
'Beijing' ,
NULL ,
'CN'
);
INSERT
INTO locations VALUES
(
2100 ,
'1298 Vileparle (E)' ,
'490231' ,
'Bombay' ,
'Maharashtra' ,
'IN'
);
INSERT
INTO locations VALUES
(
2200 ,
'12-98 Victoria Street' ,
'2901' ,
'Sydney' ,
'New South Wales' ,
'AU'
);
INSERT
INTO locations VALUES
(
2300 ,
'198 Clementi North' ,
'540198' ,
'Singapore' ,
NULL ,
'SG'
);
INSERT
INTO locations VALUES
(
2400 ,
'8204 Arthur St' ,
NULL ,
'London' ,
NULL ,
'UK'
);
INSERT
INTO locations VALUES
(
2500 ,
'Magdalen Centre, The Oxford Science Park' ,
'OX9 9ZB' ,
'Oxford' ,
'Oxford' ,
'UK'
);
INSERT
INTO locations VALUES
(
2600 ,
'9702 Chester Road' ,
'09629850293' ,
'Stretford' ,
'Manchester' ,
'UK'
);
INSERT
INTO locations VALUES
(
2700 ,
'Schwanthalerstr. 7031' ,
'80925' ,
'Munich' ,
'Bavaria' ,
'DE'
);
INSERT
INTO locations VALUES
(
2800 ,
'Rua Frei Caneca 1360 ' ,
'01307-002' ,
'Sao Paulo' ,
'Sao Paulo' ,
'BR'
);
INSERT
INTO locations VALUES
(
2900 ,
'20 Rue des Corps-Saints' ,
'1730' ,
'Geneva' ,
'Geneve' ,
'CH'
);
INSERT
INTO locations VALUES
(
3000 ,
'Murtenstrasse 921' ,
'3095' ,
'Bern' ,
'BE' ,
'CH'
);
INSERT
INTO locations VALUES
(
3100 ,
'Pieter Breughelstraat 837' ,
'3029SK' ,
'Utrecht' ,
'Utrecht' ,
'NL'
);
INSERT
INTO locations VALUES
(
3200 ,
'Mariano Escobedo 9991' ,
'11932' ,
'Mexico City' ,
'Distrito Federal,' ,
'MX'
);
INSERT INTO departments VALUES
( 10 , 'Administration' , 200 , 1700
);
INSERT INTO departments VALUES
( 20 , 'Marketing' , 201 , 1800
);
INSERT INTO departments VALUES
( 30 , 'Purchasing' , 114 , 1700
);
INSERT INTO departments VALUES
( 40 , 'Human Resources' , 203 , 2400
);
INSERT INTO departments VALUES
( 50 , 'Shipping' , 121 , 1500
);
INSERT INTO departments VALUES
( 60 , 'IT' , 103 , 1400
);
INSERT INTO departments VALUES
( 70 , 'Public Relations' , 204 , 2700
);
INSERT INTO departments VALUES
( 80 , 'Sales' , 145 , 2500
);
INSERT INTO departments VALUES
( 90 , 'Executive' , 100 , 1700
);
INSERT INTO departments VALUES
( 100 , 'Finance' , 108 , 1700
);
INSERT INTO departments VALUES
( 110 , 'Accounting' , 205 , 1700
);
INSERT INTO departments VALUES
( 120 , 'Treasury' , NULL , 1700
);
INSERT INTO departments VALUES
( 130 , 'Corporate Tax' , NULL , 1700
);
INSERT INTO departments VALUES
( 140 , 'Control And Credit' , NULL , 1700
);
INSERT INTO departments VALUES
( 150 , 'Shareholder Services' , NULL , 1700
);
INSERT INTO departments VALUES
( 160 , 'Benefits' , NULL , 1700
);
INSERT INTO departments VALUES
( 170 , 'Manufacturing' , NULL , 1700
);
INSERT INTO departments VALUES
( 180 , 'Construction' , NULL , 1700
);
INSERT INTO departments VALUES
( 190 , 'Contracting' , NULL , 1700
);
INSERT INTO departments VALUES
( 200 , 'Operations' , NULL , 1700
);
INSERT INTO departments VALUES
( 210 , 'IT Support' , NULL , 1700
);
INSERT INTO departments VALUES
( 220 , 'NOC' , NULL , 1700
);
INSERT INTO departments VALUES
( 230 , 'IT Helpdesk' , NULL , 1700
);
INSERT INTO departments VALUES
( 240 , 'Government Sales' , NULL , 1700
);
INSERT INTO departments VALUES
( 250 , 'Retail Sales' , NULL , 1700
);
INSERT INTO departments VALUES
( 260 , 'Recruiting' , NULL , 1700
);
INSERT INTO departments VALUES
( 270 , 'Payroll' , NULL , 1700
);
INSERT INTO jobs VALUES
( 'AD_PRES' , 'President' , 20000 , 40000
);
INSERT
INTO jobs VALUES
(
'AD_VP' ,
'Administration Vice President' ,
15000 ,
30000
);
INSERT
INTO jobs VALUES
(
'AD_ASST' ,
'Administration Assistant' ,
3000 ,
6000
);
INSERT INTO jobs VALUES
( 'FI_MGR' , 'Finance Manager' , 8200 , 16000
);
INSERT INTO jobs VALUES
( 'FI_ACCOUNT' , 'Accountant' , 4200 , 9000
);
INSERT INTO jobs VALUES
( 'AC_MGR' , 'Accounting Manager' , 8200 , 16000
);
INSERT INTO jobs VALUES
( 'AC_ACCOUNT' , 'Public Accountant' , 4200 , 9000
);
INSERT INTO jobs VALUES
( 'SA_MAN' , 'Sales Manager' , 10000 , 20000
);
INSERT INTO jobs VALUES
( 'SA_REP' , 'Sales Representative' , 6000 , 12000
);
INSERT INTO jobs VALUES
( 'PU_MAN' , 'Purchasing Manager' , 8000 , 15000
);
INSERT INTO jobs VALUES
( 'PU_CLERK' , 'Purchasing Clerk' , 2500 , 5500
);
INSERT INTO jobs VALUES
( 'ST_MAN' , 'Stock Manager' , 5500 , 8500
);
INSERT INTO jobs VALUES
( 'ST_CLERK' , 'Stock Clerk' , 2000 , 5000
);
INSERT INTO jobs VALUES
( 'SH_CLERK' , 'Shipping Clerk' , 2500 , 5500
);
INSERT INTO jobs VALUES
( 'IT_PROG' , 'Programmer' , 4000 , 10000
);
INSERT INTO jobs VALUES
( 'MK_MAN' , 'Marketing Manager' , 9000 , 15000
);
INSERT
INTO jobs VALUES
(
'MK_REP' ,
'Marketing Representative' ,
4000 ,
9000
);
INSERT
INTO jobs VALUES
(
'HR_REP' ,
'Human Resources Representative' ,
4000 ,
9000
);
INSERT
INTO jobs VALUES
(
'PR_REP' ,
'Public Relations Representative' ,
4500 ,
10500
);
INSERT
INTO employees VALUES
(
100 ,
'Steven' ,
'King' ,
'SKING' ,
'515.123.4567' ,
TO_DATE('17-JUN-1987', 'dd-MON-yyyy') ,
'AD_PRES' ,
24000 ,
NULL ,
NULL ,
90
);
INSERT
INTO employees VALUES
(
101 ,
'Neena' ,
'Kochhar' ,
'NKOCHHAR' ,
'515.123.4568' ,
TO_DATE('21-SEP-1989', 'dd-MON-yyyy') ,
'AD_VP' ,
17000 ,
NULL ,
100 ,
90
);
INSERT
INTO employees VALUES
(
102 ,
'Lex' ,
'De Haan' ,
'LDEHAAN' ,
'515.123.4569' ,
TO_DATE('13-JAN-1993', 'dd-MON-yyyy') ,
'AD_VP' ,
17000 ,
NULL ,
100 ,
90
);
INSERT
INTO employees VALUES
(
103 ,
'Alexander' ,
'Hunold' ,
'AHUNOLD' ,
'590.423.4567' ,
TO_DATE('03-JAN-1990', 'dd-MON-yyyy') ,
'IT_PROG' ,
9000 ,
NULL ,
102 ,
60
);
INSERT
INTO employees VALUES
(
104 ,
'Bruce' ,
'Ernst' ,
'BERNST' ,
'590.423.4568' ,
TO_DATE('21-MAY-1991', 'dd-MON-yyyy') ,
'IT_PROG' ,
6000 ,
NULL ,
103 ,
60
);
INSERT
INTO employees VALUES
(
105 ,
'David' ,
'Austin' ,
'DAUSTIN' ,
'590.423.4569' ,
TO_DATE('25-JUN-1997', 'dd-MON-yyyy') ,
'IT_PROG' ,
4800 ,
NULL ,
103 ,
60
);
INSERT
INTO employees VALUES
(
106 ,
'Valli' ,
'Pataballa' ,
'VPATABAL' ,
'590.423.4560' ,
TO_DATE('05-FEB-1998', 'dd-MON-yyyy') ,
'IT_PROG' ,
4800 ,
NULL ,
103 ,
60
);
INSERT
INTO employees VALUES
(
107 ,
'Diana' ,
'Lorentz' ,
'DLORENTZ' ,
'590.423.5567' ,
TO_DATE('07-FEB-1999', 'dd-MON-yyyy') ,
'IT_PROG' ,
4200 ,
NULL ,
103 ,
60
);
INSERT
INTO employees VALUES
(
108 ,
'Nancy' ,
'Greenberg' ,
'NGREENBE' ,
'515.124.4569' ,
TO_DATE('17-AUG-1994', 'dd-MON-yyyy') ,
'FI_MGR' ,
12000 ,
NULL ,
101 ,
100
);
INSERT
INTO employees VALUES
(
109 ,
'Daniel' ,
'Faviet' ,
'DFAVIET' ,
'515.124.4169' ,
TO_DATE('16-AUG-1994', 'dd-MON-yyyy') ,
'FI_ACCOUNT' ,
9000 ,
NULL ,
108 ,
100
);
INSERT
INTO employees VALUES
(
110 ,
'John' ,
'Chen' ,
'JCHEN' ,
'515.124.4269' ,
TO_DATE('28-SEP-1997', 'dd-MON-yyyy') ,
'FI_ACCOUNT' ,
8200 ,
NULL ,
108 ,
100
);
INSERT
INTO employees VALUES
(
111 ,
'Ismael' ,
'Sciarra' ,
'ISCIARRA' ,
'515.124.4369' ,
TO_DATE('30-SEP-1997', 'dd-MON-yyyy') ,
'FI_ACCOUNT' ,
7700 ,
NULL ,
108 ,
100
);
INSERT
INTO employees VALUES
(
112 ,
'Jose Manuel' ,
'Urman' ,
'JMURMAN' ,
'515.124.4469' ,
TO_DATE('07-MAR-1998', 'dd-MON-yyyy') ,
'FI_ACCOUNT' ,
7800 ,
NULL ,
108 ,
100
);
INSERT
INTO employees VALUES
(
113 ,
'Luis' ,
'Popp' ,
'LPOPP' ,
'515.124.4567' ,
TO_DATE('07-DEC-1999', 'dd-MON-yyyy') ,
'FI_ACCOUNT' ,
6900 ,
NULL ,
108 ,
100
);
INSERT
INTO employees VALUES
(
114 ,
'Den' ,
'Raphaely' ,
'DRAPHEAL' ,
'515.127.4561' ,
TO_DATE('07-DEC-1994', 'dd-MON-yyyy') ,
'PU_MAN' ,
11000 ,
NULL ,
100 ,
30
);
INSERT
INTO employees VALUES
(
115 ,
'Alexander' ,
'Khoo' ,
'AKHOO' ,
'515.127.4562' ,
TO_DATE('18-MAY-1995', 'dd-MON-yyyy') ,
'PU_CLERK' ,
3100 ,
NULL ,
114 ,
30
);
INSERT
INTO employees VALUES
(
116 ,
'Shelli' ,
'Baida' ,
'SBAIDA' ,
'515.127.4563' ,
TO_DATE('24-DEC-1997', 'dd-MON-yyyy') ,
'PU_CLERK' ,
2900 ,
NULL ,
114 ,
30
);
INSERT
INTO employees VALUES
(
117 ,
'Sigal' ,
'Tobias' ,
'STOBIAS' ,
'515.127.4564' ,
TO_DATE('24-JUL-1997', 'dd-MON-yyyy') ,
'PU_CLERK' ,
2800 ,
NULL ,
114 ,
30
);
INSERT
INTO employees VALUES
(
118 ,
'Guy' ,
'Himuro' ,
'GHIMURO' ,
'515.127.4565' ,
TO_DATE('15-NOV-1998', 'dd-MON-yyyy') ,
'PU_CLERK' ,
2600 ,
NULL ,
114 ,
30
);
INSERT
INTO employees VALUES
(
119 ,
'Karen' ,
'Colmenares' ,
'KCOLMENA' ,
'515.127.4566' ,
TO_DATE('10-AUG-1999', 'dd-MON-yyyy') ,
'PU_CLERK' ,
2500 ,
NULL ,
114 ,
30
);
INSERT
INTO employees VALUES
(
120 ,
'Matthew' ,
'Weiss' ,
'MWEISS' ,
'650.123.1234' ,
TO_DATE('18-JUL-1996', 'dd-MON-yyyy') ,
'ST_MAN' ,
8000 ,
NULL ,
100 ,
50
);
INSERT
INTO employees VALUES
(
121 ,
'Adam' ,
'Fripp' ,
'AFRIPP' ,
'650.123.2234' ,
TO_DATE('10-APR-1997', 'dd-MON-yyyy') ,
'ST_MAN' ,
8200 ,
NULL ,
100 ,
50
);
INSERT
INTO employees VALUES
(
122 ,
'Payam' ,
'Kaufling' ,
'PKAUFLIN' ,
'650.123.3234' ,
TO_DATE('01-MAY-1995', 'dd-MON-yyyy') ,
'ST_MAN' ,
7900 ,
NULL ,
100 ,
50
);
INSERT
INTO employees VALUES
(
123 ,
'Shanta' ,
'Vollman' ,
'SVOLLMAN' ,
'650.123.4234' ,
TO_DATE('10-OCT-1997', 'dd-MON-yyyy') ,
'ST_MAN' ,
6500 ,
NULL ,
100 ,
50
);
INSERT
INTO employees VALUES
(
124 ,
'Kevin' ,
'Mourgos' ,
'KMOURGOS' ,
'650.123.5234' ,
TO_DATE('16-NOV-1999', 'dd-MON-yyyy') ,
'ST_MAN' ,
5800 ,
NULL ,
100 ,
50
);
INSERT
INTO employees VALUES
(
125 ,
'Julia' ,
'Nayer' ,
'JNAYER' ,
'650.124.1214' ,
TO_DATE('16-JUL-1997', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3200 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
126 ,
'Irene' ,
'Mikkilineni' ,
'IMIKKILI' ,
'650.124.1224' ,
TO_DATE('28-SEP-1998', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2700 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
127 ,
'James' ,
'Landry' ,
'JLANDRY' ,
'650.124.1334' ,
TO_DATE('14-JAN-1999', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2400 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
128 ,
'Steven' ,
'Markle' ,
'SMARKLE' ,
'650.124.1434' ,
TO_DATE('08-MAR-2000', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2200 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
129 ,
'Laura' ,
'Bissot' ,
'LBISSOT' ,
'650.124.5234' ,
TO_DATE('20-AUG-1997', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3300 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
130 ,
'Mozhe' ,
'Atkinson' ,
'MATKINSO' ,
'650.124.6234' ,
TO_DATE('30-OCT-1997', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2800 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
131 ,
'James' ,
'Marlow' ,
'JAMRLOW' ,
'650.124.7234' ,
TO_DATE('16-FEB-1997', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2500 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
132 ,
'TJ' ,
'Olson' ,
'TJOLSON' ,
'650.124.8234' ,
TO_DATE('10-APR-1999', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2100 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
133 ,
'Jason' ,
'Mallin' ,
'JMALLIN' ,
'650.127.1934' ,
TO_DATE('14-JUN-1996', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3300 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
134 ,
'Michael' ,
'Rogers' ,
'MROGERS' ,
'650.127.1834' ,
TO_DATE('26-AUG-1998', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2900 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
135 ,
'Ki' ,
'Gee' ,
'KGEE' ,
'650.127.1734' ,
TO_DATE('12-DEC-1999', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2400 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
136 ,
'Hazel' ,
'Philtanker' ,
'HPHILTAN' ,
'650.127.1634' ,
TO_DATE('06-FEB-2000', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2200 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
137 ,
'Renske' ,
'Ladwig' ,
'RLADWIG' ,
'650.121.1234' ,
TO_DATE('14-JUL-1995', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3600 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
138 ,
'Stephen' ,
'Stiles' ,
'SSTILES' ,
'650.121.2034' ,
TO_DATE('26-OCT-1997', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3200 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
139 ,
'John' ,
'Seo' ,
'JSEO' ,
'650.121.2019' ,
TO_DATE('12-FEB-1998', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2700 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
140 ,
'Joshua' ,
'Patel' ,
'JPATEL' ,
'650.121.1834' ,
TO_DATE('06-APR-1998', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2500 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
141 ,
'Trenna' ,
'Rajs' ,
'TRAJS' ,
'650.121.8009' ,
TO_DATE('17-OCT-1995', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3500 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
142 ,
'Curtis' ,
'Davies' ,
'CDAVIES' ,
'650.121.2994' ,
TO_DATE('29-JAN-1997', 'dd-MON-yyyy') ,
'ST_CLERK' ,
3100 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
143 ,
'Randall' ,
'Matos' ,
'RMATOS' ,
'650.121.2874' ,
TO_DATE('15-MAR-1998', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2600 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
144 ,
'Peter' ,
'Vargas' ,
'PVARGAS' ,
'650.121.2004' ,
TO_DATE('09-JUL-1998', 'dd-MON-yyyy') ,
'ST_CLERK' ,
2500 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
145 ,
'John' ,
'Russell' ,
'JRUSSEL' ,
'011.44.1344.429268' ,
TO_DATE('01-OCT-1996', 'dd-MON-yyyy') ,
'SA_MAN' ,
14000 ,
.4 ,
100 ,
80
);
INSERT
INTO employees VALUES
(
146 ,
'Karen' ,
'Partners' ,
'KPARTNER' ,
'011.44.1344.467268' ,
TO_DATE('05-JAN-1997', 'dd-MON-yyyy') ,
'SA_MAN' ,
13500 ,
.3 ,
100 ,
80
);
INSERT
INTO employees VALUES
(
147 ,
'Alberto' ,
'Errazuriz' ,
'AERRAZUR' ,
'011.44.1344.429278' ,
TO_DATE('10-MAR-1997', 'dd-MON-yyyy') ,
'SA_MAN' ,
12000 ,
.3 ,
100 ,
80
);
INSERT
INTO employees VALUES
(
148 ,
'Gerald' ,
'Cambrault' ,
'GCAMBRAU' ,
'011.44.1344.619268' ,
TO_DATE('15-OCT-1999', 'dd-MON-yyyy') ,
'SA_MAN' ,
11000 ,
.3 ,
100 ,
80
);
INSERT
INTO employees VALUES
(
149 ,
'Eleni' ,
'Zlotkey' ,
'EZLOTKEY' ,
'011.44.1344.429018' ,
TO_DATE('29-JAN-2000', 'dd-MON-yyyy') ,
'SA_MAN' ,
10500 ,
.2 ,
100 ,
80
);
INSERT
INTO employees VALUES
(
150 ,
'Peter' ,
'Tucker' ,
'PTUCKER' ,
'011.44.1344.129268' ,
TO_DATE('30-JAN-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
10000 ,
.3 ,
145 ,
80
);
INSERT
INTO employees VALUES
(
151 ,
'David' ,
'Bernstein' ,
'DBERNSTE' ,
'011.44.1344.345268' ,
TO_DATE('24-MAR-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
9500 ,
.25 ,
145 ,
80
);
INSERT
INTO employees VALUES
(
152 ,
'Peter' ,
'Hall' ,
'PHALL' ,
'011.44.1344.478968' ,
TO_DATE('20-AUG-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
9000 ,
.25 ,
145 ,
80
);
INSERT
INTO employees VALUES
(
153 ,
'Christopher' ,
'Olsen' ,
'COLSEN' ,
'011.44.1344.498718' ,
TO_DATE('30-MAR-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
8000 ,
.2 ,
145 ,
80
);
INSERT
INTO employees VALUES
(
154 ,
'Nanette' ,
'Cambrault' ,
'NCAMBRAU' ,
'011.44.1344.987668' ,
TO_DATE('09-DEC-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
7500 ,
.2 ,
145 ,
80
);
INSERT
INTO employees VALUES
(
155 ,
'Oliver' ,
'Tuvault' ,
'OTUVAULT' ,
'011.44.1344.486508' ,
TO_DATE('23-NOV-1999', 'dd-MON-yyyy') ,
'SA_REP' ,
7000 ,
.15 ,
145 ,
80
);
INSERT
INTO employees VALUES
(
156 ,
'Janette' ,
'King' ,
'JKING' ,
'011.44.1345.429268' ,
TO_DATE('30-JAN-1996', 'dd-MON-yyyy') ,
'SA_REP' ,
10000 ,
.35 ,
146 ,
80
);
INSERT
INTO employees VALUES
(
157 ,
'Patrick' ,
'Sully' ,
'PSULLY' ,
'011.44.1345.929268' ,
TO_DATE('04-MAR-1996', 'dd-MON-yyyy') ,
'SA_REP' ,
9500 ,
.35 ,
146 ,
80
);
INSERT
INTO employees VALUES
(
158 ,
'Allan' ,
'McEwen' ,
'AMCEWEN' ,
'011.44.1345.829268' ,
TO_DATE('01-AUG-1996', 'dd-MON-yyyy') ,
'SA_REP' ,
9000 ,
.35 ,
146 ,
80
);
INSERT
INTO employees VALUES
(
159 ,
'Lindsey' ,
'Smith' ,
'LSMITH' ,
'011.44.1345.729268' ,
TO_DATE('10-MAR-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
8000 ,
.3 ,
146 ,
80
);
INSERT
INTO employees VALUES
(
160 ,
'Louise' ,
'Doran' ,
'LDORAN' ,
'011.44.1345.629268' ,
TO_DATE('15-DEC-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
7500 ,
.3 ,
146 ,
80
);
INSERT
INTO employees VALUES
(
161 ,
'Sarath' ,
'Sewall' ,
'SSEWALL' ,
'011.44.1345.529268' ,
TO_DATE('03-NOV-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
7000 ,
.25 ,
146 ,
80
);
INSERT
INTO employees VALUES
(
162 ,
'Clara' ,
'Vishney' ,
'CVISHNEY' ,
'011.44.1346.129268' ,
TO_DATE('11-NOV-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
10500 ,
.25 ,
147 ,
80
);
INSERT
INTO employees VALUES
(
163 ,
'Danielle' ,
'Greene' ,
'DGREENE' ,
'011.44.1346.229268' ,
TO_DATE('19-MAR-1999', 'dd-MON-yyyy') ,
'SA_REP' ,
9500 ,
.15 ,
147 ,
80
);
INSERT
INTO employees VALUES
(
164 ,
'Mattea' ,
'Marvins' ,
'MMARVINS' ,
'011.44.1346.329268' ,
TO_DATE('24-JAN-2000', 'dd-MON-yyyy') ,
'SA_REP' ,
7200 ,
.10 ,
147 ,
80
);
INSERT
INTO employees VALUES
(
165 ,
'David' ,
'Lee' ,
'DLEE' ,
'011.44.1346.529268' ,
TO_DATE('23-FEB-2000', 'dd-MON-yyyy') ,
'SA_REP' ,
6800 ,
.1 ,
147 ,
80
);
INSERT
INTO employees VALUES
(
166 ,
'Sundar' ,
'Ande' ,
'SANDE' ,
'011.44.1346.629268' ,
TO_DATE('24-MAR-2000', 'dd-MON-yyyy') ,
'SA_REP' ,
6400 ,
.10 ,
147 ,
80
);
INSERT
INTO employees VALUES
(
167 ,
'Amit' ,
'Banda' ,
'ABANDA' ,
'011.44.1346.729268' ,
TO_DATE('21-APR-2000', 'dd-MON-yyyy') ,
'SA_REP' ,
6200 ,
.10 ,
147 ,
80
);
INSERT
INTO employees VALUES
(
168 ,
'Lisa' ,
'Ozer' ,
'LOZER' ,
'011.44.1343.929268' ,
TO_DATE('11-MAR-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
11500 ,
.25 ,
148 ,
80
);
INSERT
INTO employees VALUES
(
169 ,
'Harrison' ,
'Bloom' ,
'HBLOOM' ,
'011.44.1343.829268' ,
TO_DATE('23-MAR-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
10000 ,
.20 ,
148 ,
80
);
INSERT
INTO employees VALUES
(
170 ,
'Tayler' ,
'Fox' ,
'TFOX' ,
'011.44.1343.729268' ,
TO_DATE('24-JAN-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
9600 ,
.20 ,
148 ,
80
);
INSERT
INTO employees VALUES
(
171 ,
'William' ,
'Smith' ,
'WSMITH' ,
'011.44.1343.629268' ,
TO_DATE('23-FEB-1999', 'dd-MON-yyyy') ,
'SA_REP' ,
7400 ,
.15 ,
148 ,
80
);
INSERT
INTO employees VALUES
(
172 ,
'Elizabeth' ,
'Bates' ,
'EBATES' ,
'011.44.1343.529268' ,
TO_DATE('24-MAR-1999', 'dd-MON-yyyy') ,
'SA_REP' ,
7300 ,
.15 ,
148 ,
80
);
INSERT
INTO employees VALUES
(
173 ,
'Sundita' ,
'Kumar' ,
'SKUMAR' ,
'011.44.1343.329268' ,
TO_DATE('21-APR-2000', 'dd-MON-yyyy') ,
'SA_REP' ,
6100 ,
.10 ,
148 ,
80
);
INSERT
INTO employees VALUES
(
174 ,
'Ellen' ,
'Abel' ,
'EABEL' ,
'011.44.1644.429267' ,
TO_DATE('11-MAY-1996', 'dd-MON-yyyy') ,
'SA_REP' ,
11000 ,
.30 ,
149 ,
80
);
INSERT
INTO employees VALUES
(
175 ,
'Alyssa' ,
'Hutton' ,
'AHUTTON' ,
'011.44.1644.429266' ,
TO_DATE('19-MAR-1997', 'dd-MON-yyyy') ,
'SA_REP' ,
8800 ,
.25 ,
149 ,
80
);
INSERT
INTO employees VALUES
(
176 ,
'Jonathon' ,
'Taylor' ,
'JTAYLOR' ,
'011.44.1644.429265' ,
TO_DATE('24-MAR-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
8600 ,
.20 ,
149 ,
80
);
INSERT
INTO employees VALUES
(
177 ,
'Jack' ,
'Livingston' ,
'JLIVINGS' ,
'011.44.1644.429264' ,
TO_DATE('23-APR-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
8400 ,
.20 ,
149 ,
80
);
INSERT
INTO employees VALUES
(
178 ,
'Kimberely' ,
'Grant' ,
'KGRANT' ,
'011.44.1644.429263' ,
TO_DATE('24-MAY-1999', 'dd-MON-yyyy') ,
'SA_REP' ,
7000 ,
.15 ,
149 ,
NULL
);
INSERT
INTO employees VALUES
(
179 ,
'Charles' ,
'Johnson' ,
'CJOHNSON' ,
'011.44.1644.429262' ,
TO_DATE('04-JAN-2000', 'dd-MON-yyyy') ,
'SA_REP' ,
6200 ,
.10 ,
149 ,
80
);
INSERT
INTO employees VALUES
(
180 ,
'Winston' ,
'Taylor' ,
'WTAYLOR' ,
'650.507.9876' ,
TO_DATE('24-JAN-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3200 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
181 ,
'Jean' ,
'Fleaur' ,
'JFLEAUR' ,
'650.507.9877' ,
TO_DATE('23-FEB-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3100 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
182 ,
'Martha' ,
'Sullivan' ,
'MSULLIVA' ,
'650.507.9878' ,
TO_DATE('21-JUN-1999', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2500 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
183 ,
'Girard' ,
'Geoni' ,
'GGEONI' ,
'650.507.9879' ,
TO_DATE('03-FEB-2000', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2800 ,
NULL ,
120 ,
50
);
INSERT
INTO employees VALUES
(
184 ,
'Nandita' ,
'Sarchand' ,
'NSARCHAN' ,
'650.509.1876' ,
TO_DATE('27-JAN-1996', 'dd-MON-yyyy') ,
'SH_CLERK' ,
4200 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
185 ,
'Alexis' ,
'Bull' ,
'ABULL' ,
'650.509.2876' ,
TO_DATE('20-FEB-1997', 'dd-MON-yyyy') ,
'SH_CLERK' ,
4100 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
186 ,
'Julia' ,
'Dellinger' ,
'JDELLING' ,
'650.509.3876' ,
TO_DATE('24-JUN-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3400 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
187 ,
'Anthony' ,
'Cabrio' ,
'ACABRIO' ,
'650.509.4876' ,
TO_DATE('07-FEB-1999', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3000 ,
NULL ,
121 ,
50
);
INSERT
INTO employees VALUES
(
188 ,
'Kelly' ,
'Chung' ,
'KCHUNG' ,
'650.505.1876' ,
TO_DATE('14-JUN-1997', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3800 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
189 ,
'Jennifer' ,
'Dilly' ,
'JDILLY' ,
'650.505.2876' ,
TO_DATE('13-AUG-1997', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3600 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
190 ,
'Timothy' ,
'Gates' ,
'TGATES' ,
'650.505.3876' ,
TO_DATE('11-JUL-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2900 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
191 ,
'Randall' ,
'Perkins' ,
'RPERKINS' ,
'650.505.4876' ,
TO_DATE('19-DEC-1999', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2500 ,
NULL ,
122 ,
50
);
INSERT
INTO employees VALUES
(
192 ,
'Sarah' ,
'Bell' ,
'SBELL' ,
'650.501.1876' ,
TO_DATE('04-FEB-1996', 'dd-MON-yyyy') ,
'SH_CLERK' ,
4000 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
193 ,
'Britney' ,
'Everett' ,
'BEVERETT' ,
'650.501.2876' ,
TO_DATE('03-MAR-1997', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3900 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
194 ,
'Samuel' ,
'McCain' ,
'SMCCAIN' ,
'650.501.3876' ,
TO_DATE('01-JUL-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3200 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
195 ,
'Vance' ,
'Jones' ,
'VJONES' ,
'650.501.4876' ,
TO_DATE('17-MAR-1999', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2800 ,
NULL ,
123 ,
50
);
INSERT
INTO employees VALUES
(
196 ,
'Alana' ,
'Walsh' ,
'AWALSH' ,
'650.507.9811' ,
TO_DATE('24-APR-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3100 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
197 ,
'Kevin' ,
'Feeney' ,
'KFEENEY' ,
'650.507.9822' ,
TO_DATE('23-MAY-1998', 'dd-MON-yyyy') ,
'SH_CLERK' ,
3000 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
198 ,
'Donald' ,
'OConnell' ,
'DOCONNEL' ,
'650.507.9833' ,
TO_DATE('21-JUN-1999', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2600 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
199 ,
'Douglas' ,
'Grant' ,
'DGRANT' ,
'650.507.9844' ,
TO_DATE('13-JAN-2000', 'dd-MON-yyyy') ,
'SH_CLERK' ,
2600 ,
NULL ,
124 ,
50
);
INSERT
INTO employees VALUES
(
200 ,
'Jennifer' ,
'Whalen' ,
'JWHALEN' ,
'515.123.4444' ,
TO_DATE('17-SEP-1987', 'dd-MON-yyyy') ,
'AD_ASST' ,
4400 ,
NULL ,
101 ,
10
);
INSERT
INTO employees VALUES
(
201 ,
'Michael' ,
'Hartstein' ,
'MHARTSTE' ,
'515.123.5555' ,
TO_DATE('17-FEB-1996', 'dd-MON-yyyy') ,
'MK_MAN' ,
13000 ,
NULL ,
100 ,
20
);
INSERT
INTO employees VALUES
(
202 ,
'Pat' ,
'Fay' ,
'PFAY' ,
'603.123.6666' ,
TO_DATE('17-AUG-1997', 'dd-MON-yyyy') ,
'MK_REP' ,
6000 ,
NULL ,
201 ,
20
);
INSERT
INTO employees VALUES
(
203 ,
'Susan' ,
'Mavris' ,
'SMAVRIS' ,
'515.123.7777' ,
TO_DATE('07-JUN-1994', 'dd-MON-yyyy') ,
'HR_REP' ,
6500 ,
NULL ,
101 ,
40
);
INSERT
INTO employees VALUES
(
204 ,
'Hermann' ,
'Baer' ,
'HBAER' ,
'515.123.8888' ,
TO_DATE('07-JUN-1994', 'dd-MON-yyyy') ,
'PR_REP' ,
10000 ,
NULL ,
101 ,
70
);
INSERT
INTO employees VALUES
(
205 ,
'Shelley' ,
'Higgins' ,
'SHIGGINS' ,
'515.123.8080' ,
TO_DATE('07-JUN-1994', 'dd-MON-yyyy') ,
'AC_MGR' ,
12000 ,
NULL ,
101 ,
110
);
INSERT
INTO employees VALUES
(
206 ,
'William' ,
'Gietz' ,
'WGIETZ' ,
'515.123.8181' ,
TO_DATE('07-JUN-1994', 'dd-MON-yyyy') ,
'AC_ACCOUNT' ,
8300 ,
NULL ,
205 ,
110
);
INSERT
INTO job_history VALUES
(
102 ,
TO_DATE('13-JAN-1993', 'dd-MON-yyyy') ,
TO_DATE('24-JUL-1998', 'dd-MON-yyyy') ,
'IT_PROG' ,
60
);
INSERT
INTO job_history VALUES
(
101 ,
TO_DATE('21-SEP-1989', 'dd-MON-yyyy') ,
TO_DATE('27-OCT-1993', 'dd-MON-yyyy') ,
'AC_ACCOUNT' ,
110
);
INSERT
INTO job_history VALUES
(
101 ,
TO_DATE('28-OCT-1993', 'dd-MON-yyyy') ,
TO_DATE('15-MAR-1997', 'dd-MON-yyyy') ,
'AC_MGR' ,
110
);
INSERT
INTO job_history VALUES
(
201 ,
TO_DATE('17-FEB-1996', 'dd-MON-yyyy') ,
TO_DATE('19-DEC-1999', 'dd-MON-yyyy') ,
'MK_REP' ,
20
);
INSERT
INTO job_history VALUES
(
114 ,
TO_DATE('24-MAR-1998', 'dd-MON-yyyy') ,
TO_DATE('31-DEC-1999', 'dd-MON-yyyy') ,
'ST_CLERK' ,
50
);
INSERT
INTO job_history VALUES
(
122 ,
TO_DATE('01-JAN-1999', 'dd-MON-yyyy') ,
TO_DATE('31-DEC-1999', 'dd-MON-yyyy') ,
'ST_CLERK' ,
50
);
INSERT
INTO job_history VALUES
(
200 ,
TO_DATE('17-SEP-1987', 'dd-MON-yyyy') ,
TO_DATE('17-JUN-1993', 'dd-MON-yyyy') ,
'AD_ASST' ,
90
);
INSERT
INTO job_history VALUES
(
176 ,
TO_DATE('24-MAR-1998', 'dd-MON-yyyy') ,
TO_DATE('31-DEC-1998', 'dd-MON-yyyy') ,
'SA_REP' ,
80
);
INSERT
INTO job_history VALUES
(
176 ,
TO_DATE('01-JAN-1999', 'dd-MON-yyyy') ,
TO_DATE('31-DEC-1999', 'dd-MON-yyyy') ,
'SA_MAN' ,
80
);
INSERT
INTO job_history VALUES
(
200 ,
TO_DATE('01-JUL-1994', 'dd-MON-yyyy') ,
TO_DATE('31-DEC-1998', 'dd-MON-yyyy') ,
'AC_ACCOUNT' ,
90
);
--ALTER TABLE departments ENABLE CONSTRAINT dept_mgr_fk;
COMMIT;
\ No newline at end of file
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";
--* SELECT *--
-- SELECT służy do odczytu danych
-- Wyniki zasadniczo mają kształt tabeli,
-- ale w szczególnych przypadkach mogą wyjść: pojedynczy wiersz, pojedyncza kolumna, pojedyncza wartość (tzw. zapytanie skalarne)
-- zapytanie może też zwrócić puste wyniki
SELECT * FROM employees;
SELECT first_name, last_name FROM employees;
SELECT first_name, last_name FROM employees WHERE salary > 50000;
SELECT avg(salary) FROM employees;
/* Ogólna składnia polecenia SELECT:
SELECT kolumny
FROM tabele
WHERE warunek rekordów
GROUP BY kryteria grupowania
HAVING warunek grup
ORDER BY kryteria sortowania
...
Poszczególne części nazywa się "kaluzulami" (clause).
Oprócz tej podstawy istnieją dodatkowe mniej lub bardziej przenośne klauzule, jak
np. LIMIT / OFFSET w PostgreSQL
FETCH, WITH, UNION itp, WINDOW....
Można też używać "podzapytań".
*/
-- W większości baz danych, ale nie w Oracle, możn a napisać samego selecta bez dodatkowych klauzul.
SELECT 2*3;
SELECT current_timestamp;
-- W Oracle: SELECT 2*3 FROM dual;
--* Klauzula SELECT *--
-- Sama klauzula SELECT określa kolumny, które mają znaleźć się w wyniku.
-- Można podać te kolumny tabeli źródłowej, tóre mają trafić do wyniku.
SELECT first_name, last_name, salary
FROM employees;
-- W klauzuli SELECT można nie tylko wskazywać istniejące kolumny,
-- ale można też wpisywać wyrażenia, które coś obliczają.
-- Przy okazji: || w SQL oznacza łączenie tekstów
SELECT 'Pracownik ' || first_name || ' ' || last_name, 12 * salary, current_timestamp
FROM employees;
SELECT concat('Pracownik ', first_name, ' ', last_name), 12 * salary, current_timestamp
FROM employees;
-- Wynikowym kolumnom można nadać własne nazwy, tzw "aliasy kolumn".
-- Nazwa bez cudzysłowów nie może zawierać spacji ani wielu innych znaków specjalnych
-- jest automatycznie zamieniana na małe litery.
SELECT first_name || ' ' || last_name AS kto
, 12 * salary AS roczne_zarobki
, current_date - hire_date AS Staz
FROM employees;
-- W aliasach kolumn bardzo często stosuje się "nazwy cytowane".
-- Wtedy można użyć spacji i innych znaków, a wielkość liter jest zachowywana.
SELECT first_name || ' ' || last_name AS "Kto"
, 12 * salary AS "Roczne zarobki"
, current_date - hire_date AS "Staż pracy"
FROM employees;
-- Tworząc alias kolumny nie musimy pisać słowa AS,
-- ale wersja z AS jest bardziej przenośna (MS SQL Server)
SELECT first_name || ' ' || last_name Kto
, 12 * salary "Roczne zarobki"
, current_date - hire_date "Staż pracy"
FROM employees;
-- Jeśli podzapytanie wprowadza aliasy kolumn,
-- to zapytanie zewnętrzne musi używać tych aliasów jako nazw kolumn,
-- a oryginalne nazwy nie są dostępne.
SELECT imie, nazwisko, zarobki AS "Zarobki", "Roczne zarobki"
FROM (SELECT first_name AS imie, last_name AS nazwisko
, salary AS zarobki
, 12 * salary AS "Roczne zarobki"
FROM employees) podzapytanie
WHERE "Roczne zarobki" >= 100000;
-- * - weź wszystkie dostępne kolumny
SELECT * FROM employees;
-- <zapytania logicznie bez sensu>
SELECT * FROM locations, countries;
SELECT locations.*, regions.* FROM locations, countries, regions;
-- nie ma country_name
-- </zapytania logicznie bez sensu>
-- Jeśli chcę odczytać wszystkie kolumny z tabeli, a oprócz nich dodać kolejne (wyliczane wyrażeniem):
SELECT *, lower(email) || '@alx.pl' AS prawdziwy_email
FROM employees;
-- Powyższe działa w PostgreSQL, ale nie Oracle.
-- W obu bazach zadziała taka wersja:
SELECT employees.*, lower(email) || '@alx.pl' AS prawdziwy_email
FROM employees;
-- DISTINCT powoduje pobranie danych bez powtórzeń:
SELECT first_name FROM employees;
SELECT first_name FROM employees ORDER BY 1;
SELECT DISTINCT first_name FROM employees;
SELECT DISTINCT first_name FROM employees ORDER BY first_name;
-- Jeśli zapytanie zwraca kilka kolumn, to unikalne mają być "krotki", czyli kombinacje wartości.
-- Np. teraz niektóre imiona się powtarzają, niektóre nazwiska się powtarzają,
-- ale nie ma osoby o identycznej kombinacji imienia i nazwiska.
SELECT DISTINCT first_name, last_name FROM employees;
-- Spróbujmy na inicjałach, aby faktycznie pojawiły się powtórzone pary.
-- Teraz sa powtorzenia
SELECT substr(first_name, 1, 1), substr(last_name, 1, 1) FROM employees;
-- Teaz moga powtarzac sie pierwsze litery imion, moga nazwisk, ale nie powtarzaja sie pary.
SELECT DISTINCT substr(first_name, 1, 1), substr(last_name, 1, 1) FROM employees;
-- Dodatkowy temat - "lista imion"
SELECT first_name FROM employees;
SELECT string_agg(first_name, ', ') FROM employees;
--* WHERE i warunki logiczne *--
-- W PostgreSQL istnieje typ boolean (wartości logicznych)
-- W PostgreSQL (inaczej niż m.in. w Oracle) można wynik porównanie czy innego sprawdzenia wypisać jako jedną z kolumn wynikowych:
SELECT first_name, last_name, salary, salary >= 10000 AS bogaty FROM employees;
-- Warunków logicznych najczęściej używa się do przefiltrowania rekordów.
-- W wynikach zapytania z klauzulą WHERE znajdą tylko te rekordy, które spełniają warunek.
SELECT first_name, last_name, salary
FROM employees
WHERE salary >= 10000;
-- W warunku można odwołać się także do kolumn, których się nie zwraca w wyniku
SELECT first_name, last_name
FROM employees
WHERE job_id = 'IT_PROG';
-- operatory porównania = < <= > >=
-- nierówne na dwa sposoby: != <>
SELECT first_name, last_name
FROM employees
WHERE job_id != 'IT_PROG';
-- Spójniki logiczne
-- AND - koniunkcja - oba warunki muszą być spełnione
-- OR - alternatywa - co najmniej jeden warunek musi być spełniony
-- NOT - negacja
SELECT * FROM employees WHERE salary = 9000;
SELECT * FROM employees WHERE job_id = 'IT_PROG';
-- AND : programiści, którzy zarabiają 9000
SELECT * FROM employees WHERE job_id = 'IT_PROG' AND salary = 9000;
-- OR : wszyscy programiści oraz wszyscy, którzy zarabiają 9000
-- Alexander Hunold pojawia się tylko raz
SELECT * FROM employees WHERE job_id = 'IT_PROG' OR salary = 9000;
-- Wskazówka dot. formatownia kodu, gdy jest dużo warunków składowych
SELECT employee_id
, first_name || ' ' || last_name AS "Kto"
, 12 * salary AS "Roczne zarobki"
, current_date - hire_date AS "Staż pracy"
FROM employees
WHERE 1=1
AND salary >= 5000
AND job_id = 'SA_REP'
ORDER BY employee_id;
-- Aby sprawdzić, czy wartość mieści się w przedziale, możemy:
-- 1) użyć dwóch porówań połączonych AND
SELECT * FROM employees WHERE salary >= 5000 AND salary <= 10000;
-- 2) za pomocą BETWEEN
-- uwaga: BETWEEN jest obustronnie domknięty
SELECT * FROM employees WHERE salary BETWEEN 5000 AND 10000;
-- Odczytajmy pracowników ze stanowisk ST_CLERK oraz ST_MAN
-- sposób 1: spójnik OR
SELECT * FROM employees WHERE job_id = 'ST_CLERK' OR job_id = 'ST_MAN';
-- sposób 2: operator IN - czy wartość należy do podanego zbioru
SELECT * FROM employees WHERE job_id IN ('ST_CLERK', 'ST_MAN');
-- Ale operator IN może być też używany w połączeniu z podzapytaniem...
-- Zakładając, że posiadamy tabelę selected_jobs, można by tak:
/* SELECT * FROM employees
WHERE job_id IN (SELECT job FROM selected_jobs);
*/
-- sposób 3: operator LIKE (tylko gdy joby mają część wspólną)
-- wszystkie rekordy, gdzie job_id zaczyna się na ST
SELECT * FROM employees WHERE job_id LIKE 'ST%';
-- Po lewej stronie LIKE znajduje się zwykły napis, zazwyczaj kolumna z tabeli.
-- Po prawej jest "wzorzec". We wzorcach:
-- znak % oznacza dowolnej długości ciąg dowolnych znaków
-- znak _ oznacza pojedynczy dowolny znak
-- za pomocą symbolu \ można "wy-escape-ować" znaki specjalne, gdyby miały być faktyczną treścią
-- Wielkość liter MA znaczenie.
SELECT * FROM employees WHERE job_id LIKE 'ST\_%';
-- wszystkie nazwiska zaczynające się na K
SELECT first_name, last_name FROM employees WHERE last_name LIKE 'K%';
-- pierwsza i ostatnia
SELECT first_name, last_name FROM employees WHERE last_name LIKE 'K%g';
-- druga litera nazwiska jest równa a
SELECT * FROM employees WHERE last_name LIKE '_a%';
-- Zadanie: wypisz te nazwy miast, które zawierają co najmniej 2 litery o
SELECT city FROM locations WHERE city LIKE '%o%o%';
-- Wyniki nie obejmują Oxford.
-- Jeśli chcemy, aby również duże 'O' zostało uwzględnione:
-- rozwiązanie ogólne (np. działające w Oraclu)
SELECT city FROM locations WHERE lower(city) LIKE '%o%o%'
-- rozwiązanie Postgresa - specjalna wersja LIKE o nazwie ILIKE
SELECT city FROM locations WHERE city ILIKE '%o%o%'
-- Jeszcze inna wersja bazująca na "wyrażeniach regularnych SQL", które są trochę inne, niż regexpy znane z innych platform
SELECT city FROM locations WHERE city SIMILAR TO '%(o%){2}';
SELECT city FROM locations WHERE regexp_like(city, '.*((o|O).*){2}');
-- Informacja nt SIMILAR TO oraz regexp_like
-- https://www.postgresql.org/docs/17/functions-matching.html
-- Generalnie porówania tekstu są case-sensitive
-- Aby zignorować wielkość liter
SELECT first_name, last_name
FROM employees
WHERE lower(job_id) = 'st_clerk';
-- PostgreSQL posiada też operator ILIKE, czyli wersję LIKE nie zwracającą uwagi na wielkość liter.
SELECT first_name, last_name
FROM employees
WHERE job_id ILIKE 'st\_clerk';
-- W WHERE nie wolno używać aliasów kolumn wprowadzonych w SELECT.
SELECT first_name || ' ' || last_name AS kto
, 12 * salary AS roczne_zarobki
FROM employees
WHERE roczne_zarobki >= 100000;
SELECT first_name || ' ' || last_name AS kto
, 12 * salary AS "roczne_zarobki"
FROM employees
WHERE "roczne_zarobki" >= 100000;
--* Temat NULL *--
-- Zobaczmy, że NULL nie jest zwykłą wartością, tylko powoduje dziwne zachowania, gdy się pojawia.
-- W tabeli employees istnieje kolumna commission_pct, która u niektórych pracowników zawiera liczbę,
-- a u pozostałych ma wartość NULL.
SELECT * FROM employees;
SELECT * FROM employees WHERE commission_pct > 0.2;
SELECT * FROM employees WHERE commission_pct <= 0.2;
-- Dla wartości NULL nieprawdą jest ani > , ani <
-- Ci, którzy nie uzyskują prowizji, nie pojawią się ani w jednej, ani w drugiej grupie.
-- Aby sprawdzić czy jest jest równa NULL, czy nie, należy użyć wyspecjalizowanych operatorów IS (NOT) NULL
SELECT * FROM employees WHERE commission_pct = NULL;
SELECT * FROM employees WHERE commission_pct IS NULL;
-- Inny problem - NULL w wyrażniu arytmetycznym powoduje, że cały wynik też jest nullem.
-- NULL to nie jest zero.
SELECT first_name || ' ' || last_name
, salary + commission_pct * salary AS wyplata
, substr(first_name, 1, 1) || '. ' || last_name || ' ma prowizję ' || 100*commission_pct || ' procent' AS opis
FROM employees;
-- rozwiązanie tego problemu:
SELECT first_name || ' ' || last_name
, salary + coalesce(commission_pct, 0) * salary AS wyplata
, substr(first_name, 1, 1) || '. ' || last_name ||
coalesce(' ma prowizję ' || 100*commission_pct || ' procent', ' bez prowizji') AS opis
, concat(substr(first_name, 1, 1), '. ', last_name, ' ', 100*commission_pct) AS opis2
FROM employees;
/* Praprzyczyną tego dziwnego zachowania jest fakt, iż na początku historii SQL uznano,
że NULL będzie oznaczał BRAK WIEDZY jaka jest wartość danego pola.
Czyli tak jakby w tym przykładzie wartość NULL znaczyła "nie wiadomo jaką prowizję uzyskuje pracownik".
Takie myślenie miałoby sens w takim prykładzie:
Osoba ma kolumnę data_urodzenia, ale dla pewnej osoby nie znamy jej daty urodzenia.
To wtedy wpisujemy NULL.
Do takiego znaczenia wartości NULL dostosowano całą logikę i działanie operatorów.
W praktyce jednak często przyjmuje się, że NULL oznacza "pustą wartość".
Oznacza "wiemy, że czegoś nie ma", jest puste, zerowe, brak.
Tak jak w tym przykładzie, intencją autora tabeli employees było na pewno,
aby NULL w polu commission_pct znaczył "pracownik nie otrzymuje żadnej prowizji".
I tak jest zazwyczaj w praktyce.
Jednak logika SQL tak tego nie rozumie. Dla niej NULL oznacza "nie wiadomo".
Więcej na temat teorii można szukać pod hasłem "logika trójwartościowa SQL".
*/
-- Porównanie z NULLem daje wynik NULL
SELECT 1 = NULL;
-- W warunkach SQL-owych obowiązuje "logika trójwartościowa": trye / false / null (nie wiadomo)
-- Spójniki logiczne starają się robić swoje nawet jeśli czasami jest nieznana wartość.
SELECT 1 = 1 AND 2 = NULL;
SELECT 1 = 0 AND 2 = NULL;
SELECT 1 = 1 OR 2 = NULL;
-- Operacje arytmetyczne itp. , gdzie wewnątrz pojawia się NULL, dają wynik NULL.
SELECT 2 * (3 + NULL) - 15 * 4;
SELECT 0 * NULL; -- jednak null
/* Gdy wykonujemy obliczenia, w których pojawia się wartość NULL,
to zazwyczaj wynikiem całego wyrażenia też jest NULL („null zaraża”).
Tutaj pewne uproszczenie: przyjmujemy, że prowizja jest liczona względem pensji,
a nie sprzedaży (której w tej bazie danych nie ma).
Dla każdego pracownika chcemy obliczyć jego wypłatę wraz z dodaną prowizją.
Przy poniższym zapisie wynikiem w ostatniej kolumnie
jest NULL u tych pracowników, którzy nie uzyskują prowizji.
*/
SELECT first_name, last_name, salary, commission_pct
,commission_pct * salary -- prowizja kwotowo
,salary + commission_pct * salary -- kwota do wypłaty
FROM employees;
-- Aby zastąpić wartość NULL inną "konkretną" wartością, można użyć funkcji COALESCE
SELECT first_name, last_name, salary, commission_pct
,coalesce(commission_pct * salary, 0) as "kwota prowizji"
,salary + coalesce(commission_pct, 0) * salary as "wypłata z prowizją"
FROM employees;
-- coalesce(wartosc1, wartosc2, ....) -- dowolnie wiele wartości po przecinku
-- zwraca pierwszą z wartości podanych jako parametry, która nie jest nullem (no chyba ze wszystkie są)
SELECT coalesce(null, null, 'bingo', null, 'kapusta');-- FROM dual;
-- coalesce jest częścią standardu SQL i działa w większości baz danych
-- W praktyce najczęściej zapisuje się tak: coalesce(kolumna_ktora_moze_byc_null, wartosc_domyslna)
-- Nie ma obowiązku podstawiania zera.
SELECT first_name, last_name, salary, commission_pct,
coalesce(commission_pct, 1234)
FROM employees;
--! Specyfika Oracle !--
-- W Oraclu jest też funkcja nvl, która działa tak jak dwuargumentowy coalesce:
-- NVL(x, y): jeśli x nie jest nullem, to zwraca x, a jeśli x jest nullem, to zwraca y
SELECT first_name, last_name, salary, commission_pct
,nvl(commission_pct * salary, 0) as "kwota prowizji"
,salary + nvl(commission_pct, 0) * salary as "wypłata z prowizją"
FROM employees;
-- Inne funkcje Oracla związane z obsługą NULL:
-- NVL2(x, y, z)
-- jeśli x nie jest NULL, to zwraca y
-- jeśli x jest NULLem, to zwraca z
SELECT first_name, last_name, commission_pct,
nvl2(commission_pct, 'jest prowizja', 'brak prowizji')
FROM employees;
-- Na siłę można to wykorzystać do wyliczenia łącznej wypłaty:
SELECT first_name, last_name, salary, commission_pct,
nvl2(commission_pct, salary*(1+commission_pct), salary) AS "wypłata"
FROM employees;
-- NULLIF(x, y) -- o!, to działa też w PostgreSQL
-- jeśli x = y, to wynikiem jest NULL
-- jeśli x != y, to wynikiem jest x
SELECT city, nullif(city, 'Toronto') FROM locations;
SELECT street_address, city, coalesce(state_province, '--BRAK DANYCH--') AS state_province
FROM locations;
-- Specyfika PostgreSQL i operatora ||
-- Gdy używamy || do łączenia napisów, a którykolwiek z argumentów jest NULL,
-- to cały wynik jest NULLem.
-- Funkcja concat traktuje nulle jak puste napisy.
SELECT city, state_province
, city || ' ' || state_province AS "wersja_operator"
, concat(city, ' ', state_province) AS "concat"
FROM locations;
-- W Oracle operator || nie ma problemów z nullami, gdyż w Oracle NULL jest utożsamiany z pustym tekstem.
-- '' i NULL to w Oracle to samo.
--* 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
-- Zgodnie z najlepszymi praktykami dane w bazach rozdziela się do osobnych tabel i wiąże za pomocą "kluczy obcych".
-- Często w zapytaniach chcemy jednak uzyskać rekordy z jednej tabeli wraz z powiązanymi danymi z innych tabel.
-- zajrzyjmy do tabel
SELECT * FROM employees;
SELECT * FROM jobs;
SELECT * FROM departments;
SELECT * FROM locations;
SELECT * FROM countries;
SELECT * FROM regions;
-- Dla odmiany widok emp_details_view prezentuje dane połączone, w wersji nieznormalizowanej,
-- zauważmy mnóstwo powtórzonych wartości w kolumnach city, country...
SELECT * FROM emp_details_view;
-- Jak samemu odczytać dane rozporoszone po wielu tabelach?
-- We FROM można podać więcej niż jedną tabelę:
SELECT * FROM employees, departments;
-- W takiej sytuacji baza danych zwraca wszystkie możliwe kombinacje rekordów z jednej tabeli z rekordami z drugiej tabeli
-- To jest tzw. "iloczyn kartezjański".
SELECT count(*) FROM employees, departments;
SELECT count(*) FROM employees; -- 107
SELECT count(*) FROM departments; -- 27
SELECT 107 * 27;
-- Aby powiązać pracownika z jego departamentem można:
-- 1) zadać zapytanie do obu tabel (rozdziela przecinek) a następnie w WHERE podać "warunek złączenia"
SELECT * FROM employees, departments
WHERE departments.department_id = employees.department_id;
-- 2) użyć operatora JOIN do połączenia tabel
SELECT * FROM employees JOIN departments
ON departments.department_id = employees.department_id;
-- JOIN oferuje więcej możliwości (LEFT JOIN, JOIN USING, NATURAL JOIN - to wszystko zaraz opowiemy)
-- składniowo JOIN jest częścią klauzuli FROM
-- przekonamy się pisząc zapytanie łączące 3 tabele
-- dodając warunek filtrowania
-- 1) Warunki dotyczące złączenia oraz zwykłe warunki logiczne są razem w WHERE
SELECT *
FROM employees, departments, locations
WHERE departments.department_id = employees.department_id
AND locations.location_id = departments.location_id
AND salary >= 10000;
-- 2) Po kolei dodajemy tabel i dla każdej od razu po JOIN wpisujemy warunek złączenia,
-- natomiast w WHERE mamy tylko inne warunki logiczne
SELECT *
FROM employees
JOIN departments ON departments.department_id = employees.department_id
JOIN locations ON locations.location_id = departments.location_id
WHERE salary >= 10000;
-- Aliasy tabel
-- Zamiast powtarzać długie nazwy tabel:
SELECT *
FROM employees, departments, public.locations
WHERE departments.department_id = employees.department_id
AND public.locations.location_id = departments.location_id;
-- ... można wprowadzić "aliasy tabel" w sekcji FROM:
SELECT *
FROM employees emp, departments d, public.locations l
WHERE d.department_id = emp.department_id
AND l.location_id = d.location_id;
-- Alias to nasza lokalna nazwa, którą nadajemy tabeli w obrębie jednego zapytania.
-- Alias wprowadzamy we FROM, a używamy w warunkach i pozostałych klauzulach zamiast nazwy tabeli
-- (nie możemy już używać oryginalnej nazwy tabeli!).
-- Są sytuacje, gdy alias tabeli jest niezbędny, aby napisać jakieś zapytanie (np. tzw. self join).
-- Uwaga składniowa: w Oraclu nie wolno pisać AS przed aliasem tabeli (w PostgreSQL i MySQL można, ale lepiej pisać tak, aby było przenośnie...).
--* Możliwości JOIN.
-- Są trzy sposoby podawania warunku złączenia: JOIN ON, JOIN USING, NATURAL JOIN
-- Są też różne "kierunki" złączeń (jak postępować z wartościami niepasującymi): INNER, LEFT, RIGHT, FULL
-- Dodatkowo istnieje CROSS JOIN
-- Daje to 3×4+1 = 13 różnych wersji JOINa :-)
-- Przegląd 3 sposobów podawania warunku złączenia
-- 1) tabela1 JOIN tabela2 ON warunek_logiczny
SELECT *
FROM employees e JOIN jobs j ON j.job_id = e.job_id;
-- 2) tabela1 JOIN tabela2 USING(kolumny)
-- Dobierane są takie rekordy, które mają jednakową wartość w tej kolumnie, która jest wymieniona w USING.
-- (teoretycznie może być podanych więcej kolumn)
SELECT *
FROM employees JOIN jobs USING(job_id);
-- 3) tabela1 NATURAL JOIN tabela2
-- Automatycznie znajdowane są kolumny, które mają jednakową nazwę w obu tabelach,
-- i wybierane są te kombinacje rekordów, gdzie wartości tych kolumn są równe.
SELECT *
FROM employees NATURAL JOIN jobs;
-- Dodatkowe szczegóły na temat JOIN ON / JOIN USING / NATURAL JOIN
-- NATURAL JOIN działa poprawnie tylko wtedy, gdy kolumna, po której łączymy,
-- ma identyczną nazwę i jest jedyną kolumną o wspólnej nazwie w tych tabelach.
SELECT *
FROM employees NATURAL JOIN departments;
-- Oprócz kolumny department_id istnieje też kolumna manager_id, która:
-- w tabeli employees mówi, kto jest szefem danej osoby
-- w tabeli departments mówi, kto jest szefem departamentu
-- to zapytanie jest równoważne:
-- (wynik zawiera tylko te osoby, których szef jest jednocześnie szefem departamentu)
SELECT *
FROM employees JOIN departments USING(department_id, manager_id);
-- Z kolei gdy nie ma żadnej wspólnej kolumny, to NATURAL JOIN też działa,
-- ale w wyniku daje wszystkie kombinacje, jak CROSS JOIN
SELECT *
FROM departments NATURAL JOIN countries;
-- JOIN ON jest najbardziej ogólny - pozwala wpisać dowolny warunek logiczny.
-- przydaje się szczególnie, gdy:
-- * warunek jest inny niż prosta równość; np. dobierając dane musimy zignorować wielkość liter,
-- trzeba wyciągnąć pole daty, wyciąć fragment napisu, albo porównać "w przybliżeniu"
-- * kolumna, po której łączymy tabele, ma różne nazwy w obu tabelach
-- Sztuczny przykład - połącz osoby z miastami tak, aby pierwsza miasta i nazwiska litera się zgadzała
SELECT first_name, last_name, city
FROM employees LEFT JOIN locations ON substr(last_name, 1, 1) = substr(city, 1, 1)
ORDER BY employee_id;
-- Utożsamianie kolumn w USING i NATURAL JOIN
-- Gdy używamy przecinka i WHERE albo JOIN ON, to w wynikach znajdą się kolumny z obu tabel.
-- Jeśli istnieją kolumny o tej samej nazwie, to obie będą obecne jako osobne kolumny.
-- Gdybyśmy teraz chcieli wskazać taką kolumnę w części WHERE, GROUP BY, ORDER BY albo SELECT, to musimy zrobić to poprzez alias lub nazwę tabeli.
-- Poniższe zapytanie jest błędne: nazwa job_id jest niejednoznacza:
SELECT *
FROM employees e JOIN jobs j ON j.job_id = e.job_id
WHERE job_id = 'ST_CLERK';
-- To jest OK:
SELECT *
FROM employees e JOIN jobs j ON j.job_id = e.job_id
WHERE j.job_id = 'ST_CLERK';
-- Gdy używamy USING albo NATURAL JOIN, to w wynikach znajdzie się tylko jeden egzemplarz kolumny, wg której łączymy.
-- Uwaga dot. Oracla, ale nie dotyczy Postgresa! :
-- W Oracle NIE MOŻNA odwoływać się tej kolumny poprzez alias/nazwę tabeli, można tylko bez prefiksu.
-- Poniższe zapytanie jest błędne w Oracle, niepotrzebny kwalifikator (prefiks)
SELECT *
FROM employees e JOIN jobs j USING(job_id)
WHERE j.job_id = 'ST_CLERK';
-- Błędny w Oracle jest również taki zapis
-- gdzie próbujemy pobrać wszystkie kolumny z jednej z tabel za pomocą tabela.*
SELECT e.*, job_id, j.job_title
FROM employees e JOIN jobs j USING(job_id);
-- Ale oba te zapytania zadziałały w PostgreSQL.
-- To jest OK w obu bazach:
SELECT *
FROM employees e JOIN jobs j USING(job_id)
WHERE job_id = 'ST_CLERK';
-- 4 "kierunki" złączeń
-- wprowadzenie w temat:
-- istnieje osoba, która nie ma ustawionego department_id
SELECT * FROM employees WHERE department_id IS NULL;
-- istnieją departamenty, w których nikt nie pracuje
SELECT * FROM departments
WHERE department_id NOT IN (SELECT DISTINCT department_id
FROM employees
WHERE department_id IS NOT NULL);
SELECT department_id FROM departments
EXCEPT SELECT department_id FROM employees;
-- INNER JOIN - złączenie wewnętrzne,
-- w wynikach pojawią się tylko te rekordy z lewej i prawej tabeli, dla których znaleziono dopasowanie po drugiej stronie
-- Tutaj 106 wyników.
-- Nie ma Kimberely Grant, bo ona ma NULLa w departamencie
-- Nie ma departamentów, w których nikt nie pracuje, np. Payroll.
SELECT *
FROM employees e INNER JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- słowo INNER jest opcjonalne, i gdy napiszemy samo JOIN , to zadziała tak, jak przy słowie INNER
SELECT *
FROM employees e JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- Pozostałe 3 rodzaje złączeń nazywane są złączeniami zewnętrznymi: LEFT, RIGHT i FULL
-- Za każdym z tych słów można dopisać OUTER, ale to nie wpływa na działanie.
-- LEFT JOIN - pojawią się wszystkie rekordy z lewej tabeli,
-- a z prawej tylko te, które zostały do czegoś dopasowane.
-- Tutaj w wynikach jest Kimberely Grant, ale nie ma nadmiarowych departamentów.
SELECT *
FROM employees e LEFT JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- OUTER nic nie znaczy, ale można sobie napisać
SELECT *
FROM employees e LEFT OUTER JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- RIGHT JOIN - pojawią się wszystkie rekordy z prawej tabeli,
-- a z lewej tylko te, które zostały do czegoś dopasowane.
-- Tutaj w wynikach nie ma Kimberely Grant, ale są te dodatkowe departamenty.
SELECT *
FROM employees e RIGHT JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- FULL JOIN - pojawią się wszystkie rekordy z lewej oraz prawej tabeli, połączone, jeśli się da.
-- Tutaj będzie zarówno K.Grant, jak i dodatkowe departamenty.
SELECT *
FROM employees e FULL JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- to samo!
SELECT *
FROM employees e FULL OUTER JOIN departments d ON d.department_id = e.department_id
ORDER BY e.employee_id, d.department_id;
-- dziwny przykład, aby pokazać kolejność słów kluczowych:
SELECT * FROM countries NATURAL FULL OUTER JOIN locations;
-- Poza 12 kombinacjami powyższych ustawień, mamy jeszcze CROSS JOIN / "złączenie krzyżowe",
-- czyli "iloczyn kartezjański" (Cartesian product)
-- kombinacja każdego rekordu z lewej tabeli z każdym rekordem z prawej; działa jak przecinek we FROM
SELECT first_name, last_name, city
FROM employees CROSS JOIN locations
ORDER BY 2, 1, 3;
SELECT 'Pracowniku ' || first_name || ' ' || last_name || ', jedziesz w delegację do ' || city
FROM employees CROSS JOIN locations
ORDER BY 1;
-- To, co wpisujemy jako elementy składowe JOIN, to są tzw "common table expressions".
-- W pewnych sytuacjach można brać je w nawiasy i zmieniać strukturę.
SELECT * FROM
(locations
LEFT JOIN countries USING(country_id)
LEFT JOIN regions USING(region_id))
RIGHT JOIN
(employees
LEFT JOIN departments USING(department_id))
ON locations.location_id = departments.location_id
ORDER BY employee_id;
-- Mini - ćwiczenia
-- Wypisz imię i nazwisko tych osób, które pracują w mieście Oxford
SELECT first_name, last_name
FROM employees
JOIN departments USING(department_id)
JOIN locations USING(location_id)
WHERE city = 'Oxford';
-- Przykład na zawodnikach:
-- Wypisz zawodników oraz ich trenerów łącząc na podstawie kraju
SELECT * FROM zawodnicy;
SELECT * FROM trenerzy;
SELECT * FROM zawodnicy JOIN trenerzy USING(kraj);
-- nie każdy zawodnik ma trenera, i nie każdy trener ma zawodników :)
SELECT * FROM zawodnicy FULL JOIN trenerzy USING(kraj);
/* Połącz tabele employees, jobs, departments, locations, countries, regions
tak, aby pojawiły się dane wszystkich pracowników.
Używając operatora || do łączenia tekstów,
stwórz zapytanie, które zwraca dane adresowe pracownika.
Kolejne kolumny to mają być tak jakby kolejne wiersze do wydrukowania na kopercie z korespondencją służbową.
*/
SELECT employee_id AS "id"
, first_name || ' ' || last_name AS "Pracownik"
, job_title AS "Stanowisko"
, department_name AS "Departament"
, street_address AS "Ulica"
, postal_code || ', ' || city AS "Miasto"
, country_name AS "Kraj"
FROM employees
LEFT JOIN jobs USING(job_id)
LEFT JOIN departments USING(department_id)
LEFT JOIN locations USING(location_id)
LEFT JOIN countries USING(country_id)
-- LEFT JOIN regions USING(region_id)
ORDER BY employee_id;
/* Kolumna manager_id w pracowniku mówi, kto jest jego szefem (podane jest id tego szefa).
Wypisz
- imię i nazwisko każdego pracownika X
- imię i nazwisko jego szefa
- nazwę departamentu, w którym pracuje pracownik X
- imię i nazwisko szefa tego departamentu
*/
SELECT * FROM employees;
SELECT e1.employee_id, e1.first_name, e1.last_name, e1.manager_id,
e2.employee_id, e2.first_name, e2.last_name
FROM employees e1 LEFT JOIN employees e2 ON e2.employee_id = e1.manager_id;
SELECT e.employee_id
, e.first_name || ' ' || e.last_name AS "Pracownik"
, m.first_name || ' ' || m.last_name AS "Szef"
, d.department_name AS "Departament"
, dm.first_name || ' ' || dm.last_name AS "Szef departamentu"
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN employees m ON e.manager_id = m.employee_id
LEFT JOIN employees dm ON d.manager_id = dm.employee_id
ORDER BY e.employee_id;
-- Inne sposoby odczytywania zależności hierarchicznych...
CREATE EXTENSION IF NOT EXISTS tablefunc;
SELECT * FROM connectby('employees', 'employee_id', 'manager_id', 'employee_id', '100', 0, '/')
AS managers(keyid int, parent_keyid int, level int, aaa text, pos int)
;
--* Operacje na zbiorach ("operacje teoriomnogościowe") *--
-- Przykład: nazwy krajów i miast
SELECT DISTINCT city FROM locations; -- 23
SELECT DISTINCT country_name FROM countries; -- 25
-- Jest jedna nazwa, która występuje w obu tabelach - Singapore
-- UNION ALL - konkatenacja rekordów
-- do wyników jednego zapytania dołączane są pod spodem wyniki kolejnego zapytania
-- wynik obejmuje 48 wierszy - Singapore jest 2 razy
SELECT city FROM locations
UNION ALL
SELECT country_name FROM countries;
-- UNION - suma zbiorów
-- w odróżnieniu od UNION ALL, wynik UNION nie zawiera powtórzeń
-- 47 wartości - Singapore tylko 1 raz
SELECT city FROM locations
UNION
SELECT country_name FROM countries;
-- INTERSECT - iloczyn zbiorów, przecięcie, część wspólna
-- wynikiem jest Singapore
SELECT city FROM locations
INTERSECT
SELECT country_name FROM countries;
-- EXCEPT - różnica zbiorów
-- 22 wartości - wszystkie miasta bez Singapuru
SELECT city FROM locations
EXCEPT
SELECT country_name FROM countries;
-- 24 wartości - wszystkie kraje bez Singapuru
SELECT country_name FROM countries
EXCEPT
SELECT city FROM locations;
-- Zgodnie ze standardem SQL powinno być EXCEPT, tak jest m.in. w PostgreSQL,
-- w starszych wersjach Oracle działał tylko "MINUS",
-- ale w Oracle 21 działa też już słowo EXCEPT
-- Gdy używa się operatorów zbiorów, to:
-- * nazwy kolumn sa ustalane w pierwszym SELECT
-- * liczba oraz typ kolumn muszą się zgadzać
-- * SELECTY moga skladac sie z klauzul FROM, WHERE, GROUP BY, HAVING
-- * natomiast ORDER BY moze wystapic tylko na samym koncu i posortuje wszystkie czesci lacznie
-- Najpierw zobaczymy bogatych, potem biednych, ale kazda z tych czesci nie bedzie w zaden specjalny sposob sortowana
SELECT first_name, last_name, salary
FROM employees
WHERE salary >= 10000
UNION ALL
SELECT first_name, upper(last_name), salary
FROM employees
WHERE salary < 5000;
-- Powiedzmy, że chciałbym mieć najpierw bogatych, potem biednych, ale każdą część alfabetycznie,
-- to nie da się w ten sposób, bo nie wolno wpisac ORDER BY przed UNION ALL:
/* SELECT first_name, last_name, salary
FROM employees
WHERE salary >= 10000
ORDER BY last_name
UNION ALL
SELECT first_name, last_name, salary
FROM employees
WHERE salary < 5000
ORDER BY last_name; */
-- Natomiast ORDER BY na końcu sortuje całe wyniki łącznie
SELECT first_name, last_name, salary
FROM employees
WHERE salary >= 10000
UNION ALL
SELECT first_name, last_name, salary
FROM employees
WHERE salary < 5000
ORDER BY last_name;
-- A jak zachować informacje z której części zapytania pochodzi dany rekord
SELECT first_name, last_name, salary, 'bogaty' AS status_majatkowy
FROM employees
WHERE salary >= 10000
UNION ALL
SELECT first_name, last_name, salary, 'biedny'
FROM employees
WHERE salary < 5000
ORDER BY status_majatkowy DESC, last_name, first_name;
-- Jeśli nie potrzebujemy wyświetlać żadnego fajnego tekstu, tylko chodzi nam o kolejność, w dodatkowej kolumnie może być numer
SELECT first_name, last_name, salary, 1
FROM employees
WHERE salary >= 10000
UNION ALL
SELECT first_name, last_name, salary, 2
FROM employees
WHERE salary < 5000
ORDER BY 4, 2, 1;
-- A ten numer możemy ostatecznie ukryć stosując podzapytanie.
SELECT first_name || ' ' || last_name AS kto, salary
FROM (SELECT first_name, last_name, salary, 1 AS status
FROM employees
WHERE salary >= 10000
UNION ALL
SELECT first_name, last_name, salary, 2
FROM employees
WHERE salary < 5000)
ORDER BY status, last_name, first_name;
-- W wyniki można wpleść dodatkowe wiersze nie pochodzące z bazy danych.
SELECT kto, salary
FROM (
SELECT 'Bogaci:' AS kto, NULL AS salary, 1 AS kolejnosc
UNION ALL
SELECT ' ' || first_name || ' ' || last_name, salary, 2
FROM employees
WHERE salary > 10000
UNION ALL
SELECT 'Biedni:', NULL, 3
UNION ALL
SELECT ' ' || first_name || ' ' || last_name, salary, 4
FROM employees
WHERE salary < 5000
ORDER BY 3, 1);
-- Popatrzmy do tabeli job_history...
-- Tam są informacje, że pracownik X wcześniej pracował w departamencie Y na stanowisku Z
SELECT * FROM job_history;
-- Zadanie:
-- Wypisz dane wszystkich pracowników i dla każdego podaj
-- employee_id, department_id, job_id
-- Oprócz danych o bieżącym statusie (wartość department_id, job_id z tabeli employees)
-- wypisz także informacje historyczne z job_history.
-- W pierwsze wersji wyświetl wyłącznie ID.
SELECT * FROM job_history ORDER BY employee_id, start_date;
SELECT employee_id, department_id, job_id, start_date, end_date
FROM job_history
UNION ALL
SELECT employee_id, department_id, job_id, hire_date, NULL
FROM employees
ORDER BY employee_id, end_date;
-- W drugiej wersji dołącz tabele, aby wypisać department_name i job_title,
-- wypisz imię i nazwisko
-- wypisz daty graniczne (w przypadku stanu bieżącego za początek przyjmij hire_date, a jako koniec NULL)
SELECT e.employee_id, e.first_name, e.last_name,
d.department_id, d.department_name,
j.job_id, j.job_title,
h.start_date, h.end_date
FROM job_history h
JOIN employees e ON e.employee_id = h.employee_id
JOIN jobs j ON j.job_id = h.job_id
JOIN departments d ON d.department_id = h.department_id
UNION ALL
SELECT e.employee_id, e.first_name, e.last_name,
department_id, d.department_name,
job_id, j.job_title,
e.hire_date, NULL
FROM employees e
JOIN jobs j USING(job_id)
LEFT JOIN departments d USING(department_id)
ORDER BY employee_id, end_date;
--* Konstrukcja CASE *--
-- dla KAŻDEGO pracownika, chcemy info, czy zarabia co najmnie 10 tys, czy mniej
-- W Oracle nie da się wpisać warunku logicznego w kaluzuli SELECT.
-- Wartości logiczne to nie są normalne wartości takie jak liczby, napisy itp.
-- W innych bazach często istnieje typ boolean - w Oracle nie
SELECT first_name, last_name, salary, salary >= 10000
FROM employees;
-- Konstrukcja CASE pozwala sprawdzić warunek logiczny i w zależności od niego zwrócić jeden lub inny wynik
SELECT first_name, last_name, salary,
CASE WHEN salary >= 10000 THEN 'bogaty' ELSE 'biedny' END
FROM employees;
-- CASE może mieć wiele gałęzi WHEN i może mieć jedną gałąź ELSE
-- Warunki są sprawdzane po kolei i wykonany zostanie pierwszy WHEN, który jest prawdziwy,
-- a ELSE, jeśli żaden WHEN nie był prawdziwy
-- a jeśli nie ma ELSE, to w tej sytuacji wynikiem jest NULL
-- Na końcu konstrukcji CASE trzeba napisać END
-- CASE ma dwa warianty jeśli chodzi o zapis
-- pierwszy wariant - wpisujemy warunki logiczne podobne do warunków WHERE
SELECT first_name, last_name, salary,
CASE
WHEN salary >= 15000 THEN 'bardzo bogaty'
WHEN salary >= 10000 THEN 'bogaty'
WHEN salary >= 5000 THEN 'średni'
ELSE 'biedny'
END AS status
FROM employees;
-- Jeśli nie ma gałęzi ELSE, a rekord nie pasuje do żadnego WHEN,
-- to wynikiem będzie NULL
SELECT first_name, last_name, salary,
CASE
WHEN salary >= 15000 THEN 'bardzo bogaty'
WHEN salary >= 10000 THEN 'bogaty'
END AS status
FROM employees;
-- warunek wyliczający się do NULL jest traktowany jak fałsz, podobnie jak w WHERE
SELECT first_name, last_name, commission_pct,
CASE
WHEN commission_pct >= 0.2 THEN 'wysoka prowizja'
ELSE 'niska prowizja'
END AS status
FROM employees;
SELECT first_name, last_name, commission_pct,
CASE
WHEN commission_pct >= 0.2 THEN 'wysoka prowizja'
WHEN commission_pct < 0.2 THEN 'niska prowizja'
ELSE 'brak prowizji'
END AS status
FROM employees;
SELECT first_name, last_name, hire_date,
CASE
WHEN extract(month FROM hire_date) = 1 THEN 'styczeń'
WHEN extract(month FROM hire_date) = 2 THEN 'luty'
WHEN extract(month FROM hire_date) = 3 THEN 'marzec'
WHEN extract(month FROM hire_date) = 4 THEN 'kwiecień'
WHEN extract(month FROM hire_date) = 5 THEN 'maj'
WHEN extract(month FROM hire_date) = 6 THEN 'czerwiec'
WHEN extract(month FROM hire_date) = 7 THEN 'lipiec'
WHEN extract(month FROM hire_date) = 8 THEN 'sierpień'
WHEN extract(month FROM hire_date) = 9 THEN 'wrzesień'
WHEN extract(month FROM hire_date) = 10 THEN 'październik'
WHEN extract(month FROM hire_date) = 11 THEN 'listopad'
WHEN extract(month FROM hire_date) = 12 THEN 'grudzień'
END AS miesiac
FROM employees;
-- drugi wariant - dopasowywanie wartości
-- Tylko raz podajemy wartość, którą chcemy sprawdzić (tutaj extract), a w WHEN wartości, do których porównujemy
SELECT first_name, last_name, hire_date,
CASE extract(month FROM hire_date)
WHEN 1 THEN 'styczeń'
WHEN 2 THEN 'luty'
WHEN 3 THEN 'marzec'
WHEN 4 THEN 'kwiecień'
WHEN 5 THEN 'maj'
WHEN 6 THEN 'czerwiec'
WHEN 7 THEN 'lipiec'
WHEN 8 THEN 'sierpień'
WHEN 9 THEN 'wrzesień'
WHEN 10 THEN 'październik'
WHEN 11 THEN 'listopad'
WHEN 12 THEN 'grudzień'
ELSE 'niewiadomoco'
END AS miesiac
FROM employees;
-- Oracle (nie działa w Postgresie) posiada też funkcję decode , która działa podobnie jak drugi wariant CASE'a,
-- ale jest bardziej zwięzła (ale też nieprzenośna).
-- decode(wejście, wartość1, wynik1, wartość2, wynik2, ....)
SELECT first_name, last_name, hire_date,
decode(extract(month FROM hire_date),
1 , 'styczeń',
2 , 'luty',
3 , 'marzec',
4 , 'kwiecień',
5 , 'maj',
6 , 'czerwiec',
7 , 'lipiec',
8 , 'sierpień',
9 , 'wrzesień',
10 , 'październik',
11 , 'listopad',
12 , 'grudzień') AS miesiac
FROM employees;
SELECT first_name, last_name, job_id,
decode(job_id, 'IT_PROG', 'programista', 'AD_PRES', 'prezes', 'ST_CLERK', 'magazynier', 'ktoś inny') AS kto
FROM employees
ORDER BY employee_id;
/* Na podstawie wysokości pensji ustalamy stawkę podatkową.
Gdy roczne dochody < 50 tys, to stawka wynosi 0.1
między 50 tys a 100 tys, to stawka wynosi 0.2
między 100 a 150 tys, to stawka wynosi 0.3
między 150 a 200 tys, to stawka wynosi 0.4
powyżej 200 tys 0.5
Najpierw wypisz stawkę dla pracownika
Ale później oblicz roczny podatek - w miarę możliwości oblicz zgodnie z zasadami podatku progresywnego,
że wyższy procent jest liczony tylko od kwoty powyżej progu.
*/
SELECT first_name, last_name, salary, 12*salary AS roczne,
CASE WHEN 12*salary < 50000 THEN 0.1
WHEN 12*salary < 100000 THEN 0.2
WHEN 12*salary < 150000 THEN 0.3
WHEN 12*salary < 200000 THEN 0.4
ELSE 0.5
END AS stawka
FROM employees;
-- przy uproszczonym podejściu trzeba by tę stawkę pomnożyć przez roczne zarobki
-- ale wtedy mielibyśmy efekt "przekroczenia progu o złotówkę"
-- Aby nie pisać 12*salary we wszystkich miejscach, za pomocą WITH obliczymy sobie tę wartość wcześniej
WITH emps_roczne AS (
SELECT employee_id, first_name, last_name, salary, 12*salary AS roczne
FROM employees)
SELECT first_name, last_name, salary, roczne,
CASE WHEN roczne < 50000 THEN 0.1 * roczne
WHEN roczne < 100000 THEN 0.1*50000 + 0.2 * (roczne - 50000)
WHEN roczne < 150000 THEN 0.1*50000 + 0.2*50000 + 0.3 * (roczne-100000)
WHEN roczne < 200000 THEN 0.1*50000 + 0.2*50000 + 0.3*50000 + 0.4*(roczne-150000)
ELSE 0.1*50000 + 0.2*50000 + 0.3*50000 + + 0.4*50000 + 0.5*(roczne-200000)
END AS podatek
FROM emps_roczne;
-- już niedostępne SELECT * FROM emps_roczne;
--* Funkcje agregujące *--
-- W zapytaniach można używać funkcji
SELECT lower(first_name), upper(last_name), sqrt(salary)
FROM employees;
-- ALE niektóre funkcje to są "funkcje agregujące" i one wpływają na zapytanie w taki sposób,
-- że zamiast wielu oddzielnych wyników, widzimy jeden wynik zbiorczy:
SELECT avg(salary)
FROM employees;
-- Zauważmy, że tylko sama nazwa funkcji decyduje o tym zachowaniu. Inna funkcja liczbowa daje 107 oddzielnych wyników.
-- Na takie funkcje mówimy "funkcja skalarna".
SELECT sqrt(salary)
FROM employees;
-- Istnieje 5 kanonicznych funkcji agregujących - dostępne w każdej bazie SQL:
SELECT count(salary), sum(salary), min(salary), avg(salary), max(salary)
FROM employees;
-- Funkcji count można używać na 3 sposoby:
-- count(*) - zwraca liczbę rekordów
-- count(kolumna) - ile niepustych (nie-NULLowych) wartości jest w tej kolumnie
-- count(distinct kolumna) - ile jest różnych niepustych wartości jest w tej kolumnie
SELECT count(*), count(department_id), count(DISTINCT department_id)
FROM employees;
/* Lista funkcji agregujących PostgreSQL:
https://www.postgresql.org/docs/current/functions-aggregate.html
*/
SELECT avg(salary), stddev(salary), variance(salary) FROM employees;
SELECT every(length(street_address) > length(city)) FROM locations;
SELECT * FROM locations
WHERE NOT length(street_address) > length(city);
SELECT string_agg(city, '; ') FROM locations;
SELECT string_agg(city, '; ') FROM (SELECT city FROM locations ORDER BY city) podzapytanie;
SELECT string_agg(DISTINCT last_name, ', ') FROM employees;
SELECT json_agg(city) FROM locations;
-- PostgreSQL pozwala na używanie słowa DISTINCT w większości funkcji agregujących.
-- Wtedy powstarzące się wartości są liczone tylko raz (jako jedna wartość).
-- Przeciwieństwem DISTINCT jest ALL - "licz wszystkie wartości", ale to jest ustawienie domyślne i tego się nie pisze.
SELECT avg(salary), avg(ALL salary), avg(DISTINCT salary)
FROM employees;
-- W PostgreSQL można też za wywołaniem funkcji agregującej dopisać FILTER
-- i warunek ograniczający zbiór wartości uwzględnianych przez funkcję.
SELECT count(*) AS wszyscy
, count(*) FILTER (WHERE salary >= 10000) AS bogaci
, count(*) FILTER (WHERE salary < 10000) AS biedni
FROM employees;
-- BTW, to da się zrobić jako podzapytanie i wtedy zadziała także w innych bazach danych.
SELECT (SELECT count(*) FROM employees) AS wszyscy
, (SELECT count(*) FROM employees WHERE salary >= 10000) AS bogaci
, (SELECT count(*) FROM employees WHERE salary < 10000) AS biedni
-- Oracle: FROM dual
;
-- Jeśli f.agr. połączymy z filtrowaniem WHERE, możemy obliczyć statystyki dla wybranych rekordów.
-- W szczególności można poznać liczbę rekordów spełniających warunek.
-- min, max, i średnia pensja programistów:
SELECT count(*), min(salary), max(salary), avg(salary)
FROM employees
WHERE job_id = 'IT_PROG';
--* Grupowanie *--
-- GROUP BY dzieli dane na grupy zwn na wartość podanego kryterium i dla każdej grupy oblicza statystyki / funkcje agregujące
-- W SELECT można wypisać wartość, po której grupowaliśmy, a do pozostałych kolumn trzeba zastosować funkcję agregującą.
SELECT job_id, count(*), min(salary), max(salary), avg(salary)
FROM employees
GROUP BY job_id;
-- porównajmy:
-- wartość bez agregacji - 107 oddzielnych wartości
SELECT salary FROM employees;
-- agregacja całej tabeli - pojedyncza wartość wynikowa
SELECT avg(salary) FROM employees;
-- grupowanie - tyle wyników, ile różnych wartości wybranego kryterium wystepuje w danych
SELECT avg(salary) FROM employees GROUP BY job_id;
-- HAVING to jest warunek nakładany na grupy, a nie na pojedyncze rekordy
-- spośród grup opartych o job_id wypisz tylko co najmniej 10-osobowe
SELECT job_id, count(*), min(salary), max(salary), avg(salary)
FROM employees
GROUP BY job_id
HAVING count(*) >= 10;
-- WHERE - warunki dot. pojedynczych rekordów stosowane przed grupowaniem
-- HAVING - warunki dotyczące całych rekordów, po grupowaniu
-- Bierzemy pracowników, którzy zarabiają < 6 tys, grupujemy wg stanowisk
-- i następnie wyliczamy średnią tylko dla tych pracowników.
-- Zauważmy, że:
-- 1) wypisuje się stanowisko ST_MAN, bo jeden z ST_MANów zarabia < 6 tys.
-- 2) dla stanowiska IT_PROG średnia wynosi 4600, bo uwzględniamy tylko tych 3 programistów, którzy zarabiają < 6tys.
SELECT job_id, count(*) AS ilu, avg(salary) AS srednia, min(salary) AS min, max(salary) AS max
FROM employees
WHERE salary < 6000
GROUP BY job_id;
-- Wszystkich pracowników grupujemy wg stanowisk i wyliczamy średnią dla całych grup.
-- A następnie wyświetlamy tylko te grupy (te stanowiska), na których średnia pensja > 6 tys.
-- Zauważmy, ze:
-- 1) NIE wypisuje się stanowisko ST_MAN, bo średnia ST_MANów wynosi 7280 i nie przechodzi przez HAVING
-- 2) dla stanowiska IT_PROG średnia wynosi 5760, bo to średnia wszystkich programistów
SELECT job_id, count(*) AS ilu, avg(salary) AS srednia, min(salary) AS min, max(salary) AS max
FROM employees
GROUP BY job_id
HAVING avg(salary) < 6000;
-- Przy okazji: Zamiast HAVING można też umieści zapytanie z GROUP BY jako podzapytanie
-- w zewnętrznym zapytaniu, a w tym zewnętrznym użyć WHERE.
SELECT * FROM (
SELECT job_id, count(*) AS ilu, avg(salary) AS srednia, min(salary) AS min, max(salary) AS max
FROM employees
GROUP BY job_id) podzapytanie
WHERE srednia < 6000;
-- NULL w grupowaniu tworzy oddzielną grupę
SELECT department_id, count(*), min(salary), max(salary), avg(salary)
FROM employees
GROUP BY department_id;
-- Zasadniczo gdy użyjemy funkcji agregującej lub zrobimy GROUP BY, to w klauzuli SELECT (oraz HAVING i ORDER BY)
-- możemy odwoływać się już tylko do:
-- - bezpośrednio wartości, po których grupowaliśmy
-- - pozostałych kolumn tylko poprzez funkcje agregujące
-- złe:
SELECT first_name, last_name, avg(salary)
FROM employees;
-- też złe:
SELECT first_name, last_name, job_id, max(salary)
FROM employees
GROUP BY job_id;
-- w SELECT wolno używać tylko to, po czym grupowaliśmy
-- (lub to, co jednoznacznie wynika z kolumn, po których grupowaliśmy - jeszcze będzie o tym mowa)
-- PostgreSQL pozwala uzywać aliasów kolumn w GROUP BY (Oracle nie pozwala)
SELECT extract(YEAR FROM hire_date) AS rok, count(*) AS ilu
FROM employees
GROUP BY rok
ORDER BY rok;
-- Zadania, które będą miały "ciąg dalszy..."
-- Stosujemy grupowanie wraz z łączeniem tabel...
-- Takie połączenie tabel pozwala uzyskać dostęp do kolumny job_title:
SELECT * FROM employees JOIN jobs USING(job_id);
-- Napisz zapytanie, które dla każdego stanowiska oblicza statystki pracowników:
-- count, min(salary), max(salary), avg(salary)
-- Należy wypisać job_title i te statystyki...
SELECT job_title
, count(*) AS ilu
, min(salary) AS minimalna
, max(salary) AS maksymalna
, avg(salary) AS srednia
FROM employees JOIN jobs USING(job_id)
GROUP BY job_title;
-- numer kolumny, po której grupujemy
SELECT job_title
, count(*) AS ilu
, min(salary) AS minimalna
, max(salary) AS maksymalna
, avg(salary) AS srednia
FROM employees JOIN jobs USING(job_id)
GROUP BY 1;
-- Zasadniczo w SELECT można używać tylko tych kolumn, po ktrych się grupowało
-- (a pozostałych tylko porpzez f. agr.). Np. Oracle przestrzega tego bardzo ściśle.
SELECT job_id, job_title
, count(*) AS ilu
, min(salary) AS minimalna
, max(salary) AS maksymalna
, avg(salary) AS srednia
FROM employees JOIN jobs USING(job_id)
GROUP BY job_id, job_title;
-- Pod pewnymi warunkami PostgreSQL jednak pozwala odwołać się do wartości,
-- która jednoznacznie wynika z tego, po czym grupowaliśmy.
SELECT job_id, job_title
, count(*) AS ilu
, min(salary) AS minimalna
, max(salary) AS maksymalna
, avg(salary) AS srednia
FROM jobs JOIN employees USING(job_id)
GROUP BY job_id;
-- Dalszy ciąg zadania...
-- Analogiczne zapytanie, ale dla departamentów.
-- Chcemy, aby wypisały się nazwy wszystkich departamentów, także tych, w których nikt nie pracuje (np. Payroll).
-- Chcemy mieć informację o liczbie pracowników tego departamentu oraz średniej pensji (min max też można, ale to nie jest najważniejsze).
-- Takie zapytanie dałoby bezsensowne wyniki - jedynka dla departamentów, w których nikt nie pracuje
SELECT department_id, department_name
, count(*) AS ilu
, min(salary) AS minimalna
, max(salary) AS maksymalna
, avg(salary) AS srednia
FROM departments LEFT JOIN employees USING(department_id)
GROUP BY department_id
ORDER BY department_id;
-- Zamiast count(*) trzeba użyć count(employee_id) lub count(employees)
-- A najlepiej napisać count(DISTINCT employee_id)
SELECT department_id, department_name
, count(DISTINCT employee_id) AS ilu
, min(salary) AS minimalna
, max(salary) AS maksymalna
, avg(salary) AS srednia
FROM departments LEFT JOIN employees USING(department_id)
GROUP BY department_id
ORDER BY department_id;
--* Grupowanie po wielu kryteriach, CUBE, ROLLUP, GROUPING SETS *--
-- <początek przykładów dot. tabeli sprzedaz>
SELECT * FROM sprzedaz;
-- specyfiką tej tabeli jest to, że istnieje mało wartości unikalnych, a za to one wiele razy się powtarzają
SELECT count(DISTINCT miasto), count(DISTINCT sklep), count(DISTINCT kategoria), count(DISTINCT towar)
FROM sprzedaz;
-- wartością transakcji jest iloczyn cena * sztuk
-- wyświetlmy taką kolumnę obok kolumn odczytanych z tabeli
-- btw: W Oracle nie wolno dopisywać kolumn po przecinku za *
-- SELECT *, cena * sztuk as wartosc FROM sprzedaz;
-- ale można, jeśli użyje się nazwy lub aliasu tabeli:
SELECT sprzedaz.*, cena*sztuk as wartosc FROM sprzedaz;
-- Oblicz sumę wartości wszystkich transakcji z całego pliku
SELECT sum(cena * sztuk) AS suma FROM sprzedaz;
-- Grupowanie zwn pojedyncze kryterium:
SELECT miasto, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY miasto;
SELECT towar, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY towar;
-- Można też grupować zwn na kilka kryteriów jednocześnie
-- Gdy podamy po prostu dwa kryteria po przecinku, to wyliczane statystyki dla każdej pary wartości,
-- jaka jest możliwa (o ile występuje w danych)
-- 72 wiersze
SELECT miasto, towar, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY miasto, towar
ORDER BY miasto, towar;
-- 223 wierszy
-- (w sklepie Podsiało nie ma mebli... ;) )
SELECT miasto, sklep, kategoria, towar,
count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY miasto, sklep, kategoria, towar
ORDER BY miasto, sklep, kategoria, towar;
-- konstrukcje CUBE, ROLLUP oraz GROUPING SET pozwalają dodać do wyników dodatkowe wiersze, "podsumy"
-- inaczej mówiąc, w jednym zapytaniu wykonujemy grupowanie na wielu poziomach
-- CUBE generuje wszystkie możliwe kombinacje kryteriów.
-- W tym przypadku oprócz wyników normalnego grupowania, mamy tez:
-- 1) dla każdego miasta podsumowanie tego miasta bez względu na towar ("wszystkie towary razem wzięte w danym mieście")
-- 2) dla każdego towaru sumę tego towaru bez względu na miasto
-- 3) sumę wszystkich rekordów z całej tabeli
-- W miejscu tego kryterium, które jest aktualnie pomijane, wstawiany jest NULL
SELECT miasto, towar
, count(*) AS ile
, sum(cena * sztuk) AS wartosc
FROM sprzedaz
GROUP BY CUBE(miasto, towar)
ORDER BY miasto, towar;
-- Gdy kryteriów grupowania jest dużo, to CUBE powoduje wygenerowanie bardzo wielu dodatkowych wierszy
-- bo na każdej pozycji może być NULL. Jednak zwykle jest tak, że niektóre częściowe podsumowania nie mają sensu.
-- 1288 wierszy
SELECT miasto, sklep, kategoria, towar,
count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY CUBE(miasto, sklep, kategoria, towar)
ORDER BY miasto, sklep, kategoria, towar;
-- ROLLUP dodaje wiersze z podsumami, ale tylko idąc od lewej do prawej; kolejność kryteriów grupowania ma tu znaczenie
-- W tym przykładzie będzie podobnie jak w CUBE, ale bez punktu 2 - podsumowań dla towarów
-- 81 wierszy
SELECT miasto, towar
, count(*) AS ile
, sum(cena * sztuk) AS wartosc
FROM sprzedaz
GROUP BY ROLLUP(miasto, towar)
ORDER BY miasto, towar;
-- 331 wierszy
SELECT miasto, sklep, kategoria, towar,
count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY ROLLUP(miasto, sklep, kategoria, towar)
ORDER BY miasto, sklep, kategoria, towar;
-- CUBE opłaca się używać wtedy, gdy kryteria grupowania są od siebie niezależne logicznie,
-- tak jak miast o i towar
-- Nie ma sensu używać CUBE gdy kryteria tworzą logiczna zależność, np. towary należą do różnych kategorii
-- Tutaj: nie ma sensu podsumowanie "wszystkich biurek z dowolnej kategorii", bo i tak każde biurko należy do kategorii meble
SELECT kategoria, towar
, count(*) AS ile
, sum(cena * sztuk) AS wartosc
FROM sprzedaz
GROUP BY CUBE(kategoria, towar)
ORDER BY kategoria, towar;
-- Tutaj sens ma ROLLUP
SELECT kategoria, towar
, count(*) AS ile
, sum(cena * sztuk) AS wartosc
FROM sprzedaz
GROUP BY ROLLUP(kategoria, towar)
ORDER BY kategoria, towar;
-- W ROLLUP znaczenie ma kolejność kryteriów.
-- Powinniśmy iść od lewej do prawej podając najpierw kryteria "nadrzędne", a później coraz bardziej "podrzędne".
-- To jest bez sensu:
SELECT towar, kategoria, count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY ROLLUP(towar, kategoria)
ORDER BY towar, kategoria;
-- Można też użyć GROUPING SET i samodzielnie określić jakie grupowania mają być przeprowadzone
-- To zapytanie jest równoważne ROLLUP(miasto, towar)
SELECT miasto, towar, count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY GROUPING SETS((miasto, towar), (miasto), ())
ORDER BY miasto, towar;
-- GROUPING SETS daje efekt, jak byśmy wykonali wiele zapytań z GROUP BY takich, jak podany w nawiasach,
-- uzupełni brakujące kolumny NULLAMI
-- i wszystko połączyli za pomocą UNION ALL
-- To samo, co wyżej:
SELECT miasto, towar, count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY miasto, towar
UNION ALL
SELECT miasto, NULL, count(*), sum(cena * sztuk)
FROM sprzedaz
GROUP BY miasto
UNION ALL
SELECT NULL, NULL, count(*), sum(cena * sztuk)
FROM sprzedaz
ORDER BY 1, 2;
-- Wszystkie sensowne grupowania dla czterech kryteriów - więcej niż ROLLUP, ale mniej niż CUBE
-- 439 wierszy
SELECT miasto, sklep, kategoria, towar,
count(*) AS l_transakcji, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY GROUPING SETS (
(miasto, sklep, kategoria, towar),
(miasto, sklep, kategoria),
(miasto, sklep),
(miasto, kategoria, towar),
(miasto, kategoria),
(miasto),
(kategoria, towar),
(kategoria),
())
ORDER BY miasto, sklep, kategoria, towar;
-- </koniec przykładów dot. tabeli sprzedaz>
-- Wracamy do employees i na ich przykładzie zajmiemy się tematem rozpoznawiania,
-- który wiersz pochodzi z normalnego grupowania,
-- a który z dodatkowej podsumy...
-- Problem - ponieważ Kimberely Grant ma wpisany NULL w department_id,
-- to wartość NULL w wyniku CUBE lub ROLLUP będzie pojawiać się dwa razy.
-- Raz jako "prawdziwe department_id", a drugi raz jako "wygenerowany dodatkowy wiersz, w którym department_id nie ma znaczenia".
SELECT department_id, job_id, count(*), avg(salary)
FROM employees
GROUP BY CUBE(department_id, job_id)
ORDER BY department_id, job_id;
-- Jak odróżnić sytuację gdzie null faktycznie występował w danych (np. department_id dla K.Grant)
-- od sytuacji gdy ten null jest wypełniaczem w wierszu dodatkowego podsumowania ("dla wszystkich departamentów razem wziętych...")
-- Ta funkcja zwraca 0, gdy wartością danej kolumny jest faktyczna wartość pochodząca z tabeli
-- a 1 gdy pomijamy wartości tej kolumny, bo robimy podsumowanie "bez względu na tę kolumnę" (i jest w niej sztucznie dodany null)
SELECT department_id, grouping(department_id) AS grouping_dep
, job_id, grouping(job_id) AS grouping_job
, count(*) AS ilu
, sum(salary) AS suma
FROM employees
GROUP BY CUBE(department_id, job_id)
ORDER BY grouping_dep, department_id, grouping_job, job_id;
-- Połącz odpowiednie tabele i pogrupuj po 3 kryteriach używając ROLLUP:
-- city, department_name, job_title
-- W tej wersji null pojawia się zarówno z powodu podsumowania, jak i gdy występuje w danych
SELECT city, department_name, job_title
, count(*) AS ilu
, sum(salary) AS suma
, round(avg(salary), 2) AS srednia
FROM employees
LEFT JOIN departments USING(department_id)
LEFT JOIN locations USING(location_id)
LEFT JOIN jobs USING(job_id)
GROUP BY ROLLUP(city, department_name, job_title)
ORDER BY city, department_name, job_title;
-- Za pomocą CASE i funkcji GROUPING możemy wpisać specjalny tekst w miejscach podsumowań
SELECT CASE WHEN grouping(l.city) = 1 THEN '--MIASTA RAZEM--' ELSE l.city END AS city
, CASE WHEN grouping(d.department_name) = 1 THEN '--DEPARTAMENTY RAZEM--' ELSE d.department_name END AS department_name
, CASE WHEN grouping(j.job_title) = 1 THEN '--JOBY RAZEM--' ELSE j.job_title END AS job_title
, count(*) AS ilu
, sum(salary) AS suma
, round(avg(salary), 2) AS srednia
FROM employees e
LEFT JOIN departments d USING(department_id)
LEFT JOIN locations l USING(location_id)
LEFT JOIN jobs j USING(job_id)
GROUP BY ROLLUP(l.city, d.department_name, j.job_title)
ORDER BY l.city, d.department_name, j.job_title;
--* Temat Tabel przestawnych" *--
-- Czyli tworzenia kolumn na podstawie wierszy w celu dwuwymiarowanej przezentacji danych.
-- Takie zapytanie zwraca dane w wymiarze pionowym
SELECT miasto, kategoria, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY miasto, kategoria
ORDER BY miasto, kategoria;
-- w PostgreSQL można skorzystać z funkcji crosstab,
-- która należy do rozszerzenia table_func. W praktyce wystarczy je aktywować.
-- (tylko raz dla danej bazy danych, a nie trzeba w każdej nowej sesji)
CREATE EXTENSION IF NOT EXISTS tablefunc;
-- https://www.postgresql.org/docs/current/tablefunc.html
SELECT * FROM crosstab('
SELECT miasto, kategoria, sum(cena * sztuk) AS suma
FROM sprzedaz
GROUP BY miasto, kategoria
ORDER BY miasto, kategoria')
AS sumy(miasto VARCHAR(100),
meble NUMERIC,
"szkolno-biurowe" NUMERIC,
"wyposażenie szkolne" NUMERIC);
-- Informacyjnie: W Oraclu można zrobić to tak:
SELECT * FROM (
SELECT miasto, kategoria, round(cena * sztuk) AS wartosc
FROM sprzedaz
) PIVOT (sum(wartosc)
FOR kategoria IN (
'meble' AS "meble",
'szkolno-biurowe' AS "szkb",
'wyposażenie szkolne' AS "wypszk"
)
);
-- Wersja Python / Pandas
-- sprzedaz.pivot_table(columns=['kategoria', 'towar']
-- index=['miasto', 'sklep'], values='wartosc', aggfunc='sum', margins=True)
--* 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