2011-02-07 07:48:44 +01:00
# SQL Query
## Introduction
2012-06-28 11:43:56 +02:00
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 the SilverStripe ORM internally.
Dealing with low-level SQL is not encouraged, since the ORM provides
powerful abstraction APIs (see [datamodel ](/topics/datamodel ).
2011-02-07 07:48:44 +01:00
2012-06-28 11:43:56 +02:00
You'll run the risk of breaking various assumptions the ORM and code based on it have:
2011-02-07 07:48:44 +01:00
2012-06-28 11:43:56 +02:00
* Custom getters/setters (object property can differ from database column)
* DataObject hooks like onBeforeWrite() and onBeforeDelete()
2011-02-07 07:48:44 +01:00
* Automatic casting
2012-06-28 11:43:56 +02:00
* Default values set through objects
2011-02-07 07:48:44 +01:00
* Database abstraction
2012-06-28 11:43:56 +02:00
We'll explain some ways to use *SELECT* with the full power of SQL,
but still maintain a connection to the ORM where possible.
2011-02-07 07:48:44 +01:00
## Usage
### SELECT
:::php
$sqlQuery = new SQLQuery();
2012-06-28 11:43:56 +02:00
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name');
$sqlQuery->selectField('YEAR("Birthday")', 'BirthYear');
$sqlQuery->addLeftJoin(
'Team',
'"Player"."TeamID" = "Team"."ID"'
2011-02-07 07:48:44 +01:00
);
2012-06-28 11:43:56 +02:00
$sqlQuery->addWhere('YEAR("Birthday") = 1982');
// $sqlQuery->setOrderBy(...);
// $sqlQuery->setGroupBy(...);
// $sqlQuery->setHaving(...);
// $sqlQuery->setLimit(...);
// $sqlQuery->setDistinct(true);
2011-02-07 07:48:44 +01:00
// get the raw SQL
$rawSQL = $sqlQuery->sql();
// execute and return a Query-object
$result = $sqlQuery->execute();
### DELETE
:::php
// ...
2012-06-28 11:43:56 +02:00
$sqlQuery->setDelete(true);
2011-02-07 07:48:44 +01:00
### INSERT/UPDATE
(currently not supported -see below for alternative solutions)
## Working with results
The result is an array lightly wrapped in a database-specific subclass of `[api:Query]` . This class implements the
*Iterator*-interface defined in PHP5, and provides convenience-methods for accessing the data.
### Iterating
:::php
foreach($result as $row) {
echo $row['BirthYear'];
}
### Quick value checking
Raw SQL is handy for performance-optimized calls.
:::php
class Team extends DataObject {
2012-01-30 23:13:42 +01:00
public function getPlayerCount() {
2012-06-28 11:43:56 +02:00
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
2011-02-07 07:48:44 +01:00
return $sqlQuery->execute()->value();
}
Way faster than dealing with `[api:DataObject]` s, but watch out for premature optimisation:
:::php
$players = $myTeam->Players();
echo $players->Count();
### Mapping
Useful for creating dropdowns.
:::php
2012-06-28 11:43:56 +02:00
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('YEAR("Birthdate")', 'Birthdate');
2011-02-07 07:48:44 +01:00
$map = $sqlQuery->execute()->map();
$field = new DropdownField('Birthdates', 'Birthdates', $map);
### "Raw" SQL with DB::query()
2011-03-08 22:05:51 +01:00
This is not recommended for most cases, but you can also use the SilverStripe database-layer to fire off a raw query:
2011-02-07 07:48:44 +01:00
:::php
2012-06-28 11:43:56 +02:00
DB::query('UPDATE "Player" SET "Status"=\'Active\'');
2011-02-07 07:48:44 +01:00
2012-06-28 11:43:56 +02:00
< < < < < < < Updated upstream
### Transforming a result to `[api:ArrayList]`
=======
2011-02-07 07:48:44 +01:00
One example for using a raw DB::query is when you are wanting to order twice in the database:
:::php
$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.
### "Semi-raw" SQL with buildSQL()
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.
:::php
2012-06-28 11:43:56 +02:00
$sqlQuery = singleton('Player')->buildSQL('YEAR("Birthdate") = 1982');
2011-02-07 07:48:44 +01:00
This form of building a query has the following advantages:
* Respects DataObject::$default_sort
2012-06-28 11:43:56 +02:00
* Automatically `LEFT JOIN` on all base-tables (see [database-structure ](database-structure ))
2011-02-07 07:48:44 +01:00
* Selection of *ID* , *ClassName* , *RecordClassName* , which are necessary to use *buildDataObjectSet* later on
* Filtering records for correct *ClassName*
2011-03-08 22:05:51 +01:00
### Transforming a result to `[api:DataObjectSet]`
2012-06-28 11:43:56 +02:00
>>>>>>> Stashed changes
2011-02-07 07:48:44 +01:00
2011-03-08 22:05:51 +01:00
This is a commonly used technique inside SilverStripe: Use raw SQL, but transfer the resulting rows back into
`[api:DataObject]` s.
2011-02-07 07:48:44 +01:00
:::php
$sqlQuery = new SQLQuery();
2012-06-28 11:43:56 +02:00
$sqlQuery->setSelect(array(
'"Firstname" AS "Name"',
'YEAR("Birthday") AS "BirthYear"',
2011-02-07 07:48:44 +01:00
// IMPORTANT: Needs to be set after other selects to avoid overlays
2012-06-28 11:43:56 +02:00
'"Player"."ClassName" AS "ClassName"',
'"Player"."ClassName" AS "RecordClassName"',
'"Player"."ID" AS "ID"'
));
$sqlQuery->setFrom('Player');
$sqlQuery->addLeftJoin('Team', '"Player"."TeamID" = "Team"."ID"');
$sqlQuery->addWhere("YEAR("Player"."Birthday") = 1982");
2011-02-07 07:48:44 +01:00
$result = $sqlQuery->execute();
var_dump($result->first()); // array
// let Silverstripe work the magic
2012-06-28 11:43:56 +02:00
$myList = singleton('Player')->buildDataObjectSet($result);
2011-02-07 07:48:44 +01:00
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
2011-03-08 22:05:51 +01:00
**CAUTION:** Depending on the selected columns in your query, you might get into one of the following scenarios:
2011-02-07 07:48:44 +01:00
* Not all object-properties accessible: You need to take care of selecting the right stuff yourself
* Overlayed object-properties: If you *LEFT JOIN* a table which also has a column 'Birthdate' and do a global select on
this table, you might not be able to access original object-properties.
2011-03-08 22:05:51 +01:00
* You can't create `[api:DataObject]` s where no scalar record-data is available, e.g. when using *GROUP BY*
2011-02-07 07:48:44 +01:00
* Naming conflicts with custom getters: A getter like Player->getName() will overlay the column-data selected in the
above example
2011-03-08 22:05:51 +01:00
Be careful when saving back `[api:DataObject]` s created through *buildDataObjectSet* , you might get strange side-effects due to
2011-02-07 07:48:44 +01:00
the issues noted above.
## Using FormFields with custom SQL
Some subclasses of `[api:FormField]` for ways to create sophisticated report-tables based on SQL.
## Related
* [datamodel ](/topics/datamodel )
* `[api:DataObject]`
* [database-structure ](database-structure )
## API Documentation
`[api:SQLQuery]`