Левое соединение строк в Go
Внутреннее соединение отбрасывает строку слева, если справа не нашлось пары. Левое соединение оставляет строку слева, даже если справа пары нет: поля правой таблицы тогда приходят пустыми.
В Go такие столбцы читают в sql.NullString
или sql.NullInt64 и смотрят флаг
Valid перед использованием значения.
Выведем всех пользователей и их заказы, если они есть:
package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "modernc.org/sqlite"
)
func main() {
os.Remove("shop.db")
db, err := sql.Open("sqlite", "shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec("PRAGMA foreign_keys = ON")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO users (name) VALUES (?)`, "ann")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO users (name) VALUES (?)`, "bob")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(
`INSERT INTO orders (user_id, title) VALUES (?, ?)`,
1, "pen",
)
if err != nil {
log.Fatal(err)
}
rows, err := db.Query(`
SELECT u.name, o.title
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
ORDER BY u.id, o.id
`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var userName string
var orderTitle sql.NullString
err = rows.Scan(&userName, &orderTitle)
if err != nil {
log.Fatal(err)
}
fmt.Println(userName)
if orderTitle.Valid {
fmt.Println(orderTitle.String)
} else {
fmt.Println("no order")
}
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
Для числового столбца справа подойдет
sql.NullInt64:
package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "modernc.org/sqlite"
)
func main() {
os.Remove("shop.db")
db, err := sql.Open("sqlite", "shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec("PRAGMA foreign_keys = ON")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
qty INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO users (name) VALUES (?)`, "bob")
if err != nil {
log.Fatal(err)
}
rows, err := db.Query(`
SELECT u.name, o.qty
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.name = ?
`, "bob")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
if rows.Next() {
var userName string
var qty sql.NullInt64
err = rows.Scan(&userName, &qty)
if err != nil {
log.Fatal(err)
}
fmt.Println(userName)
if qty.Valid {
fmt.Println(qty.Int64)
} else {
fmt.Println("empty qty")
}
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
Даны таблицы и строки:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
);
INSERT INTO users (name) VALUES ('ann');
INSERT INTO users (name) VALUES ('bob');
INSERT INTO orders (user_id, title) VALUES (1, 'pen');
Составьте запрос от таблицы пользователей,
чтобы в результате был "bob" с
подписью "no order" вместо названия
заказа. Прочитайте название в
sql.NullString.
Даны таблицы teams и members
с полем team_id. Одна команда без
участников должна попасть в вывод с пустым
именем участника. Опишите, какой тип в
Scan взять для имени справа.
Скажите, чем левое соединение отличается от внутреннего для пользователя, у которого нет ни одного заказа.