├── 1-1 └── sql.md ├── 2-1 └── 2_1.png ├── 2-2 ├── data-2.py ├── data-3.py ├── data-4.py ├── data-5.py ├── data-6.py ├── data-get.py ├── names.txt └── resources.sql ├── 2-3 ├── 1.sql ├── 2.sql ├── 3.sql └── 4.sql ├── 2-4 ├── 1.sql ├── 2.sql └── trigger.sql ├── 3-1 ├── 3_1.png ├── generate_enormous_csv.py ├── stats-plus-two-tables.sql └── stats.sql ├── 3-2 └── users.sql ├── 3-3 └── function.sql ├── 3-4 └── indexes.sql └── README.md /1-1/sql.md: -------------------------------------------------------------------------------- 1 | ## 1 2 | Среднее количество билетов в каждой брони 3 | ```sql 4 | select avg(t.c) FROM (select book_ref, count(ticket_no) as c from bookings.tickets GROUP BY book_ref) as t; 5 | ``` 6 | ## 2 7 | Считает, сколько раз самолёты прибывали и улетали в/из этого аэропорта. 8 | ```sql 9 | SELECT airport_code, airport_name, city, departure_times, arrive_times 10 | FROM airports 11 | INNER JOIN 12 | ( 13 | SELECT departure_airport AS airport_code, COUNT(departure_airport) AS departure_times 14 | FROM 15 | flights AS f 16 | INNER JOIN airports_data ON departure_airport = airport_code 17 | GROUP BY departure_airport 18 | ORDER BY departure_airport 19 | ) 20 | AS d_count_table 21 | USING(airport_code) 22 | INNER JOIN 23 | ( 24 | SELECT arrival_airport AS airport_code, COUNT(arrival_airport) AS arrive_times 25 | FROM 26 | flights AS f 27 | INNER JOIN airports_data ON departure_airport = airport_code 28 | GROUP BY arrival_airport 29 | ORDER BY arrival_airport 30 | ) 31 | AS a_count_table USING(airport_code); 32 | ``` 33 | ## 3 34 | Показать сколько каких классов обслуживания было 35 | ```sql 36 | SELECT seats.fare_conditions, COUNT(*) FROM seats GROUP BY seats.fare_conditions; 37 | ``` 38 | ## 4 39 | Показать, какой пассажир в каком классе летал 40 | ```sql 41 | SELECT book_ref, 42 | to_char(book_date, 'YYYY') as y, 43 | to_char(book_date, 'MM') as m, 44 | to_char(book_date, 'DD') as d 45 | FROM bookings.bookings 46 | WHERE total_amount > 30000.00 47 | ORDER BY y, m, d; 48 | ``` 49 | 50 | ## Задания на паре 51 | 52 | 1. вывести всех пассажиров, у которых уровень обсл. наимеенее популярный среди всех 53 | ```sql 54 | SELECT passenger_name, fare_conditions 55 | FROM 56 | ticket_flights AS tf 57 | INNER JOIN 58 | (SELECT 59 | ticket_flights.fare_conditions, 60 | COUNT(*) as c 61 | FROM Ticket_flights 62 | GROUP BY ticket_flights.fare_conditions 63 | ORDER BY c ASC 64 | LIMIT 1) as least_popular 65 | USING (fare_conditions) 66 | INNER JOIN tickets USING (ticket_no); 67 | ``` 68 | ALT 69 | ```sql 70 | WITH 71 | least_popular as 72 | (SELECT 73 | ticket_flights.fare_conditions, 74 | COUNT(*) as c 75 | FROM Ticket_flights 76 | GROUP BY ticket_flights.fare_conditions 77 | ORDER BY c ASC 78 | LIMIT 1) 79 | SELECT passenger_name, fare_conditions 80 | FROM tickets INNER JOIN ticket_flights USING (ticket_no) 81 | WHERE fare_conditions IN (SELECT fare_conditions from least_popular) 82 | 83 | limit 1000; 84 | ``` 85 | 2. посчитать топ аэропортов по суммарным стоимостям бронирования (dep + arr, каждая бронь уч. 2 раза (если не один и тот же город))) 86 | ```sql 87 | SELECT airport_name, sum_1 + sum_2 as sum 88 | FROM 89 | (SELECT departure_airport as airport_code, sum(total_amount) as sum_1 90 | FROM 91 | bookings 92 | LEFT OUTER JOIN 93 | tickets USING (book_ref) 94 | INNER JOIN 95 | ticket_flights USING (ticket_no) 96 | INNER JOIN 97 | flights USING (flight_id) 98 | GROUP BY departure_airport 99 | LIMIT 50 100 | ) as d_table 101 | INNER JOIN 102 | (SELECT arrival_airport as airport_code, sum(total_amount) as sum_2 103 | FROM 104 | bookings 105 | LEFT OUTER JOIN 106 | tickets USING (book_ref) 107 | INNER JOIN 108 | ticket_flights USING (ticket_no) 109 | INNER JOIN 110 | flights USING (flight_id) 111 | GROUP BY arrival_airport 112 | 113 | LIMIT 50 114 | ) as a_table 115 | USING (airport_code) 116 | INNER JOIN airports USING (airport_code) 117 | ORDER BY sum 118 | ; 119 | ``` 120 | 121 | 122 | правда ли, что самолет имеет бОльшую дальность, чем другие из той же модели: 123 | ```sql 124 | select model, range, range > ( 125 | select avg(range) from aircrafts where model like 126 | concat(SUBSTR(model,1, POSITION(' ' IN model)), $$%$$) 127 | ) 128 | from aircrafts 129 | ``` 130 | 131 | . 132 | . 133 | . 134 | . 135 | . 136 | . 137 | . 138 | . 139 | . 140 | . 141 | . 142 | . 143 | . 144 | . 145 | . 146 | . 147 | . 148 | ## Намётки 149 | 150 | ```sql 151 | SELECT city FROM weather 152 | WHERE temp = (SELECT max(temp) FROM weather) 153 | ``` 154 | 155 | ```sql 156 | select * from bookings.seats as s, bookings.aircrafts_data as a 157 | where a.aircraft_code = s.aircraft_code; 158 | ``` 159 | можно заюзать USING тут 160 | ```sql 161 | T1 INNER T2 USING (id) 162 | ``` 163 | 164 | 165 | ```sql 166 | SELECT string_agg(a, ',' ORDER BY b DESC) FROM table 167 | ``` 168 | 169 | ```sql 170 | SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) 171 | FROM empsalary; 172 | ``` 173 | 174 | Пример: показать номер по зарплате отдела: 175 | 176 | ```sql 177 | SELECT depname, empno, salary, 178 | rank() OVER (PARTITION BY depname ORDER BY salary DESC) 179 | FROM empsalary; 180 | ``` 181 | Данный запрос покажет только те строки внутреннего запроса, у которых `rank` (порядковый номер) меньше 3: 182 | ```sql 183 | SELECT depname, empno, salary, enroll_date 184 | FROM 185 | (SELECT depname, empno, salary, enroll_date, 186 | rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos 187 | FROM empsalary 188 | ) AS ss 189 | WHERE pos < 3; 190 | ``` 191 | 192 | Вывести самый населённый город в каждом штате: 193 | ```sql 194 | SELECT name, 195 | (SELECT max(population) FROM cities WHERE cities.state = states.name) 196 | FROM states; 197 | ``` 198 | 199 | ```sql 200 | SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit 201 | FROM products p LEFT JOIN sales s USING (product_id) 202 | WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks' 203 | GROUP BY product_id, p.name, p.price, p.cost 204 | HAVING sum(p.price * s.units) > 5000; 205 | ``` -------------------------------------------------------------------------------- /2-1/2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-danya/postgres-study/071c691d33be7e97c03e56ebaee908ccb555b24a/2-1/2_1.png -------------------------------------------------------------------------------- /2-2/data-2.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | for i in range(10): 3 | print(f"({i+1}, {randint(1, 10)}, {randint(1, 10)}, {randint(0, 100)}.{randint(0, 9)}{randint(0,9)}, {randint(1000, 1000000)}),") -------------------------------------------------------------------------------- /2-2/data-3.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | data = (1, 9, 1, 93.22, 168012),(2, 9, 4, 83.12, 987365),(3, 5, 1, 62.44, 731303),(4, 4, 5, 6.01, 15003),(5, 10, 3, 13.06, 640765),(6, 5, 1, 38.75, 21758),(7, 4, 3, 62.42, 370100),(8, 2, 4, 72.09, 610735),(9, 7, 7, 37.73, 23168),(10, 6, 4, 4.00, 302852) 3 | 4 | for i in range(10): 5 | already_got = (data[i][4] * (100-data[i][3])/100) * randint(50, 100) / 100 6 | a = randint(10, 20) 7 | b = randint(10, 30) 8 | c = 100 - a - b 9 | # year = randint(2005, 2010) 10 | print(f"({i+1}, 2018, {(already_got * a / 100):.2f}),") 11 | print(f"({i+1}, 2019, {(already_got * b / 100):.2f}),") 12 | print(f"({i+1}, 2020, {(already_got * c / 100):.2f}),") -------------------------------------------------------------------------------- /2-2/data-4.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from random import random 3 | 4 | names = ['1Мультифазный перекачивающий насос двухвинтовый 2ВВ', # нефть 5 | '2Цементировочный агрегат ЦА-320 (АЦ-32) на шасси УРАЛ 4320', # транспорт 6 | '3Агрегат для исследования скважин АИС-1 на Урал 4320',# транспорт 7 | '4Буровой насос F-1300', # нефть 8 | '5Устьевые елки Ду 65 мм АФК 2 с двумя дополнительными задвижками на рабочее давление 21,35 Мпа', # газ 9 | '6Клапан отсекающий КО 302 М Ду-100, 80 Ру-32 МПа (320), 16 МПа (160)', # газ 10 | '7Агрегат для депарафинизации скважин АДПМ 12/150 на шасси Урал 4320 (6х6)', # транспорт 11 | '8Лодка для добычи родонита', # родонит 12 | '9Лодка для добычи алмазов Б-53 11134к', # алмазы 13 | '10Автоцистерна АЦПТ-9,5 на шасси УРАЛ 4320' # транспорт 14 | ] 15 | ONE = 'оборудование для добычи' 16 | TWO = 'транспорт' 17 | cat = [ONE, TWO, TWO, ONE, ONE, ONE, TWO, ONE, ONE, TWO] 18 | for i in range(10): 19 | print(f"({i + 1}, '{names[i]}', {random() * 10000:.2f}, {random() + randint(50000, 50000000):.2f}, '{cat[i]}'),") -------------------------------------------------------------------------------- /2-2/data-5.py: -------------------------------------------------------------------------------- 1 | from random import random, randint 2 | id = 1 3 | ONE = 'оборудование для добычи' 4 | TWO = 'транспорт' 5 | cat = [ONE, TWO, TWO, ONE, ONE, ONE, TWO, ONE, ONE, TWO, ONE, ONE, ONE] 6 | 7 | # (id, equipment_model_id, decommissioning_year) 8 | for i in range(10): 9 | year = randint(2024, 2046) 10 | for j in range(randint(1, 3)): 11 | model = randint(1, 10) 12 | print(f"({id}, {model}, {year}), -- {cat[model - 1]}") 13 | id += 1 -------------------------------------------------------------------------------- /2-2/data-6.py: -------------------------------------------------------------------------------- 1 | from random import random, randint, choice 2 | id = 1 3 | ONE = 'оборудование для добычи' 4 | TWO = 'транспорт' 5 | cat = [ONE, TWO, TWO, ONE, ONE, ONE, TWO, ONE, ONE, TWO, ONE, ONE, ONE] 6 | 7 | for i, c in enumerate(cat): 8 | # if c == ONE: 9 | # print(f"({i + 1}, {randint(1, 100)}),") 10 | if c == TWO: 11 | print(f"({i + 1}, {randint(1, 100)}, {choice(['Low', 'Normal', 'High'])}),") -------------------------------------------------------------------------------- /2-2/data-get.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | lst = [] 3 | with open('names.txt') as file: 4 | for line in file: 5 | s = line.split() 6 | name = " ".join(s[:3]) 7 | dob = s[3] 8 | #(name, date_of_birth, point_id, salary) 9 | print(f"('{name}', '{dob}', {randint(1, 10)}, {randint(15000, 150000)}),") 10 | -------------------------------------------------------------------------------- /2-2/names.txt: -------------------------------------------------------------------------------- 1 | Арсеиньев Вячеслав Германович 17.01.1985 2 | Труфанов Семен Егорович 20.06.1971 3 | Сагадиев Антон Кириллович 07.01.1970 4 | Желдин Даниил Федотович 22.03.1969 5 | Нугаев Юрий Адамович 23.12.1973 6 | Юферев Игнат Маркович 16.11.1972 7 | Долженко Федор Кириллович 24.07.1995 8 | Вазов Иван Даниилович 20.11.1982 9 | Касимов Петр Яковлевич 06.05.1983 10 | Шерстов Алексей Панкратович 17.02.1961 11 | Рожков Валерий Григорьевич 10.10.1964 12 | Мишутин Роман Игнатьевич 14.11.1981 13 | Рудов Георгий Константинович 28.07.1985 14 | Назаров Никита Лукьевич 02.04.1994 15 | Дернов Петр Денисович 08.06.1975 16 | Колдаев Егор Степанович 12.09.1968 17 | Шулепин Илья Львович 09.09.1994 18 | Ижутин Степан Григорьевич 18.09.1966 19 | Ерохин Макар Никитьевич 03.01.1990 20 | Блок Степан Давидович 10.12.1992 21 | Витвинский Арсений Иванович 10.12.1969 22 | Труш Севастьян Фадеевич 24.01.1976 23 | Исмайлов Николай Ильич 23.08.1967 24 | Мясников Антон Савванович 08.02.1963 25 | Козловский Савва Николаевич 04.03.1970 26 | Юшков Тимофей Семенович 08.12.1982 27 | Сапогов Юлиан Акимович 21.10.1990 28 | Пудин Валерий Нифонтович 14.07.1984 29 | Коптильников Геннадий Ипполитович 06.08.1962 30 | Уланов Прохор Иванович 26.10.1994 31 | Нестеров Максим Сергеевич 24.08.1971 32 | Кинжаев Тимофей Наумович 10.08.1964 33 | Ханипов Алексей Климентьевич 12.05.1973 34 | Зырянов Дмитрий Арсеньевич 20.07.1963 35 | Заславский Феликс Трофимович 02.11.1970 36 | Болтунов Петр Ильич 20.04.1991 37 | Ухов Феликс Даниилович 17.12.1986 38 | Бояринов Арсений Семенович 17.08.1987 39 | Николаевский Адам Герасимович 15.05.1982 40 | Тихвинский Иван Прокопьевич 03.03.1975 41 | Чернобровин Филипп Семенович 28.08.1968 42 | Солдатов Трофим Константинович 27.04.1971 43 | Журавлев Валерий Максимович 27.05.1989 44 | Ярема Никита Адамович 01.07.1995 45 | Грибанов Тарас Леонтьевич 24.10.1992 46 | Николаев Максим Юрьевич 03.01.1983 47 | Фомичев Степан Ильич 14.10.1977 48 | Дудко Николай Вениаминович 12.05.1960 49 | Адаксин Лев Никифорович 08.08.1964 50 | Есиков Венедикт Панкратович 16.07.1981 51 | Жиганов Роман Игнатьевич 16.01.1967 52 | Ячиков Иннокентий Валерианович 20.08.1973 53 | Моргунов Даниил Михаилович 15.11.1985 54 | Сенотрусов Григорий Юринович 09.10.1983 55 | Курпатов Захар Георгиевич 13.03.1977 56 | Сонин Георгий Евгеньевич 17.01.1962 57 | Шелепин Филипп Филиппович 10.06.1982 58 | Драгомиров Михаил Романович 10.06.1988 59 | Янкелевич Антон Тимофеевич 07.12.1990 60 | Репин Дмитрий Герасимович 18.02.1960 61 | Слобожанин Кирилл Романович 07.06.1964 62 | Хренов Василий Порфирьевич 04.07.1974 63 | Надервель Павел Николаевич 18.05.1994 64 | Журавлёв Валентин Николаевич 22.10.1976 65 | Ярцов Геннадий Климентович 25.11.1990 66 | Яфаев Ростислав Семенович 24.08.1967 67 | Борцов Севастьян Семенович 26.12.1974 68 | Воронцов Севастьян Трофимович 14.07.1995 69 | Игнатов Никита Маркович 15.07.1994 70 | Немов Федот Макарович 25.01.1965 71 | Квартин Аркадий Антонрвич 19.05.1977 72 | Наполов Феликс Феодосивич 20.11.1967 73 | Ясинов Денис Власович 19.05.1990 74 | Казарезов Ефрем Игнатьевич 20.12.1977 75 | Тихонов Филипп Нифонтович 06.04.1980 76 | Шубин Адам Иннокентиевич 03.07.1968 77 | Грибалев Егор Прохорович 28.10.1990 78 | Теплухин Прохор Степанович 04.08.1993 79 | Житков Максим Алексеевич 09.07.1980 80 | Кулдошин Игнат Витальевич 10.04.1984 81 | Янушевский Семен Саввеевич 15.01.1990 82 | Зуб Ефрем Наумович 23.02.1976 83 | Барсуков Виталий Степанович 26.06.1968 84 | Ширинов Севастьян Петрович 23.09.1986 85 | Кабаидзе Леонид Максимович 24.05.1984 86 | Цой Тарас Ефимович 17.08.1985 87 | Цыгвинцев Никифор Ефимович 09.03.1990 88 | Циолковский Арсений Давидович 17.09.1975 89 | Гнусарев Аркадий Петрович 09.06.1981 90 | Яночкин Яков Иванович 05.04.1991 91 | Грицевец Константин Тимофеевич 11.11.1961 92 | Кондраков Виктор Валерьевич 13.10.1970 93 | Кацен Юрин Ильич 01.10.1996 94 | Анохин Петр Венедиктович 21.09.1962 95 | Корнилов Адам Максимоич 04.08.1979 96 | Голубчиков Константин Васильевич 18.11.1972 97 | Гремпель Севастьян Емельянович 23.11.1973 98 | Тамахин Егор Викторович 13.08.1961 99 | Сязи Ефим Павлович 01.08.1979 100 | Суворов Максим Максимоич 17.11.1986 -------------------------------------------------------------------------------- /2-2/resources.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS resources CASCADE; 2 | CREATE SCHEMA resources; 3 | 4 | SET search_path TO resources; 5 | 6 | 7 | CREATE TABLE points ( 8 | id SERIAL 9 | PRIMARY KEY, 10 | name varchar(256) 11 | NOT NULL, 12 | region varchar(256) 13 | NOT NULL, 14 | company varchar(256) 15 | NULL 16 | ); 17 | 18 | CREATE TABLE resources ( 19 | id SERIAL 20 | PRIMARY KEY, 21 | name varchar(256) 22 | NOT NULL, 23 | price numeric(10, 2) 24 | NOT NULL 25 | CONSTRAINT positive_resource_price CHECK (price > 0), 26 | type varchar(256) 27 | NOT NULL, 28 | unit varchar(256) 29 | NOT NULL, 30 | image_url varchar(256) 31 | ); 32 | 33 | CREATE TABLE deposits ( 34 | id SERIAL 35 | PRIMARY KEY, 36 | resource_id integer 37 | REFERENCES resources ON DELETE RESTRICT 38 | NOT NULL, 39 | point_id integer 40 | REFERENCES points ON DELETE CASCADE 41 | NOT NULL, -- при удалении пункта удаляются и месторождения 42 | percents_left numeric(4, 2) 43 | DEFAULT 100 NOT NULL 44 | CONSTRAINT check_percents_left CHECK (percents_left >= 0 and percents_left <= 100), 45 | initial_amount integer 46 | NOT NULL 47 | CONSTRAINT positive_initial_amount CHECK (initial_amount > 0) -- in units! 48 | ); 49 | 50 | 51 | CREATE TABLE workers ( 52 | id SERIAL PRIMARY KEY, 53 | name varchar(256) 54 | NOT NULL, 55 | date_of_birth date 56 | CONSTRAINT ok_dob CHECK (date_of_birth > date '1920-01-01' and date_of_birth < current_timestamp), 57 | deposit_id integer 58 | NULL 59 | DEFAULT NULL -- по умолчанию человек безработный 60 | REFERENCES deposits ON DELETE SET DEFAULT, -- когда месторождение закрывается, человек становится безработным 61 | salary numeric(10, 2) 62 | DEFAULT 1000 NOT NULL 63 | CONSTRAINT positive_salary CHECK (salary > 0) 64 | ); 65 | 66 | 67 | 68 | 69 | 70 | CREATE TABLE extracted_stats ( 71 | deposit_id integer 72 | REFERENCES deposits ON DELETE CASCADE -- при удалении месторождения стирается и статистика 73 | NOT NULL, 74 | year integer 75 | DEFAULT date_part('year', current_timestamp) 76 | CONSTRAINT stats_year_ok CHECK (year > 1900 and year <= date_part('year', current_timestamp)), 77 | obtained_units numeric(10, 2) 78 | NOT NULL, 79 | PRIMARY KEY (deposit_id, year) 80 | ); 81 | 82 | 83 | CREATE TABLE equipment_models ( 84 | id SERIAL 85 | PRIMARY KEY, 86 | name varchar(256) 87 | NOT NULL, 88 | weight numeric(8, 2) 89 | NULL 90 | CONSTRAINT positive_weight_of_eqiupment CHECK (weight > 0), 91 | cost numeric(10, 2) 92 | NULL 93 | CONSTRAINT positive_cost_of_equipment CHECK (cost > 0), 94 | category varchar(256) 95 | NOT NULL 96 | ); 97 | 98 | CREATE TABLE equipment_units ( 99 | id SERIAL 100 | PRIMARY KEY, 101 | equipment_model_id integer 102 | REFERENCES equipment_models ON DELETE RESTRICT 103 | NOT NULL, 104 | decommissioning_year integer 105 | NOT NULL 106 | CONSTRAINT dec_year_ok CHECK (decommissioning_year > 1900 and decommissioning_year > date_part('year', current_timestamp)) 107 | ); 108 | 109 | CREATE TABLE equipment_availability ( 110 | deposit_id integer 111 | REFERENCES deposits ON DELETE CASCADE 112 | NOT NULL, -- при удалении месторождения стираем информацию об оборудовании 113 | equipment_unit_id integer 114 | REFERENCES equipment_units ON DELETE CASCADE 115 | NOT NULL, -- при удалении единицы оборудования стираем информацию о её наличии 116 | PRIMARY KEY (deposit_id, equipment_unit_id) 117 | ); 118 | 119 | 120 | CREATE TABLE mining_equipment_stats ( 121 | model_id integer 122 | PRIMARY KEY 123 | REFERENCES equipment_models ON DELETE CASCADE, 124 | mining_speed numeric(8, 2) 125 | NULL 126 | CONSTRAINT positive_mining_speed CHECK (mining_speed > 0) 127 | ); 128 | 129 | CREATE TABLE transport_stats ( 130 | model_id integer 131 | PRIMARY KEY 132 | REFERENCES equipment_models ON DELETE CASCADE, 133 | speed numeric(5, 2) 134 | NULL 135 | CONSTRAINT positive_transport_speed CHECK (speed > 0), 136 | gasoline_consumption varchar(256) 137 | DEFAULT 'Normal' 138 | NULL 139 | ); 140 | 141 | 142 | INSERT INTO points (name, region, company) 143 | VALUES 144 | ('Новый Уренгой', 'Ямало-Ненецкий округ', 'Газпром'), 145 | ('Бованенковский', 'Ямало-Ненецкий округ', 'Газпром'), 146 | ('Харасавэйский', 'Западная Сибирь', 'Роснефть'), 147 | ('Оренбургский', 'Оренбуржье', 'Газпром'), 148 | ('Заполярный', 'Западная Сибирь', 'Лукойл'), 149 | ('Штокмановский', 'Баренцево море', 'Лукойл'), 150 | ('Ямбургский', 'Западная Сибирь', 'Новатэк'), 151 | ('Штокмановский', 'Ямало-Ненецкий округ', 'Татнефть'), 152 | ('Бованенковский', 'Ямало-Ненецкий округ', 'Роснефть'), 153 | ('Харасавэйский', 'Западная Сибирь ', 'Газпром'); 154 | 155 | INSERT INTO resources (name, price, type, unit, image_url) 156 | VALUES 157 | ('нефть', 77, 'горючие', 'баррель', 'https://upload.wikimedia.org/wikipedia/commons/a/ae/Petroleum_cm05.jpg'), 158 | ('природный газ', 900000, 'горючие', '1000 кубометров', NULL), 159 | ('горючие сланцы', 500, 'горючие', 'тонна', 'https://xn--80aegj1b5e.xn--p1ai/factory-files/styles/900n/public/image/pub/69.jpg?itok=E385xLmN'), 160 | ('торф', 30, 'горючие', 'кубический метр', 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Peat.JPG/1280px-Peat.JPG'), 161 | ('уголь', 110, 'горючие', 'тонна', 'https://upload.wikimedia.org/wikipedia/commons/2/20/Coal_bituminous.jpg'), 162 | ('железная руда', 70, 'руда', 'тонна', 'https://upload.wikimedia.org/wikipedia/commons/2/2e/HematitaEZ.jpg'), 163 | ('калийная соль', 1, 'минеральный ресурс', 'килограмм', 'https://mk0fertilizerdauhxos.kinstacdn.com/wp-content/uploads/2020/06/C0074174-Sylvite_mineral_smanjeno-1044x686.jpeg'), 164 | ('фосфорит', 50, 'минеральный ресурс', 'килограмм', 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Phosphorit_%28Staffelit%29_-_Staffel_Lahngebiet.jpg/1024px-Phosphorit_%28Staffelit%29_-_Staffel_Lahngebiet.jpg'), 165 | ('алмаз', 10000, 'минеральный ресурс', 'карат', 'https://static.wikia.nocookie.net/minecraft_ru_gamepedia/images/5/57/%D0%90%D0%BB%D0%BC%D0%B0%D0%B7.png/revision/latest/scale-to-width-down/160?cb=20190424162100'), 166 | ('родонит', 136, 'камнесамоцветные', 'карат', 'https://upload.wikimedia.org/wikipedia/commons/6/6b/Rodonita2EZ.jpg'); 167 | 168 | INSERT INTO deposits (resource_id, point_id, percents_left, initial_amount) 169 | VALUES 170 | (9, 1, 93.22, 168012), 171 | (2, 4, 83.12, 987365), 172 | (1, 1, 62.44, 731303), 173 | (4, 5, 6.01, 15003), 174 | (10, 3, 13.06, 640765), 175 | (5, 1, 38.75, 21758), 176 | (5, 3, 62.42, 370100), 177 | (2, 4, 72.09, 610735), 178 | (1, 7, 37.73, 23168), 179 | (6, 4, 4.00, 302852); 180 | 181 | INSERT INTO workers (name, date_of_birth, deposit_id, salary) 182 | VALUES 183 | ('Арсеиньев Вячеслав Германович', '17.01.1985', 6, 134255), 184 | ('Труфанов Семен Егорович', '20.06.1971', 9, 148645), 185 | ('Сагадиев Антон Кириллович', '07.01.1970', 2, 127114), 186 | ('Желдин Даниил Федотович', '22.03.1969', 1, 42044), 187 | ('Нугаев Юрий Адамович', '23.12.1973', 5, 142410), 188 | ('Юферев Игнат Маркович', '16.11.1972', 4, 104581), 189 | ('Долженко Федор Кириллович', '24.07.1995', 5, 26816), 190 | ('Вазов Иван Даниилович', '20.11.1982', 6, 130039), 191 | ('Касимов Петр Яковлевич', '06.05.1983', 4, 131649), 192 | ('Шерстов Алексей Панкратович', '17.02.1961', 4, 119446), 193 | ('Рожков Валерий Григорьевич', '10.10.1964', 6, 122774), 194 | ('Мишутин Роман Игнатьевич', '14.11.1981', 8, 20646), 195 | ('Рудов Георгий Константинович', '28.07.1985', 2, 115752), 196 | ('Назаров Никита Лукьевич', '02.04.1994', 4, 87856), 197 | ('Дернов Петр Денисович', '08.06.1975', 1, 27129), 198 | ('Колдаев Егор Степанович', '12.09.1968', 5, 46685), 199 | ('Шулепин Илья Львович', '09.09.1994', 8, 101393), 200 | ('Ижутин Степан Григорьевич', '18.09.1966', 9, 72868), 201 | ('Ерохин Макар Никитьевич', '03.01.1990', 4, 110384), 202 | ('Блок Степан Давидович', '10.12.1992', 2, 19365), 203 | ('Витвинский Арсений Иванович', '10.12.1969', 10, 57247), 204 | ('Труш Севастьян Фадеевич', '24.01.1976', 2, 64047), 205 | ('Исмайлов Николай Ильич', '23.08.1967', 6, 68607), 206 | ('Мясников Антон Савванович', '08.02.1963', 9, 144160), 207 | ('Козловский Савва Николаевич', '04.03.1970', 3, 85483), 208 | ('Юшков Тимофей Семенович', '08.12.1982', 10, 66098), 209 | ('Сапогов Юлиан Акимович', '21.10.1990', 3, 27417), 210 | ('Пудин Валерий Нифонтович', '14.07.1984', 5, 89992), 211 | ('Коптильников Геннадий Ипполитович', '06.08.1962', 6, 20284), 212 | ('Уланов Прохор Иванович', '26.10.1994', 4, 31249), 213 | ('Нестеров Максим Сергеевич', '24.08.1971', 4, 116417), 214 | ('Кинжаев Тимофей Наумович', '10.08.1964', 4, 56851), 215 | ('Ханипов Алексей Климентьевич', '12.05.1973', 10, 124234), 216 | ('Зырянов Дмитрий Арсеньевич', '20.07.1963', 7, 104310), 217 | ('Заславский Феликс Трофимович', '02.11.1970', 9, 85647), 218 | ('Болтунов Петр Ильич', '20.04.1991', 10, 47690), 219 | ('Ухов Феликс Даниилович', '17.12.1986', 5, 59765), 220 | ('Бояринов Арсений Семенович', '17.08.1987', 8, 137633), 221 | ('Николаевский Адам Герасимович', '15.05.1982', 1, 62556), 222 | ('Тихвинский Иван Прокопьевич', '03.03.1975', 6, 126802), 223 | ('Чернобровин Филипп Семенович', '28.08.1968', 5, 140606), 224 | ('Солдатов Трофим Константинович', '27.04.1971', 8, 119369), 225 | ('Журавлев Валерий Максимович', '27.05.1989', 6, 75677), 226 | ('Ярема Никита Адамович', '01.07.1995', 10, 34628), 227 | ('Грибанов Тарас Леонтьевич', '24.10.1992', 4, 61676), 228 | ('Николаев Максим Юрьевич', '03.01.1983', 1, 99352), 229 | ('Фомичев Степан Ильич', '14.10.1977', 9, 111606), 230 | ('Дудко Николай Вениаминович', '12.05.1960', 6, 103039), 231 | ('Адаксин Лев Никифорович', '08.08.1964', 7, 97082), 232 | ('Есиков Венедикт Панкратович', '16.07.1981', 2, 136352), 233 | ('Жиганов Роман Игнатьевич', '16.01.1967', 7, 93227), 234 | ('Ячиков Иннокентий Валерианович', '20.08.1973', 4, 51391), 235 | ('Моргунов Даниил Михаилович', '15.11.1985', 9, 135655), 236 | ('Сенотрусов Григорий Юринович', '09.10.1983', 4, 60321), 237 | ('Курпатов Захар Георгиевич', '13.03.1977', 7, 101122), 238 | ('Сонин Георгий Евгеньевич', '17.01.1962', 1, 53691), 239 | ('Шелепин Филипп Филиппович', '10.06.1982', 10, 101903), 240 | ('Драгомиров Михаил Романович', '10.06.1988', 2, 131524), 241 | ('Янкелевич Антон Тимофеевич', '07.12.1990', 3, 35986), 242 | ('Репин Дмитрий Герасимович', '18.02.1960', 1, 93717), 243 | ('Слобожанин Кирилл Романович', '07.06.1964', 3, 17438), 244 | ('Хренов Василий Порфирьевич', '04.07.1974', 1, 117831), 245 | ('Надервель Павел Николаевич', '18.05.1994', 8, 25668), 246 | ('Журавлёв Валентин Николаевич', '22.10.1976', 2, 66364), 247 | ('Ярцов Геннадий Климентович', '25.11.1990', 2, 52500), 248 | ('Яфаев Ростислав Семенович', '24.08.1967', 1, 19358), 249 | ('Борцов Севастьян Семенович', '26.12.1974', 4, 96649), 250 | ('Воронцов Севастьян Трофимович', '14.07.1995', 4, 120687), 251 | ('Игнатов Никита Маркович', '15.07.1994', 2, 113391), 252 | ('Немов Федот Макарович', '25.01.1965', 2, 63016), 253 | ('Квартин Аркадий Антонрвич', '19.05.1977', 7, 125282), 254 | ('Наполов Феликс Феодосивич', '20.11.1967', 3, 89237), 255 | ('Ясинов Денис Власович', '19.05.1990', 3, 49223), 256 | ('Казарезов Ефрем Игнатьевич', '20.12.1977', 6, 28877), 257 | ('Тихонов Филипп Нифонтович', '06.04.1980', 2, 61020), 258 | ('Шубин Адам Иннокентиевич', '03.07.1968', 1, 29808), 259 | ('Грибалев Егор Прохорович', '28.10.1990', 4, 64965), 260 | ('Теплухин Прохор Степанович', '04.08.1993', 1, 91426), 261 | ('Житков Максим Алексеевич', '09.07.1980', 1, 53890), 262 | ('Кулдошин Игнат Витальевич', '10.04.1984', 8, 71217), 263 | ('Янушевский Семен Саввеевич', '15.01.1990', 4, 68847), 264 | ('Зуб Ефрем Наумович', '23.02.1976', 2, 81407), 265 | ('Барсуков Виталий Степанович', '26.06.1968', 2, 53006), 266 | ('Ширинов Севастьян Петрович', '23.09.1986', 2, 140018), 267 | ('Кабаидзе Леонид Максимович', '24.05.1984', 5, 65346), 268 | ('Цой Тарас Ефимович', '17.08.1985', 5, 118075), 269 | ('Цыгвинцев Никифор Ефимович', '09.03.1990', 6, 89865), 270 | ('Циолковский Арсений Давидович', '17.09.1975', 8, 64508), 271 | ('Гнусарев Аркадий Петрович', '09.06.1981', 5, 47472), 272 | ('Яночкин Яков Иванович', '05.04.1991', 4, 75530), 273 | ('Грицевец Константин Тимофеевич', '11.11.1961', 4, 109588), 274 | ('Кондраков Виктор Валерьевич', '13.10.1970', 5, 72392), 275 | ('Кацен Юрин Ильич', '01.10.1996', 10, 146871), 276 | ('Анохин Петр Венедиктович', '21.09.1962', 1, 15653), 277 | ('Корнилов Адам Максимоич', '04.08.1979', 2, 117074), 278 | ('Голубчиков Константин Васильевич', '18.11.1972', 2, 134939), 279 | ('Гремпель Севастьян Емельянович', '23.11.1973', 1, 59045), 280 | ('Тамахин Егор Викторович', '13.08.1961', 3, 43061), 281 | ('Сязи Ефим Павлович', '01.08.1979', 6, 81048); 282 | 283 | 284 | INSERT INTO extracted_stats (deposit_id, year, obtained_units) 285 | VALUES 286 | (1, 2018, 1116.34), 287 | (1, 2019, 3237.38), 288 | (1, 2020, 6809.67), 289 | (2, 2018, 15500.05), 290 | (2, 2019, 25833.42), 291 | (2, 2020, 62000.20), 292 | (3, 2018, 51144.93), 293 | (3, 2019, 29610.22), 294 | (3, 2020, 188428.70), 295 | (4, 2018, 1255.02), 296 | (4, 2019, 2384.53), 297 | (4, 2020, 8910.62), 298 | (5, 2018, 57936.43), 299 | (5, 2019, 71306.38), 300 | (5, 2020, 316422.06), 301 | (6, 2018, 1715.16), 302 | (6, 2019, 2110.96), 303 | (6, 2020, 9367.39), 304 | (7, 2018, 17733.16), 305 | (7, 2019, 26078.17), 306 | (7, 2020, 60501.36), 307 | (8, 2018, 18613.81), 308 | (8, 2019, 21272.93), 309 | (8, 2020, 93069.05), 310 | (9, 2018, 1454.21), 311 | (9, 2019, 3514.35), 312 | (9, 2020, 7149.88), 313 | (10, 2018, 54135.40), 314 | (10, 2019, 51286.17), 315 | (10, 2020, 179501.59); 316 | INSERT INTO equipment_models (name, weight, cost, category) 317 | VALUES 318 | ('Мультифазный перекачивающий насос двухвинтовый 2ВВ', 8710.72, 11446809.37, 'оборудование для добычи'), 319 | ('Цементировочный агрегат ЦА-320 (АЦ-32) на шасси УРАЛ 4320', 6449.09, 8758768.67, 'транспорт'), 320 | ('Агрегат для исследования скважин АИС-1 на Урал 4320', 821.02, 9315463.41, 'транспорт'), 321 | ('Буровой насос F-1300', 9125.64, 14736026.96, 'оборудование для добычи'), 322 | ('Устьевые елки Ду 65 мм АФК 2 с двумя дополнительными задвижками на рабочее давление 21,35 Мпа', 391.04, 6120704.76, 'оборудование для добычи'), 323 | ('Клапан отсекающий КО 302 М Ду-100, 80 Ру-32 МПа (320), 16 МПа (160)', 307.02, 22271213.98, 'оборудование для добычи'), 324 | ('Агрегат для депарафинизации скважин АДПМ 12/150 на шасси Урал 4320 (6х6)', 3547.96, 37660035.17, 'транспорт'), 325 | ('Лодка для добычи золота', 4667.91, 26310208.52, 'оборудование для добычи'), 326 | ('Лодка для добычи алмазов Б-53 11134к', 7410.51, 31428411.50, 'оборудование для добычи'), 327 | ('Автоцистерна АЦПТ-9,5 на шасси УРАЛ 4320', 5773.60, 11113254.90, 'транспорт'), 328 | ('Машина для добычи торфа', 4141.60, 150000.00, 'оборудование для добычи'), 329 | ('Шламы буровые при бурении, связанном с добычей железа', 250.42, 487345.00, 'оборудование для добычи'), 330 | ('Дробилка большой ёмкости щековая', 5000.00, 4256750.00, 'оборудование для добычи'), 331 | ('Мини-погрузчик HELFFER MSV-200', 500.00, 1700000.00, 'транспорт'), 332 | ('Автомобиль "Газель"', 3091.83, 12964251.07, 'транспорт'), 333 | ('Думпер HELFER MSV-301', 9975.26, 2091461.86, 'транспорт'), 334 | ('Демонтажные вилы', 436.36, 11272365.23, 'транспорт'), 335 | ('АДПМ 12/150 на шасси КАМАЗ 43118', 4271.17, 889373.33, 'транспорт'), 336 | ('Седельный тягач Камаз 43118 с КМУ АНТ-22-2', 2424.47, 2208075.46, 'транспорт'); 337 | INSERT INTO equipment_units (equipment_model_id, decommissioning_year) 338 | VALUES 339 | (7, 2027), -- транспорт 340 | (1, 2027), -- оборудование для добычи 341 | (7, 2045), -- транспорт 342 | (8, 2045), -- оборудование для добычи 343 | (6, 2045), -- оборудование для добычи 344 | (4, 2023), -- оборудование для добычи 345 | (4, 2038), -- оборудование для добычи 346 | (3, 2022), -- транспорт 347 | (6, 2039), -- оборудование для добычи 348 | (10, 2039), -- транспорт 349 | (7, 2036), -- транспорт 350 | (5, 2036), -- оборудование для добычи 351 | (10, 2036), -- транспорт 352 | (10, 2045), -- транспорт 353 | (2, 2045), -- транспорт 354 | (6, 2025), -- оборудование для добычи 355 | (10, 2022), -- транспорт 356 | (2, 2023), -- транспорт 357 | (5, 2031), -- оборудование для добычи 358 | (6, 2031), -- оборудование для добычи 359 | (9, 2031), -- оборудование для добычи 360 | (11, 2024), -- оборудование для добычи 361 | (12, 2053), -- оборудование для добычи 362 | (13, 2048), -- оборудование для добычи 363 | (14, 2041), -- оборудование для добычи 364 | (16, 2031), -- транспорт 365 | (18, 2034); -- транспорт 366 | 367 | INSERT INTO equipment_availability (deposit_id, equipment_unit_id) 368 | VALUES 369 | 370 | (1, 1), -- transport 371 | (3, 2), 372 | (3, 3), -- transport 373 | (10, 4), 374 | (2, 5), 375 | (9, 6), 376 | (9, 7), 377 | (4, 8), -- transport 378 | (4, 9), -- transport 379 | (5, 10), -- transport 380 | (6, 11), -- transport 381 | (6, 26), -- transport 382 | (8, 12), 383 | (7, 13), -- transport 384 | (7, 14), -- transport 385 | (6, 15), -- transport 386 | (2, 16), 387 | (9, 17), -- transport 388 | (10, 18), -- transport 389 | (10, 27), -- transport 390 | (2, 19), 391 | (8, 20), 392 | (1, 21), 393 | (4, 22), 394 | (5, 23), 395 | (6, 24), 396 | (7, 25); 397 | 398 | INSERT INTO mining_equipment_stats (model_id, mining_speed) 399 | VALUES 400 | (1, 50), 401 | (4, 88), 402 | (5, 22), 403 | (6, 60), 404 | (8, 53), 405 | (9, 84), 406 | (11, 20), 407 | (12, 48), 408 | (13, 65); 409 | INSERT INTO transport_stats (model_id, speed, gasoline_consumption) 410 | VALUES 411 | (2, 8, 'Normal'), 412 | (3, 37, 'Normal'), 413 | (7, 50, 'High'), 414 | (10, 47, 'Low'), 415 | (14, 47, 'Low'), 416 | (15, 84, 'Low'), 417 | (16, 11, 'Low'), 418 | (17, 90, 'High'), 419 | (18, 21, 'Normal'), 420 | (19, 94, 'Low'); 421 | 422 | RESET search_path; 423 | -------------------------------------------------------------------------------- /2-3/1.sql: -------------------------------------------------------------------------------- 1 | SET search_path TO resources; 2 | 3 | -- узнаём, сколько денег на текущий момент принесла добыча конкретного ресурса 4 | 5 | select name, count(*) as count_of_deposits, sum(100 - percents_left) * price as overall_sum 6 | from 7 | resources left join deposits on (resources.id = resource_id) 8 | group by resource_id, name, price 9 | order by overall_sum desc NULLS LAST; 10 | 11 | -- подумать про left join 12 | 13 | 14 | -- удалить все ресурсы 15 | 16 | delete from resources; -- так не получится 17 | 18 | --1. удалить все месторождения, тогда можно будет удалить все ресурсы 19 | delete from deposits; 20 | delete from resources; 21 | --2. удалить ресурсы, которые не найдены ни в одном месторождении 22 | SET search_path TO resources; 23 | 24 | delete from 25 | resources 26 | where id in 27 | ( 28 | select resources.id 29 | from resources 30 | left outer join deposits on (resources.id = resource_id) 31 | where deposits.id is null 32 | ) 33 | returning resources.name; 34 | 35 | -- full! -------------------------------------------------------------------------------- /2-3/2.sql: -------------------------------------------------------------------------------- 1 | SET search_path TO resources; 2 | 3 | -- сколько единиц техники нужно будет вывести из эксплуатации каждому пункту добычи в течении трёх лет 4 | 5 | select points.id, points.name, count(*) as going_to_be_decommissioned from 6 | equipment_units 7 | left join equipment_availability on (equipment_unit_id = equipment_units.id) 8 | left join deposits on (deposit_id = deposits.id) 9 | left join points on (point_id = points.id) 10 | where decommissioning_year <= date_part('year', current_timestamp) + 3 11 | group by points.id, points.name 12 | order by going_to_be_decommissioned DESC 13 | ; 14 | 15 | -- выводить критические первыми 16 | 17 | 18 | SET search_path TO resources; 19 | 20 | -- прибавить еще 5 лет ко всем истекающим срокам службы оборудования на указанном пункте, который нас попоросил об этом 21 | 22 | update equipment_units as units 23 | set decommissioning_year = decommissioning_year + 5 24 | from equipment_availability 25 | left join deposits on (deposit_id = deposits.id) 26 | left join points on (point_id = points.id) 27 | where equipment_unit_id = units.id and points.id = 7 and units.decommissioning_year <= date_part('year', current_timestamp) + 3 28 | returning equipment_unit_id, decommissioning_year as new_decommissioning_year; -------------------------------------------------------------------------------- /2-3/3.sql: -------------------------------------------------------------------------------- 1 | SET search_path TO resources; 2 | 3 | -- википедия изменила домен для хранения картинок => надо везде поменять ссылки 4 | 5 | select * from resources where image_url like $$%wikimedia.org%$$; 6 | 7 | UPDATE resources 8 | SET image_url = regexp_replace(image_url, 'wikimedia.org','wikipedia.org','i'); -- case-insensitive 9 | 10 | select * from resources where image_url like $$%wikipedia.org%$$; -------------------------------------------------------------------------------- /2-3/4.sql: -------------------------------------------------------------------------------- 1 | SET search_path TO resources; 2 | 3 | -- единица транспорта и количество работников, имеющих доступ к ней 4 | 5 | select equipment_units.id, equipment_models.name, equipment_units.decommissioning_year, count(*) as workers from 6 | equipment_units left join equipment_availability on (id = deposit_id) 7 | --left join equipment_units on (equipment_unit_id = equipment_units.id) 8 | left join equipment_models on (equipment_model_id = equipment_models.id) 9 | left join deposits on (deposits.id = deposit_id) 10 | left join workers on (workers.deposit_id = deposits.id) 11 | group by equipment_units.id, equipment_models.name 12 | order by equipment_models.name; 13 | -------------------------------------------------------------------------------- /2-4/1.sql: -------------------------------------------------------------------------------- 1 | -- Грязное чтение и повторное чтение в READ (UN)COMMITTED 2 | 3 | set search_path to resources; 4 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 5 | begin; 6 | set search_path to resources; 7 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 8 | begin; 9 | 10 | update resources set price = 500 where name='алмаз'; 11 | 12 | select * from resources where (name = 'алмаз') 13 | -- не работает, т.к. в postgres read uncommited работает как read commited 14 | rollback; 15 | select * from resources where (name = 'алмаз') 16 | -- аномалия повторного чтения 17 | 18 | 19 | -- аномалия потерянных изменений 20 | 21 | set search_path to resources; 22 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 23 | begin; 24 | update resources set price = price + 10 where name='алмаз'; 25 | set search_path to resources; 26 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 27 | begin; 28 | update resources set price = price + 1 where name='алмаз'; 29 | commit; 30 | -- ожидание завершения первой транзакции 31 | -- в итоге цена будет 32 | 33 | 34 | commit; -------------------------------------------------------------------------------- /2-4/2.sql: -------------------------------------------------------------------------------- 1 | -- Неповторяющееся чтение и фантомы в REPEATABLE READ 2 | 3 | set search_path to resources; 4 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 5 | begin; 6 | select price from resources where name like 'алмаз%'; 7 | set search_path to resources; 8 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 9 | begin; 10 | update resources set price = price * 10 where name='алмаз'; 11 | commit; 12 | select price from resources where name like 'алмаз%'; 13 | -- то же самое, т.к. есть защита от update и delete 14 | set search_path to resources; 15 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 16 | begin; 17 | insert into resources (name, price, type, unit) 18 | values 19 | ('алмазная пыль', 533, 'пыль', 'грамм'); 20 | commit; 21 | select price from resources where name like 'алмаз%'; 22 | 23 | -- опять ничего не поменялось, несмотря на то, что по стандарту могло. 24 | -- потому что в postgresql есть защита и от insert (фантомных чтений) 25 | -- поэтому то же самое будет с serializable 26 | 27 | 28 | -- пример serializable: class/value таблица. a: sum(class=1) -> insert(value=sum,class=2), b: наоборот 29 | -- выдаст ошибку: не удалось сериализовать доступ из-за зависимостей чтения/записи между транзакциями 30 | -------------------------------------------------------------------------------- /2-4/trigger.sql: -------------------------------------------------------------------------------- 1 | set search_path to resources; 2 | 3 | -- p.s. этот триггер простоват — его можно заменить через check 4 | 5 | CREATE OR REPLACE FUNCTION models_checker() RETURNS trigger AS $models_checker$ 6 | BEGIN 7 | -- сумма юнитов меньше 8 | IF 9 | NEW.category <> 'оборудование для добычи' 10 | and 11 | NEW.category <> 'транспорт' 12 | THEN 13 | RAISE EXCEPTION 'wrong category!'; 14 | END IF; 15 | RETURN NEW; 16 | END; 17 | $models_checker$ LANGUAGE plpgsql; 18 | 19 | DROP TRIGGER IF EXISTS models_checker on equipment_models; 20 | 21 | CREATE CONSTRAINT TRIGGER models_checker 22 | AFTER INSERT OR UPDATE ON equipment_models 23 | DEFERRABLE INITIALLY DEFERRED 24 | FOR EACH ROW 25 | EXECUTE FUNCTION models_checker(); 26 | 27 | 28 | -- проверка триггера: 29 | 30 | set search_path to resources; 31 | 32 | select * from equipment_models; 33 | insert into equipment_models(name, category) values('имя', 'транспорт'); 34 | 35 | -- auto-commit OFF 36 | 37 | set search_path to resources; 38 | begin; 39 | select * from equipment_models; 40 | insert into equipment_models(name, category) values('имя', 'нетранспорт'); 41 | -- ничего нельзя будет сделать до rollback -------------------------------------------------------------------------------- /3-1/3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-danya/postgres-study/071c691d33be7e97c03e56ebaee908ccb555b24a/3-1/3_1.png -------------------------------------------------------------------------------- /3-1/generate_enormous_csv.py: -------------------------------------------------------------------------------- 1 | # 12|150000.00|3 2 | # {1,2,3}|'2021-10-17 14:56:17.016634+03'|12|{"123":500,"456":200} 3 | 4 | from random import randint, sample, choice, random 5 | from datetime import datetime, timedelta 6 | from tqdm import tqdm 7 | 8 | def random_ts(min_year=2015, max_year=datetime.now().year - 1): 9 | # generate a datetime in format yyyy-mm-dd hh:mm:ss.000000+03 10 | start = datetime(min_year, 1, 1, 00, 00, 00) 11 | years = max_year - min_year + 1 12 | end = start + timedelta(days=365 * years) 13 | return str(start + (end - start) * random()) + '+03' 14 | 15 | # import cProfile 16 | # from pstats import Stats, SortKey 17 | 18 | # def run_script(): 19 | N = 10**8 20 | path_u = '/mnt/Data/users.csv' 21 | path_v = '/mnt/Data/visits.csv' 22 | 23 | deposits = list(map(str, list(range(1, 10 + 1)))) 24 | models = list(map(str, list(range(1, 19 + 1)))) 25 | 26 | characteristics_0 = \ 27 | [ 28 | 'Этот немолодой человек появляется в таверне Джима Хокинса и его матери. Он испуган, подозрителен, просит Джима сопровождать его. После драки с Чёрным Псом у него случается удар. Он раскрывает Джиму тайну старого Флинта. Когда-то Билли был первым помощником Флинта. В день получения чёрной метки Билли пытается бежать, но с ним случается сердечный приступ.', 29 | 'Молодой парень, первый узнавший о сокровищах от старого пирата. Он отправляется за сокровищами на корабле в качестве юнги. Честный и правильный, немного горяч, но смел и умён. Он всегда оказывается в центре событий. Предусмотрительный, добрый, воспитанный мальчик. Оказавшись на острове, Джим умудряется увести корабль в безопасное место, понимая неизбежность “войны”. Попав в руки Сильвера, становится причиной ссоры между ним и пиратами. Сильверу вручают черную метку. Благодаря знакомству Джима и Бена Ганна, их путешествие заканчивается удачно.', 30 | 'Очень мужественный, смелый человек, готов умереть, защищая товарища. Работает судьёй. Отличный врач, умный, дальновидный человек. Именно ему отдаёт карту Джим, взяв её у умершего Билли. Это самый надёжный человек, которого мог выбрать Джим. В обмен на жизнь Джима Ливси отдаёт Сильверу карту. Обещает спасти его от виселицы, куда ему заказано, если тот вернётся домой.', 31 | 'Богатый человек, жаждущий приключений. За его средства был куплен корабль, оплачена поездка. Трелони стремится стать лидером, но ему не хватает специальных знаний, выдержки, мудрости. Он настолько болтлив, что рассказал всему городу о поездке за сокровищами Флинта. Из-за этого в команду попало много пиратов и разбойников. Немного трусоват. Хорошо стреляет.', 32 | 'Нанимается в качестве кока на судно к доктору Ливси. Ходит с костылём на “деревянной” ноге. Хитрый, жадный, ставит деньги во главе всего. Для него нет ничего святого: дружбы, верности слову, благородства – он старый пират. Он тайно командует пиратами на борту, планирует убить всех “лишних”, после того, как сокровища будут найдены. Получив на острове чёрную метку, решает переметнуться на сторону тех, кто скорее всего победит. Спасает Хокинса от смерти.', 33 | 'Пират, оставленный командой на острове три года назад. С ним знакомится Джим, они становятся союзниками. Бен соглашается помогать честным людям, он мстит бросившим его на острове бывшим товарищам. Сокровища, оставленные Флинтом, Бен Ганн выкопал и перепрятал, что оказалось очень кстати.', 34 | 'Мужественный и честный человек, занимающийся не только навигацией, но обустройством быта на корабле. Этот требовательный и сухой человек был нанят сквайром Трелони. Стреляет отвратительно, но зато прекрасно владеет холодным оружием. Организовал бегство с корабля и оборону форта. Во время сражения за форт с пиратами, получил два огнестрельных ранения. После возвращения в Англию, вынужден был оставить флот. Но в 1782 году вновь призывается на войну, принимает участие в битве против адмирала Роднея. Именно тогда капитан и погибает. Ядро попадает ему прямо в грудь.', 35 | 'Старый пират, который находит Билли Бонса в таверне, они дерутся. Чёрный пёс ранен, он убегает, угрожая Билли.' 36 | ] 37 | 38 | characteristics_parts = [a for y in characteristics_0 for a in y.split('. ')] 39 | MAX_CHAR_PARTS = len(characteristics_parts) // (len(characteristics_0) - 1) 40 | N_HUNDRED = N // 100 41 | 42 | visits_count = 0 43 | portion = 0 44 | percents = 0 45 | 46 | 47 | # эти 2 строчки (вместо sample в цикле) уменьшили время работы скрипта в inf раз 48 | deposits_samples = [sample(deposits, randint(0, len(deposits))) for _ in range(50)] 49 | models_samples = [sample(models, randint(0, len(models))) for _ in range(50)] 50 | 51 | with open(path_u, 'w') as users: 52 | with open(path_v, 'w') as visits: 53 | for user_id in range(1, N): 54 | user_visits = randint(1, 10) 55 | characteristics = sample(characteristics_parts, randint(1, MAX_CHAR_PARTS)) 56 | characteristics = '. '.join(characteristics) 57 | users.write(f'{user_id}|{randint(15000, 250000)}.00|{user_visits}|\'{characteristics}\'\n') 58 | for v in range(user_visits): 59 | random_subseq = choice(deposits_samples) 60 | line = '{' + ','.join(random_subseq) + '}|' + random_ts() + '|' + str(user_id) 61 | req_models = '{' #"data": [' 62 | random_models = choice(models_samples) 63 | for i, m in enumerate(random_models): 64 | req_models += '"' + str(m) + '":' + str(randint(1, 1000)) 65 | if i != len(random_models) - 1: 66 | req_models += ',' 67 | req_models += '}' 68 | line += f'|{req_models}' 69 | # print(line) 70 | visits.write(f'{line}\n') 71 | visits_count += user_visits 72 | portion += user_visits 73 | if portion > N_HUNDRED: 74 | percents += 1 75 | print(str(percents) + '%') 76 | portion = 0 77 | if visits_count >= N: 78 | break 79 | -------------------------------------------------------------------------------- /3-1/stats-plus-two-tables.sql: -------------------------------------------------------------------------------- 1 | set search_path to stats; 2 | 3 | -- добавляю 2 таблицы, которые у меня пунктирные на рисунке 4 | 5 | DROP TABLE IF EXISTS computed_deposits_stats; 6 | DROP TABLE IF EXISTS computed_models_stats; 7 | 8 | CREATE TABLE computed_deposits_stats ( 9 | deposit_id integer 10 | PRIMARY KEY 11 | REFERENCES resources.deposits, 12 | overall_visits_count integer NOT NULL 13 | CONSTRAINT pos_count_1 CHECK (overall_visits_count > 0), 14 | unique_visitors_count integer NOT NULL 15 | CONSTRAINT pos_count_2 CHECK (unique_visitors_count > 0), 16 | average_salary numeric(10, 2) NOT NULL 17 | CONSTRAINT positive_avg_salary CHECK (average_salary > 0) 18 | ); 19 | 20 | CREATE TABLE computed_models_stats ( 21 | model_id integer 22 | PRIMARY KEY 23 | REFERENCES resources.equipment_models, 24 | requested_units integer NOT NULL 25 | CONSTRAINT pos_count_3 CHECK (requested_units > 0), 26 | overall_visitors_count integer NOT NULL 27 | CONSTRAINT pos_count_4 CHECK (overall_visitors_count > 0), 28 | unique_visitors_count integer NOT NULL 29 | CONSTRAINT pos_count_5 CHECK (unique_visitors_count > 0) 30 | ); -------------------------------------------------------------------------------- /3-1/stats.sql: -------------------------------------------------------------------------------- 1 | -- set auto-commit OFF 2 | begin; 3 | DROP SCHEMA IF EXISTS stats CASCADE; 4 | CREATE SCHEMA stats; 5 | 6 | set search_path to stats; 7 | 8 | CREATE TABLE users ( 9 | worker_id integer 10 | -- REFERENCES resources.workers в теории 11 | PRIMARY KEY, 12 | salary numeric(10, 2) 13 | NOT NULL 14 | CONSTRAINT positive_salary CHECK (salary > 0), 15 | visits_count integer 16 | NOT NULL 17 | CONSTRAINT not_neg_count CHECK (visits_count >= 0), 18 | characteristics text 19 | NOT NULL 20 | ); 21 | 22 | CREATE TABLE visits ( 23 | id serial 24 | PRIMARY KEY, 25 | deposit_ids_seen integer[] 26 | NOT NULL, 27 | time timestamp 28 | NOT NULL 29 | DEFAULT current_timestamp, 30 | user_id integer 31 | REFERENCES users ON DELETE CASCADE -- если кто-то решил удалить пользователя, пусть удаляется и его статистика 32 | NOT NULL, 33 | required_models jsonb 34 | NOT NULL 35 | ); 36 | 37 | SET CONSTRAINTS ALL DEFERRED; 38 | 39 | COPY users 40 | FROM 'users.csv' WITH (FORMAT TEXT, delimiter '|'); 41 | 42 | COPY visits(deposit_ids_seen, time, user_id, required_models) 43 | FROM 'visits.csv' WITH (FORMAT TEXT, delimiter '|'); 44 | 45 | commit; 46 | 47 | -- set auto-commit ON 48 | analyze; -------------------------------------------------------------------------------- /3-2/users.sql: -------------------------------------------------------------------------------- 1 | REASSIGN OWNED BY test TO postgres; 2 | 3 | REVOKE ALL ON ALL TABLES IN SCHEMA stats FROM test; 4 | REVOKE ALL ON SCHEMA stats FROM test; 5 | REVOKE ALL ON DATABASE resources FROM test; 6 | REVOKE ALL ON TABLE prev_year_visits FROM test; 7 | DROP ROLE IF EXISTS test; 8 | 9 | CREATE USER test; -- == CREATE ROLE name LOGIN 10 | GRANT USAGE ON SCHEMA stats TO test; 11 | GRANT ALL ON DATABASE resources to test; 12 | -- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA stats TO test; 13 | 14 | GRANT SELECT, UPDATE, INSERT ON stats.visits to test; 15 | GRANT SELECT (worker_id, salary, visits_count, characteristics), UPDATE(characteristics) ON stats.users to test; 16 | GRANT SELECT ON stats.computed_deposits_stats to test; 17 | GRANT SELECT ON stats.computed_models_stats to test; 18 | 19 | CREATE OR REPLACE VIEW public.prev_year_visits AS 20 | SELECT * FROM stats.visits 21 | WHERE extract(year from time) = extract(year from current_timestamp) - 1; 22 | 23 | CREATE OR REPLACE VIEW public.prev_5_years_visits AS 24 | SELECT * FROM stats.visits 25 | WHERE extract(year from time) >= extract(year from current_timestamp) - 5; 26 | 27 | CREATE OR REPLACE VIEW public.prev_10_years_visits AS 28 | SELECT * FROM stats.visits 29 | WHERE extract(year from time) >= extract(year from current_timestamp) - 10; 30 | 31 | 32 | GRANT SELECT ON prev_year_visits to test; 33 | 34 | CREATE ROLE visits_from_2020; 35 | GRANT UPDATE(deposit_ids_seen, required_models) ON prev_year_visits to visits_from_2020; 36 | 37 | CREATE USER view_test; 38 | GRANT visits_from_2020 TO view_test; 39 | 40 | 41 | 42 | -- ======================================================================================================== 43 | 44 | SET ROLE test; 45 | -- тут пытаемся что-то делать от его имени 46 | 47 | 48 | 49 | 50 | 51 | -- удаляем роли visits_from_2020 и view_test для повторного запуска скрипта 52 | DROP ROLE IF EXISTS visits_from_2020 53 | 54 | -- удаляем все права, данные пользователю, чтобы стереть пользователя. 55 | -- как это сделать проще, я не нашёл 56 | REASSIGN OWNED BY visits_from_2020 TO postgres; 57 | REVOKE ALL ON ALL TABLES IN SCHEMA stats FROM visits_from_2020; 58 | REVOKE ALL ON DATABASE resources FROM visits_from_2020; 59 | REVOKE ALL ON TABLE prev_year_visits FROM visits_from_2020; 60 | DROP ROLE IF EXISTS visits_from_2020; 61 | 62 | REASSIGN OWNED BY view_test TO postgres; 63 | REVOKE ALL ON ALL TABLES IN SCHEMA stats FROM view_test; 64 | REVOKE ALL ON DATABASE resources FROM view_test; 65 | REVOKE ALL ON TABLE prev_year_visits FROM view_test; 66 | DROP ROLE IF EXISTS view_test; -------------------------------------------------------------------------------- /3-3/function.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM stats.computed_models_stats; 2 | 3 | CREATE OR REPLACE PROCEDURE get_all_needed_models() AS 4 | $$ 5 | DECLARE 6 | c1 CURSOR IS 7 | SELECT * FROM stats.visits 8 | WHERE extract(year from time) >= extract(year from current_timestamp) - 5; 9 | cur_row stats.visits%ROWTYPE; 10 | c integer; 11 | cur_model_id integer; 12 | cur_model_count integer; 13 | c2 CURSOR IS 14 | SELECT model_id FROM stats.computed_models_stats; 15 | cur_model integer; 16 | BEGIN 17 | c = 100; 18 | FOR cur_row in c1 LOOP 19 | exit when c = 0; -- досрочный выход 20 | c = c - 1; 21 | raise notice 'cur_row: %', cur_row.required_models; 22 | FOR cur_model_id, cur_model_count in (select * from jsonb_each(cur_row.required_models)) LOOP 23 | raise notice 'models: %', cur_model_id; 24 | IF (select count(*) from stats.computed_models_stats where model_id = cur_model_id) = 0 25 | THEN BEGIN 26 | raise notice 'gonna insert model % with count %', cur_model_id, cur_model_count; 27 | INSERT INTO stats.computed_models_stats(model_id, requested_units, overall_visitors_count, unique_visitors_count) 28 | VALUES(cur_model_id, cur_model_count, 1, 1); 29 | END; 30 | ELSE BEGIN 31 | raise notice 'gonna add % to model %', cur_model_count, cur_model_id; 32 | UPDATE stats.computed_models_stats 33 | SET 34 | requested_units = requested_units + cur_model_count, 35 | overall_visitors_count = overall_visitors_count + 1 36 | where model_id = cur_model_id; 37 | END; 38 | END IF; 39 | 40 | END LOOP; 41 | END LOOP; 42 | 43 | -- now fill the unique_visitors_count 44 | IF 0 THEN 45 | BEGIN 46 | FOR cur_model in c2 LOOP 47 | update stats.computed_models_stats 48 | set 49 | unique_visitors_count = (select COUNT(*) from stats.visits where required_models?2::text group by user_id limit 10) 50 | where model_id = cur_model.model_id; 51 | END LOOP; 52 | END; 53 | END IF; 54 | 55 | EXCEPTION 56 | WHEN SQLSTATE '42501' THEN -- insufficient_privilege 57 | begin 58 | raise notice 'Not enough priveleges!'; 59 | end; 60 | END; 61 | 62 | $$ 63 | LANGUAGE plpgsql; 64 | 65 | CALL get_all_needed_models(); 66 | select * from stats.computed_models_stats 67 | -------------------------------------------------------------------------------- /3-4/indexes.sql: -------------------------------------------------------------------------------- 1 | SET enable_seqscan TO on; -- всё честно, не выключаем возможность тупо всё сканировать 2 | 3 | -- ARRAY 4 | 5 | DROP INDEX IF EXISTS stats.gin_array; 6 | 7 | explain analyze select * from stats.visits 8 | WHERE (deposit_ids_seen = ARRAY[1, 5, 6, 7, 9]::integer[]); 9 | 10 | "Gather (cost=1000.00..3689494.98 rows=1 width=218) (actual time=274392.712..274397.689 rows=0 loops=1)" 11 | " Workers Planned: 2" 12 | " Workers Launched: 2" 13 | " -> Parallel Seq Scan on visits (cost=0.00..3688494.88 rows=1 width=218) (actual time=273800.504..273800.504 rows=0 loops=3)" 14 | " Filter: (deposit_ids_seen = '{1,5,6,7,9}'::integer[])" 15 | " Rows Removed by Filter: 33333334" 16 | "Planning Time: 0.320 ms" 17 | "Execution Time: 274402.210 ms" 18 | 19 | CREATE INDEX gin_array ON stats.visits USING GIN(deposit_ids_seen); 20 | " 21 | Query returned successfully in 5 min. 6 secs. 22 | " 23 | 24 | explain analyze select * from stats.visits 25 | WHERE (deposit_ids_seen = ARRAY[1, 5, 6, 7, 9]::integer[]); 26 | 27 | "Bitmap Heap Scan on visits (cost=1772.00..1776.01 rows=1 width=218) (actual time=264099.428..264099.429 rows=0 loops=1)" 28 | " Recheck Cond: (deposit_ids_seen = '{1,5,6,7,9}'::integer[])" 29 | " Rows Removed by Index Recheck: 97845730" 30 | " Heap Blocks: exact=47727 lossy=3092555" 31 | " -> Bitmap Index Scan on gin_array (cost=0.00..1772.00 rows=1 width=0) (actual time=9446.225..9446.225 rows=14002051 loops=1)" 32 | " Index Cond: (deposit_ids_seen = '{1,5,6,7,9}'::integer[])" 33 | "Planning Time: 135.226 ms" 34 | "Execution Time: 264110.251 ms" 35 | 36 | 37 | -- FULL TEXT 38 | 39 | explain analyze select * from stats.visits join stats.users on (id = worker_id) 40 | WHERE to_tsvector('russian', characteristics) @@ to_tsquery('Пират') limit 1000000; 41 | 42 | "Limit (cost=1000.57..5329451.57 rows=90877 width=673) (actual time=0.435..54556.370 rows=1000000 loops=1)" 43 | " -> Gather (cost=1000.57..5329451.57 rows=90877 width=673) (actual time=0.433..54460.148 rows=1000000 loops=1)" 44 | " Workers Planned: 2" 45 | " Workers Launched: 2" 46 | " -> Nested Loop (cost=0.57..5319363.87 rows=37865 width=673) (actual time=1.975..54270.166 rows=333335 loops=3)" 47 | " -> Parallel Seq Scan on users (cost=0.00..5010614.33 rows=37865 width=455) (actual time=1.952..49412.421 rows=333335 loops=3)" 48 | " Filter: (to_tsvector('russian'::regconfig, characteristics) @@ to_tsquery('Пират'::text))" 49 | " Rows Removed by Filter: 365786" 50 | " -> Index Scan using visits_pkey on visits (cost=0.57..8.15 rows=1 width=218) (actual time=0.014..0.014 rows=1 loops=1000004)" 51 | " Index Cond: (id = users.worker_id)" 52 | "Planning Time: 0.640 ms" 53 | "Execution Time: 54614.426 ms" 54 | 55 | DROP INDEX IF EXISTS stats.gin_text; 56 | 57 | CREATE INDEX gin_text ON stats.users USING GIN(to_tsvector('russian', characteristics)); 58 | 59 | " 60 | Query returned successfully in 21 min 4 secs. 61 | " 62 | 63 | explain analyze select * from stats.visits join stats.users on (id = worker_id) 64 | WHERE to_tsvector('russian', characteristics) @@ to_tsquery('Пират') limit 1000000; 65 | 66 | "Limit (cost=1845.45..3031012.72 rows=90920 width=673) (actual time=1980.181..50014.394 rows=1000000 loops=1)" 67 | " -> Gather (cost=1845.45..3031012.72 rows=90920 width=673) (actual time=1980.178..49932.080 rows=1000000 loops=1)" 68 | " Workers Planned: 2" 69 | " Workers Launched: 2" 70 | " -> Nested Loop (cost=845.45..3020920.72 rows=37883 width=673) (actual time=1911.878..49164.138 rows=333334 loops=3)" 71 | " -> Parallel Bitmap Heap Scan on users (cost=844.88..2712028.36 rows=37883 width=455) (actual time=1900.190..43113.103 rows=333334 loops=3)" 72 | " Recheck Cond: (to_tsvector('russian'::regconfig, characteristics) @@ to_tsquery('Пират'::text))" 73 | " Rows Removed by Index Recheck: 365911" 74 | " Heap Blocks: lossy=43202" 75 | " -> Bitmap Index Scan on gin_text (cost=0.00..822.15 rows=90920 width=0) (actual time=1970.094..1970.095 rows=8665465 loops=1)" 76 | " Index Cond: (to_tsvector('russian'::regconfig, characteristics) @@ to_tsquery('Пират'::text))" 77 | " -> Index Scan using visits_pkey on visits (cost=0.57..8.15 rows=1 width=218) (actual time=0.017..0.017 rows=1 loops=1000002)" 78 | " Index Cond: (id = users.worker_id)" 79 | "Planning Time: 593.381 ms" 80 | "Execution Time: 50067.277 ms" 81 | 82 | -- JSON 83 | 84 | explain analyze select * from stats.visits 85 | where (required_models?2::text) and ((required_models->>2::text)::integer > 5000) 86 | and (required_models?3::text) and ((required_models->>3::text)::integer > 5000) 87 | limit 10000000; 88 | 89 | "Limit (cost=1000.00..4627465.43 rows=11 width=218) (actual time=277993.564..278005.590 rows=0 loops=1)" 90 | " -> Gather (cost=1000.00..4627465.43 rows=11 width=218) (actual time=277993.324..278005.350 rows=0 loops=1)" 91 | " Workers Planned: 2" 92 | " Workers Launched: 2" 93 | " -> Parallel Seq Scan on visits (cost=0.00..4626464.33 rows=5 width=218) (actual time=277845.468..277845.469 rows=0 loops=3)" 94 | " Filter: ((required_models ? '2'::text) AND (required_models ? '3'::text) AND (((required_models ->> '2'::text))::integer > 5000) AND (((required_models ->> '3'::text))::integer > 5000))" 95 | " Rows Removed by Filter: 33333334" 96 | "Planning Time: 0.265 ms" 97 | "Execution Time: 278012.635 ms" 98 | 99 | DROP INDEX IF EXISTS stats.gin_json; 100 | 101 | 102 | CREATE INDEX gin_json ON stats.visits USING GIN(required_models); 103 | 104 | " 105 | Query returned successfully in 22 min 51 secs. 106 | " 107 | 108 | explain analyze select * from stats.visits 109 | where (required_models?2::text) 110 | and (required_models?3::text) 111 | and (required_models?5::text) 112 | and (required_models?10::text) 113 | limit 10000000; 114 | 115 | "Limit (cost=40.00..44.02 rows=1 width=218) (actual time=21690.678..145207.441 rows=10000000 loops=1)" 116 | " -> Bitmap Heap Scan on visits (cost=40.00..44.02 rows=1 width=218) (actual time=21669.739..144285.669 rows=10000000 loops=1)" 117 | " Recheck Cond: ((required_models ? '2'::text) AND (required_models ? '3'::text) AND (required_models ? '5'::text) AND (required_models ? '10'::text))" 118 | " Rows Removed by Index Recheck: 39990637" 119 | " Heap Blocks: lossy=1583672" 120 | " -> Bitmap Index Scan on gin_json (cost=0.00..40.00 rows=1 width=0) (actual time=21609.647..21609.648 rows=19998615 loops=1)" 121 | " Index Cond: ((required_models ? '2'::text) AND (required_models ? '3'::text) AND (required_models ? '5'::text) AND (required_models ? '10'::text))" 122 | "Planning Time: 185.382 ms" 123 | "Execution Time: 145692.082 ms" 124 | 125 | 126 | -- PARTITIONING 127 | 128 | -- p.s. тут я делаю секционирование на таблице размером в 1 млн, а не в 100, чтобы не плодить еще 40 гб данных 129 | 130 | CREATE TABLE IF NOT EXISTS users_parted ( 131 | worker_id integer, 132 | salary numeric(10, 2) 133 | NOT NULL 134 | CONSTRAINT positive_salary CHECK (salary > 0), 135 | visits_count integer 136 | NOT NULL 137 | CONSTRAINT not_neg_count CHECK (visits_count >= 0), 138 | characteristics text 139 | NOT NULL, 140 | PRIMARY KEY(visits_count, worker_id) 141 | ) PARTITION BY RANGE (visits_count); 142 | 143 | -- salary range: (15000, 250000) 144 | 145 | 146 | CREATE TABLE IF NOT EXISTS users_parted_by_salary_0_to_25000 147 | PARTITION OF users_parted FOR VALUES FROM (0) TO (25000); 148 | 149 | CREATE TABLE IF NOT EXISTS users_parted_by_salary_25000_to_50000 150 | PARTITION OF users_parted FOR VALUES FROM (25000) TO (50000); 151 | 152 | CREATE TABLE IF NOT EXISTS users_parted_by_salary_50000_to_75000 153 | PARTITION OF users_parted FOR VALUES FROM (50000) TO (75000); 154 | 155 | CREATE TABLE IF NOT EXISTS users_parted_by_salary_75000_to_100000 156 | PARTITION OF users_parted FOR VALUES FROM (75000) TO (100000); 157 | 158 | CREATE TABLE IF NOT EXISTS users_parted_by_salary_100000_to_300000 159 | PARTITION OF users_parted FOR VALUES FROM (100000) TO (300000); 160 | 161 | INSERT INTO users_parted (worker_id, salary, visits_count, characteristics) 162 | SELECT * FROM stats.users; 163 | 164 | " 165 | Query returned successfully in 29 min 22 secs. 166 | " 167 | 168 | explain analyze select * from stats.users where salary > 76500 and salary < 86444; 169 | 170 | "Gather (cost=1000.00..1321024.83 rows=769711 width=455) (actual time=38.203..351261.112 rows=770216 loops=1)" 171 | " Workers Planned: 2" 172 | " Workers Launched: 2" 173 | " -> Parallel Seq Scan on users (cost=0.00..1243053.73 rows=320713 width=455) (actual time=317.756..349617.221 rows=256739 loops=3)" 174 | " Filter: ((salary > '76500'::numeric) AND (salary < '86444'::numeric))" 175 | " Rows Removed by Filter: 5804580" 176 | "Planning Time: 37.556 ms" 177 | "Execution Time: 351392.924 ms" 178 | 179 | explain analyze select * from users_parted where salary > 76500 and salary < 86444; 180 | 181 | "Gather (cost=1000.00..1292989.18 rows=747562 width=456) (actual time=21.539..272454.693 rows=770216 loops=1)" 182 | " Workers Planned: 2" 183 | " Workers Launched: 2" 184 | " -> Parallel Append (cost=0.00..1217232.98 rows=311484 width=456) (actual time=7.070..271959.458 rows=256739 loops=3)" 185 | " -> Parallel Seq Scan on users_parted_by_salary_0_to_25000 (cost=0.00..1215601.32 rows=311476 width=456) (actual time=7.068..271908.228 rows=256739 loops=3)" 186 | " Filter: ((salary > '76500'::numeric) AND (salary < '86444'::numeric))" 187 | " Rows Removed by Filter: 5804580" 188 | " -> Parallel Seq Scan on users_parted_by_salary_25000_to_50000 (cost=0.00..18.56 rows=3 width=56) (actual time=0.000..0.001 rows=0 loops=1)" 189 | " Filter: ((salary > '76500'::numeric) AND (salary < '86444'::numeric))" 190 | " -> Parallel Seq Scan on users_parted_by_salary_50000_to_75000 (cost=0.00..18.56 rows=3 width=56) (actual time=0.000..0.000 rows=0 loops=1)" 191 | " Filter: ((salary > '76500'::numeric) AND (salary < '86444'::numeric))" 192 | " -> Parallel Seq Scan on users_parted_by_salary_75000_to_100000 (cost=0.00..18.56 rows=3 width=56) (actual time=0.000..0.000 rows=0 loops=1)" 193 | " Filter: ((salary > '76500'::numeric) AND (salary < '86444'::numeric))" 194 | " -> Parallel Seq Scan on users_parted_by_salary_100000_to_300000 (cost=0.00..18.56 rows=3 width=56) (actual time=0.001..0.001 rows=0 loops=1)" 195 | " Filter: ((salary > '76500'::numeric) AND (salary < '86444'::numeric))" 196 | "Planning Time: 184.658 ms" 197 | "Execution Time: 272524.085 ms" 198 | 199 | -- посмотреть все индексы 200 | SELECT 201 | tablename, 202 | indexname, 203 | indexdef 204 | FROM 205 | pg_indexes 206 | WHERE 207 | schemaname = 'stats' 208 | ORDER BY 209 | tablename, 210 | indexname; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Практикум по Postgresql 2 | 3 | ERD для заданий 2.x: 4 | 5 | ![2-1](2-1/2_1.png) 6 | 7 | ERD для заданий 3.x: 8 | 9 | ![3-1](3-1/3_1.png) 10 | 11 | Их делал [вот тут](https://online.visual-paradigm.com/diagrams/templates/entity-relationship-diagram/notations-for-traditional-erd/) 12 | 13 | Ниже есть 2 инструкции — по установке postgresql на manjaro и по переносу папки с бд на hdd. 14 | 15 | Инструкции, как и лабы, могут содержать ошибки, неточности и т.д., предоставляются по принципу "AS IS" без каких-либо гарантий :) 16 | 17 | Удачи! 18 | 19 | ## Установка Postgres 11 на Manjaro 20 | Почему 11? На сайте курса написано, что нужен 11. Думаю, что если использовать самую новую версию, никаких проблем не возникнет. Но я не стал рисковать, поэтому вот гайд: 21 | 22 | Ставится больно, но работает. 23 | 24 | Ставим AUR-пакет `postgresql-11` (требуют 11.x версию) 25 | 26 | postgres работает через отдельного пользователя. Особенность — в него нельзя зайти через пароль. Поэтому сделать это может только рут: `sudo su postgres` (тут уже пароль от рута нужен). 27 | 28 | ```bash 29 | sudo chown postgres:postgres /var/lib/postgres 30 | sudo su postgres 31 | cd 32 | initdb -D data 33 | echo "pg_ctl -D data/ -l logfile start" > run.sh 34 | chmod +x run.sh 35 | ./run.sh 36 | vim data/postgresql.conf # изменить `unix_socket_directories` на '/tmp' 37 | ``` 38 | 39 | #### PgAdmin4 40 | Ставится как pip-пакет. По-другому не работает, я очень долго мучился с этим. 41 | ```bash 42 | sudo mkdir /var/lib/pgadmin /var/log/pgadmin 43 | pip install pgadmin4 44 | sudo chown greedisgood:greedisgood /var/lib/pgadmin 45 | 46 | pgadmin4 47 | ``` 48 | 49 | Каждый раз нужно будет запускать сервак так: 50 | 51 | ```bash 52 | sudo su postgres 53 | cd 54 | ./run.sh 55 | 56 | pgadmin4 57 | ``` 58 | 59 | В Pgadmin4 подключиться к серверу с хостом `localhost` и дефолтным портом. Пароль у меня был пустой. 60 | 61 | Запросы пишем через query tool к конкретной бд. В теме про транзакции надо использовать два query tool параллельных, выключить auto-commit, выделять мышкой нужные строки. Тогда при нажатии f5 будут выполняться только они. 62 | 63 | ## Перенос кластера бд на HDD для лаб 3.x 64 | 65 | Чтобы не создавать 20-50 Гб активно использующихся данных на ssd, можно перенести папку с данными постгреса на hdd. Предполагается, что система у вас на ssd. 66 | 67 | Если жёсткий диск имеет файловую систему **NTFS** и вы на **Linux**, придётся немного повозиться, потому что при дефолтном монтировании NTFS в Linux права тупо не работают, а postgres требует, чтобы права на папку с данными были `0700` — исключительно для владельца (юзера `postgres`) 68 | 69 | В `/etc/fstab` добавьте следующую строку в конец: 70 | 71 | `UUID=E8B4580DB457DC9E /mnt/Data ntfs auto,users,permissions 0 0` 72 | 73 | Здесь вместо `E8B4580DB457DC9E` надо указать UUID вашего раздела диска. Узнать его можно в GParted (или через какую-нибудь консольную утилиту) 74 | 75 | Сохраняем файл, выходим. 76 | 77 | Дальше **перезагружаемся** и пишем в терминале 78 | 79 | ```bash 80 | sudo mkdir -p /mnt/Data/psql-data 81 | sudo su postgres 82 | cd 83 | echo "pg_ctl -D /mnt/Data/psql-data/ -l logfile start" > run.sh 84 | chmod +x run.sh 85 | ./run.sh 86 | ``` 87 | 88 | Если `pg_ctl` не найден, то в `run.sh` меняем `pg_ctl` на полный путь, который можно узнать через `find /usr/lib/postgresql/ -name pg_ctl`. Вообще так делать, [вроде как, не стоит](https://dba.stackexchange.com/questions/156717/command-not-found-pg-ctl-on-ubuntu), но оно работает. 89 | 90 | Если сервер не стартует, читаем `/var/lib/postgres/logfile`. Скорее всего надо будет что-то поправить в `/mnt/Data/psql-data/postgresql.conf` 91 | 92 | Каждый раз нужно будет запускать сервак так: 93 | 94 | ```bash 95 | sudo su postgres 96 | cd 97 | ./run.sh 98 | ``` 99 | 100 | Ну и в Pgadmin4 подключиться к localhost:<порт из `/mnt/Data/psql-data/postgresql.conf`> 101 | 102 | #### Куда кидать файлы с данными 103 | 104 | Чтобы обойти приколы с правами на файл и генерить его из-под обычного юзера, файлы мы будем хранить в `/mnt/Data`: 105 | 106 | ```bash 107 | -rw-r--r— 1 greedisgood greedisgood 8340763194 окт 24 19:37 users.csv 108 | ``` 109 | 110 | (это вывод `ls -la`, который показывает, что владелец файла — обычный юзер) 111 | 112 | Дальше создадим символическую ссылку внутри `psql-data`: 113 | 114 | ```bash 115 | sudo su postgres 116 | cd /mnt/Data/psql-data 117 | ln -s ../users.csv users.csv 118 | ``` 119 | 120 | Всё. Теперь символическая ссылка будет указывать на нужный нам файл, и всё будет работать. В аргументах команды `COPY` в скриптах нужно писать просто `'users.csv'`. 121 | --------------------------------------------------------------------------------