Loading a polymorphic model recursive with conditions - mysql

I try to eager load a polymorphic model with some conditions that's connected to two other models and having some problems.
My models:
class One extends Model {
public function twos() {
return $this->hasMany(Two::class);
}
public function threes() {
return $this->morphMany(Three::class, 'threeable');
}
}
class Two extends Model {
public function one() {
return $this->belongsTo(One::class);
}
public function threes() {
return $this->morphMany(Three::class, 'threeable');
}
}
class Three extends Model {
public function threeable() {
return $this->morphTo();
}
}
So far everythings is everything great: I've a lot of One items that have a lot of Two relations and both have even more Three relations (on its own).
Now I try to get the latest Three from Two, where it also can come from a One relation (highest threes.updated_at from either A or B).
The threes table looks something like this
| id | threeable_id | threeable_type | updated_at |
|---|---|---|---|
| 1 | 1 | A | 1 |
| 2 | 1 | A | 2 |
| 3 | 1 | B | 3 |
| 4 | 1 | B | 4 |
| 5 | 2 | A | 2 |
| 6 | 2 | A | 4 |
| 7 | 2 | B | 1 |
| 8 | 2 | B | 3 |
I want threes.id = 4 for B::find(1)->withC() and threes.id = 6 for B::find(2)->withC().
For performance reasons I want to load it in one query (I'm loading multiple Two and want the related Three and don't want to fire an own query for that). So I tried to put it in a scope (in class Two). Joining on the Three table and doing some max on the updated_at in a sub query ... It was a total mess and didn't worked that well
It didn't really feel the "Laravel-way" neither :(
Thanks for any help
EDIT
Maybe as an addition here's the pure SQL
SELECT twos.id, threes.id
FROM
(SELECT
twos.id twoId, MAX(th.updated_at) threeMaxUA
FROM
twos
LEFT JOIN (SELECT
th.*
FROM
(SELECT
threeable_id, threeable_type, MAX(updated_at) AS updated_at
FROM
threes
WHERE
deleted_at IS NULL
GROUP BY threeable_id , threeable_type) thmax
LEFT JOIN
threes pr ON (th.threeable_id = thmax.threeable_id
AND th.updated_at = thmax.updated_at
AND th.threeable_type = thmax.threeable_type)
GROUP BY th.threeable_id , th.threeable_type) th
ON
(
(th.threeable_id = twos.id AND th.threeable_type = 'App\\Two')
OR
(th.threeable_id = twos.product_id AND th.threeable_type = 'App\\One')
)
GROUP BY twos.id
) twthmax
LEFT JOIN twos ON twos.id = twthmax.twoId
LEFT JOIN threes ON (
twthmax.threeMaxUA = threes.updated_at
AND
(
(threes.threeable_id = twos.id AND threes.threeable_type = 'App\\Two')
OR
(threes.threeable_id = twos.product_id AND threes.threeable_type = 'App\\One')
)
)

Related

Isolate the Count that comes from a LEFT JOIN independent of the WHERE clause filters

