An object representing a SQL query. It is easier to deal with object-wrappers than string-parsing a raw SQL-query. This object is used by DataObject::get()/get_one(). Using SQL directly can also be faster than tunneling everything through the datamodel. Please be wary of premature optimization, though…
A word of caution: Dealing with low-level SQL is not encouraged in the Silverstripe datamodel for various reasons. You'll break the behaviour of:
We'll explain some ways to use SELECT with the full power of SQL, but still maintain a connection to the Silverstripe datamodel.
$sqlQuery = new SQLQuery(); $sqlQuery->select = array( 'Firstname AS Name', 'YEAR(Birthday) AS BirthYear' ); $sqlQuery->from = " Player LEFT JOIN Team ON Player.TeamID = Team.ID "; $sqlQuery->where = " YEAR(Birthday) = 1982 "; // $sqlQuery->having = ""; // $sqlQuery->orderby = ""; // $sqlQuery->limit = ""; // $sqlQuery->distinct = true; // get the raw SQL $rawSQL = $sqlQuery->sql(); // execute and return a Query-object $result = $sqlQuery->execute();
// ... $sqlQuery->delete = true;
(currently not supported)
The result is an array lightly wrapped in a database-specific subclass of Query. This class implements the Iterator-interface defined in PHP5, and provides convenience-methods for accessing the data.
foreach($result as $row) { echo $row['BirthYear']; }
Raw SQL is handy for performance-optimized calls.
class Team extends DataObject { function getPlayerCount() { $sqlQuery = new SQLQuery( "COUNT(Player.ID)", "Team LEFT JOIN Player ON Team.ID = Player.TeamID" ); return $sqlQuery->execute()->value(); }
Way faster than dealing with DataObjects:
$players = $myTeam->Players(); echo $players->Count();
Useful for creating dropdowns.
$sqlQuery = new SQLQuery( array('YEAR(Birthdate)', 'Birthdate'), 'Player' ); $map = $sqlQuery->execute()->map(); $field = new DropdownField('Birthdates', 'Birthdates', $map);
This is not recommended for most cases, but you can also use the Silverstripe database-layer to fire off a raw query:
DB::query("UPDATE Player SET Status='Active'");
One example for using a raw DB::query is when you are wanting to order twice in the database:
$records = DB::query('SELECT *, CASE WHEN "ThumbnailID" = 0 THEN 2 ELSE 1 END AS "HasThumbnail" FROM "TempDoc" ORDER BY "HasThumbnail", "Name" ASC'); $items = singleton('TempDoc')->buildDataObjectSet($records);
This CASE SQL creates a second field “HasThumbnail” depending if “ThumbnailID” exists in the database which you can then order by “HasThumbnail” to make sure the thumbnails are at the top of the list and then order by another field “Name” separately for both the items that have a thumbnail and then for those that don't have thumbnails.
You can gain some ground on the datamodel-side when involving the selected class for querying. You don't necessarily need to call buildSQL from a specific object-instance, a singleton will do just fine.
$sqlQuery = singleton('Player')->buildSQL( 'YEAR(Birthdate) = 1982' );
This form of building a query has the following advantages:
This is a commonly used technique inside Silverstripe: Use raw SQL, but transfer the resulting rows back into DataObjects.
$sqlQuery = new SQLQuery(); $sqlQuery->select = array( 'Firstname AS Name', 'YEAR(Birthday) AS BirthYear', // IMPORTANT: Needs to be set after other selects to avoid overlays 'Player.ClassName AS ClassName', 'Player.ClassName AS RecordClassName', 'Player.ID AS ID', ); $sqlQuery->from = array( "Player", "LEFT JOIN Team ON Player.TeamID = Team.ID" ); $sqlQuery->where = array( "YEAR(Player.Birthday) = 1982" ); $result = $sqlQuery->execute(); var_dump($result->first()); // array // let Silverstripe work the magic $myDataObjectSet = singleton('Player')->buildDataObjectSet($result); var_dump($myDataObjectSet->First()); // DataObject // this is where it gets tricky $myFirstPlayer = $myDataObjectSet->First(); var_dump($myFirstPlayer->Name); // 'John' var_dump($myFirstPlayer->Firstname); // undefined, as it was not part of the SELECT-clause; var_dump($myFirstPlayer->Surname); // undefined, as it was not part of the SELECT-clause // lets assume that class Player extends BasePlayer, // and BasePlayer has a database-column "Status" var_dump($myFirstPlayer->Status); // undefined, as we didn't LEFT JOIN the BasePlayer-table
CAUTION: Depending on the selected columns in your query, you might get into one of the following scenarios:
Be careful when saving back DataObjects created through buildDataObjectSet, you might get strange side-effects due to the issues noted above.
Some subclasses of FormField are constructed to deal with the SQLQuery-object for maximum flexibility. Have a look at TableListField and ComplexTableField for ways to create sophisticated report-tables based on SQL.
Please use comments for notes, tips and corrections about the described
functionality.
Use the Silverstripe Forum to
ask questions.