iOS
...
Data Objects
Relational Queries
13 min
relational queries introduction in the previous guide https //www back4app com/docs/ios/parse swift sdk/data objects/query cookbook we detailed how we can perform miscellaneous queries on a back4app database in this guide we focus on a specific type of query that involves objects with relations prerequisites to complete this tutorial, you will need an app created on back4app a basic ios app to test queries goal query relational data stored on a back4app database using the parseswift sdk parseswift sdk 1 quick review about the query\<u>class any query performed on a back4app database is done via the generic class query\<u> query\<u> the generic parameter u u (conforming to the parseobject parseobject protocol) is the data type of the objects we are trying to retrieve from the database given a data type like myobject myobject , we retrieve these objects from a back4app database in the following way 1 import parseswift 2 3 struct myobject parseobject { 4 5 } 6 7 let query = myobject query() 8 9 // executes the query asynchronously 10 query find { result in 11 // handle the result (of type result<\[myobject], parseerror>) 12 } you can read more about the query\<u> query\<u> class here at the official documentation https //github com/parse community/parse swift 2 save some data on a back4app database before we begin to execute queries, it is necessary to set up some data on a back4app database we will store five types of objects author 1 struct author parseobject { 2 3 var name string? 4 } book 1 struct book parseobject { 2 3 var title string? 4 var publisher publisher? 5 var publishingdate date? 6 } isbd 1 struct isbd parseobject { 2 3 var isbn string? 4 var book pointer\<book>? 5 } publisher 1 struct publisher parseobject { 2 3 var name string? 4 } bookstore 1 struct bookstore parseobject { 2 3 var name string? 4 } additionally, in order to construct queries for relational data, we will implement the following relations 1 1 relation between book book and isbd isbd 1\ n relation between book book and publisher publisher m\ n relation between book book and author author m\ n relation between bookstore bookstore and book book we now proceed to store some data on the back4app database this step can be implemented using swift swift or directly from your app’s console on the back4app platform swift //after setting up your xcode project, calling the following method should save some sample data on your back4app database 1 import parseswift 2 3 func savesampledata() { 4 do { 5 // authors 6 let aaronwriter = try author(name "aaron writer") save() 7 let beatricenovelist = try author(name "beatrice novelist") save() 8 let caseycolumnist = try author(name "casey columnist") save() 9 10 // publishers 11 let acaciapublishings = try publisher(name "acacia publishings") save() 12 let birchdistributions = try publisher(name "birch distributions") save() 13 14 // books with their corresponding isbd 15 let alovestorybook = try book( 16 title "a love story", 17 publisher acaciapublishings, 18 publishingdate date(string "05/07/1998") 19 ) save() 20 relation? add("authors", objects \[aaronwriter]) save() // establishes the m\ n relatin between book and author 21 22 // fetches the isbd associated to alovestorybook and establishes the 1 1 relation 23 if let book = alovestorybook, var isbd = try book fetch(includekeys \["isbd"]) isbd { 24 isbd book = try pointer\<book>(book) 25 = try isbd save() 26 } else { 27 fatalerror() 28 } 29 30 let benevolentelvesbook = try book( 31 title "benevolent elves", 32 publisher birchdistributions, 33 publishingdate date(string "11/30/2008") 34 ) save() 35 relation? add("authors", objects \[beatricenovelist]) save() // establishes the m\ n relatin between book and author 36 37 // fetches the isbd associated to benevolentelvesbook and establishes the 1 1 relation 38 if let book = benevolentelvesbook, var isbd = try book fetch(includekeys \["isbd"]) isbd { 39 isbd book = try pointer\<book>(book) 40 = try isbd save() 41 } else { 42 fatalerror() 43 } 44 45 let canyoubelieveitbook = try book( 46 title "can you believe it", 47 publisher birchdistributions, 48 publishingdate date(string "08/21/2018") 49 ) save() 50 relation? add("authors", objects \[aaronwriter, caseycolumnist]) save() // establishes the m\ n relatin between book and author 51 52 // fetches the isbd associated to canyoubelieveitbook and establishes the 1 1 relation 53 if let book = canyoubelieveitbook, var isbd = try book fetch(includekeys \["isbd"]) isbd { 54 isbd book = try pointer\<book>(book) 55 = try isbd save() 56 } else { 57 fatalerror() 58 } 59 60 // book store 61 guard let safealovestorybook = alovestorybook, 62 let safebenevolentelvesbook = benevolentelvesbook, 63 let safecanyoubelieveitbook = canyoubelieveitbook 64 else { 65 throw nserror( 66 domain bundle main description, 67 code 0, 68 userinfo \[nslocalizeddescriptionkey "failed to unwrapp stored books "] 69 ) 70 } 71 72 // saves the stores together with their 1\ n relation with book's 73 let booksoflove = try bookstore(name "books of love") save() 74 = try booksoflove relation? add("books", objects \[safealovestorybook]) save() 75 76 let fantasybooks = try bookstore(name "fantasy books") save() 77 = try fantasybooks relation? add("books", objects \[safebenevolentelvesbook]) save() 78 79 let generalbooks = try bookstore(name "general books") save() 80 = try generalbooks relation? add("books", objects \[safealovestorybook, safecanyoubelieveitbook]) save() 81 82 } catch let error as parseerror { 83 print("error \n", error message) 84 } catch { 85 print("error \n", error localizeddescription) 86 } 87 } back4app's console //a quick way to insert elements on your back4app database is via the console located in your app’s api section once you are there, you can start running javascript code to save the sample data 1 // authors 2 const aaronwriter = new parse object('author'); 3 aaronwriter set('name', 'aaron writer'); 4 await aaronwriter save(); 5 6 const beatricenovelist = new parse object('author'); 7 beatricenovelist set('name', 'beatrice novelist'); 8 await beatricenovelist save(); 9 10 const caseycolumnist = new parse object('author'); 11 caseycolumnist set('name', 'casey columnist'); 12 await caseycolumnist save(); 13 14 // publishers 15 const acaciapublishings = new parse object('publisher'); 16 acaciapublishings set('name', 'acacia publishings'); 17 await acaciapublishings save(); 18 19 const birchdistributions = new parse object('publisher'); 20 birchdistributions set('name', 'birch distributions'); 21 await birchdistributions save(); 22 23 // books with their corresponding isbd 24 const alovestoryisbd = new parse object('isbd'); 25 alovestoryisbd set('isbn', '9781401211868'); 26 await alovestoryisbd save(); 27 28 const alovestorybook = new parse object('book'); 29 alovestorybook set('title', 'a love story'); 30 alovestorybook set('publisher', acaciapublishings); 31 alovestorybook set('publishingdate', new date('05/07/1998')); 32 alovestorybook set('isbd', alovestoryisbd); 33 const bookarelation = alovestorybook relation("authors"); 34 bookarelation add(aaronwriter); 35 await alovestorybook save(); 36 alovestoryisbd set('book', alovestorybook topointer()); 37 await alovestoryisbd save(); 38 39 const benevolentelvesisbd = new parse object('isbd'); 40 benevolentelvesisbd set('isbn', '9781401211868'); 41 await benevolentelvesisbd save(); 42 43 const benevolentelvesbook = new parse object('book'); 44 benevolentelvesbook set('title', 'benevolent elves'); 45 benevolentelvesbook set('publisher', birchdistributions); 46 benevolentelvesbook set('publishingdate', new date('11/31/2008')); 47 benevolentelvesbook set('isbd', benevolentelvesisbd); 48 const bookbrelation = benevolentelvesbook relation("authors"); 49 bookbrelation add(beatricenovelist); 50 await benevolentelvesbook save(); 51 benevolentelvesisbd set('book', benevolentelvesbook topointer()); 52 await benevolentelvesisbd save(); 53 54 const canyoubelieveitisbd = new parse object('isbd'); 55 canyoubelieveitisbd set('isbn', '9781401211868'); 56 await canyoubelieveitisbd save(); 57 58 const canyoubelieveitbook = new parse object('book'); 59 canyoubelieveitbook set('title', 'can you believe it?'); 60 canyoubelieveitbook set('publisher', birchdistributions); 61 canyoubelieveitbook set('publishingdate', new date('08/21/2018')); 62 canyoubelieveitbook set('isbd', canyoubelieveitisbd); 63 const bookcrelation = canyoubelieveitbook relation("authors"); 64 bookcrelation add(aaronwriter); 65 bookcrelation add(caseycolumnist); 66 await canyoubelieveitbook save(); 67 canyoubelieveitisbd set('book', canyoubelieveitbook topointer()); 68 await canyoubelieveitisbd save(); 69 70 // book store 71 const booksoflovestore = new parse object('bookstore'); 72 booksoflovestore set('name', 'books of love'); 73 const bookstorearelation = booksoflovestore relation("books"); 74 bookstorearelation add(alovestorybook); 75 await booksoflovestore save(); 76 77 const fantasybooksstore = new parse object('bookstore'); 78 fantasybooksstore set('name', 'fantasy books'); 79 const bookstorebrelation = fantasybooksstore relation("books"); 80 bookstorebrelation add(benevolentelvesbook); 81 await fantasybooksstore save(); 82 83 const generalbooksstore = new parse object('bookstore'); 84 generalbooksstore set('name', 'general books'); 85 const bookstorecrelation = generalbooksstore relation("books"); 86 bookstorecrelation add(alovestorybook); 87 bookstorecrelation add(canyoubelieveitbook); 88 await generalbooksstore save(); 3 query the data once the database has some sample data to work with, we start executing the different kinds of queries associated with the relations detailed earlier queries involving 1 1 relations given two data types sharing a 1 1 relation ( book book and isbd isbd in this case), we can retrieve one from the other as follows the way we implemented the relation in book book allows us to retrieve its related isbd isbd object simply by calling the include( ) include( ) method on the query let us retrieve the isbd isbd from the book a love story 1 let alovestorybookquery = book query("title" == "a love story") include("isbd") // note how we include the isbd with the include( ) method 2 3 let book = try? alovestorybookquery first() // retrieves synchronously the book including its isbd 4 5 alovestorybookquery first { result in // retrieves asynchronously the book including its isbd 6 // handle the result (of type result\<book, parseerror>) 7 } on the other hand, a query to retrieve a book book object related to a given isbd isbd is implemented in the following way by looking at the implementation of isbd isbd , we note that the relation is represented by the book book property (of type pointer\<book> pointer\<book> ) this pointer provides a set of methods and properties to retrieve information about the object it points to in particular, we call the fetch( ) fetch( ) method on the book book property to fetch the associated book book 1 let someisbd isbd 2 3 let book book? = try? someisbd book? fetch() // retrieves synchronously the book asscociated to someisbd 4 5 someisbd book? fetch { result in // retrieves asynchronously the book asscociated to someisbd 6 // handle the result (of type result\<book, parseerror>) 7 } we should remark that this implemetation for a 1 1 relation is not unique depending on your use case, you can implement 1 1 relations in different ways queries involving 1\ n relations in a scenario where we need to query all the books published by a given publisher, we first need to retrieve the publisher for instance, we first retrieve the data object associated with the publisher acacia publishings depending on the situation, this procces may vary 1 do { 2 // using the object's objectid 3 let acaciapublishings = try publisher(objectid "some object id") fetch() 4 5 // or 6 // using a query 7 let acaciapublishings = try publisher query("name" == "acacia publishings") first() // returns (synchronously) the first publisher with name 'acacia publishings' the constraint is constructed using the == operator provided by the parseswift sdk 8 9 // to be completed below 10 } catch { 11 // hanlde the error (of type parseerror) 12 } now that we have access to acaciapublishings acaciapublishings , we can construct the query to retrieve its related books we proceed to create the query by instantiating a query\<book> query\<book> class in this case, this class is instantiated using the static method query( ) query( ) provided by the book book object the (variadic) arguments for this method are the standard queryconstraint queryconstraint objects therefore, the books we are looking for are retrieved with the following snippet 1 do { 2 let acaciapublishings = try publisher query("name" == "acacia publishings") first() // returns the first publisher with name 'acacia publishings' 3 4 let constraint queryconstraint = try "publisher" == publisher 5 let query = book query(constraint) // creates the query to retrieve all book objects where its publisher field equalt to 'acaciapublishings' 6 7 let books \[book] = try query find() // executes the query synchronously 8 9 // books should contain only one element the book 'a love story' 10 } catch { 11 // hanlde the error (of type parseerror) 12 } an asynchronous implentation for the above snippet may be written in the following way 1 // we retrieve the publisher with name 'acacia publishings' 2 publisher query("name" == "acacia publishings") first { result in 3 switch result { 4 case success(let publisher) 5 guard let constraint queryconstraint = try? "publisher" == publisher else { fatalerror() } 6 7 // then, we retrieve the books with the corresponding constraint 8 book query(constraint) find { result in 9 switch result { 10 case success(let books) 11 // books should contain only one element the book 'a love story' 12 break 13 case failure(let error) 14 // handle the error (of type parseerror) 15 break 16 } 17 } 18 19 case failure(let error) 20 // handle the error (of type parseerror) 21 break 22 } 23 } queries involving m\ n relations (case 1) to illustrate this case, we consider the following scenario; we want to list all the stores containing books published after a given date (e g , 01/01/2010 ) firstly we require an intermediate query to select the books next, we construct the main query to list the stores therefore, we prepare the first query for the books 1 let booksquery = book query("publishingdate" > date(string "01/01/2010")) // we construct the date constraint using the > operator provided by the parseswift sdk 2 3 do { 4 let books = try booksquery find() 5 // to be completed below 6 } catch let error as parseerror { 7 // handle any potential error 8 } catch { 9 // handle any potential error 10 } we then construct the stores’ query using booksquery booksquery ’s results the method containedin( array ) containedin( array ) returns the constraint we need for this case 1 let booksquery = book query("publishingdate" > date(string "01/01/2010")) // we construct the date constraint using the > operator provided by the parseswift sdk 2 3 do { 4 let books = try booksquery find() 5 6 // here is where we construct the stores' query with the corresponding constraint 7 let storesquery = bookstore query(try containedin(key "books", array books)) 8 9 let stores = try storesquery find() 10 11 // stores should containt only one element the 'general books' bookstore 12 } catch let error as parseerror { 13 // handle any potential error 14 } catch { 15 // handle any potential error 16 } similarly, we can implement this process asynchronously 1 let booksquery = book query("publishingdate" > date(string "01/01/2010")) // we construct the date constraint using the > operator provided by the parseswift sdk 2 3 booksquery find { result in 4 switch result { 5 case success(let books) 6 guard let constraint = try? containedin(key "books", array books) else { fatalerror() } 7 let storesquery = bookstore query(constraint) 8 9 storesquery find { result in 10 switch result { 11 case success(let stores) 12 13 case failure(let error) 14 // handle the error (of type parseerror) 15 } 16 } 17 case failure(let error) 18 // handle the error (of type parseerror) 19 } 20 } queries involving m\ n relations (case 2) suppose we need to select all the stores that have books written by a given author, say, aaron writer in order to achieve this, we require two additional queries a query ( query\<author> query\<author> ) to obtain the object associated with the author aaron writer a query ( query\<book> query\<book> )to select all the books written by aaron writer the main query ( query\<bookstore> query\<bookstore> ) to select the stores we are looking for the procedure to implement these queries are very similar to the previous ones 1 let authorquery = author query("name" == "aaron writer") // the first query to retrieve the data object associated to 'aaron writer' 2 3 do { 4 let aaronwriter = try authorquery first() 5 6 let booksquery = book query(try containedin(key "authors", array \[aaronwriter])) // the second query to retrieve the books written by 'aaron writer' 7 8 let books = try booksquery find() 9 10 let storesquery = bookstore query(try containedin(key "books", array books)) // the main query to select the stores where the author ('aaron writer') has his books available 11 12 let stores = try storesquery find() 13 14 // stores should contain two items 'books of love' and 'general books' 15 } catch let error as parseerror { 16 // handle the error 17 } catch { 18 // handle the error 19 } conclusion with the parseswift sdk parseswift sdk , we were able to construct relational queries that allowed us to select items based on the type of relations they have with other data types