Basically I want to list all the profiles name (Profile Table), each profile has a bunch of entities associated with it (Profile_Entity) and types of entities (Profile_EntityType). So, for each Profile I want to count how many entities there are on each.
This (the count) works fine If I don't filter the results. But then, If I try to filter by Entity (see if such entity belongs to a profile) it messes up my Entity Count. This happens because when the table is filtered, the rows where the EntityIDBP (serves as EntityIDBP) don't appear disappear, and the count will count the rows of the filtered table, Where I would like it to stick to the original one.
So I tried to isolate the count with a LEFT JOIN, but with no sucess.
This is what I currently have
SELECT {Profile}.[Id],
{Profile}.[Name],
Count (ProfileCount.IDBP)
FROM {Profile}
LEFT JOIN
((
/* Get all the entities that belong to a profile, trough the entity type */
SELECT P2.[Id] as Id,
P2.[Name] as ProfileName,
{Entity}.[EntityIDBP] as IDBP
FROM {Profile} as P2
LEFT JOIN {Profile_EntityType} ON ({Profile_EntityType}.[ProfileId] = P2.[Id])
LEFT JOIN {Entity} ON ({Entity}.[EntityType_IDBP] = {Profile_EntityType}.[EntityType_IDBP] )
UNION
/* Get all the entities that belong to a profile directly, trough the Profile_Entity.isToInclude = 1 */
SELECT P2.[Id] as Id,
P2.[Name] as ProfileName,
{Entity}.[EntityIDBP] as IDBP
FROM {Profile} as P2
LEFT JOIN {Profile_Entity} ON ({Profile_Entity}.[ProfileId] = P2.[Id] AND {Profile_Entity}.[IsToInclude] = 1)
LEFT JOIN {Entity} ON ({Entity}.[EntityIDBP] = {Profile_Entity}.[EntityIDBP]
)EXCEPT(
/* The subquery that gets all the entities that shouldn't be accounted for the Count (Profile_Entity.isToInclude = 0) */
SELECT P2.[Id] as Id,
P2.[Name] as ProfileName,
{Entity}.[EntityIDBP] as IDBP
FROM {Profile} as P2
JOIN {Profile_Entity} ON ({Profile_Entity}.[ProfileId] = P2.[Id] AND {Profile_Entity}.[IsToInclude] = 0)
JOIN {Entity} ON ({Entity}.[EntityIDBP] = {Profile_Entity}.[EntityIDBP] ))) as ProfileCount ON ({Profile}.[Id] = ProfileCount.Id)
WHERE ProfileCount.IDBP IN (301000044)
/* The Filter used to know if a profile has a entity or not ; Right now it's a fixed value just to demonstrate*/
/*The 301000044 represents the Entity IDBP */
GROUP BY {Profile}.[Name],{Profile}.[Id])
For the example here are the data model tables.
Profile table:
|---------------------|------------------|
|Id | Name | (...) |
|---------------------|------------------|
|10 | Profile1 | (...) |
|---------------------|------------------|
Profile_Entity table:
|---------------------|------------------|-----------------------------|
| ProfileId | EntityIDBP |isToInclude |
| |serves as the |/*this representes wheter the|
| |unique id | entity should be considered |
| | | for the count (=1) or not |
| | | (=0) */ |
|---------------------|------------------|-----------------------------|
| 10 | 301000044 | 1 |
|---------------------|------------------|-----------------------------|
| | | |
| 10 | 301000045 | 1 |
----------------------|------------------|-----------------------------|
| 10 | 301000043 | 0 /* goes into the EXCEPT |
| | | clause */ |
|---------------------|------------------| /*thus the EXCEPT clause*/|
Profile-EntityType table:
|---------------------|------------------|
|Id |EntityType_IDBP | (...) |
|---------------------|------------------|
|10 | ProfileType | ----- |
|---------------------|------------------|
/*Then on the EntityTable I would have all the Entities that belong to this
type and aggregate them all together. Let's imagine it's 10 */
Entity Table
|---------------------|------------------|
|Id | EntityIDBP | EntityType_IDBP | /* 10 entities (records) with this
| | | TypeCod */
|---------------------|------------------|
|10 | IDBP | ProfileType |
|---------------------|------------------|
The expected result:
|---------------------|------------------|
|Id | ProfileName | EntityCount |
|---------------------|------------------|
|10 | Profile1 | 11 |
|---------------------|------------------|
The count is 11 because there are two (2) entities with isToInclude = 1 on Profile_Entity table minus 1 entity from Profile_Entity with isToInclude = 0 (the Except clause) plus 10 enties with that type.
Obs. The syntax may be a little bit different than what you are used to because this is done in a Outsystems platform.
Ended up using the temporary table I get to retrieve all the entities from a Profile (The Union and the Except) as a Condition on this same query, where the only difference is that I feed this second one the IDBP of the entity I want to filter by.
So I have something like this
SELECT A.ProfileName, A.ProfileId, Count(A.IDBP)
FROM (
SELECT 'all entities IDBP associated with a profile, as well as its Id and Name' as A
WHERE A.IDBP IN (A WHERE Entity.IDBP = 'xxxx')
)
This preserves the Count and does the filtering

Laravel5: Eloquent and JOIN

Items Table
| id | item_id | item_title |
|-------|---------|------------|
| 1 | 1002 | A |
| 2 | 1003 | B |
| 3 | 1004 | C |
Sells Table
| id | item_id |
|----|-----------|
| 1 | 1002 1003 |
| 2 | 1003 1004 |
| 3 | 1004 1002 |
I want result : Sells Table 1. item title is A B
I want to combine the sells table with the item table and then match the item_id of the sells table to the item_title of the item table.
The table definitions look incorrect, you should have a pivot table linking items with sells, so a sell_item table:
item_id | sell_id
-----------------
1 | 1
1 | 3
2 | 1
2 | 2
3 | 2
3 | 3
Then using eloquent, you'd create models to represent your tables and define the relationships using BelongsToMany:
class Item extends Model {
public function sells() {
return $this->belongsToMany(Sell::class);
}
}
class Sell extends Model {
public function items() {
return $this->belongsToMany(Item::class);
}
}
Each instance of either model will then have access to it's related models via $item->sells and $sell->items.
The query builder can perform a join if not going the Eloquent route:
DB::table('sells')->join('items', 'sells.item_id', '=', 'items.item_id')
->select('sells.*', 'items.title')
->get();
The table definitions look incorrect, If you corrected already then your model replationship should be like
class Item extends Model {
public function sells() {
return $this->belongsToMany(Sell::class);
}
}
class Sell extends Model {
public function items() {
return $this->belongsToMany(Item::class);
}
}
Each instance of either model will then have access to it's related models via $item->sells and $sell->items.
The query builder can perform a join if not going the Eloquent route:
DB::table('sells')->join('items', 'sells.item_id', '=', 'items.item_id')
->select('sells.*', 'items.title')
->get();
Or if your model name is Sell then
$response=Sell::with('items')->get();

