Doctrine2 Query where Subquery would be needed - mysql

Let me first introduce the Entities used in this example:
Order (_order in mysql)
$id (primary key, auto increment)
OrderStatus (order_status in mysql)
$id (primary key, auto increment)
$order (storing the order object it is related to, named order_id in mysql)
$statuscode (Storing the integer code)
$created_at (Storing the datetime of creation)
The relationship is Order n:1 OrderStatus. For every status change, I create a new OrderStatus with the new statuscode. So for one Order there can be many OrderStatus. The actual OrderStatus can be figured out by looking at the OrderStatus with the latest created_at.
I now want to get all objects which have the status 0 right now. In SQL, my query would look like this:
SELECT o.id,os.statuscode,os.created_at
FROM `_order` o
LEFT JOIN `order_status` os ON o.id = os.order_id
WHERE os.created_at = (SELECT MAX(created_at)
FROM order_status
WHERE order_id = os.order_id);
Can I do such a query in DQL or do I have to work with objects? If so, would I need to read all OrderStatus objects and manually figure out which is the most current one or can I somehow preselect?

I figured out a way which is not quite what I was searching for, but it doesn't require any changes in my application, so it's ok as long as I don't find a better solution.
What I'll do is I'll store the actual status directly within the Order. Through LifecycleCallbacks I'll update the order as soon as a OrderStatus related to it is created.
So my Order-Object looks like this:
/**
* #ORM\Entity
* #ORM\Table(name="_order")
*/
class Order{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//..
/**
* #ORM\Column(type="smallint", nullable=false)
*/
protected $status = -1;
// Getter and Setter!
}
My Order-Status will look like this:
/**
* #ORM\Entity
* #ORM\Table(name="order_status")
* #ORM\HasLifecycleCallbacks
*/
class OrderStatus{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="order_status", cascade={"persist"})
* #ORM\JoinColumn(name="order_id", referencedColumnName="id", nullable=false)
*/
protected $order;
/**
* #ORM\Column(type="smallint", nullable=false)
*/
protected $statuscode;
// ..
/**
* #ORM\prePersist
*/
public function prePersist(){
$this->order->setStatus($this->statuscode);
}
// Getter and Setter!
}
Mind the , cascade={"persist"} within the OrderStatus so the Order will also be persisted when the OrderStatus is persisted, otherwise the status will be set, but not stored!
On the plus side of this solution is that I now get the OrderStatus by calling $order->getStatus() instead of having to query the database for the status and that I'm able to do $repository->findByStatus(0). On the other hand, the moment the status field gets changed due to some error, the data will be inconsistent.
As said, if you got a solution where I don't need an extra field to store the status, post it! I'm very interested in a better solution!

Related

Laravel Query builder - select top 1 row from another table

I have this SQL query for MySQL which works fine. But I need to rewrite it using query builder and need to avoid DB::raw() completely because development database is different from production. I know far from ideal, but unfortunately it is what it is.
SELECT athletes.*,
(
SELECT performance
FROM performances
WHERE athletes.id = performances.athlete_id AND performances.event_id = 1
ORDER BY performance DESC
LIMIT 0,1
) AS personal_best
FROM athletes
ORDER BY personal_best DESC
Limit 0, 100
And I'm struggling how to rewrite the personal_best part. I have table of performances for athletes and I need to select only the best performance for each athletes as his personal best.
I was trying to search for answer but all of the answers I found included raw adding raw SQL.
Any ideas or hint would be much appreciated.
Thank you in advance!
So I accepted I might have to use Eloquent for this, but still having trouble to progress. Heres my code:
class Athlete extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'athletes';
/**
* The primary key associated with the table.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* Get the performances for the Athelete post.
*
* #return HasMany
*/
public function performances()
{
return $this->hasMany('App\EloquentModels\Performance', 'athlete_id');
}
}
class Performance extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'performances';
/**
* The primary key associated with the table.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
}
Create a new connection at database.php like mysql_dev for development parameters.
DB::connection('mysql_dev')->table('athletes')
->leftJoin('performances','athletes.id','performances.athlete_id')
->where('performances.event_id',1)
->groupBy('athletes.id')
->orderByDesc('personal_best')
->select('athletes.*',DB::raw('MAX(performances.performance) AS personal_best')
->paginate(100);
try like this without raw,
DB::connection('mysql_dev')->table('athletes')
->leftJoin('performances','athletes.id','performances.athlete_id')
->where('performances.event_id',1)
->groupBy('athletes.id')
->orderByDesc('performances.performance')
->select('athletes.*','performances.performance'
->paginate(100);
If you are using raw SQL just do MAX for performance for each athlete using GROUP BY.
SELECT athletes.*, MAX(performance) AS personal_best
FROM athletes
INNER JOIN performances ON athletes.id = performances.athlete_id AND performances.event_id = 1
GROUP BY athletes.id
ORDER BY personal_best DESC
LIMIT 0, 100
Laravel Query Builder:
DB::table('athletes')
->join('performances', 'athletes.id', '=', 'performances.athlete_id')
->where('performances.event_id', '=', 1)
->groupBy('athletes.id')
->orderBy('personal_best', 'desc')
->select('athletes.*',DB::raw('MAX(performance) AS personal_best')
->limit(100);
Doc says that we can do max(personal_best) but not sure how to use it with group by.
I'm afraid you can't avoid DB::raw in Query Builder but you can use eloquent model for the same, as answered by Shaielndra Gupta.
For that you can create model and relationship.
1. Create Model:
php artisan make:model Athelete
php artisan make:model Performance
2. Create relationship between Athelete and Perforamnce.
Update Athelete.php
/**
* Get the performances for the Athelete post.
*/
public function performances()
{
return $this->hasMany('App\Performance');
}
3. Get data(didn't verify by myself)
$data = Athelete::with('performances',function ($query) use ($eventId){
$query->max('performance')
$query->where('event_id',$eventId)
$query->orderBy('performance');
})->get();
Reference:
Laravel Model
Laravel Relationship
You can use like below.
$sql1 = "(
SELECT performance
FROM performances
WHERE athletes.id = performances.athlete_id AND performances.event_id = 1
ORDER BY performance DESC
LIMIT 0,1
) AS personal_best";
$sql2 = "SELECT athletes.*,$sql1
FROM athletes
ORDER BY personal_best DESC
Limit 0, 100";
$result = DB::select($sql2);
you can user Eloquent ORM like this
$data = Athelete::with('performances',function ($query) use ($eventId){
$query->max('performance')
$query->where('event_id',$eventId)
$query->orderBy('performance');
})->get()

