Соединение связанных таблиц в Rust
Когда данные разнесены по таблицам, отчет часто требует поля с обеих сторон. Внутреннее соединение складывает строки двух таблиц, у которых совпал ключ: номер в дочерней строке равен номеру в родительской.
Условие совпадения пишут после слова
ON. Для пары user и post
обычно сравнивают post.user_id
и user.id.
Выведем каждый пост вместе с именем автора:
use rusqlite::{Connection, params};
fn list_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 user (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE post (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(id)
)",
[],
)?;
conn.execute("INSERT INTO user (name) VALUES (?1)", params!["ann"])?;
conn.execute(
"INSERT INTO post (user_id, title) VALUES (?1, ?2)",
params![1, "pen"],
)?;
conn.execute(
"INSERT INTO post (user_id, title) VALUES (?1, ?2)",
params![1, "book"],
)?;
let mut stmt = conn.prepare(
"SELECT u.name, p.title
FROM post p
INNER JOIN user u ON p.user_id = u.id
ORDER BY p.id",
)?;
let rows = stmt.query_map([], |row| {
let name: String = row.get(0)?;
let title: String = row.get(1)?;
Ok((name, title))
})?;
for item in rows {
let (name, title) = item?;
println!("{} {}", name, title);
}
Ok(())
}
fn main() {
match list_pairs() {
Ok(()) => {}
Err(e) => println!("{}", e),
}
}
Фильтр по имени пользователя ставят в
WHERE уже после соединения:
use rusqlite::{Connection, params};
fn posts_by_name() -> 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 user (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE post (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(id)
)",
[],
)?;
conn.execute("INSERT INTO user (name) VALUES (?1)", params!["ann"])?;
conn.execute("INSERT INTO user (name) VALUES (?1)", params!["bob"])?;
conn.execute(
"INSERT INTO post (user_id, title) VALUES (?1, ?2)",
params![1, "pen"],
)?;
conn.execute(
"INSERT INTO post (user_id, title) VALUES (?1, ?2)",
params![2, "lamp"],
)?;
let mut stmt = conn.prepare(
"SELECT p.title
FROM post p
INNER JOIN user u ON p.user_id = u.id
WHERE u.name = ?1",
)?;
let rows = stmt.query_map(params!["ann"], |row| {
let title: String = row.get(0)?;
Ok(title)
})?;
for item in rows {
let title = item?;
println!("{}", title);
}
Ok(())
}
fn main() {
match posts_by_name() {
Ok(()) => {}
Err(e) => println!("{}", e),
}
}
Даны таблицы и данные:
CREATE TABLE user (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE post (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(id)
);
INSERT INTO user (name) VALUES ('ann');
INSERT INTO post (user_id, title) VALUES (1, 'pen');
Напишите запрос, который выведет имя пользователя и заголовок поста в одной строке результата для каждой пары.
Даны таблицы garage и vehicle
с полем garage_id у машины. Составьте
текст запроса, который выведет код гаража
и номер машины для каждой связанной пары.
Даны те же user и post,
что в примере с фильтром. Опишите словами,
какие посты попадут в выборку, если в
условии имя "bob".