yii CdbCriteria nested join - join

I have trouble creating a CdbCriteria with nested join. Here's the code in model (sorry it's in Indonesian. Hopefully anyone can understand the query):
public function report() {
$criteria=new CDbCriteria;
$criteria->alias = "p";
$criteria->select = "p.tanggal_transaksi,
MONTH( p.tanggal_transaksi ) AS bulan,
p.kode,
p.kode_supplier,
s.nama,
d.kode_bahan_baku,
b.nama_barang,
d.jumlah_kg,
d.jumlah_cones,
d.harga_satuan,
d.harga_satuan * d.jumlah_cones AS total,
FLOOR(d.harga_satuan * d.jumlah_cones * p.ppn / 100) AS ppn,
(d.harga_satuan * d.jumlah_cones) + FLOOR(d.harga_satuan * d.jumlah_cones * p.ppn / 100) AS total_akhir
";
$criteria->join = "JOIN m_supplier s ON s.kode = p.kode_supplier
RIGHT JOIN t_pembelian_detail d ON d.kode_pembelian = p.kode
JOIN m_bahan_baku b ON b.kode = d.kode_bahan_baku
";
$criteria->together = TRUE;
return new CActiveDataProvider(get_class($this), array(
'criteria'=>$criteria,
));
}
The name of the model is TPembelian and here is the relation:
public function relations()
{
return array(
'supplier' => array(self::BELONGS_TO, 'MSupplier', 'kode_supplier'),
'tPembelianDetails' => array(self::HAS_MANY, 'TPembelianDetail', 'kode_pembelian'),
);
}
In a controller, I wrote these lines of codes to just simply print out each attributes of CActiveDataProvider:
$model = new TPembelian('search');
$dataProvider = $model->report();
foreach ($dataProvider->getData() as $data) {
echo "<pre>".print_r($data->attributes, 1)."</pre>";
}
the problem is, it only prints out the attributes from TPembelian model (which use alias "p"). Why are other attributes (with other alias beside "p") not printed?
I have searched for a while and it appears that CActiveDataProvider not returning one long query, but instead it returns many query with HAS_MANY relation. Someone said to use "together" and set it to TRUE (I wrote it already in the code above) to make it returns one long query, but it's still not working. Can anybody please help me?
Note:
in the first code, m_supplier, t_pembelian_detail, and m_bahan_baku are tables, not models

Related

zf2 select columns from joined tables - how?