Counting rows for the column in mysql

My problem is simple. I have two tables
transaction_bodies
------------------
body_id
full_name
and the other one is
transaction_accounts
--------------------
account_id
body_id
account_name
Relation is one to many. One body can have multiple accounts. I am trying to create a query that counts the accounts that bodies have.
I tried this
SELECT *
FROM
(
SELECT count(*) as trans, tb.full_name
FROM transaction_accounts ta
LEFT JOIN transaction_bodies tb
ON tb.body_id = ta.body_id
) as row;
But this doesn't give the right result. Can anyone help me out with this?
And if can provide how to write sub-queries in Laravel that would be a appreciated much.
Try this :
$result = DB::table('transaction_bodies')
->leftJoin('transaction_accounts as
ta','transaction_bodies.body_id','ta.body_id')
->select(DB::raw('count(ta.account_id) AS trans'),'transaction_bodies.full_name')
->groupBy('transaction_bodies.body_id')
->get();
You can do it with LEFT JOIN, e.g.:
SELECT tb.body_id, COUNT(ta.*)
FROM transaction_bodies LEFT JOIN transaction_accounts ta
ON tb.body_id = ta.body_id
GROUP BY tb.body_id;
With a simple LEFT JOIN you can achieve it like
SELECT tb.full_name, COUNT(account_id) as accounts
FROM transaction_bodies tb LEFT JOIN transaction_accounts ta
ON tb.body_id = ta.body_id
GROUP BY tb.body_id;
In Laravel you can do it like with model
$accounts = Transaction_body::leftJoin('transaction_accounts as ta','transaction_bodies.body_id','ta.body_id')->groupBy('transaction_bodies.body_id')->get();
without model
$accounts = DB::table('transaction_bodies')->leftJoin('transaction_accounts as ta','transaction_bodies.body_id','ta.body_id')->groupBy('transaction_bodies.body_id')->get();
/**
* Class Body
*/
class Body extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'transaction_bodies';
/**
* Get the accounts for the Transaction Body.
*/
public function accounts()
{
return $this->hasMany(Account::class);
}
}
/**
* Class Account
*/
class Account extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'transaction_accounts';
/**
* Get the body that owns the account.
*/
public function body()
{
return $this->belongsTo(Body::class);
}
}
//usage
$accounts = Body::find(1)->accounts;
https://laravel.com/docs/5.4/eloquent-relationships#one-to-many

Doctrine one-to-many association: Issues with composite primary key

