Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
  • Loading branch information...
commit 402297ee1f518dff2d6af6080fd1ce9818b0185c 1 parent 19e087d
@chillu chillu authored
Showing with 92 additions and 165 deletions.
  1. +72 −129 docs/en/reference/sqlquery.md
  2. +20 −36 docs/en/topics/datamodel.md
View
201 docs/en/reference/sqlquery.md
@@ -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.
+
+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();
-You'll run the risk of breaking various assumptions the ORM and code based on it have:
+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();
+ // 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.
### 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
+Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
:::php
- foreach($result as $row) {
- echo $row['BirthYear'];
- }
+ DB::query('UPDATE "Player" SET "Status"=\'Active\'');
+### Value Checks
-### Quick value checking
+Raw SQL is handy for performance-optimized calls,
+e.g. when you want a single column rather than a full-blown object representation.
-Raw SQL is handy for performance-optimized calls.
+Example: Get the count from a relationship.
:::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 = 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();
-Way faster than dealing with `[api:DataObject]`s, but watch out for premature optimisation:
+Note that in the ORM, this call would be executed in an efficient manner as well:
:::php
- $players = $myTeam->Players();
- echo $players->Count();
-
+ $count = $myTeam->Players()->count();
### Mapping
-Useful for creating dropdowns.
+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->setFrom('Player');
- $sqlQuery->selectField('YEAR("Birthdate")', 'Birthdate');
+ $sqlQuery->setSelect('Birthdate');
+ $sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
$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
- 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:
+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.
:::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
- $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.
-
- :::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
-
-
-**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.
+ 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
56 docs/en/topics/datamodel.md
@@ -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.
- :::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
+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
+ $myTeams = $myPlayer->Team(); // returns HasManyList
+ $myTeam->add($myOtherPlayer);
Please sign in to comment.
Something went wrong with that request. Please try again.