I feel like I must me missing something very simple. It's a very simple task, all I want to do is get something like:
SELECT * FROM lookup_items
JOIN lookup ON lookup_items.lookup_id = lookup.id
This returns all the columns for all the joined tables, in regular SQL. Here's my attempt in zf2:
$select = new Select();
$select->from('lookup_items');
$select->join('lookup', 'lookup_items.lookup_id = lookup.id');
The result set only includes the columns in 'lookup_items'. I've tried various ways to get the 'lookup' columns, including:
$select->columns(array('lookup_items.*', 'lookup.*'));
But they all just blow up. Surely there's a way to do this, and it's just so simple I'm missing it completely.
I thought a simple example would be avoid confusion, but here's more code:
class LookupItemsTable extends AbstractTableGateway
{
public function getList($resource)
{
$system_name = str_replace('*', '%', strtoupper($resource));
$joinTable = 'lookup';
$select = new Select();
$select->from($this->table);
$select->join($joinTable, "{$this->table}.lookup_id = {$joinTable}.id");
$where = array();
$where[] = "{$this->table}.enabled is true";
$where[] = "{$joinTable}.enabled is true";
$where[] = "UPPER({$joinTable}.system_name) ilike '{$system_name}'";
$select->where($where);
$sort[] = 'sort_order ASC';
$sort[] = 'value ASC';
$select->order($sort);
$rowset = $this->selectWith($select);
return $rowset;
}
}
Where:
$resource = $this->params()->fromRoute('resource', 'BUSINESS');
And $this->table is 'lookup_items'. Really all I want to do is get columns from both joined tables. I guess there's a zf2 way to just make a straight SQL statement without all the OO falderal, so I could just force it that way. But I'd rather work within the framework as much as possible.
Just change this line
$select->join('lookup', 'lookup_items.lookup_id = lookup.id');
to
$select->join('lookup', 'lookup_items.lookup_id = lookup.id', array('lookupcol1', 'lookupcol2');
Raj answer is the best one but it only works if you don't forget to add these fiels in your LookupItems model.
class LookupItems
{
// Your lookup_items fields here...
...
// And the added lookup fields here, the ones you add in the array
public $lookupcol1;
public $lookupcol2;
And in the exchangeArray method :
public function exchangeArray($data)
{
// .... your fields, and the new ones
$this->lookupcol1 = (! empty($data['lookupcol1'])) ? $data['lookupcol1'] : null;
$this->lookupcol2 = (! empty($data['lookupcol2'])) ? $data['lookupcol2'] : null;
}
I figured it out.
Added this:
$select->columns(array('*'));
And then this near the end:
$sql = new Sql($this->adapter);
$statement = $sql->prepareStatementForSqlObject($select);
$rowset = $statement->execute();
This returns the expected result, with the caveat that now my rows are returned as associative arrays instead of objects.
This is how you can create queries with join in zf2.
$resultSet = $this->select(function (Select $select) {
// omit the table name
//$select->from('foo');
$select->join('users', "users.id foo.createdby", 'firstname', '');
$select->order('id ASC');
// echo $select->getSqlString();// to print your query
});
$entities = array();
foreach ($resultSet as $row) {
$entity = new Entity\Foo();
$entity->setId($row->id)
->setFullname($row->fullname)
->setCaseid($row->caseid)
->setTestimonial($row->testimonial)
->setSortorder($row->sortorder)
->setActive($row->active)
->setCreated($row->created)
->setModified($row->modified)
->setFirstname($row->firstname)
->setCreatedby($row->createdby);
$entities[] = $entity;
}
return $entities;

Run view query in Symfony/propel

I've an existing project in Symfony 1.4/Propel 1.4
For database optimization purpose, I created a view of few tables. The function/ view query are as follow:
create function getPlayer() returns INTEGER DETERMINISTIC NO SQL return #getPlayer;
create view getPlay as
SELECT
CASE WHEN play.hiderid = getPlayer() THEN play.seekerid ELSE play.hiderid END AS opponent, play . *
FROM odd_play play, odd_match mat
WHERE (seekerid = getPlayer() OR hiderid = getPlayer())
AND play.id = mat.latestplay;
After creating above view, I can write following simple SQL query to get required data effectively.
select play.*
from (select #getPlayer:=1 p) p, getPlay play;
Now the problem is that, how to write this query in Symfony/Propel 1.4. Can someone please suggest how to write that query in propel 1.4?
Edit after J0K comments
I'm trying following
class GetplayPeer extends BaseGetplayPeer {
static public function getOpponents($player){
$con = Propel::getConnection();
$sql = "select play.* from (select #getPlayer:=:player p) ply, getPlay play;";
$stmt = $con->prepare($sql);
$stmt->bindParam(":player",&$player,PDO::PARAM_INT);
$rs = $stmt->execute();
//$opponents = GetplayPeer::populateObjects($rs);
echo "opponents=<pre>";print_r($rs);exit;
}
} // GetplayPeer
I'm getting 1 as output, which is not expected output.

Getting a list of distinct entities projected into a new type with extra field for the count

I'm designing an interface where the user can join a publicaiton to a keyword, and when they do, I want to suggest other keywords that commonly occur in tandem with the selected keyword. The trick is getting the frequency of correlation alongside the properties of the suggested keywords.
The Keyword type (EF) has these fields:
int Id
string Text
string UrlString
...and a many-to-many relation to a Publications entity-set.
I'm almost there. With :
var overlappedKeywords =
selectedKeyword.Publications.SelectMany(p => p.Keywords).ToList();
Here I get something very useful: a flattened list of keywords, each duplicated in the list however many times it appears in tandem with selectedKeyword.
The remaining Challenge:
So I want to get a count of the number of times each keyword appears in this list, and project the distinct keyword entities onto a new type, called KeywordCounts, having the same fields as Keyword but with one extra field: int PublicationsCount, into which I will populate the count of each Keyword within overlappedKeywords. How can I do this??
So far I've tried 2 approaches:
var keywordCounts = overlappingKeywords
.Select(oc => new KeywordCount
{
KeywordId = oc.Id,
Text = oc.Text,
UrlString = oc.UrlString,
PublicationsCount = overlappingKeywords.Count(ok2 => ok2.Id == oc.Id)
})
.Distinct();
...PublicationsCount is getting populated correctly, but Distinct isn't working here. (must I create an EqualityComarer for this? Why doesn't the default EqualityComarer work?)
var keywordCounts = overlappingKeywords
.GroupBy(o => o.Id)
.Select(c => new KeywordCount
{
Id = ???
Text = ???
UrlString = ???
PublicationsCount = ???
})
I'm not very clear on GroupBy. I don't seem to have any access to 'o' in the Select, and c isn't comping up with any properties of Keyword
UPDATE
My first approach would work with a simple EqualityComparer passed into .Distinct() :
class KeywordEqualityComparer : IEqualityComparer<KeywordCount>
{
public bool Equals(KeywordCount k1, KeywordCount k2)
{
return k1.KeywordId== k2.KeywordId;
}
public int GetHashCode(KeywordCount k)
{
return k.KeywordId.GetHashCode();
}
}
...but Slauma's answer is preferable (and accepted) because it does not require this. I'm still stumped as to what the default EqualityComparer would be for an EF entity instance -- wouldn't it just compare based on primary ids, as I did above here?
You second try is the better approach. I think the complete code would be:
var keywordCounts = overlappingKeywords
.GroupBy(o => o.Id)
.Select(c => new KeywordCount
{
Id = c.Key,
Text = c.Select(x => x.Text).FirstOrDefault(),
UrlString = c.Select(x => x.UrlString).FirstOrDefault(),
PublicationsCount = c.Count()
})
.ToList();
This is LINQ to Objects, I guess, because there doesn't seem to be a EF context involved but an object overlappingKeywords, so the grouping happens in memory, not in the database.

How manage a VIEW with Doctrine 2?

I would like mapping a sql-view with Doctrine2.
This view is a TempTable containing some statistics that would show without rewriting the sql that generates the view
I try to map like a table, but updating schema drop the view and create a table
I try also with NativeSQL...
public function getMessages(\Project\Bundle\MyBundle\Entity\User $user) {
$rsm = new \Doctrine\ORM\Query\ResultSetMapping();
$rsm->addEntityResult('MessageCenter', 'v');
$rsm->addFieldResult('v', 'user_id', 'user_id');
$rsm->addFieldResult('v', 'tot', 'tot');
$rsm->addFieldResult('v', 'read', 'read');
$rsm->addFieldResult('v', 'to_read', 'to_read');
$rsm->addFieldResult('v', 'stored', 'stored');
$rsm->addFieldResult('v', 'spam', 'spam');
$q = "SELECT * FROM message_stats_view WHERE user_id = ?";
$rsm = new \Doctrine\ORM\Query\ResultSetMapping;
$query = $this->getEntityManager()->createNativeQuery($q, $rsm);
$query->setParameter(1, $user->getId());
echo $query->getSQL();
var_dump($query->execute());
exit;
}
I create the entity MessageCenter with getter and setter, but my output is:
SELECT * FROM message_stats_view WHERE user_id = ?
array
empty
(Answered by the OP in aq question edit. Transcribed to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
I SOLVED!!!!
public function getCentroMessaggi(\Project\Bundle\MyBundle\Entity\User $user) {
$connection = $this->getEntityManager()->getConnection();
$q = "SELECT * FROM message_stats_view WHERE user_id = :id";
$stmt = $connection->executeQuery($q, array('id' => $user->getId()));
return $stmt->fetch();
}
This return an array. PERFECT!

How to specify a condition in an Entity Framework join?

I have a Blogs table related to BlogComments table with a FK.
I need to get, through Linq, all the BlogComments items that match a certain flag
If i do:
db.Blogs.Where(b => b.BlogComments.Where(bc=>bc.Where(bc.Flag1==true));
I get "Cannot implicity convert type IEnumerable to bool"
Which is the best way to solve this problem?
Because this expression:
b.BlogComments.Where(...)
returns an IEnumerable (of BlogComments), but you are then passing it into this method:
db.Blogs.Where(...)
which expects a function that returns a bool, not an IEnumerable.
You probably need something like this:
var blogId = 5;
db.BlogComments.Where(bc => bc.BlogId == blogId && bc.Flag1 == true)
If you need to select comments from multiple blogs, then you could try using Contains:
var blogIds = new [] {1,2,3,4,5};
db.BlogComments.Where(bc => blogIds.Contains(bc.BlogId) && bc.Flag1 == true)
If you want to place criteria on the set of blogs, as well as the comments, then you could do this in one query using a join:
var query = from b in db.Blogs
join c in db.BlogComments on c.Blog equals b
where b.SomeField == "some value"
&& c.Flag1 == true
select c;
You could write it in LINQ form.
var blogs = from b in db.Blogs
join c in db.BlogComments
on b.BlogId equals c.BlogId
where c.Flag1
select b;
If you have a composite key you can write
on new { A = b.BlogKey1, B = b.BlogKey2 }
equals new { A = c.CommentKey1, B = c.CommentKey2 }
If it were me, I would just have another DbSet in your DbContext.
DbSet<BlogComment> BlogComments
and just search through there without going through Blogs.
db.BlogComments.Where(bc => bc.Flag1 == true);
If anyone knows if there's anything wrong in doing so, then I'm all ears :)

Resources