Facebook's FQL Multi-Query
Introduction
At work, my team is currently developing a social media management application. I am focused now on displaying some data charts and tables. All of the code is client-side JavaScript. To populate the charts, we make calls to Facebook.
Facebook has a SQL like query language, FQL, which allows you to request various data points about your pages, applications, etc. When you need a single data point like your page's total fans, you make a call to the method, fql.query to get your data. For a single data point this is great, but what if you are building a chart and need more than one data point? Making repeated calls to Facebook, will quickly begin to become tedious and slow. Luckily, Facebook has another method, fql.multi-query, which allows you to submit multiple queries at the same time and receive them back in a single result-set.
Submitting Data
Submitting queries to multi-query is only slightly more complicated than doing a single query. A single query request looks like:
var query = “SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-01') AND period=period('lifetime')”;
Submitting queries to multi-query is only slightly more complicated than doing a single query. A single query request looks like:
var query = “SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-01') AND period=period('lifetime')”;
FB.api({ access_token:accessToken, method: 'fql.query', query: query }, function (data) { // do something });
A multi-query request looks like:
var queries = {
d20110401:“SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-01') AND period=period('lifetime')”,
d20110404:"SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-04') AND period=period('lifetime')",
d20110407:"SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-07') AND period=period('lifetime')",
d20110410:"SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-10') AND period=period('lifetime')"}
d20110401:“SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-01') AND period=period('lifetime')”,
d20110404:"SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-04') AND period=period('lifetime')",
d20110407:"SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-07') AND period=period('lifetime')",
d20110410:"SELECT metric, value FROM insights WHERE object_id=162705687107157 AND metric='page_fans' AND end_time=end_time_date('2011-04-10') AND period=period('lifetime')"}
FB.api({ access_token:accessToken, method: 'fql.multiquery', queries: queries }, function (data) { // do something });
The only things that change are the name of the method from fql.query to fql.multiquery and instead of query, we now have queries. Please note: queries is a property and not an array. A property in JavaScript is like a dictionary in other languages. So each entry is a key/value pair. The key of each query is used to identify its data in the result-set.
Retrieving Data
One challenge of using multi-query is in changing the returned result-set into more easily used data. Here is what the result-set looks like for the preceding queries::
resultset = {
[ { "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110401"
},
{ "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110404"
},
{ "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110407"
},
{ "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110410"
}
]};
The resultset neatly encapsulates all of the information asked for by the query. Please note: the keys of the request are now identified by the name keys of each fql_result_set. Unfortunately, this data is a bit cumbersome to work with. For displaying charts an array is much easier to use. So I wrote the following general purpose method to change the result-set into an array.
FilterData: function (data, processedData, criteria, name) {
var prop, ptr, type;
for (prop in data) {
ptr = data[prop];
type = typeof (ptr);
if (type === "object") {
// is this the field holding the desired data?
if (ptr && ptr.metric && ptr.metric === criteria) {
processedData.push({ metric: ptr.metric, value: ptr.value, name: name });
} else {
// search deeper into the object
this.FilterData(ptr, processedData, criteria, name);
}
} else if (type === "string" && prop === "name") {
// save the name of the current query
name = ptr;
}
}
}
The FilterData method uses recursion to search through the JavaScript property, data. Each time it finds a match to criteria, it pushes a new value onto the array, processedData. The value pushed onto the array is a property consisting of: the name of the query, the name of the metric, and the value of the metric. This design allows the method to be able to search through result-sets which have more than one metric returned.
resultset = {
[ { "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110401"
},
{ "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110404"
},
{ "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110407"
},
{ "fql_result_set" : [ { "metric" : "page_fans", "value" : "31" } ],
"name" : "d20110410"
}
]};
The resultset neatly encapsulates all of the information asked for by the query. Please note: the keys of the request are now identified by the name keys of each fql_result_set. Unfortunately, this data is a bit cumbersome to work with. For displaying charts an array is much easier to use. So I wrote the following general purpose method to change the result-set into an array.
FilterData: function (data, processedData, criteria, name) {
var prop, ptr, type;
for (prop in data) {
ptr = data[prop];
type = typeof (ptr);
if (type === "object") {
// is this the field holding the desired data?
if (ptr && ptr.metric && ptr.metric === criteria) {
processedData.push({ metric: ptr.metric, value: ptr.value, name: name });
} else {
// search deeper into the object
this.FilterData(ptr, processedData, criteria, name);
}
} else if (type === "string" && prop === "name") {
// save the name of the current query
name = ptr;
}
}
}
The FilterData method uses recursion to search through the JavaScript property, data. Each time it finds a match to criteria, it pushes a new value onto the array, processedData. The value pushed onto the array is a property consisting of: the name of the query, the name of the metric, and the value of the metric. This design allows the method to be able to search through result-sets which have more than one metric returned.
Summary
It is always a best practice to minimize the number of out of process calls your application makes. Facebook’s multi-query makes it easy to get all of the data you need in one call.