I use yii2 to build one app which need to connect some tables. I can join them simply and search for data in related fields. I am doing it by adding the connection like this>
public function getNextTab()
{
return $this->hasOne(NextTab::className(),['id' =>'id_nexttab']);
}
and ask for the data in search model using like this ->
->where ('id'='ok') ->
->joinWith('nextTab')
->joinWith('nextTab.nextTab1')
->joinWith('nextTab.nextTab1.nextTab2');
My problem is when I try to do this with tables from different database. The query is give me error like
SQLSTATE[42S02]: Base table or view not found:
any tips how to pass it? or how to do other way of connection to have the data.
Joining tables from different databases may not be supported by your RDBMS (PostgreSQL for example). But if supported (MSSQL, MySQL) then table names should be prefixed with database name (and schema if needed). You can achieve this in Yii2 using {{%TableName}} syntax in tableName() function.
public static function tableName()
{
return '{{%table_name}}';
}
But be careful with joining tables from different databases if they are located on different servers -- this can be very slow.
If you just want to get related data (joined tables are not used in WHERE) then use with() instead of joinWith(). This will be executed as separate query with IN statement. In most cases this way has a better performance and no problems with different sources (and even different DBMS).
->with('nextTab', 'nextTab.nextTab1', 'nextTab.nextTab1.nextTab2')
Configure your second database component in the application's config.
Override the getDB() function in your ActiveRecord Model to return the second DB component.
This will attach your Model to the secondary DB and allow you to query from the table in secondary DB.
Config sample:
'components' => [
'db2' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=db2',
'username' => 'hello',
'password' => 'world',
],
],
getDB() function override sample:
class Tab extends ActiveRecord
{
public static function getDb()
{
// use the "db2" component
return \Yii::$app->db2;
}
}
Good Luck!
Related
json variable collection:
"collection1"
{
"recordset1":
{
"database_id": [
"/subscriptions/----/HemaSqlDB",
"/subscriptions/----/HemaSqlDB2",
"/subscriptions/----/HemaSqlDB3"
]
}
}
resource "azurerm_sql_failover_group" "sql_failover" {
databases = [var.database_id[0],var.database_id[1],var.database_id[2]]
}
how to achieve this dynamically ? i cannot use count_index because failover group is already existing and ì'm trying to add more databases to it.
You don't need the count, you just need to set the databases with the list variable like this:
resource "azurerm_sql_failover_group" "sql_failover" {
...
databases = var.database_id
...
}
Of course, the list must contain the existing databases and the new databases you want to add into the failover group.
I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.
Usage of 't' in model relations can give the error
"Column not found: 1054 Unknown column 't.etc' in 'on clause'."
It's the usage of 't' to refer to the current table in CActiveRecord Model relations.
I often stumble accross it when using ,findAll, CActiveDataProvider, etc
Sometimes it works sometimes it doesn't depending on what model you execute and from where.
I tried using tableAlias but it doesn't work. There must be an easy way.
How can I setup my models and it's relations in such a way that the relations are stable
and always works?
Here is an example of two classes to show the problem...
class Order extends CActiveRecord
{
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'author'=>array(self::BELONGS_TO, 'User', 'user_id'),
'shopproduct'=>array(self::BELONGS_TO, 'ShopProduct', 'product_id',
'with'=>array(
'tagsrelations',
),
),
);
}
}
class ShopProduct extends CActiveRecord
{
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'author'=>array(self::BELONGS_TO, 'User', 'user_id'),
'tagsrelations'=>array(self::HAS_MANY, 'TagsRelations','',
'on'=>' tagsrelations.tbl_uuid = t.uuid ',
'with'=>'tag',
'order'=>'tag.name ASC',
),
);
}
}
// works
$model=Order::model()->with('shopproduct')->findAll();
// doesn't work
// "Column not found: 1054 Unknown column 't.uuid' in 'on clause'. The SQL statement executed was"
$model=ShopProduct::model()->with('tagsrelations')->findAll();
Maybe someone can explain why this isn't working in an understanding way.
How to fix this one once and for all. BELONG TO relations usually work.
What is the best to make an HAS MANY relationship that always works
Do you need the t only for the join or for some specific purpose? Because for the join this is not the right way to do it. It should be done sth like this:
'tagsrelations'=>array(self::HAS_MANY, 'TagsRelations', array('tbl_uuid'=>'uuid')),
Of course you can then add the with condition if you need that as well. But the join should be done this way.
I'm working with Fluent nHibernate on a legacy database and have a main Person table and several extension tables containing additional information about the person. These extension tables are one-to-one, meaning that a person will only have one row on the extension table and the extension table should always map back to one person.
Table: Person
Columns: PersonID, FirstName, LastName, etc.
Table: PersonLogin
Columns: PersonID (FK, unique), UserName, Password, etc.
I have my mappings defined as this (with the irrelevant properties omitted):
public PersonMap()
{
Table("Person");
Id(x => x.Id, "PersonID").Not.Nullable();
References(x => x.Login, "PersonID").LazyLoad();
}
public LoginMap()
{
Table("PersonLogin");
Id(x => x.Id, "PersonID").GeneratedBy.Foreign("Person");
References(x => x.Person, "PersonID").LazyLoad();
}
This works when I have data on both tables, but I recently learned that some of the extension tables don't have data for all Person rows. This caused me to get errors during the query. So, I added .NotFound.Ignore() to my PersonMap making it look like this:
References(x => x.Login, "PersonID").LazyLoad().NotFound.Ignore();
That caused me to get unnecessary selects from the Login table due to https://nhibernate.jira.com/browse/NH-1001 when my business layer doesn't need to project any of the extension table values. It is causing the performance to be terrible in some of my search queries.
I've scoured a lot of posts, but haven't found a rock solid answer about how to address this scenario. Below are the options I've tried:
Option One:
Create rows on the extension table to ensure there is no Person without a row on the extension table and then remove the .NotFound.Ignore().
The issue with this option is that it's a legacy database and I'm not sure where I'd need to update to ensure that a PersonLogin is inserted when a Person is inserted.
Option Two:
Remove the PersonLogin reference from my PersonMap and custom load it inside my Person class. Like this:
public class Person
{
/// <summary> Gets or sets the PersonID </summary>
public virtual int Id { get; set; }
private bool loadedLogin;
private PersonLogin login;
public virtual PersonLogin Login
{
get
{
if (!loadedLogin)
{
login = SessionManager.Session().Get<PersonLogin>(Id);
loadedLogin = true;
}
return login;
}
set
{
login = value;
loadedLogin = true;
}
}
}
The issue I'm having with it is that I can't eagerly fetch the data when performing a query to pull back a large number of Person objects and their Logins.
Option Three:
I just started playing to see if I could write a custom IEntityNotFoundDelegate to not throw the exception for these objects.
private class CustomEntityNotFoundDelegate : IEntityNotFoundDelegate
{
public void HandleEntityNotFound(string entityName, object id)
{
if (entityName == "my.namespace.PersonLogin")
{
return;
}
else
{
throw new ObjectNotFoundException(id, entityName);
}
}
}
And I added this to the config
cfg.EntityNotFoundDelegate = new CustomEntityNotFoundDelegate();
It catches my scenario and returns back now instead of throwing the error, but now when I try to project those PersonLogin properties onto my business objects, it's attempting to use the Proxy object and throws this error that I'm trying to figure out if I can handle cleanly (possibly in a IPostLoadEventListener).
System.Reflection.TargetException occurred
Message = Non-static method requires a target
I think I've got this working now by keeping the .NotFound.Ignore().
I originally stated:
That caused me to get unnecessary selects from the Login table due to https://nhibernate.jira.com/browse/NH-1001 when my business layer doesn't need to project any of the extension table values. It is causing the performance to be terrible in some of my search queries.
I was able to tweak my LINQ queries to use the IQueryOver in some instances and to improve my use of LINQ in other scenarios to project only the necessary values. This appears to have resolved the queries from pulling back the extension tables since their values were not needed in the projections.
I thought that my queries weren't projecting these extension tables, but figured out that I had a method ToKeyValuePair that I was using in the projection to concatenate the ID and a Name field together of some related properties. That method was causing the objects to load completely since LINQ wasn't able to determine that the needed fields were present without joining to the extension table.
I have this code in my model for "Application", I'm trying to get all the related "Campaign" objects
public function relations()
{
return array(
'campaigns' => array(self::HAS_MANY, 'Campaign', 'appKey'),
);
}
My problem is, the 'appKey' field in the campaigns table is not the primary key of the applications table and this is what Yii is using to try and find the campaigns.
The primary key of my applications table is 'id' but I would like it to use 'appKey'. How can I update my relations method to do this without making it the primary key?
Thanks.
You could set up a named scope in the Campaign model, like so:
public function byApplication($appKey)
{
$this->getDbCriteria()->mergeWith(array(
'condition'=>'appKey = :appkey',
'params'=>array('appKey'=>$appKey),
));
return $this;
}
And call it like this:
$campaigns = Campaign::model()->byApplication($appKey);
Or, as Irobb said, just set up a query function in your Application model instead of using an actual Relation, like so:
public function getCampaigns() {
return Campaign::model()->findallbyAttributes(array('appKey'=>$this->appKey));
}
And call it like a regular relation on your Application $model:
$campaigns = $model->campaigns; // remember with Yii you can call getters w/o the 'get'
A couple of things... AR is primarily useful for modeling a single table to a class, with a well-defined primary key... Anything else I would use the query builder.
Note: AR is not meant to solve all database-related tasks. It is best
used for modeling database tables in PHP constructs and performing
queries that do not involve complex SQLs. Yii DAO should be used for
those complex scenarios.
http://www.yiiframework.com/doc/guide/1.1/en/database.ar
AR relies on well defined primary keys of tables. If a table does not
have a primary key, it is required that the corresponding AR class
specify which column(s) should be the primary key by overriding the
primaryKey() method as follows,
public function primaryKey() {
return 'id';
// For composite primary key, return an array like the following
// return array('pk1', 'pk2'); }