Дмитрий Герасименко
Откройте сайт www.menti.com
и введите код 32 57 44
Организованная структура,
которая умеет
хранить, обрабатывать
и изменять информацию
в больших объемах.
Все что нужно знать о реляционных БД — они хранят информацию в таблицах, и основная работа крутится вокруг отношений этих таблиц друг к другу
Где скачать:
https://postgresql.org/download/Как выполнять запросы:
Интерактив
-- создание/удаление базы данных
CREATE/DROP DATABASE database;
-- создание/изменение/удаление таблицы
CREATE/ALTER/DROP TABLE table;
-- выборка данных из таблицы
SELECT * FROM table ...;
-- вставка данных в таблицу
INSERT INTO table ... VALUES ...;
-- обновление данных в таблице
UPDATE table SET field=value ...;
-- удаление данных из таблицы
DELETE FROM table WHERE ...;
Управление доступом к данным
выходит за рамки лекции.
CREATE DATABASE instagram;
CREATE TABLE profiles
(
id serial PRIMARY KEY,
email VARCHAR(255) NOT NULL,
username VARCHAR(255) UNIQUE,
fullname VARCHAR(255) NOT NULL,
bio TEXT,
avatar vARCHAR(255),
created TIMESTAMP WITH TIME ZONE
DEFAULT NOW() NOT NULL,
updated TIMESTAMP WITH TIME ZONE,
is_blocked BOOLEAN DEFAULT false
);
PRIMARY KEY
UNIQUE
CREATE TABLE publications
(
id serial PRIMARY KEY,
author_id INTEGER NOT NULL
REFERENCES profiles (id)
ON DELETE CASCASE,
published TIMESTAMP WITH TIME ZONE,
location VARCHAR(255)
);
FOREIGN KEY
CREATE TABLE media
(
id SERIAL PRIMARY KEY,
publication_id INTEGER NOT NULL
REFERENCES publications(id)
ON DELETE CASCADE,
type VARCHAR(12) NOT NULL,
file VARCHAR(255) NOT NULL,
ordering SMALLINT DEFAULT 0 NOT NULL,
created TIMESTAMP WITH TIME ZONE
DEFAULT NOW() NOT NULL
);
CREATE TABLE followers
(
id SERIAL PRIMARY KEY,
profile_id INTEGER NOT NULL
REFERENCES profiles(id)
ON DELETE CASCADE,
follower_id INTEGER NOT NULL
REFERENCES profiles(id)
ON DELETE CASCADE,
created TIMESTAMP WITH TIME ZONE
DEFAULT NOW() NOT NULL,
UNIQUE(profile_id, follower_id)
);
CREATE TABLE comments
(
id SERIAL PRIMARY KEY,
publication_id INTEGER NOT NULL
REFERENCES publications(id)
ON DELETE CASCADE,
reply_id INTEGER
REFERENCES comments(id)
ON DELETE CASCADE,
author_id INTEGER
REFERENCES profiles(id)
ON DELETE SET NULL,
message VARCHAR(255) NOT NULL,
created TIMESTAMP WITH TIME ZONE
DEFAULT NOW() NOT NULL
);
CREATE TABLE likes
(
id SERIAL PRIMARY KEY,
object_type VARCHAR(12) NOT NULL,
object_id INTEGER NOT NULL,
author_id INTEGER NOT NULL
REFERENCES profiles(id)
ON DELETE CASCADE,
created TIMESTAMP WITH TIME ZONE
DEFAULT NOW() NOT NULL,
UNIQUE (object_type, object_id, author_id)
);
Универсальность vs Целостность
CHAR(4) | "abc " |
VARCHAR(4) | "abc" |
TEXT | "abcdef" |
SMALLINT | [-215, 215 - 1] |
INTEGER | [-231, 231 - 1] |
BIGINT | [-263, 263 - 1] |
NUMERIC [(p[,s])] |
DECIMAL [(p[,s])] |
REAL | [10-37, 1037] |
DOUBLE PRECISION | [10-307, 10308] |
SMALLSERIAL | [1, 215 - 1] |
SERIAL | [1, 231 - 1] |
BIGSERIAL | [1, 263 - 1] |
Auto-increment
CREATE TABLE users (
id SERIAL
);
CREATE SEQUENCE users_id_seq;
CREATE TABLE users (
id integer NOT NULL DEFAULT nextval('users_id_seq')
);
ALTER SEQUENCE users_id_seq OWNED BY users.id;
TRUE | FALSE |
't' | 'f' |
'true' | 'false' |
'y' | 'n' |
'yes' | 'no' |
'on' | 'off' |
'1' | '0' |
timestamp [without time zone] | 8 bytes |
timestamp with time zone | 8 bytes |
date | 4 bytes |
time [without time zone] | 8 bytes |
time with time zone | 12 bytes |
interval | 16 bytes |
timestamp | '2004-10-19 10:23:54' |
timestamp with time zone | '2004-10-19 10:23:54+02' |
date | '2018-04-03' |
time | '04:05:06.789' |
time with time zone | '04:05:06.789-3' |
interval | '1 12:59:10' |
'{ значение1 разделитель значение2 разделитель ... }'
integer[][] -> '{{1,2,3},{4,5,6},{7,8,9}}'
{"tags": [
"consequat",
"amet",
"fugiat",
"nulla"
],
"friends": [
{
"id": 0,
"name": "Letha Gill"
},
{
"id": 1,
"name": "Yvette Hudson"
}
]}
Умение писать SQL-запросы — это не только ремесло, но и, пожалуй, искусство.
одна строка
INSERT INTO table
(id, name, created)
VALUES
(1, 'John', '2020-01-01 12:00:00');
несколько строк сразу
INSERT INTO table
(id, name, created)
VALUES
(1, 'John', '2020-02-01 08:00:00'),
(2, 'Mark', '2020-02-01 10:00:00');
Auto-increment или Default
CREATE TABLE ... (
id SERIAL,
...,
created TIMESTAMP WITH TIME ZONE
DEFAULT NOW() NOT NULL
)
INSERT INTO table
(id, name, created)
VALUES
(1, 'John', '2020-02-01 08:00:00');
UPDATE table
SET name = 'Thing', value = 1
WHERE id = 1;
текущее значение поля
UPDATE table
SET value = value + 5
WHERE id = 2;
DELETE FROM table WHERE id = 1;
DELETE FROM table;
Общая форма запроса:
SELECT поля FROM таблица
WHERE условия
GROUP BY поля для группировки
ORDER BY поля для сортировки
LIMIT лимит строк;
Поля в запросе:
SELECT * FROM table;
SELECT id, name, created FROM table;
SELECT id, name AS fullname, created
FROM table AS catalog;
Вычисляемые поля:
SELECT value/2 AS half_value FROM table;
Вычисляемые функции:
-- подсчет строк в таблице
SELECT COUNT(*) FROM table;
-- среднее арифметическое
SELECT AVG(value) AS avg FROM table GROUP BY value;
-- округление минимального значения
SELECT ROUND(MIN(value) - 1) FROM table GROUP BY value;
Фильтрация WHERE:
SELECT id, name, type FROM table
WHERE name = 'my' AND (type = 'one' OR type = 'two')
Сравнение: <>,>,<,>=,<=,=
Операторы: NOT, AND, OR
Поиск LIKE / NOT LIKE:
SELECT ... WHERE description LIKE 'Once%';
SELECT ... WHERE description LIKE '%at the end';
-- работает медленно, индексы не работают!
BETWEEN
SELECT title, created FROM table
WHERE created BETWEEN '2020-01-01' AND '2020-01-20';
Группировка GROUP BY
SELECT type FROM table GROUP BY type;
SELECT type, MIN(created) FROM table GROUP BY type;
SELECT ... GROUP BY name, type;
Фильтрация просле группировки - HAVING
SELECT type, MIN(value) AS min_value FROM table
GROUP BY type HAVING min_value > 100;
Сортировка ORDER BY
SELECT id, name, created FROM table ORDER by name;
SELECT … ORDER BY name DESC;
SELECT … ORDER BY name, created;
Органичение строк LIMIT/OFFSET
SELECT * FROM table LIMIT 1;
SELECT id, name FROM table LIMIT 1 OFFSET 2;
SELECT … FROM (
SELECT * FROM table
) AS table1
SELECT … FROM table WHERE id = (
SELECT * FROM table ...
)
для нашего приложения
Все публикации одного автора
SELECT * FROM publications WHERE author_id = 1;
Кол-во картинок в публикациях
SELECT id, publication_id, COUNT(*)
FROM media
WHERE type = 'photo'
GROUP BY publication_id;
Вывести все сообщения
для одной публикации без ответов
SELECT * FROM comments
WHERE publication_id = 1 AND reply_id IS NULL;
когда информации из одной таблицы недостаточно
SELECT t1.id, t1.author_id, t2.name AS author_name
FROM table1 AS t1
INNER JOIN table2 AS t2 ON t2.id=t1.author_id;
id | author_id | author_name |
---|---|---|
1 | 34 | Mark |
2 | 35 | Harold |
SELECT t1.id, t1.author_id, t2.name AS author_name
FROM table1 AS t1
LEFT JOIN table2 AS t2 ON t2.id=t1.author_id;
id | author_id | author_name |
---|---|---|
1 | 34 | Mark |
2 | 35 | Harold |
3 | 40 | NULL |
Список публикаций авторов за период времени
SELECT p.id, p.published, u.username
FROM publications AS p
INNER JOIN profiles AS u ON u.id = p.author_id
WHERE published BETWEEN '2020-01-01' AND '2020-01-31'
ORDER BY published;
id | published | username |
---|---|---|
6 | 2020-01-10 10:00:00 | hill |
10 | 2020-01-20 10:00:00 | margo |
13 | 2020-01-20 10:00:00 | kyle |
Список авторов с кол-вом публикаций
SELECT u.id, u.username, COUNT(p.id) as count
FROM profiles AS u
LEFT JOIN publications p on u.id = p.author_id
GROUP BY u.id
ORDER BY count;
id | username | count |
---|---|---|
12 | jean | 0 |
11 | cristin | 0 |
8 | margo | 3 |
Авторы с максимальным кол-во лайков
SELECT p.author_id, u.username, SUM(ll.like_cnt) AS max_likes
FROM publications AS p
INNER JOIN (
SELECT l.object_id as publication_id, COUNT(l.id) as like_cnt
FROM likes AS l
WHERE l.object_type = 'publication'
GROUP BY l.object_id
) AS ll ON ll.publication_id = p.id
INNER JOIN profiles u on p.author_id = u.id
GROUP BY p.author_id, u.username
ORDER BY max_likes DESC;
author_id | username | max_like |
---|---|---|
7 | hill | 32 |
6 | leo | 20 |
повышение производительности выполнения запросов
Объект БД, который устанавливает соответствие между ключом и строками таблицы, в которых этот ключ встречается.
SELECT COUNT(*) FROM table; -- 100500
SELECT COUNT(*) FROM table WHERE name = 'слон'; -- 7
CREATE INDEX table_name_idx ON table (name);
CREATE UNIQUE INDEX name_idx ON table (name);
SELECT * FROM table
WHERE name = 'Books' AND owner_id = 1;
CREATE INDEX name_owner_id_idx
ON notes (name, owner_id);
BEGIN;
UPDATE users SET account = account - 10000
WHERE id = 3;
UPDATE users SET account = account + 10000
WHERE id = 4;
COMMIT;
npm install sequelize
npm install pg
const Sequelize = require('sequelize');
const sequelize = new Sequelize('db','user','pass', {
host: 'localhost',
dialect: 'postgres' // 'mariadb','sqlite',
// 'mysql','mssql'
});
const uri = 'postgresql://user:pass@localhost:5432/dbname';
const sequelize = new Sequelize(uri, options);
PostgreSQL | Sequelize |
CHAR | CHAR |
VARCHAR(255) | STRING |
TEXT | TEXT |
PostgreSQL | Sequelize |
INTEGER | INTEGER |
BIGINT | BIGINT |
REAL | REAL |
DOUBLE PRECISION | DOUBLE |
DECIMAL | DECIMAL |
PostgreSQL | Sequelize |
TIMESTAMP WITH TIME ZONE | DATE / NOW |
DATE | DATEONLY |
TIME | TIME |
PostgreSQL | Sequelize |
ARRAY | ARRAY |
sequelize.define('name', {attributes}, {options});
const Note = sequelize.define('note', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {...},
text: {...},
ownerId: {...}
}, {...});
const Note = sequelize.define('note', {
id: {...},
name: {...},
text: {...},
ownerId: {
type: Sequelize.INTEGER,
field: 'owner_id'
}
}, {...});
const Note = sequelize.define('note', {
text: {
set(value) {
var text = value.replace(/\*(\w+)\*/g, '$1');
this.setDataValue('text', text);
}
}
}, {...});
'Films to *watch*' -> 'Films to watch'
const Note = sequelize.define('note', {
id: {...},
name: {
type: Sequelize.STRING,
validate: {
is: /^[a-z]+$/i
}
}
}, {...});
Подробнее
const Note = sequelize.define('note', {...}, {
timestamps: true, // add 'created_at', 'updated_at',
// 'deleted_at'
underscored: true,
paranoid: true,
tableName: 'notes'
});
Note.sync({ force: true });
Note.sync()
.then(function () {
// :)
})
.catch(function (err) {
// :(
});
Note.drop()
.then(function () {})
.catch(function (err) {});
Note.create({
name: 'Books',
text: 'Books to read'
});
Note.findOne({
where: {
name: 'Films'
}
}).then(function (noteModel) {
return noteModel.get('text'); // 'Films to watch'
});
Note.findOne({
where: { name: 'Films' },
attributes: ['id', 'text']
});
Note.findOne({
where: {
name: {
$like: '%s'
},
created_at: {
$gt: '2017-04-09 13:25:13'
}
}
});
Подробнее
Note.findOne({
where: { id: 23 }
});
Note.findById(23);
Note.findAll({
where: {
ownerId: 1
}
});
Note.findAll({
order: [
['name', 'DESC']
],
offset: 2,
limit: 2
});
Note.update(
{
text: 'My favorite book'
},
{
where: { name: 'Books' }
}
);
Note.destroy({
where: { id: 23 }
});
Note.findOrCreate({
where: { name: 'Books' },
defaults: {
name: 'Books',
text: 'Books to read',
ownerId: 1
}
});
Note.belongsTo(User);
Note.belongsTo(User, { as: 'owner' });
Note.belongsTo(User, {
foreignKey: 'owner_id',
targetKey: 'id'
});
User.hasMany(Note, {
foreignKey: 'user_id',
targetKey: 'id'
});
Note.hasOne(Category, { as: 'best_note' });
User.findAll({
attributes: ['name'],
include: [
{
model: Note,
attributes: ['name']
}
]
});
[
{
name: 'Олег',
notes: [
{ name: 'Films' },
{ name: 'Books' }
]
},
{
name: 'Сергей',
notes: [
{ name: 'Music' }
]
},
{
name: 'Михаил',
notes: []
}
]
User.findAll({
attributes: ['name'],
include: [
{
model: Note,
where: {
name: {
$not: null
}
},
attributes: ['name']
}
]
});
[
{
name: 'Олег',
notes: [
{ name: 'Films' },
{ name: 'Books' }
]
},
{
name: 'Сергей',
notes: [
{ name: 'Music' }
]
}
]
User.findAll({
attributes: ['name'],
include: [
{
model: Note,
where: {
name: {
$not: null
}
},
attributes: ['name'],
required: false
}
]
});
[
{
name: 'Олег',
notes: [
{ name: 'Films' },
{ name: 'Books' }
]
},
{
name: 'Сергей',
notes: [
{ name: 'Music' }
]
},
{
name: 'Михаил',
notes: []
}
]
User.findAll({
include: [
{
model: Note,
include: [
{
model: Category
}
]
}
]
});
sequelize.define('note', {...}, {
indexes: [
{
name: 'name_idx'
fields: ['name']
}
]
});
sequelize.define('note', {...}, {
indexes: [
{
name: 'unique_name_idx'
fields: ['name']
unique: true
}
]
});
sequelize.define('note', {...}, {
indexes: [
{
fields: ['name', 'ownerId']
}
]
});
sequelize.transaction(function (t) {
return User.increment(
'account',
{ by: -10000, where: { id: 1 }, transaction: t }
)
.then(() => User.increment(
'account',
{ by: 10000, where: { id: 2 }, transaction: t })
);
});