Webアプリでテーブルの一覧画面を絞り込む、4種類のタイミング

REST APIでデータを返してJavaScriptで整形して画面表示するような今どきのWebアプリでは、一覧データを検索して絞り込むタイミングは何パターンか考えられます。今回は以下の4パターンを実験してみました。

| No. | 方法 | 絞り込む側 |
|---:|:---|:---|
| 1 | フロントエンドでフィルタリングする | フロントエンド |
| 2 | フロントエンドでデータを絞り込む | フロントエンド |
| 3 | バックエンドの言語で絞り込む | バックエンド |
| 4 | SQLで絞り込む | バックエンド |

データベースはSQLite、バックエンドはPHP、フロントエンドはAngularJSを使用します。

![ss.gif](https://qiita-image-store.s3.amazonaws.com/0/32030/8438777c-97a5-1de3-42ae-08274a1755ea.gif)


# DB・テーブル定義

まず最初にテスト用のテーブルを作成します。pdo_sqlite拡張の入っている環境で以下を一度だけ実行してください。

```php:create.php
<?php
$names = ['山田一郎', '山田二郎', '山田三郎', '本田一郎', '本田二郎'];
$db = new PDO('sqlite:meibo.db');
$db->exec("CREATE TABLE IF NOT EXISTS meibo(id INTEGER PRIMARY KEY, name TEXT)");
foreach ($names as $n) $db->exec("INSERT INTO meibo(name) VALUES('$n')");
```

以下のようなテーブルが作成されます。

| id | name |
|---:|:---|
| 1 | 山田一郎 |
| 2 | 山田二郎 |
| 3 | 山田三郎 |
| 4 | 本田一郎 |
| 5 | 本田二郎 |

# パターン1. フロントエンドでフィルタリングする

バックエンドは全レコードを送信し、フロントエンドのDOMレベルでフィルタリングする方法です。

```php:back1.php
<?php
$db = new PDO('sqlite:meibo.db');
echo json_encode($db->query('SELECT * FROM meibo')->fetchAll(PDO::FETCH_ASSOC));
```

```html:front1.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="UTF-8">
<title>名簿1</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
angular.module('app', []).controller('Controller', function($scope, $http) {
  $http.get('back1.php').then(function(res) {
    $scope.meibo = res.data;
  });
});
</script>
</head>
<body ng-controller="Controller">
<p ng-repeat="m in meibo | filter:search">{{m.name}}</p>
<input ng-model="search">
</body>
</html>
```

AngularJSは標準でfilterが付いていますので、簡単にフィルタリングすることが出来ます。

# パターン2. フロントエンドでデータを絞り込む

パターン1と似ていますが、絞り込みを実行したときに毎回バックエンドから全レコードを取得し、結果の配列を絞り込みます。

```php:back2.php
<?php
$db = new PDO('sqlite:meibo.db');
echo json_encode($db->query('SELECT * FROM meibo')->fetchAll(PDO::FETCH_ASSOC));
```

```html:front2.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="UTF-8">
<title>名簿2</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
angular.module('app', []).controller('Controller', function($scope, $http) {
  $scope.$watch('search', function(search) {
    search = search || '';
    $http.get('back2.php').then(function(res) {
      $scope.meibo = res.data.filter(function(m) {
        return m.name.indexOf(search) >= 0;
      });
    });
  });
});
</script>
</head>
<body ng-controller="Controller">
<p ng-repeat="m in meibo">{{m.name}}</p>
<input ng-model="search">
</body>
</html>
```

`$watch`を使い検索ワードが変化したらバックエンドから全レコードを取得し、JavaScript標準のArray.prototype.filter()によってフィルタリングします。

# パターン3. バックエンドの言語で絞り込む

SQLで全レコードを取得した後、PHPで絞り込むパターンです。

```php:back3.php
<?php
$db = new PDO('sqlite:meibo.db');
$rows = $db->query('SELECT * FROM meibo')->fetchAll(PDO::FETCH_ASSOC);
if (!empty($_GET['search'])) {
  $rows = array_filter($rows, function($row) {
    return strpos($row['name'], $_GET['search']) !== false;
  });
}
echo json_encode($rows);
```

```html:front3.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="UTF-8">
<title>名簿3</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
angular.module('app', []).controller('Controller', function($scope, $http) {
  $scope.$watch('search', function(search) {
    $http.get('back3.php', {params: {search: search}}).then(function(res) {
      $scope.meibo = res.data;
    });
  });
});
</script>
</head>
<body ng-controller="Controller">
<p ng-repeat="m in meibo">{{m.name}}</p>
<input ng-model="search">
</body>
</html>
```

フロントエンド側からはGETパラメータで検索ワードを送り、バックエンド側で全レコードを取得した後、array_filter関数によって絞り込みしています。

# パターン4. SQLで絞り込む

SQL文で絞り込みます。

```php:back4.php
<?php
$db = new PDO('sqlite:meibo.db');
if (empty($_GET['search'])) {
  $st = $db->query('SELECT * FROM meibo');
} else {
  $st = $db->prepare('SELECT * FROM meibo WHERE name LIKE ?');
  $st->execute(['%' . $_GET['search'] . '%']);
}
echo json_encode($st->fetchAll(PDO::FETCH_ASSOC));
```

```html:front4.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="UTF-8">
<title>名簿4</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
angular.module('app', []).controller('Controller', function($scope, $http) {
  $scope.$watch('search', function(search) {
    $http.get('back4.php', {params: {search: search}}).then(function(res) {
      $scope.meibo = res.data;
    });
  });
});
</script>
</head>
<body ng-controller="Controller">
<p ng-repeat="m in meibo">{{m.name}}</p>
<input ng-model="search">
</body>
</html>
```

フロントエンド側からはGETパラメータで検索ワードを送り、SQLのWHERE句により絞り込みします。

# 結局どれを使うべきなのか

時と場合によります。

本番使用するときはだいたい4が多いのですが、複雑な条件の場合は3を併用する場合もあります。データ量が少ない場合は1や2を使ったほうがサクサク動く場合もあります。