I have the following virtual field on my Page model
function __construct($id = false, $table = null, $ds = null) {
$this->virtualFields['fans'] = 'SELECT COUNT(Favorite.id) FROM favorites AS Favorite WHERE Favorite.page_id = Page.id AND Favorite.status = 0';
parent::__construct($id, $table, $ds);
}
This works as expected and displays the number of users who have added the page to their favorites. The issue is that, during development, some rows have duplicate user_id to page_id pairs so it returns the incorrect number or unique users. I tried adding a group by clause like so
$this->virtualFields['fans'] = 'SELECT COUNT(Favorite.id) FROM favorites AS Favorite WHERE Favorite.page_id = Page.id AND Favorite.status = 0 GROUP BY Favorite.user_id';
But it does not work. I tried debugging the issue but I receive the error message "allowed memory size exhausted". I also tried using SELECT COUNT('Favorite.user_id') and SELECT DISTINCT('Favorite.user_id') neither of which worked either. I believe DISTINCT is further away from the answer as that would return an array (I believe?)
Is this a known CakePHP issue? Am I implementing the group by wrong? Is there another solution to do this other than afterfind?
try this
SELECT COUNT(DISTINCT Favorite.user_id)
like that :
$this->virtualFields['fans'] = 'SELECT COUNT(DISTINCT id) FROM favorites WHERE status = 0';
Related
Really new to working with CI4's Model and struggling to adapt my existing MySQL JOIN queries to work with the examples in its User Guide.
I have adapted part of my code like so:
public function brand_name($brand_name_slug)
{
return $this->asArray()
->where('availability', 'in stock')
->where('sku !=', '')
->where('brand_name_slug', $brand_name_slug)
->groupBy('gtin')
->orderBy('brand_name, subbrand_name, product, size, unit')
->findAll();
}
It works fine. I have looked at examples, and figured out I can add the code ->table('shop a') and it still works, but I also need to to add the following JOIN statement:
JOIN (SELECT gtin, MIN(sale_price) AS sale_price FROM shop GROUP BY gtin) AS b ON a.gtin = b.gtin AND a.sale_price = b.sale_price
As soon as I add ->join('shop b', 'a.gtin = b.gtin and a.sale_price = b.sale_price') I get a '404 - File Not Found' error.
When I look at all examples of CI4 joins and adapt my code to fit, my foreach($shop as $row) loop generates a 'Whoops...' error because they end with a getResult() or getResultArray - instead of findAll().
Which is the way forward, and do I need to change my foreach loop.
Full MySQL statement:
SELECT * FROM shop a JOIN (SELECT gtin, MIN(sale_price) AS sale_price FROM shop GROUP BY gtin) AS b ON a.gtin = b.gtin AND a.sale_price = b.sale_price WHERE availability = 'in stock' AND sku != '' AND brand_name_slug = $brand_name_slug GROUP BY gtin ORDER BY brand_name, subbrand_name, product, size
Query builders have their limits. That's why the query method exists. If you have a complex query I'd advise you to just use $this->query();.
It will make you lose less time and effort converting something you know already works. And in the top of that, while converting complex queries you usually end up using the query builder but with big part of your SQL in it.
In your model extending CodeIgniter\Model :
$query = $this->db->query("SELECT * FROM shop a JOIN (SELECT gtin, MIN(sale_price) AS sale_price FROM shop GROUP BY gtin) AS b ON a.gtin = b.gtin AND a.sale_price = b.sale_price WHERE availability = 'in stock' AND sku != '' AND brand_name_slug = \$brand_name_slug GROUP BY gtin ORDER BY brand_name, subbrand_name, product, size");
// your array result
$result_array = $query->getResultArray();
// your object result
$result_object = $query->getResult();
BaseBuilder Class in Codeigniter expects the first join parameter to be the table name. So try passing the table name and join it on the table name itself. I haven't personally used the table aliases so I might also be wrong.
Following are the parameter that the JOIN query expects :
public function join(string $table, string $cond, string $type = '', bool $escape = null)
Here, it expects the first name be a table, so try out by switching aliases for the table's name directly.
For your second part of query, It would be better if you could show the whole error rather than just posting the first of the error.
Managed to figure it out in the end:
public function brand_name($brand_name_slug)
{
return $this
->db
->table('shop a')
->select()
->join('(SELECT sku, MIN(sale_price) AS sale_price FROM shop GROUP BY sku) AS b', 'a.sku = b.sku AND a.sale_price = b.sale_price')
->where('availability', 'in stock')
->where('a.sku !=', '')
->where('brand_name_slug', $brand_name_slug)
->groupBy('a.sku')
->orderBy('brand_name, subbrand_name, product, size, unit')
->get()
->getResult();
}
Thanks for all your pointers!
I'm migrating a project to Laravel 4 and I am stuck with a quite complex query, which I'd like to migrate into a proper Eloquent query.
I have a table that contains chat messages, called chat_messages with a representing Model Chatmessage
The table contains a sender and a receipient column with a user id linking to the users table and User Model.
The query to get a list with all user IDs of all chat partners in raw SQL on the old version of the application is as follows:
$sql_allChatPartners = "SELECT DISTINCT chatPartner
FROM ( SELECT * FROM (
SELECT cm_receipient AS chatPartner, cm_sent_at
FROM chat_messages WHERE cm_sender = '".$me->userID."'
UNION
SELECT cm_sender AS chatPartner, cm_sent_at
FROM chat_messages WHERE cm_receipient = '".$me->userID."'
) whateva ORDER BY whateva.cm_sent_at DESC ) other";
Sorry for naming the "fake" tables whateva and other :-)
Could anyone put me in the right direction to do this with Eloquent Querybuilder?
It is important that I get the list of chatPartner IDs in the correct order, where the last chat message has been exchanged as first chatPartner. And the chatPartner where longest inactivity was in the chat as last entry.
This is what I got so far in my User Model...
public function allopenchats(){
$asSender = Chatmessage::where('sender', $this->id)->select('receipient as chatPartner, created_at');
$asBoth = Chatmessage::where('receipient', $this->id)->select('sender as chatPartner, created_at')
->union($asSender)->orderBy('created_at', 'desc')->get();
}
I renamed the columns cm_receipient to receipient, cm_sender to sender and sent_at to created_at in the new database for the new version
Your help would be very much appreciated!
You sql may change to:
SELECT IF (cm_receipient = '10', cm_sender, IF (cm_sender = '10',cm_receipient, 'no')) AS chatPartner, cm_sent_at
FROM chat_messages
WHERE cm_receipient = '10' OR cm_sender = '10'
GROUP BY chatPartner
HAVING chatPartner != 'no'
order by cm_sent_at DESC
In orm:
Chatmessage::where('sender','=',$this->id)
->orWhere('receipient','=',$this->id)
->select(DB::raw('IF (receipient = '.$this->id.', sender, IF (sender = '.$this->id.',receipient, 'no' )) AS chatPartner'), 'created_at')
->groupBy('chatPartner')
->having('chatPartner', '!=', 'no')
->orderBy('created_at', 'desc')
->get();
Thanks very much to Vitalik_74, I wouldn't have come that far without him.
Here is now the final query, although its not in ORM, but it is working fine and gives me the result I need.
$result = DB::select("SELECT *
FROM (
SELECT IF( receipient = '".$this->id."', sender, IF( sender = '".$this->id."', receipient, 'no' ) ) AS chatPartner, created_at
FROM chatmessages
WHERE receipient = '".$this->id."'
OR sender = '".$this->id."'
HAVING chatPartner != 'no'
ORDER BY created_at DESC
)whateva
GROUP BY whateva.chatPartner
ORDER BY whateva.created_at DESC");
if there is someone out there who can do this query with the Laravel Query Builder, I would be happy to see it. For now I'll leave it like this.
I am having a little trouble trying to accomplish this. Here is the gist of what I need to do:
UPDATE links SET
link = '$link', rid = $rid, order = $order
WHERE lid = $lid
IF (SELECT COUNT(*) FROM resources WHERE rid = $rid AND (sid = $sid OR sid IS NULL) AND types IS NULL) == 1;
So basically, I want to run the UPDATE if and only if the resource in the resources table is associated with the site (sid) or is not associated with any particular site and types is null.
I suppose I can run a PHP conditional, but it would be preferable if I could do this with one query. Is it possible?
Thansk so much in advance!
UPDATE links
SET link = '$link', rid = $rid, order = $order
WHERE lid = $lid
and (SELECT COUNT(*)
FROM resources
WHERE rid = $rid
AND (sid = $sid OR sid IS NULL)
AND types IS NULL) = 1;
I have a contact table I wish to query when a certain condition exists. I tried the query below but am getting a syntax error.
SELECT *
FROM contact_details
WHERE contactDeleted` =0
AND IF ( contactVisibility = "private"
, SELECT * FROM contact_details
WHERE contactUserId = 1
, IF( contactVisibility = "group"
, SELECT * FROM contact_details
WHERE contactGroup = 3
)
)
If I'm understanding your question correctly (which is difficult with the lack of info you've provided. Sample datasets and expected outcomes are typically helpful), then I don't believe you need IFs at all for what you want. The following will return contacts that are not deleted and who either have (visibility = "private" and userId = 1) OR (visibility = "group" and group = 3)
SELECT *
FROM contact_details
WHERE contactDeleted = 0
AND (
(contactVisibility = "public")
OR
(contactVisibility = "private" AND contactUserId = 1)
OR
(contactVisibility = "group" AND contactGroup = 3)
)
I am assuming you want to use the IF() function and not the statement which is for stored functions..
Refer to this link for more information on that.
Notice that you have put 2 select statements in there, where the custom return values are supposed to be. So you are returning a SELECT *... now notice that in your upper level sql statement you have an AND.. so you basically writing AND SELECT *.. which will give you the syntax error.
Try using .. AND x IN (SELECT *) .. to find if x is in the returned values.
Let me also list this link to make use of an existing and well written answer which may also applicable to your question.
The hibernate manual says this:
String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +
"BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
"FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
List loggedCats = sess.createSQLQuery(sql)
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class).list()
Now, what I have is basically the same. I am return two of the same type per row. I am doing a select something like this:
SELECT {ctrl1.*}, {ctrl2.*} FROM tableA AS A
LEFT JOIN tableB AS ctrl1 ON (A.controlID = ctrl1.controlID AND ctrl1.controlOptionType = ? AND ctrl1.controlOptionValue = ?)
LEFT JOIN tableB AS ctrl2 ON (A.controlID = ctrl2.controlID AND ctrl2.controlOptionType = ? AND ctrl2.controlOptionValue = ?)
And then I addEntity("ctrl1", typeof(mycontrolclass) and
addEntity("ctrl1", typeof(mycontrolclass)
Which seems exactly the same to me as their example. But I get this exception:
"Could not execute query" and the inner exception is "Could not find specified column in results".
If I copy the sql in the exception(to which it has added "AS ctrl1_1_3_3_" etc) it works fine.
Thanks.
What exactly are you trying to do? I believe you might not need using either of them.
// Using HQL:
var motherId = 25;
var hql = "select c.birthDate, c.mother from Cat c where c.mother.Id = :motherId";
var result = Session.CreateQuery(hql)
.SetParameter("motherId", motherId)
.ToList();
// Using NHibernate.LINQ:
var result = (from cat in Session.Linq<Cat>()
where cat.Mother.Id == motherId
select new { cat.birthDate, cat.mother }).ToList();
HQL query examples.
LINQ for NHibernate examples.
I dealt with your problem just for studying purposes, because you will surely
have found a solution in the meanwhile, but the problem should not lie in
the query (which is ok), but in some mapping inconsistency or somewhere else
(perhaps Database).