MYSQL - How to search in JSON array? - mysql

On my Symfony 5 app, i've a database with a candidate table that contains a json field.
candidate 1 : [{"end": "30/04/2020", "start": "01/03/2020"},{"end": "31/07/2020", "start": "01/07/2020"}]
candidate 2 : [{"end": "31/03/2020", "start": "01/03/2020"},{"end": "31/07/2020", "start": "01/07/2020"}]
Is it possible with query builder to find a candidate where this field corresponds to the arguments ?
ex: I would like to find all the candidates who are available between 10/03/2020 and 10/04/2020.
This case should just return the candidate 1.
I guess it's not possible to do this with query builder so i'm trying to use native SQL but... what's the sql syntax ?
I tried with availability_dates`->"$.start" = "01/03/2020" but it does not work because it's a "collection".

This is a poorly-conceived database structure. Clearly, the JSON string represents a "repeating group" of related data, which violates the principles of so-called "normal forms."
https://en.wikipedia.org/wiki/Database_normalization
You should be storing the start/end dates in a separate table, say, candidate_dates, with columns like candidate_id, start, end. This has a so-called "one-to-many relationship" to the parent table, candidates.
Now, you can write a simple query which JOINs the two tables to get the answers you need.

Entity like that ?Entity like that ?
One candidate can have one or more available dates and one available dates can only be linked to one candidate.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table(name="candidate_available_dates", uniqueConstraints={
* #ORM\UniqueConstraint(name="unique_candidate_available_dates", columns={"candidate_id", "start", "end"})
* })
*
* #ORM\Entity(repositoryClass="App\Repository\CandidateAvailableDatesRepository")
*/
class CandidateAvailableDates
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Candidate", inversedBy="candidateAvailableDates")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="candidate_id", referencedColumnName="candidate_id", nullable=false)
* })
*/
private $candidate;
/**
* #ORM\Column(type="date")
* #Assert\NotBlank
*/
private $start;
/**
* #ORM\Column(type="date")
* #Assert\NotBlank
*/
private $end;
[...]
// GETTER and SETTER
And in Candidate entity, the reversed side
/**
* #ORM\OneToMany(targetEntity="App\Entity\CandidateAvailableDates", mappedBy="candidate")
*/
private $candidateAvailableDates;

Related

Doctrine2 Conditional Relationship

My database schema:
LIKE PAGE ARTICLE
id id id
userid name text
itemid
LIKE.itemid -> PAGE.id "or" ARTICLE.id
Normally, i use this way;
Page.php;
/**
* #ORM\OneToMany(targetEntity="Like", mappedBy="page")
*/
protected $likes;
Article.php;
/**
* #ORM\OneToMany(targetEntity="Like", mappedBy="article")
*/
protected $likes;
Like.php;
/**
* #ORM\ManyToOne(targetEntity="Page", inversedBy="likes")
* #ORM\JoinColumn(name="itemid", referencedColumnName="id")
*/
protected $page;
/**
* #ORM\ManyToOne(targetEntity="Article", inversedBy="likes")
* #ORM\JoinColumn(name="itemid", referencedColumnName="id")
*/
protected $article;
But, But, itemid column will be the same in both tables (page and article). Will not be realized.
The question; I want to add a condition. Like this;
LIKE PAGE ARTICLE
id id id
userid name text
itemid
**itemtype**
and
/**
* #ORM\ManyToOne(targetEntity="Article", inversedBy="likes")
* #ORM\JoinColumn(name="itemid", referencedColumnName="id", **condition="itemtype=1"**)
*/
protected $article;
like this way, etc.
What should I do?
You probably want to create a supertype, say Document, from which Article and Page can inherit. Then your Likes can relate to Documents.
Doctrine2 ORM supports three types of inheritance: Mapped Superclass, Single Table, and Class Table. Picking the one you need will require some background on your usage requirements.
Read the Design-time considerations, Performance impact, and SQL Schema considerations of each option on the documentation page linked above to figure out which is right for you.

Symfony Doctrine - Joins without foreign keys

I'm trying to set up a schema to capture twitter users and their followers.
I have two tables. TwitterUser and Follower. Follower has 3 fields - id, twitterUser, follower.
When a twitter user is a added to the table, I also add a row to Followers to join them with other users I may be interested in.
However, If I get Symfony/Doctrine to build the tables using something like the following-
/**
* #ORM\Entity
* #ORM\Table(name="follower")
* #ORM\HasLifecycleCallbacks
*/
class Follower
{
public function __construct()
{
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="TwitterUser", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $twitterUser;
/**
* #ORM\ManyToOne(targetEntity="TwitterUser", inversedBy="following")
* #ORM\JoinColumn(name="follower_id", referencedColumnName="id")
*/
protected $follower;
...
It insists on creating a Foreign Key for follower that I don't want, as I don't want to have to get ALL twitter users to ensure that my joins always work.
The only way I can think to do it, is to remove the annotation and create the SQL for the join myself. Is there a smarter way to do it?
I suggest using a ManyToMany self-referencing relationship as describe below:
http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#many-to-many-self-referencing

Doctrine 2 with multiple indexes

I am developing using zend framework and doctrine2.1.
I have generated entities from database.
But the problem is: Doctrine doesn't recognize my indexes. They are not marked in entity annotations at all.
And when I go to validate-schema and dump sql from orm:schema-tool:update --dump-sql it generates sql to drop all my indexes across whole database.
I found that Doctrine has following annotation used for defining indexes:
indexes={#index(name="index_name",
columns={"database_column1","database_column2"}
)}
But this allows me to define one index for multiple columns and I don't really need that.
What I want is the ability to define multiple indexes on multiple columns, one index per column.
Is there a way I can achieve this? Is there a way that I can have annotation that defines multiple indexes.
I would say you can insert multiple indexes in the indexes property (but I haven't had the time to test it):
indexes={
#ORM\Index(name="index_name", columns={"database_column1","database_column2"}),
#ORM\Index(name="index_name2", columns={"database_column1"}),
#ORM\Index(name="index_name3", columns={"database_column2"})
}
Hope this helps you
Here is an example:
/**
* #Entity
* #Table(name="serial_number",indexes={
* #index(name="product_idx", columns={"product_id"}),
* })
*/
class SerialNumber { // Entity Class
/**
* #var int
*
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #Column(name="created_at", type="datetime")
* #var \DateTime
* */
protected $created;
/**
* #Column(name="updated_at", type="datetime")
* #var \DateTime
* */
protected $updated;
/**
* #Column(name="product_id", type="integer")
*/
protected $productID;
}

In doctrine 2.1, how do I get all entities using many to many associations - all articles in several categories?

I have a zend framework 1.11.11 application, using doctrine 2.1 (via bisna).
I have an Article and Category entities. Each article can be associated to many categories, each category can hold many articles.
I've created the following association in each of them:
Article:
/**
* #ManyToMany(targetEntity="Category", inversedBy="categoryArticles")
* #JoinTable
* (
* name="categories_articles",
* joinColumns={#JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="category_id", referencedColumnName="id")}
* )
* #var \Doctrine\Common\Collections\ArrayCollection
*/
protected $categories;
Category:
/**
* #ManyToMany(targetEntity="Article", mappedBy="categories", cascade={"persist"})
* #var ArrayCollection
*/
private $categoryArticles;
How can I get all the articles that belongs to a given array of categories ?
For example, I want to get all the articles that are associated with categories ids 1, 5 and 20.
The standard repository way ($this->findBy(array('categories' => array(1, 5, 20)))) doesn't work, dql way doesn't work (according to the tries I made) and I can't seem to think of another way.
Help will be much appreciated.
One way to do it is with join/in:
<?php
$category_ids = array(1,2,5);
$qb = $em->createQueryBuilder();
$qb->select('a')
->from('Article','a')
->join('a.categories','c')
->where($qb->expr()->in('c.id',$category_ids));
$articles = $qb->getQuery()->execute();

Order by within group by in Doctrine 2

I'm using Symfony 2 PR12 with Doctrine 2 and MySQL. I have a database storing articles and views of those articles:
// ...
class Article {
/**
* #orm:Column(type="bigint")
* #orm:Id
* #orm:GeneratedValue
* #var int
*/
protected $id;
/**
* #orm:OneToMany(targetEntity="ArticleView",mappedBy="article")
* #var ArrayCollection
*/
protected $views;
// ...
}
// ...
class ArticleView {
/**
* #orm:Column(type="bigint")
* #orm:Id
* #orm:GeneratedValue
* #var int
*/
protected $id;
/**
* #orm:Column(type="bigint",name="DateRead",nullable=true)
* #var int
*/
protected $viewDate;
/**
* #orm:ManyToOne(targetEntity="Article",inversedBy="views")
* #var Article
*/
protected $article;
// ...
}
I want to get, for example, the 20 most-recently-viewed articles. My first thought would be something like:
$qb = <instance of Doctrine\ORM\QueryBuilder>;
$qb->select('a')
->from('Article', 'a')
->join('a.views', 'v')
->orderBy('v.viewDate', 'DESC')
->groupBy('a.id')
->setMaxResults(20)
;
However, when there's more than one view associated with an article, the order-by/group-by combination gives unpredictable results for the ordering.
This is expected behavior for MySQL, since grouping is handled before ordering, and there are working raw-query solutions to this problem at http://www.artfulsoftware.com/infotree/mysqlquerytree.php (Aggregates -> Within-group aggregates). But I can't figure out how to translate any of these solutions into DQL, since as far as I can tell there's no way to select from subqueries or perform self-exclusion joins.
Any ideas on how to solve the problem with reasonable performance?
I ended up solving it with a correlated subquery:
$qb
->select('a')
->from('Article', 'a')
->join('a.views', 'v')
->orderBy('v.viewDate', 'DESC')
->setMaxResults(20)
// Only select the most recent article view for each individual article
->where('v.viewDate = (SELECT MAX(v2.viewDate) FROM ArticleView v2 WHERE v2.article = a)')
That way the sort ignores ArticleView's other than the most recent for any given article. Though my guess is that this performs fairly poorly relative to the other raw SQL solutions - any answers with better performance would still be greatly appreciated :).