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
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
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)
* 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,
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
### SELECT
@ -25,11 +49,8 @@ but still maintain a connection to the ORM where possible.
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name');
$sqlQuery->selectField('YEAR("Birthday")', 'BirthYear');
$sqlQuery->addLeftJoin(
'Team',
'"Player"."TeamID" = "Team"."ID"'
);
$sqlQuery->selectField('YEAR("Birthday")', 'Birthyear');
$sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"');
$sqlQuery->addWhere('YEAR("Birthday") = 1982');
// $sqlQuery->setOrderBy(...);
// $sqlQuery->setGroupBy(...);
@ -37,164 +58,86 @@ but still maintain a connection to the ORM where possible.
// $sqlQuery->setLimit(...);
// $sqlQuery->setDistinct(true);
// get the raw SQL
// Get the raw SQL (optional)
$rawSQL = $sqlQuery->sql();
// execute and return a Query-object
// Execute and return a Query object
$result = $sqlQuery->execute();
### 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
// Iterate over results
foreach($result as $row) {
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
Raw SQL is handy for performance-optimized calls.
### DELETE
:::php
class Team extends DataObject {
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();
}
$sqlQuery->setDelete(true);
Way faster than dealing with `[api:DataObject]`s, but watch out for premature optimisation:
### INSERT/UPDATE
:::php
$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:
Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
:::php
DB::query('UPDATE "Player" SET "Status"=\'Active\'');
<<<<<<< Updated upstream
### 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:
### Value Checks
:::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);
Raw SQL is handy for performance-optimized calls,
e.g. when you want a single column rather than a full-blown object representation.
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
$sqlQuery = singleton('Player')->buildSQL('YEAR("Birthdate") = 1982');
This form of building a query has the following advantages:
* Respects DataObject::$default_sort
* 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
* Filtering records for correct *ClassName*
### Transforming a result to `[api:DataObjectSet]`
>>>>>>> Stashed changes
This is a commonly used technique inside SilverStripe: Use raw SQL, but transfer the resulting rows back into
`[api:DataObject]`s.
Example: Get the count from a relationship.
:::php
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
$sqlQuery->addWhere('"Team"."ID" = 99');
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
$count = $sqlQuery->execute()->value();
Note that in the ORM, this call would be executed in an efficient manner as well:
:::php
$count = $myTeam->Players()->count();
### Mapping
Creates a map based on the first two columns of the query result.
This can be useful for creating dropdowns.
Example: Show player names with their birth year, but set their birth dates as values.
:::php
$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->addLeftJoin('Team', '"Player"."TeamID" = "Team"."ID"');
$sqlQuery->addWhere("YEAR("Player"."Birthday") = 1982");
$result = $sqlQuery->execute();
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
$sqlQuery->setSelect('Birthdate');
$sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
$map = $sqlQuery->execute()->map();
$field = new DropdownField('Birthdates', 'Birthdates', $map);
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:
* 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.
* 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
above example
Be careful when saving back `[api:DataObject]`s created through *buildDataObjectSet*, you might get strange side-effects due to
the issues noted above.
## Using FormFields with custom SQL
Some subclasses of `[api:FormField]` for ways to create sophisticated report-tables based on SQL.
:::php
class Player extends DataObject {
static $db = array(
'Name' =>
'Birthdate' => 'Date'
);
function getNameWithBirthyear() {
return date('y', $this->Birthdate);
}
}
$players = Player::get();
$map = $players->map('Name', 'NameWithBirthyear');
## Related
* [datamodel](/topics/datamodel)
* `[api:DataObject]`
* [database-structure](database-structure)
## API Documentation
`[api:SQLQuery]`
* [database-structure](database-structure)

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
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
@ -523,8 +524,10 @@ accessors available on both ends.
### 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
`[api:ComponentSet]`.
Adding new items to a relations works the same,
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
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",
);
/**
* @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));
public function addCategories(SS_List $cats) {
foreach($cats as $cat) $this->Categories()->add($cat);
}
}
@ -554,8 +545,8 @@ Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many
### Custom Relations
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
the described relations).
you the "Players"-relation on a team, but only containing active players.
See `[api:DataObject::$has_many]` for more info on the described relations.
:::php
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
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
See [forms](/topics/forms).
### 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
@ -763,18 +751,14 @@ code or subclassing.
Please see `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for our most
popular examples.
## FAQ
### 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
$myTeam = DataObject::get_by_id('Team',$myPlayer->TeamID); // returns DataObject
$myTeam->add(new Player()); // fails
$myTeam = $myPlayer->Team(); // returns Componentset
$myTeam->add(new Player()); // works
$myTeams = $myPlayer->Team(); // returns HasManyList
$myTeam->add($myOtherPlayer);