I have 3 tables that i basically need to display in the single gridview.
property
sale (FK property_id)
sale_staff (FK sale_id, staff_id)
staff
Now a staff member can be a lister, a seller, or both, and each sale can have multiple lister or sellers.
So table sale_staff has column 'staff_type' with possible enum values lister',seller`.
So the gridview must have a display something like
property street | sale price | sale date | lister | seller
and the lister/seller fields should be able to display simple initials comma separated like MJ, AB. This initial field comes from the staff table, which as above is linked to sale_staff via id.
I have not seen this type of scenario before so im simply unsure of how to incorporate the sale_type into the search so that only a staff listed as lister or seller is shown in that place.
Also, if i try something like this, just to see if anything is pulled back:
'attribute' => 'saleStaff.staff_id.initials'
or
'attribute' => 'saleStaff.staff'
I get (not set) in the gridview.
I have relations set up as:
Sale:
public function getProperty()
{
return $this->hasOne(Property::className(), ['id' => 'property_id']);
}
public function getSaleStaff()
{
return $this->hasMany(SaleStaff::className(), ['sale_id' => 'id']);
}
Sale_Staff:
public function getStaff()
{
return $this->hasOne(Staff::className(), ['id' => 'staff_id']);
}
public function getSale()
{
return $this->hasOne(Sale::className(), ['id' => 'sale_id']);
}
How do I match up the join table sale_staff with the sale table ? Is the structure ok or is DB incorrect ? I have done it this way to be flexible - any number of staff can be either a lister, seller, or both such as:
property street | sale price | sale date | lister | seller
Parkers Road | 400,000 | 22/06/2016| MJ | MJ, AB
Naturally, key question is how to represent multiple (unknown number of) values in a single gridview column like that.
Related
I'm developing a reddit-like site where votes are stored per-user (instead of per-post). Here's my relevant schema:
content
id | author_id | title | text
---|-----------|-------------|---
1 | 1 (adam) | First Post | This is a test post by adam
vote: All the votes ever voted by anyone on any post
id | voter_id | content_id | category_id
---|-------------|------------------|------------
1 | 1 (adam) | 1 ("First Post") | 1 (upvote)
2 | 2 (bob) | 1 ("First Post") | 1 (upvote)
vote_count: Current tally ("count") of total votes received by a post by all users
id | content_id | category_id | count
---|------------------|--------------|-------
1 | 1 ("First Post") | 1 (upvote) | 2
I've defined a voteCount relation in Objection.js model for the content table:
class Content extends Model {
static tableName = 'content';
static relationMappings = {
voteCount: {
relation: Model.HasManyRelation,
modelClass: VoteCount,
join: {
from: 'content.id',
to: 'vote_count.content_id'
}
}
}
}
But I recently (learned and) decided that I don't need to keep (and update) a separate vote_count table, when in fact I can just query the vote table and essentially get the same table as a result:
SELECT content_id
, category_id
, COUNT(*) AS count
FROM vote
GROUP
BY content_id
, category_id
So now I wanna get rid of the vote_count table entirely.
But it seems that would break my voteCount relation since there won't be a VoteCount model (not shown here but it's the corresponding the model for the vote_count table) no more either. (Right?)
How do I keep voteCount relation while getting rid of vote_count table (and thus VoteCount model with it)?
Is there a way to somehow specify in the relation that instead of looking at a concrete table, it should look at the result of a query? Or is it possible to define a model class for the same?
My underlying database in PostgreSQL if that helps.
Thanks to #Belayer. Views were exactly the solution to this problem.
Objection.js supports using views (instead of table) in a Model class, so all I had to do was create a view based on the above query.
I'm also using Knex's migration strategy to create/version my database, and although it doesn't (yet) support creating views out of the box, I found you can just use raw queries:
module.exports.up = async function(knex) {
await knex.raw(`
CREATE OR REPLACE VIEW "vote_count" AS (
SELECT content_id
, category_id
, COUNT(*) AS count
FROM vote
GROUP
BY content_id
, category_id
)
`);
};
module.exports.down = async function(knex) {
await knex.raw('DROP VIEW "vote_count";');
};
The above migration step replaces my table vote_count for the equivalent view, and the Objection.js Model class for it (VoteCount) worked as usual without needing any change, and so did the relation voteCount on the Content class.
This is my first time using a polymorphic relationship.
I am creating a LMS where a Assignment can be allocated to a individual user or a team so reading the Laravel docs it seems that the Polymorphic Relationship will be a good way to go about it.
I have created 4 tables.
Users:
| id | username | password | created_at | updated_at |
Teams: | id | friendly_name | slug |
Team User: | id | user_id | team_id |
Assignment Allocation:
| id | assignment_id | assignmentable_type | assignmentable_id | created_at | updated_at
So when the assignment_allocations has data in it looks like this...
| id | assignment_id | assignmentable_type | assignmentable_id |
| 1 | 1 | App\Models\User | 1 |
However I get this error:
SQL: select * from users where users.assignmentable_id = 1 and users.assignmentable_id is not null and users.assignmentable_type = App\Models\User
SO obviously I have done something wrong, however I cannot for the life of me figure out what I've done wrong.
This is my functions that relate to this:
AssignmentAllocation.php
public function assignmentable()
{
return $this->morphTo();
}
User.php
public function assignments()
{
return $this->morphMany('App\Models\User', 'assignmentable');
}
Team.php
public function assignments()
{
return $this->morphMany('App\Model\Team', 'assignmentable');
}
Any help is greatly appreciated.
There are 2 potential answers depending on your data structure, which isn't clear from your question.
User/Team to Assignment is a one-to-many relationship (each Assignment has one User/Team, and each User/Team has many Assignments
Many-to-many relationship where each User/Team has many Assignments and each Assignment has many Users/Teams
One to many
In a one-to-many you wouldn't need an Assignment table and an AssignmentAllocation table, so I am assuming your AssignmentAllocation is your Assignment model. If not then you need to put the assignmentable_type and assignmentable_id columns on the assignments table instead, and use Assignment.php instead of AssignmentAllocation.php.
AssignmentAllocation.php
public function assignmentable()
{
return $this->morphTo();
}
User.php
public function assignments()
{
return $this->morphMany('App\Models\AssignmentAllocation', 'assignmentable');
}
Team.php
public function assignments()
{
return $this->morphMany('App\Models\AssignmentAllocation', 'assignmentable');
}
Your error is because Laravel is searching the users table for the match (you have morphMany('User'..), when it should be searching the AssignmentAllocation table. So just switch them out.
1) The morphMany acts like a hasMany, so you're saying:
Each User hasMany Assignments, and each Team hasMany Assignments. The first part of the morphMany says "search the AssignmentAllocation table", and the second part says "search for assignmentable_id and assignmentable_type being equal to this instance of this model".
2) morphTo acts like belongsTo so you're saying:
Each Assignment belongsTo an assignmentable.
Many to many
However if AssignmentAllocation is a many-to-many pivot table and each Assignment has many Users or Teams, and each User/Team has many Assignments, then you need a morphToMany/morphedByMany pair.
User.php
public function assignments()
{
return $this->morphToMany('App\Models\Assignment', 'assignmentable');
}
Team.php
public function assignments()
{
return $this->morphToMany('App\Models\Assignment', 'assignmentable');
}
Assignment.php model NOTE: not the AssignmentAllocation model
public function users()
{
return $this->morphedByMany('App\Models\User', 'assignmentable');
}
public function teams()
{
return $this->morphedByMany('App\Models\Team', 'assignmentable');
}
You should rename the assignment_allocation table to assignmentables, or add the required third and fourth arguments to the morph functions. I prefer to keep the table names consistent.
Using PostgreSQL, I want to be able to find all of the key-value pairs in an HStore field where the value matches a given query. So, for example, given a table like the following:
Name Parentage (Hstore)
_____ ___________________
Rover Mother => Yellow Lab, Father => Black Lab
Fido Mother => Black Lab, Father => Rottweiler
Rex Mother => Labrador Retriever, Father => Springer Spaniel
Lassie Mother => Border Collie, Father => Collie
How could I do a query for any dog that has a '%lab%' in its family tree? I.e. the search would bring up Rover, Fido and Rex, but not Lassie. The only examples I've seen are of searches within a single key - I need a search within all values that allows wildcards.
I have looked at this similar question, but it seems to only do a search on a particular key, not on all of the values found in the Hstore field across all keys.
Note that this is a constructed example, I was trying to make it accessible. In my actual database, I have keys for language codes followed by values of translations of the same words in the different language. I need to be able to do a search that could hit any of the values, regardless of what language it is in.
Break out the hstore into rows of name/parent and then select distinct
names of dogs where the parent column matches your search criteria.
Depending on the nature of your actual data, this may want additional
indexes. I wouldn't use hstore for this, but your actual data may
be different.
% psql -qe -f hstore.sql
begin;
create extension hstore;
create temp table dogs (
"name" text,
parentage hstore
);
insert into dogs values
('Rover', 'Mother => "Yellow Lab",Father => "Black Lab"')
,('Fido', 'Mother => "Black Lab",Father => "Rottweiler"')
,('Rex', 'Mother => "Labrador Retriever",Father => "Springer Spaniel"')
,('Lassie', 'Mother => "Border Collie",Father => "Collie"')
;
table dogs;
name | parentage
--------+--------------------------------------------------------------
Rover | "Father"=>"Black Lab", "Mother"=>"Yellow Lab"
Fido | "Father"=>"Rottweiler", "Mother"=>"Black Lab"
Rex | "Father"=>"Springer Spaniel", "Mother"=>"Labrador Retriever"
Lassie | "Father"=>"Collie", "Mother"=>"Border Collie"
(4 rows)
select * from dogs where "name" in
(select distinct "name" from (
select "name", unnest(avals(parentage)) as parent
) as plist
where parent ilike '%lab%'
);
name | parentage
-------+--------------------------------------------------------------
Rover | "Father"=>"Black Lab", "Mother"=>"Yellow Lab"
Fido | "Father"=>"Rottweiler", "Mother"=>"Black Lab"
Rex | "Father"=>"Springer Spaniel", "Mother"=>"Labrador Retriever"
(3 rows)
rollback;
I think I'm kinda confused, I have a model whose some of the fields where to reference their detail/s (description/s) from another table.
eg.
**tblCustomers**
_______________________
Name | Address | Gender
-----------------------
A | A | M
B | B | M
C | C | F
**tblGender**
__________________
Code | Description
------------------
M | Male
F | Female
In my view here's how it look
Name A
Address A
Gender M <<< wherein what I wanted is something like
Name A
Address A
Gender M - Male
In my model, I am currently just doing something like these:
public function search($_id,$_curLevel)
{
// #todo Please modify the following code to remove attributes that should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('Name',$this->name);
$criteria->compare('Address',$this->address);
$criteria->compare('Gender',$this->gender,true);
}
I know its pretty obvious, since the model is just looking into a single table, but I can't figure how to relate and use another table to be able to get the description from other reference tables.
The way I would do this, is to add a relation to my model.
I am assuming you have a model setup for the gender table, and it is called Gender. I would also rename your 'gender' column in your customer table to genderID or similar. I have renamed it for my example.
Add this code to your Customer Model:
public function relations()
{
return array(
'gender' => array(self::BELONGS_TO, 'Gender', 'genderID'),
);
}
Then once you have created this relationship, it is very easy to extract the information in your view.
You already have the model, so now the view looks like this:
echo $model->name;
echo $model->address;
echo $model->gender->description;
Note that 'gender' in the line above is referring to the 'gender' relation we created, not the column that you had named 'gender' in the description table (I renamed that to genderID).
If you just wanna the gender description then you can user the relation and display $model->gender->description; as descriped in the above answer
If you wanna a custom text as you mentioned "M - Male" then you have to add a public property and fill it in the afterFind() method
class Customer extends CActiveRecord {
public $_gender;
public function afterFind() {
$this->_gender = $this->gender . ' - ' . $this->gender->description;
return parent::afterFind();
}
}
note: that gender relation must be exist
I currently am using the following code
$select = $this->select()
->setIntegrityCheck(false)
->from(array('st' => $this->_name))
->join(array('sp' => 'staff_permissions'), 'sp.staff_id = st.id and sp.pool_id = ' . $pool_id )
->join(array('p' => 'permissions'), 'p.id = sp.permission_id')
->where('staff_id = ?', $staff_id);
return $this->fetchAll($select)->toArray();
It combines three tables and returns the result. The 'st' table corresponds to one staff (so one row), and the other two tables correspond to multiple rows. So what I was hoping was to get a single object back such that the other two tables are arrays inside the object.
So as an example, I get back $row, so that $row->first_name is the name, but $row->permission_id is an array with all the ids in it.
Can that be done using the JOIN clause?
This query should be done in the lowest layer in your application, the next layer up the stack will be your mappers. In your mapper layer you can map to your Entities, which will be a 'staff' entity (object) which contains a collection for 'staff_permissions' & a collection for 'permissions'
model diagram:
-----------
| service | // business logic
-----------
|
-----------
| mapper | // maps external data to internal entities (or vice versa)
-----------
|
----------- ----------------------
|dao (sql)| -> | zend table gateway |
----------- ----------------------
mapper example code:
$staffEntity = new StaffEntity();
$staffEntity->setName($response['name']);
foreach($response['staff_permissions] as $permission) {
$permission = new Permission();
$permission->setName($permission['name']);
$permission->setRule($permission['rule']);
// ... etc ...
$staffEntity->addPermission($permission);
}
// ... same for permissions ...
return $staffEntity;