Левое соединение в Rust
Внутреннее соединение отбрасывает строку слева, если справа не нашлось пары. Левое соединение оставляет строку слева, даже если справа пары нет: поля правой таблицы тогда приходят пустыми.
В Rust такой столбец читают в
Option<String> или Option<i64>
и разбирают через match или
if let перед использованием значения.
Выведем всех пользователей и их посты, если они есть:
use rusqlite::{Connection, params};
fn all_users() -> 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"],
)?;
let mut stmt = conn.prepare(
"SELECT u.name, p.title
FROM user u
LEFT JOIN post p ON p.user_id = u.id
ORDER BY u.id, p.id",
)?;
let rows = stmt.query_map([], |row| {
let name: String = row.get(0)?;
let title: Option<String> = row.get(1)?;
Ok((name, title))
})?;
for item in rows {
let (name, title) = item?;
println!("{}", name);
match title {
Some(text) => println!("{}", text),
None => println!("no post"),
}
}
Ok(())
}
fn main() {
match all_users() {
Ok(()) => {}
Err(e) => println!("{}", e),
}
}
Для числового столбца справа подойдет
Option<i64>:
use rusqlite::{Connection, params};
fn qty_for_bob() -> 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,
qty INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(id)
)",
[],
)?;
conn.execute("INSERT INTO user (name) VALUES (?1)", params!["bob"])?;
let mut stmt = conn.prepare(
"SELECT u.name, p.qty
FROM user u
LEFT JOIN post p ON p.user_id = u.id
WHERE u.name = ?1",
)?;
let mut rows = stmt.query(params!["bob"])?;
if let Some(row) = rows.next()? {
let name: String = row.get(0)?;
let qty: Option<i64> = row.get(1)?;
println!("{}", name);
match qty {
Some(n) => println!("{}", n),
None => println!("empty qty"),
}
}
Ok(())
}
fn main() {
match qty_for_bob() {
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 user (name) VALUES ('bob');
INSERT INTO post (user_id, title) VALUES (1, 'pen');
Составьте запрос от таблицы пользователей,
чтобы в результате был "bob" с
подписью "no post" вместо заголовка.
Прочитайте заголовок в Option<String>.
Даны таблицы team и member
с полем team_id. Одна команда без
участников должна попасть в вывод с пустым
именем участника. Опишите, какой тип взять
для имени справа при чтении строки.
Скажите, чем левое соединение отличается от внутреннего для пользователя, у которого нет ни одного поста.