Removed outdated docs for SQLQuery and data model techniques

Some of the SQLQuery recipes are now handled better by DataList,
others should be contrasted with their respective DataList implementation,
pointing out the limitations
This commit is contained in:
Ingo Schommer 2012-06-28 14:51:04 +02:00
parent 19e087d226
commit 402297ee1f
2 changed files with 101 additions and 174 deletions

View File

@ -2,11 +2,30 @@
## Introduction ## Introduction
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. An object representing a SQL query, which can be serialized into a SQL statement.
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 Dealing with low-level SQL is not encouraged, since the ORM provides
powerful abstraction APIs (see [datamodel](/topics/datamodel). powerful abstraction APIs (see [datamodel](/topics/datamodel).
Starting with SilverStripe 3, records in collections are lazy loaded,
and these collections have the ability to run efficient SQL
such as counts or returning a single column.
You'll run the risk of breaking various assumptions the ORM and code based on it have: For example, if you want to run a simple `COUNT` SQL statement,
the following three statements are functionally equivalent:
:::php
// Through raw SQL
$count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
// Through SQLQuery abstraction layer
$query = new SQLQuery();
$count = $query->setFrom('Member')->setSelect('COUNT(*)')->value();
// Through the ORM
$count = Member::get()->count();
If you do use raw SQL, you'll run the risk of breaking
various assumptions the ORM and code based on it have:
* Custom getters/setters (object property can differ from database column) * Custom getters/setters (object property can differ from database column)
* DataObject hooks like onBeforeWrite() and onBeforeDelete() * DataObject hooks like onBeforeWrite() and onBeforeDelete()
@ -17,6 +36,11 @@ You'll run the risk of breaking various assumptions the ORM and code based on it
We'll explain some ways to use *SELECT* with the full power of SQL, We'll explain some ways to use *SELECT* with the full power of SQL,
but still maintain a connection to the ORM where possible. but still maintain a connection to the ORM where possible.
<div class="warning" markdown="1">
Please read our ["security" topic](/topics/security) to find out
how to sanitize user input before using it in SQL queries.
</div>
## Usage ## Usage
### SELECT ### SELECT
@ -25,11 +49,8 @@ but still maintain a connection to the ORM where possible.
$sqlQuery = new SQLQuery(); $sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player'); $sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name'); $sqlQuery->selectField('FieldName', 'Name');
$sqlQuery->selectField('YEAR("Birthday")', 'BirthYear'); $sqlQuery->selectField('YEAR("Birthday")', 'Birthyear');
$sqlQuery->addLeftJoin( $sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"');
'Team',
'"Player"."TeamID" = "Team"."ID"'
);
$sqlQuery->addWhere('YEAR("Birthday") = 1982'); $sqlQuery->addWhere('YEAR("Birthday") = 1982');
// $sqlQuery->setOrderBy(...); // $sqlQuery->setOrderBy(...);
// $sqlQuery->setGroupBy(...); // $sqlQuery->setGroupBy(...);
@ -37,164 +58,86 @@ but still maintain a connection to the ORM where possible.
// $sqlQuery->setLimit(...); // $sqlQuery->setLimit(...);
// $sqlQuery->setDistinct(true); // $sqlQuery->setDistinct(true);
// get the raw SQL // Get the raw SQL (optional)
$rawSQL = $sqlQuery->sql(); $rawSQL = $sqlQuery->sql();
// execute and return a Query-object // Execute and return a Query object
$result = $sqlQuery->execute(); $result = $sqlQuery->execute();
// Iterate over results
### DELETE
:::php
// ...
$sqlQuery->setDelete(true);
### 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) { foreach($result as $row) {
echo $row['BirthYear']; echo $row['BirthYear'];
} }
The result is an array lightly wrapped in a database-specific subclass of `[api:Query]`.
This class implements the *Iterator*-interface, and provides convenience-methods for accessing the data.
### Quick value checking ### DELETE
Raw SQL is handy for performance-optimized calls.
:::php :::php
class Team extends DataObject { $sqlQuery->setDelete(true);
public function getPlayerCount() {
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
return $sqlQuery->execute()->value();
}
Way faster than dealing with `[api:DataObject]`s, but watch out for premature optimisation: ### INSERT/UPDATE
:::php Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
$players = $myTeam->Players();
echo $players->Count();
### Mapping
Useful for creating dropdowns.
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('YEAR("Birthdate")', 'Birthdate');
$map = $sqlQuery->execute()->map();
$field = new DropdownField('Birthdates', 'Birthdates', $map);
### "Raw" SQL with DB::query()
This is not recommended for most cases, but you can also use the SilverStripe database-layer to fire off a raw query:
:::php :::php
DB::query('UPDATE "Player" SET "Status"=\'Active\''); DB::query('UPDATE "Player" SET "Status"=\'Active\'');
<<<<<<< Updated upstream ### Value Checks
### Transforming a result to `[api:ArrayList]`
=======
One example for using a raw DB::query is when you are wanting to order twice in the database:
:::php Raw SQL is handy for performance-optimized calls,
$records = DB::query('SELECT *, CASE WHEN "ThumbnailID" = 0 THEN 2 ELSE 1 END AS "HasThumbnail" FROM "TempDoc" ORDER BY "HasThumbnail", "Name" ASC'); e.g. when you want a single column rather than a full-blown object representation.
$items = singleton('TempDoc')->buildDataObjectSet($records);
This CASE SQL creates a second field "HasThumbnail" depending if "ThumbnailID" exists in the database which you can then Example: Get the count from a relationship.
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. :::php
$sqlQuery = new SQLQuery();
### "Semi-raw" SQL with buildSQL() $sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
You can gain some ground on the datamodel-side when involving the selected class for querying. You don't necessarily $sqlQuery->addWhere('"Team"."ID" = 99');
need to call *buildSQL* from a specific object-instance, a *singleton* will do just fine. $sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
$count = $sqlQuery->execute()->value();
:::php
$sqlQuery = singleton('Player')->buildSQL('YEAR("Birthdate") = 1982'); Note that in the ORM, this call would be executed in an efficient manner as well:
:::php
This form of building a query has the following advantages: $count = $myTeam->Players()->count();
* Respects DataObject::$default_sort ### Mapping
* Automatically `LEFT JOIN` on all base-tables (see [database-structure](database-structure))
* Selection of *ID*, *ClassName*, *RecordClassName*, which are necessary to use *buildDataObjectSet* later on Creates a map based on the first two columns of the query result.
* Filtering records for correct *ClassName* This can be useful for creating dropdowns.
### Transforming a result to `[api:DataObjectSet]` Example: Show player names with their birth year, but set their birth dates as values.
>>>>>>> Stashed changes
This is a commonly used technique inside SilverStripe: Use raw SQL, but transfer the resulting rows back into
`[api:DataObject]`s.
:::php :::php
$sqlQuery = new SQLQuery(); $sqlQuery = new SQLQuery();
$sqlQuery->setSelect(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->setFrom('Player'); $sqlQuery->setFrom('Player');
$sqlQuery->addLeftJoin('Team', '"Player"."TeamID" = "Team"."ID"'); $sqlQuery->setSelect('Birthdate');
$sqlQuery->addWhere("YEAR("Player"."Birthday") = 1982"); $sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
$map = $sqlQuery->execute()->map();
$result = $sqlQuery->execute(); $field = new DropdownField('Birthdates', 'Birthdates', $map);
var_dump($result->first()); // array
// let Silverstripe work the magic
$myList = 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
Note that going through SQLQuery is just necessary here
because of the custom SQL value transformation (`YEAR()`).
An alternative approach would be a custom getter in the object definition.
**CAUTION:** Depending on the selected columns in your query, you might get into one of the following scenarios: :::php
class Player extends DataObject {
* Not all object-properties accessible: You need to take care of selecting the right stuff yourself static $db = array(
* Overlayed object-properties: If you *LEFT JOIN* a table which also has a column 'Birthdate' and do a global select on 'Name' =>
this table, you might not be able to access original object-properties. 'Birthdate' => 'Date'
* You can't create `[api:DataObject]`s where no scalar record-data is available, e.g. when using *GROUP BY* );
* Naming conflicts with custom getters: A getter like Player->getName() will overlay the column-data selected in the function getNameWithBirthyear() {
above example return date('y', $this->Birthdate);
}
Be careful when saving back `[api:DataObject]`s created through *buildDataObjectSet*, you might get strange side-effects due to }
the issues noted above. $players = Player::get();
## Using FormFields with custom SQL $map = $players->map('Name', 'NameWithBirthyear');
Some subclasses of `[api:FormField]` for ways to create sophisticated report-tables based on SQL.
## Related ## Related
* [datamodel](/topics/datamodel) * [datamodel](/topics/datamodel)
* `[api:DataObject]` * `[api:DataObject]`
* [database-structure](database-structure) * [database-structure](database-structure)
## API Documentation
`[api:SQLQuery]`

View File

@ -14,7 +14,8 @@ logic is handled by SilverStripe, you don't need to worry about writing SQL most
Most of the ORM customizations are possible through [PHP5 Object Most of the ORM customizations are possible through [PHP5 Object
Overloading](http://www.onlamp.com/pub/a/php/2005/06/16/overloading.html) handled in the `[api:Object]`-class. Overloading](http://www.onlamp.com/pub/a/php/2005/06/16/overloading.html) handled in the `[api:Object]`-class.
See [database-structure](/reference/database-structure) for in-depth information on the database-schema. See [database-structure](/reference/database-structure) for in-depth information on the database-schema,
and the ["sql queries" topic](/reference/sqlquery) in case you need to drop down to the bare metal.
## Generating the Database Schema ## Generating the Database Schema
@ -523,8 +524,10 @@ accessors available on both ends.
### Adding relations ### Adding relations
Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many_many*-relationship. You need to get a Adding new items to a relations works the same,
`[api:ComponentSet]`. regardless if you're editing a *has_many*- or a *many_many*.
They are encapsulated by `[api:HasManyList]` and `[api:ManyManyList]`,
both of which provide very similar APIs, e.g. an `add()` and `remove()` method.
:::php :::php
class Team extends DataObject { class Team extends DataObject {
@ -533,20 +536,8 @@ Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many
"Categories" => "Category", "Categories" => "Category",
); );
/** public function addCategories(SS_List $cats) {
foreach($cats as $cat) $this->Categories()->add($cat);
* @param DataObjectSet
*/
public function addCategories($additionalCategories) {
$existingCategories = $this->Categories();
// method 1: Add many by iteration
foreach($additionalCategories as $category) {
$existingCategories->add($category);
}
// method 2: Add many by ID-List
$existingCategories->addMany(array(1,2,45,745));
} }
} }
@ -554,8 +545,8 @@ Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many
### Custom Relations ### Custom Relations
You can use the flexible datamodel to get a filtered result-list without writing any SQL. For example, this snippet gets You can use the flexible datamodel to get a filtered result-list without writing any SQL. For example, this snippet gets
you the "Players"-relation on a team, but only containing active players. (See `[api:DataObject::$has_many]` for more info on you the "Players"-relation on a team, but only containing active players.
the described relations). See `[api:DataObject::$has_many]` for more info on the described relations.
:::php :::php
class Team extends DataObject { class Team extends DataObject {
@ -569,6 +560,9 @@ the described relations).
} }
} }
Note: Adding new records to a filtered `RelationList` like in the example above
doesn't automatically set the filtered criteria on the added record.
## Validation and Constraints ## Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the controller Traditionally, validation in SilverStripe has been mostly handled on the controller
@ -742,19 +736,13 @@ It checks if a member is logged in who belongs to a group containing the permiss
} }
} }
### Saving data with forms ### Saving data with forms
See [forms](/topics/forms). See [forms](/topics/forms).
### Saving data with custom SQL ### Saving data with custom SQL
See `[api:SQLQuery]` for custom *INSERT*, *UPDATE*, *DELETE* queries. See the ["sql queries" topic](/reference/sqlquery) for custom *INSERT*, *UPDATE*, *DELETE* queries.
## Extending DataObjects ## Extending DataObjects
@ -763,18 +751,14 @@ code or subclassing.
Please see `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for our most Please see `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for our most
popular examples. popular examples.
## FAQ ## FAQ
### Whats the difference between DataObject::get() and a relation-getter? ### Whats the difference between DataObject::get() and a relation-getter?
You can work with both in pretty much the same way, but relationship-getters return a special type of collection:
A `[api:ComponentSet]` with relation-specific functionality. You can work with both in pretty much the same way,
but relationship-getters return a special type of collection:
A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific functionality.
:::php :::php
$myTeam = DataObject::get_by_id('Team',$myPlayer->TeamID); // returns DataObject $myTeams = $myPlayer->Team(); // returns HasManyList
$myTeam->add(new Player()); // fails $myTeam->add($myOtherPlayer);
$myTeam = $myPlayer->Team(); // returns Componentset
$myTeam->add(new Player()); // works