FQLで関連する複数クエリを実行する


FQLではJOINが使えないため、複数のテーブルが関係するデータを扱うには、複数クエリに分けるケースが多いです。ただ、APIのコール回数はなるべく減らさないと、スピードの点で不利になってしまいます。

ここでは、次のポイントを押さえて、イベント情報と出席者リストを取得する例を紹介します。

  1. クエリの発行回数は1回に
  2. 直前のクエリの結果を再利用
  3. イベント情報に出席者情報を埋め込んでJSONで出力

単数クエリ

通常のFQLは、次のように発行します。

<?php
$result = $facebook->api(array(
    'method' => 'fql.query',
    'query' => 'SELECT ...',
));

ひとつのFQLで、1回APIをコールしてしまうので、PHP側から複数回呼出すと非常に時間がかかるのが難点。特に、一回目のFQLの結果の件数だけ、再度FQLを実行するような実装は避けるべきです。

(後述の例で考えるなら、イベント5件を取得後、それぞれから単数クエリで出席者情報を取得すると、少なくとも6回のAPIコールが必要です)

複数クエリ

それでは、どのようにするのが望ましいでしょうか? Facebook PHP SDKには、複数クエリを発行する機能が用意されています。次のように、実行することで...

<?php
$results = $facebook->api(array(
    'method' => 'fql.multiquery',
    'queries' => array(
        'query1' => 'SELECT ..(省略)..',
        'query2' => 'SELECT ..(省略)..',
    ),
));

複数のクエリの実行結果が、配列として返ってきます。

<?php
array(
    array(
        'name' => 'query1',
        'fql_result_set' => array(
            ..(省略)..
        ),
    ),
    array(
        'name' => 'query2',
        'fql_result_set' => array(
            ..(省略)..
        ),
    ),
)

クエリの再利用

最初のクエリの結果に関係する項目だけ探したい場合、「#query1」のような記述を使うと、簡略化できます。次の例では、query2では、query1の結果をテーブルとみなして、そこからユーザID(uid)の一覧を取り出します。

<?php
$results = $facebook->api(array(
    'method' => 'fql.multiquery',
    'queries' => array(
        'query1' => 'SELECT ..(省略)..',
        'query2' => 'SELECT ..(省略).. FROM user WHERE uid IN (SELECT FROM #query1)',
    ),
));

イベント情報と出席者リストを取得する例

以上ふまえて、実際に下北沢オープンソースCafeで使われているコードを見てみましょう。処理の流れを箇条書きにしておきます。詳しくは、ソースコード内のコメント参照。

  1. Facebookクラスのインスタンスを作成
  2. イベント(event)・関係(event_member)・ユーザ(user)それぞれのFQLを構築
  3. APIを呼出す
  4. 各結果を添字配列に格納しなおす
  5. 各イベント情報に、出席者情報を付加
  6. 配列をJSON形式で出力
<?php
    $facebook = new Facebook(array(
        'appId'=>$_SERVER['FACEBOOK_APPID'],
        'secret'=>$_SERVER['FACEBOOK_SECRET'],
    ));

    $page_id = 131130253626713;//下北沢オープンソースCafeのID
    $yesterday = time()-60*60*12;//12時間前を基準に
    $fql = array();

    //イベント情報を取得するクエリ
    $fql['events'] = <<<________FQL
        SELECT description, eid, name, pic, pic_big, start_time
        FROM event
        WHERE
            eid in (
                SELECT eid 
                FROM event_member 
                WHERE uid = $page_id AND $yesterday < start_time)
            AND privacy = 'OPEN'
        ORDER BY start_time
        LIMIT 5
________FQL;

    //イベントと出席者の関係(event_member)を取得するクエリ
    $fql['map'] = <<<________FQL
        SELECT eid, uid
        FROM event_member
        WHERE
            rsvp_status IN ("attending", "unsure") AND
            eid in (SELECT eid FROM #events)
________FQL;

    //出席者情報を取得するクエリ
    $fql['attendees'] = <<<________FQL
        SELECT uid, pic_square, profile_url
        FROM user
        WHERE uid in (SELECT uid FROM #map)
________FQL;

    //Facebook SDK を使って、複数クエリを発行
    $results = $facebook->api(array(
        'method'=>'fql.multiquery',
        'queries'=>$fql,
    ));

    $map = array();
    $events = array();
    $attendees = array();

    //$results内は添字無しの配列なので、下記のように処理する必要あり。
    foreach ($results as $result)
        switch ($result['name']) {
            case 'map':
                $map = $result['fql_result_set'];
                break;
            case 'attendees':
                foreach ($result['fql_result_set'] as $row)
                    $attendees[$row['uid']] = $row;
                break;
            case 'events':
                foreach ($result['fql_result_set'] as $row)
                    $events[$row['eid']] = $row;
                break;
        }

    //$mapに従って、各イベントに出席者(attendees)情報を加える
    foreach ($map as $m){
        $uid = $m['uid']; $eid = $m['eid'];
        if (!isset($events[$eid]['attendees']))
            $events[$eid]['attendees'] = array();
        $events[$eid]['attendees'][] = $attendees[$uid];
    }

    //JSON形式で出力
    echo json_encode($events);