MySQL LEFT JOIN json field with another id from table

I have two tables:
Bouquets
+----+------------+
| id | bouquet |
+----+------------+
| 1 | Package #1 |
| 2 | Package #2 |
| 3 | Package #3 |
| 4 | Package #4 |
| 5 | Package #5 |
+----+------------+
And
Prices
+----+----------+-------------------------------------------------------------------+
| id | reseller | price
+----+----------+-------------------------------------------------------------------+
| 1 | 1 | {"1": "1.11", "2": "0.00", "3": "0.00", "4": "4.44", "5": "5.55"} |
+----+----------+-------------------------------------------------------------------+
I need to get bouquet names that price value is not "0.00"...so i try LEFT JOIN to join bouquets.id ON prices.price but i can't get how?
I need to get this:
+----+------------+
| id | bouquet |
+----+------------+
| 1 | Package #1 |
| 4 | Package #4 |
| 5 | Package #5 |
+----+------------+
Here is my try but i im getting empty result:
SELECT b.id, b.bouquet FROM bouquets b
LEFT JOIN prices p ON JSON_CONTAINS(p.price, CAST(b.id as JSON), '$') != "0.00"
WHERE p.reseller=1;
This is not easy to do purely in mysql as it seems, the best idea is to use (PHP,ASP,etc) to do the heavy lifting but after a lot of trial and error I found this post:
Convert JSON array in MySQL to rows
From there this query seems to work for me
SELECT
b.id,
b.bouquet
FROM bouquet AS b
JOIN (
SELECT
indx.id,
indx.idx,
JSON_EXTRACT(p.price, idx) AS bouquetprice
FROM prices AS p
JOIN (
SELECT '$."1"' AS idx, 1 AS id UNION
SELECT '$."2"' AS idx, 2 AS id UNION
SELECT '$."3"' AS idx, 3 AS id UNION
SELECT '$."4"' AS idx, 4 AS id UNION
SELECT '$."5"' AS idx, 5 AS id
) AS indx
WHERE JSON_EXTRACT(p.price, idx) IS NOT NULL
AND p.reseller = 1
) AS ind
ON b.id = ind.id
AND ind.bouquetprice != "0.00"
The trick seems to be that the CONCAT in the linked SO post does not work well with numeric key names in your json. So you have to resort to the 2 indexes in the temporary join to search on.
Also the temporary join table is less than ideal in terms of creating a list of ever growing indexes but it's a place to start at least. (sorry about all the bad naming idx, indx, etc.)
Edit: forgot the reseller part
I im programming in node js using mysql wrapper this is the solution that i use and it is working:
/* QUERY - aaBouquets */
connection.query("SELECT id, bouquet FROM bouquets ORDER BY bouquet ASC",function(err, rows, fields){
/* BOUQUETS - number */
var total = rows.length;
/* FOUND - bouquets */
if (rows.length) {
/* GET - prices */
for (var i in rows) {
var s = 1;
connection.query("SELECT '"+rows[i].id+"' AS id, '"+rows[i].bouquet+"' AS bouquet FROM prices p LEFT JOIN bouquets b ON JSON_SEARCH(p.price, 'one', '$.\""+rows[i].id+"\"') WHERE p.reseller=? AND FORMAT(JSON_EXTRACT(price, '$.\""+rows[i].id+"\"'), 2) != \"0.00\"",[qreseller], function(err, rows, results){
/* CHECK - prices */
if (s < total) {
if (rows.length) {
/* GET - prices */
data.push(rows[0]);
};
s++;
} else {
/* CHECK - prices */
if(data.length) {
if (rows.length) {
/* GET - prices */
data.push(rows[0]);
};
/* RETURN - servers data */
res.json(data);
};
}
});
}
}
});
You can see that first query is getting id and bouquet names then in for loop i im using that id to get values for that bouquet id and show only if value not equal "0.00"..using variable s and total is used here because if i call console.log(data) i get undefined variable..because in node js variable is local and need to be called inside for loop if is called outside i get undefined variable error.
This way i im getting only bouquets with defined price...i don't know if it can be done in single query (because you can't use LEFT JOIN ON b.id on p.prices) so need this two query...to me it is getting ok...so if someone can minimize code to get it more speed or improve...it is welcome.
Call me old-fashioned, but I'm really not a fan of storing json data. Any way, a normalized table might look like this...
Prices
+----------+------------+-------+
| reseller | bouquet_id | price |
+----------+------------+-------+
| 1 | 1 | 1.11 |
| 1 | 4 | 4.44 |
| 1 | 5 | 5.55 |
+----------+------------+-------+