I am trying to populate the below one-to-many doctrine association however I am hitting a problem because every Customer (primary key: id) has their visits (primary key: customer_id & visitday) captured in the Visit table (I am deriving visitday as the number of days since 1st Jan 2000 before persisting to the database (since datetime objects can not be in the primary key)):
Entities
class Customer
{
/**
* #ORM\Column(type="integer", options={"unsigned"=true})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Visit", mappedBy="visitday")
*/
protected $visits;
public function __construct()
{
$this->visits = new ArrayCollection();
}
/* -- */
}
Class Visit
{
/**
* #ORM\Column(name="customer_id", type="integer", options={"unsigned"=true})
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
* #ORM\Id
*/
private $customer;
/**
* #ORM\Column(type="smallint")
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="visits")
* #ORM\JoinColumn(name="visitday", referencedColumnName="id")
* #ORM\Id
*/
protected $visitday;
/* -- */
}
My problem is that my Customer objects are not getting populated with the customer's corresponding visits. I assume this is because doctrine can't see that it should also include its own customer ID in the lookup. Is there a way to fix this?
I would recommend you to change $visitday attribute to DateTime. It will be your visit date timestamp. Then customer attribute should be inversed with visits.
/**
* #ORM\Column(name="customer_id", type="integer", options={"unsigned"=true})
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="visits")
* #ORM\Id
*/
private $customer;
And as an option you could change relation Customer to Visits as ManyToMany. So you wont have duplicate visit dates.

Wrong ID given in Doctrine2 OneToOne mapping

I am seriously stack right now with the problem I have occurred with OneToOne mapping.
So let me show what I currently have:
OrderItem entity
/**
* OrderItem
*
* #ORM\Table(name="order_item")
* #ORM\Entity
*/
class OrderItem
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ... //
/**
* #ORM\OneToOne(targetEntity="UserPricingOption", mappedBy="orderItem")
*/
private $userPricingOption;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="items")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $order;
// ... //
UserPricingOption entity
/**
* UserPricingOption
*
* #ORM\Table(name="user_pricing_option")
* #ORM\Entity
* #ExclusionPolicy("all")
*/
class UserPricingOption
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ... //
/**
* #ORM\OneToOne(targetEntity="OrderItem", inversedBy="userPricingOption")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $orderItem;
// ... //
so generated tables look like this:
order_item table
* `id` 5
user_pricing_option table
* `id` 12
* `order_item_id` 5
So now I am trying to do the following:
echo $orderItem->getId(); // gives 5, `GOOD`
echo $orderItem->getUserPricingOption()->getId(); // gives 5 `BAD` (completely different user_pricing_option row), it should return 12.
I seriously have no idea why is that, please find the raw Doctrine query:
Keep in mind that query contains way more info than the showed examples above
SELECT t0.id AS id_1, t0.guid AS guid_2, t0.created_at AS created_at_3, t0.modified_at AS modified_at_4, t5.id AS id_6, t5.guid AS guid_7, t5.created_at AS created_at_8, t5.modified_at AS modified_at_9, t5.user_id AS user_id_10, t5.order_item_id AS order_item_id_11, t5.pricing_option_id AS pricing_option_id_12, t13.id AS id_14, t13.guid AS guid_15, t13.created_at AS created_at_16, t13.modified_at AS modified_at_17, t13.user_id AS user_id_18, t13.order_item_id AS order_item_id_19, t13.product_variant_id AS product_variant_id_20, t0.order_invoice_id AS order_invoice_id_21, t0.order_id AS order_id_22 FROM order_item t0 LEFT JOIN user_pricing_option t5 ON t5.order_item_id = t0.id LEFT JOIN user_product_variant t13 ON t13.order_item_id = t0.id WHERE t0.order_id = ? [131]
So basically $orderItem->getUserPricingOption()->getId() always returns the same ID as $orderItem->getId();
Anyone possibly see what is going on?
OneToOne bidireccional?
Try to define in joinColumn the name with column to reference id.
/**
* #ORM\OneToOne(targetEntity="OrderItem", inversedBy="userPricingOption")
* #ORM\JoinColumn(name="order_item_id", referencedColumnName="id")
*/
private $orderItem;
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-bidirectional

left join with the first entry that is most recently created

I have a fairly complex query as follows:
return $this->createQueryBuilder('s')
->select('s')
->addSelect('COUNT(p.id) as HIDDEN c_id')
->leftJoin('s.owner', 'o')
->leftJoin('s.userPictures', 'p')
->leftJoin('o.transactions', 't')
->leftJoin('t.packType', 'pt')
->where('pt.id =:packId')
->setParameter('packId', $packId)
->andWhere('s.expirydate >=:expiryDate')
->setParameter('expiryDate', new \DateTime('now'))
->andWhere('c_id <:numberOfPictures')
->setParameter('numberOfPictures', $numberOfPictures)
->orderBy("c_id", 'DESC')
->groupBy('p.id')
->getQuery()
;
the problem is that the query is leftJoined with all of it's transactions. I wanted such that it is left joined with the most recent transaction only. How can I do this? Is there an alternative way other than having to find the transaction id of the most recent transaction and put it into the where clause?
The Transaction entity has a created column and the entity looks like this:
class Transaction
{
/**
* #var datetime $created
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
protected $created;
}