Связывание через таблицу связи в Rust
Один пост может нести несколько меток, и одна метка может стоять на многих постах. Двух таблиц с одним полем ссылки для этого мало: ни одна сторона не может хранить список номеров в одной строке без дублирования.
Связь многие ко многим держат третьей таблицей с двумя номерами. Каждая ее строка фиксирует одну пару пост-метка, а полный набор пар собирают из нескольких строк.
Создадим справочники post, tag
и таблицу post_tag:
use rusqlite::{Connection, params};
fn link_demo() -> Result<(), rusqlite::Error> {
let _ = std::fs::remove_file("app.db");
let conn = Connection::open("app.db")?;
conn.execute("PRAGMA foreign_keys = ON", [])?;
conn.execute(
"CREATE TABLE post (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE tag (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE post_tag (
post_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
FOREIGN KEY (post_id) REFERENCES post(id),
FOREIGN KEY (tag_id) REFERENCES tag(id)
)",
[],
)?;
conn.execute(
"INSERT INTO post (title) VALUES (?1)",
params!["hello"],
)?;
conn.execute("INSERT INTO tag (name) VALUES (?1)", params!["news"])?;
conn.execute("INSERT INTO tag (name) VALUES (?1)", params!["draft"])?;
conn.execute(
"INSERT INTO post_tag (post_id, tag_id) VALUES (?1, ?2)",
params![1, 1],
)?;
conn.execute(
"INSERT INTO post_tag (post_id, tag_id) VALUES (?1, ?2)",
params![1, 2],
)?;
Ok(())
}
fn main() {
match link_demo() {
Ok(()) => println!("links ready"),
Err(e) => println!("{}", e),
}
}
Заголовок и имя метки достают двумя шагами соединения через таблицу связи:
use rusqlite::{Connection, params};
fn read_pairs() -> Result<(), rusqlite::Error> {
let _ = std::fs::remove_file("app.db");
let conn = Connection::open("app.db")?;
conn.execute("PRAGMA foreign_keys = ON", [])?;
conn.execute(
"CREATE TABLE post (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE tag (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE post_tag (
post_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
FOREIGN KEY (post_id) REFERENCES post(id),
FOREIGN KEY (tag_id) REFERENCES tag(id)
)",
[],
)?;
conn.execute(
"INSERT INTO post (title) VALUES (?1)",
params!["hello"],
)?;
conn.execute("INSERT INTO tag (name) VALUES (?1)", params!["news"])?;
conn.execute(
"INSERT INTO post_tag (post_id, tag_id) VALUES (?1, ?2)",
params![1, 1],
)?;
let mut stmt = conn.prepare(
"SELECT p.title, t.name
FROM post p
INNER JOIN post_tag pt ON pt.post_id = p.id
INNER JOIN tag t ON pt.tag_id = t.id",
)?;
let rows = stmt.query_map([], |row| {
let title: String = row.get(0)?;
let tag_name: String = row.get(1)?;
Ok((title, tag_name))
})?;
for item in rows {
let (title, tag_name) = item?;
println!("{} {}", title, tag_name);
}
Ok(())
}
fn main() {
match read_pairs() {
Ok(()) => {}
Err(e) => println!("{}", e),
}
}
Даны таблицы:
CREATE TABLE post (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
);
CREATE TABLE tag (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE post_tag (
post_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
FOREIGN KEY (post_id) REFERENCES post(id),
FOREIGN KEY (tag_id) REFERENCES tag(id)
);
Товар может относиться к нескольким категориям, и категория может включать много товаров. Опишите имена таблиц и столбцы связи без готового текста запроса выборки.
Даны заполненные post, tag
и post_tag из первого примера урока.
Напишите запрос, который выведет имена
всех меток поста с заголовком "hello".
Скажите, почему в post_tag
обычно не хранят имя метки текстом,
а только ее номер.