배열의 계층 구조를 데이터베이스에서 복원
문제
다음 계층 구조
[
'name' => 'A',
'children' => [
[
'name' => 'B',
'children' => [
[
'name' => 'C',
'children' => [],
],
],
],
[
'name' => 'D',
'children' => [],
]
],
]
다음과 같은 테이블 categories
로 표현된다.
id
이름
parent_id
1
A
NULL
2
B
1
3
D
1
4
C
2
이 테이블에 SQL을 발행하여 원래 배열의 계층 구조를 가능한 한 효율적으로 복원하고 싶습니다. 그럼 어쩌지?
해결책
재귀 쿼리를 사용합시다. 다음 예제에서는 $_GET['id']
를 받고 거기에서 계층 구조를 JSON으로 표시합니다.
<?php
// Content-TypeをUTF-8エンコードされたJSONであるとして明示
header('Content-Type: application/json; charset=UTF-8');
try {
// データベースに接続
$pdo = new \PDO('sqlite:example.db', '', '', [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
]);
// 再帰的に子ノードを取得するSQLを用意
$stmt = $pdo->prepare("
WITH RECURSIVE r AS (
SELECT * FROM categories WHERE id = ?
UNION ALL
SELECT categories.* FROM r, categories WHERE r.id = categories.parent_id
)
SELECT id, name, parent_id FROM r
");
// $_GET['id'] を確実に整数として「?」にバインドする (未定義の場合はゼロ)
$stmt->bindValue(1, (int)filter_input(INPUT_GET, 'id'), PDO::PARAM_INT);
// SQLを実行
$stmt->execute();
// ルートノードを取得
$row = $stmt->fetch();
if ($row === false) {
throw new \RuntimeException('Not Found', 404);
}
$root = [
'name' => $row['name'],
'children' => [],
];
$map = [$row['id'] => &$root];
unset($row);
// その他のノードを配置する
foreach ($stmt as $row) {
$node = [
'name' => $row['name'],
'children' => [],
];
$map[$row['id']] = &$node;
$map[$row['parent_id']]['children'][] = &$node;
unset($row, $node);
}
unset($map);
// 結果を表示
echo json_encode($root, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} catch (\PDOException $e) {
// PDOがスローした例外
http_response_code(500);
echo json_encode([
'error' => $e->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} catch (\RuntimeException $e) {
// 自分でスローした例外
http_response_code($e->getCode() ?: 500);
echo json_encode([
'error' => $e->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
SQLite 데이터베이스 작성을 위한 쉘 명령echo "
CREATE TABLE IF NOT EXISTS categories(
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id INTEGER
);
INSERT INTO categories
SELECT 1, 'A', NULL
UNION ALL
SELECT 2, 'B', 1
UNION ALL
SELECT 3, 'D', 1
UNION ALL
SELECT 4, 'C', 2
;
" | sqlite3 example.db
「연상 배열의 배열」인가? "객체의 배열"인가?
다음 부분의 코드이지만 ...
$root = [
'name' => $row['name'],
'children' => [],
];
$map = [$row['id'] => &$root];
unset($row);
foreach ($stmt as $row) {
$node = [
'name' => $row['name'],
'children' => [],
];
$map[$row['id']] = &$node;
$map[$row['parent_id']]['children'][] = &$node;
unset($row, $node);
}
unset($map);
배열에 구애받지 않고 stdClass
를 포함해도 문제가없는 경우는 아마 이쪽이 읽기 쉬워질 것입니다. 객체의 경우 배열과 달리 참조 유형이므로 &
를 사용하여 참조 할당 (참조 전달) 할 필요가 없습니다. 따라서 명시 적 unset
도 필요하지 않습니다. json_encode
하는 경우는 여기에서 전혀 문제 없습니다.
$root = (object)[
'name' => $row['name'],
'children' => [],
];
$map = [$row['id'] => $root];
foreach ($stmt as $row) {
$map[$row['parent_id']]->children[] = $map[$row['id']] = (object)[
'name' => $row['name'],
'children' => [],
];
}
원래 \PDO::FETCH_ASSOC
를 \PDO::FETCH_OBJ
로 하는 경우는 이렇게 되네요. 더 깨끗이합니다.
$root = (object)[
'name' => $row->name,
'children' => [],
];
$map = [$row->id => $root];
foreach ($stmt as $row) {
$map[$row->parent_id]->children[] = $map[$row->id] = (object)[
'name' => $row->name,
'children' => [],
];
}
여담이지만 PHP7에서 array_column가 미묘하게 강화되었습니다.
이것에 의해, 향후 이 함수를 사용하고 싶은 경우에 있어서도, 「연상 배열의 배열」에 구애할 필요는 없어져 갈 것이라고 생각됩니다. 「오브젝트의 배열」쪽이 보다 심플하게 쓸 수 있군요.
연관 배열의 배열$first_child_of_first_child = $node['children'][0]['children'][0];
$children_of_children = array_column($node['children'], 'children'); // PHP5.5+
객체 배열$first_child_of_first_child = $node->children[0]->children[0];
$children_of_children = array_column($node->children, 'children'); // PHP7.0+
PHP5를 잘라내는 것이 좋다면 json_decode
할 때도 두 번째 인수에 사고 정지 true
를 넘기는 것은 피하도록 합시다.
Reference
이 문제에 관하여(배열의 계층 구조를 데이터베이스에서 복원), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/mpyw/items/9131b6dc0158e90b71d0
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
[
'name' => 'A',
'children' => [
[
'name' => 'B',
'children' => [
[
'name' => 'C',
'children' => [],
],
],
],
[
'name' => 'D',
'children' => [],
]
],
]
재귀 쿼리를 사용합시다. 다음 예제에서는
$_GET['id']
를 받고 거기에서 계층 구조를 JSON으로 표시합니다.<?php
// Content-TypeをUTF-8エンコードされたJSONであるとして明示
header('Content-Type: application/json; charset=UTF-8');
try {
// データベースに接続
$pdo = new \PDO('sqlite:example.db', '', '', [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
]);
// 再帰的に子ノードを取得するSQLを用意
$stmt = $pdo->prepare("
WITH RECURSIVE r AS (
SELECT * FROM categories WHERE id = ?
UNION ALL
SELECT categories.* FROM r, categories WHERE r.id = categories.parent_id
)
SELECT id, name, parent_id FROM r
");
// $_GET['id'] を確実に整数として「?」にバインドする (未定義の場合はゼロ)
$stmt->bindValue(1, (int)filter_input(INPUT_GET, 'id'), PDO::PARAM_INT);
// SQLを実行
$stmt->execute();
// ルートノードを取得
$row = $stmt->fetch();
if ($row === false) {
throw new \RuntimeException('Not Found', 404);
}
$root = [
'name' => $row['name'],
'children' => [],
];
$map = [$row['id'] => &$root];
unset($row);
// その他のノードを配置する
foreach ($stmt as $row) {
$node = [
'name' => $row['name'],
'children' => [],
];
$map[$row['id']] = &$node;
$map[$row['parent_id']]['children'][] = &$node;
unset($row, $node);
}
unset($map);
// 結果を表示
echo json_encode($root, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} catch (\PDOException $e) {
// PDOがスローした例外
http_response_code(500);
echo json_encode([
'error' => $e->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} catch (\RuntimeException $e) {
// 自分でスローした例外
http_response_code($e->getCode() ?: 500);
echo json_encode([
'error' => $e->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
SQLite 데이터베이스 작성을 위한 쉘 명령
echo "
CREATE TABLE IF NOT EXISTS categories(
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id INTEGER
);
INSERT INTO categories
SELECT 1, 'A', NULL
UNION ALL
SELECT 2, 'B', 1
UNION ALL
SELECT 3, 'D', 1
UNION ALL
SELECT 4, 'C', 2
;
" | sqlite3 example.db
「연상 배열의 배열」인가? "객체의 배열"인가?
다음 부분의 코드이지만 ...
$root = [
'name' => $row['name'],
'children' => [],
];
$map = [$row['id'] => &$root];
unset($row);
foreach ($stmt as $row) {
$node = [
'name' => $row['name'],
'children' => [],
];
$map[$row['id']] = &$node;
$map[$row['parent_id']]['children'][] = &$node;
unset($row, $node);
}
unset($map);
배열에 구애받지 않고
stdClass
를 포함해도 문제가없는 경우는 아마 이쪽이 읽기 쉬워질 것입니다. 객체의 경우 배열과 달리 참조 유형이므로 &
를 사용하여 참조 할당 (참조 전달) 할 필요가 없습니다. 따라서 명시 적 unset
도 필요하지 않습니다. json_encode
하는 경우는 여기에서 전혀 문제 없습니다.$root = (object)[
'name' => $row['name'],
'children' => [],
];
$map = [$row['id'] => $root];
foreach ($stmt as $row) {
$map[$row['parent_id']]->children[] = $map[$row['id']] = (object)[
'name' => $row['name'],
'children' => [],
];
}
원래
\PDO::FETCH_ASSOC
를 \PDO::FETCH_OBJ
로 하는 경우는 이렇게 되네요. 더 깨끗이합니다.$root = (object)[
'name' => $row->name,
'children' => [],
];
$map = [$row->id => $root];
foreach ($stmt as $row) {
$map[$row->parent_id]->children[] = $map[$row->id] = (object)[
'name' => $row->name,
'children' => [],
];
}
여담이지만 PHP7에서 array_column가 미묘하게 강화되었습니다.
이것에 의해, 향후 이 함수를 사용하고 싶은 경우에 있어서도, 「연상 배열의 배열」에 구애할 필요는 없어져 갈 것이라고 생각됩니다. 「오브젝트의 배열」쪽이 보다 심플하게 쓸 수 있군요.
연관 배열의 배열
$first_child_of_first_child = $node['children'][0]['children'][0];
$children_of_children = array_column($node['children'], 'children'); // PHP5.5+
객체 배열
$first_child_of_first_child = $node->children[0]->children[0];
$children_of_children = array_column($node->children, 'children'); // PHP7.0+
PHP5를 잘라내는 것이 좋다면
json_decode
할 때도 두 번째 인수에 사고 정지 true
를 넘기는 것은 피하도록 합시다.
Reference
이 문제에 관하여(배열의 계층 구조를 데이터베이스에서 복원), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/mpyw/items/9131b6dc0158e90b71d0텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)