Missing an option to use SQL's HAVING clause in F3

Is there a way to use MySQL's HAVING clause with any of Fat Free Framework's SQL Mapper object's methods? Let's assume I have the following DB table:
+----+-------+--------+
| id | score | weight |
+----+-------+--------+
| 2 | 1 | 1 |
| 2 | 2 | 3 |
| 2 | 3 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 4 |
| 3 | 3 | 1 |
| 3 | 4 | 3 |
+----+-------+--------+
Now I would like to run a following query:
SELECT id, SUM(score*weight)/SUM(weight) AS weighted_score GROUP BY id HAVING weighted_score>2
Truth to be told I would actually like to count the number of these records, but a count method doesn't support $options.
I can run the query without a HAVING clause and then loop through them to check weighted_score against the value, but with a growing number of records will make it more and more resource consuming. Is there any built-in solution to solve this problem?
EDIT 1:
The way I know how to do it if there is no support for the HAVING clause (based on manual):
$databaseObject = new DB\SQL(...);
$dataMapper = new \DB\SQL\Mapper($databaseObject, "tableName");
$dataMapper->weightedScore = "SUM(weight*score)/SUM(weight)";
$usersInfo = $dataMapper->find([],["group"=>"id"]);
$place = 1;
foreach ( $usersInfo as $userInfo ) {
if ( $usersScores->weightedScore > 2) $place++;
}
If I were able to use HAVING clause then the foreach loop would not be needed and the number of items loaded by a query would be reduced:
$databaseObject = new DB\SQL(...);
$dataMapper = new \DB\SQL\Mapper($databaseObject, "tableName");
$dataMapper->weightedScore = "SUM(weight*score)/SUM(weight)";
$usersInfo = $dataMapper->find([],["group"=>"id", "having"=>"weighted_score<2"]); // rough idea
$place = count($usersInfo);
And if count method supported $options it would be even simpler and it would save memory used by the app as no records would be loaded:
$databaseObject = new DB\SQL(...);
$dataMapper = new \DB\SQL\Mapper($databaseObject, "tableName");
$dataMapper->weightedScore = "SUM(weight*score)/SUM(weight)";
$place = $dataMapper->count([],["group"=>"id", "having"=>"weighted_score<2"]); // rough idea
Use Sub Query.
select count (0) from (SELECT id, SUM(score*weight)/SUM(weight) AS weighted_score GROUP BY id) where weighted_score>2;
Hope it will help.
As far as I know, you can put the HAVING clause into the group option:
$usersInfo = $dataMapper->find([],["group"=>"id HAVING weighted_score<2"]);
Another way could be to create a VIEW in mysql and filter the records on a virtual fields in that view.

Hierarchical queries in MySQL

I'm trying to find all the parents, grandparents, etc. of a particular field with any depth. For example, given the below structure, if I provide 5, the values returned should be 1, 2, 3 and 4.
| a | b |
-----------
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 3 | 6 |
| 4 | 7 |
How would I do this?
SELECT #id :=
(
SELECT senderid
FROM mytable
WHERE receiverid = #id
) AS person
FROM (
SELECT #id := 5
) vars
STRAIGHT_JOIN
mytable
WHERE #id IS NOT NULL
The following answer is not MYSQL-only, but uses PHP. This answer can be useful for all those that end up on this page during their search (as I did) but are not limited to using MYSQL only.
If you have a database with a nested structure of unknown depth, you can print out the contents using a recursive loop:
function goDownALevel($parent){
$children = $parent->getChildren(); //underlying SQL function
if($children != null){
foreach($children as $child){
//Print the child content here
goDownALevel($child);
}
}
}
This function can also be rewritten in any other language like Javascript.