Considering the following tables:
---------- -------------------- -------------
| movies | | movies_directors | | directors |
|--------| |------------------| |-----------|
| | | id | | |
| id | ← | movies_id | | |
| | | directors_id | → | id |
---------- -------------------- -------------
On the movies table I have a M2M field names directors and related to the
directors table.
I'd like to fetch all movies having more than one director.
From the documentation :
Using has will return items that has at least the minimum number given as
related items.
Hence I make the following request: /items/movies?filter[directors][has]=1.
The request should return all movies having more than one director.
The generated query should be something like:
sql
SELECT movies.id, title
FROM movies
LEFT JOIN movies_directors ON movies.id = movies_directors.movies_id
GROUP BY movies.id
HAVING COUNT(movies_directors.id) > 1
I got results but not what I expected. As I didn't understand how they were
filtered, I checked the MySQL query log. Here is the generated query :
sql
SELECT movies.id AS id,
movies.title AS title
FROM movies
WHERE movies.id IN (
SELECT movies_directors.movies_id AS movies_id
FROM movies
RIGHT JOIN movies_directors ON movies.id = movies_directors.movies_id
WHERE movies_directors.id LIKE 1
OR movies_directors.movies_id LIKE 1
OR movies_directors.directors_id LIKE 1
)
ORDER BY movies.id IS NULL,
movies.id ASC,
movies.id ASC
LIMIT 200 OFFSET 0
```sql
INSERT INTOdirectus_collections(collection,managed,hidden,single,icon,note,translation)
VALUES ('directors', 1, 0, 0, NULL, NULL, NULL),
('movies', 1, 0, 0, NULL, NULL, NULL),
('movies_directors', 1, 1, 0, NULL, 'Junction Collection', NULL);
INSERT INTOdirectus_fields(collection,field,type,interface,options,locked,validation,required,readonly,hidden_detail,hidden_browse,sort,width,group,note,translation)
VALUES ('movies', 'id', 'integer', 'primary-key', NULL, 0, NULL, 0, 0, 1, 1, 0, NULL, NULL, NULL, NULL),
('movies', 'title', 'string', 'text-input', '{\"trim\":true,\"showCharacterCount\":true,\"formatValue\":false,\"width\":\"auto\"}', 0, NULL, 1, 0, 0, 0, NULL, 'full', NULL, NULL, NULL),
('directors', 'id', 'integer', 'primary-key', NULL, 0, NULL, 0, 0, 1, 1, 0, NULL, NULL, NULL, NULL),
('directors', 'name', 'string', 'text-input', '{\"trim\":true,\"showCharacterCount\":true,\"formatValue\":false,\"width\":\"auto\"}', 0, NULL, 1, 0, 0, 0, NULL, 'full', NULL, NULL, NULL),
('movies', 'directors', 'o2m', 'many-to-many', '{\"fields\":\"name\",\"template\":\"{{ name }} - {{ director.name }} - {{ director.director_id.name }} - {{ director.director_id }}\",\"preferences\":{\"viewType\":\"tabular\",\"viewQuery\":{\"fields\":[\"name\"]}}}', 0, NULL, 0, 0, 0, 0, NULL, 'full', NULL, NULL, NULL),
('movies_directors', 'id', 'integer', 'primary-key', NULL, 0, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL),
('movies_directors', 'movies_id', 'integer', 'numeric', NULL, 0, NULL, 1, 0, 0, 0, 0, NULL, NULL, NULL, NULL),
('movies_directors', 'directors_id', 'integer', 'numeric', NULL, 0, NULL, 1, 0, 0, 0, 0, NULL, NULL, NULL, NULL),
('directors', 'movies', 'o2m', 'many-to-many', '{\"fields\":\"title\"}', 0, NULL, 1, 0, 0, 0, NULL, 'full', NULL, NULL, NULL);
INSERT INTOdirectus_relations(collection_many,field_many,collection_one,field_one,junction_field`)
VALUES ('movies_directors', 'movies_id', 'movies', 'directors', 'directors_id'),
('movies_directors', 'directors_id', 'directors', NULL, 'movies_id'),
('movies_directors', 'directors_id', 'directors', 'movies', 'movies_id'),
('movies_directors', 'movies_id', 'movies', NULL, 'directors_id');
CREATE TABLE directors (
id int(15) unsigned NOT NULL AUTO_INCREMENT,
name varchar(200) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
INSERT INTO directors (id, name)
VALUES (1, 'Francis Ford Coppola'),
(2, 'Woody Allen'),
(3, 'Martin Scorsese'),
(4, 'Joel Coen'),
(5, 'Ethan Coen');
CREATE TABLE movies (
id int(15) unsigned NOT NULL AUTO_INCREMENT,
title varchar(200) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO movies (id, title)
VALUES (1, 'New York Stories'),
(2, 'The big Lebowski'),
(3, 'The departed'),
(4, 'Test');
CREATE TABLE movies_directors (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
movies_id int(10) unsigned DEFAULT NULL,
directors_id int(10) unsigned DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO movies_directors (id, movies_id, directors_id)
VALUES (1, 1, 1),
(2, 1, 2),
(3, 1, 3),
(4, 2, 4),
(5, 2, 5),
(6, 3, 3);
````
I forgot to mention that I'd be happy to help fix this, but I'd need help to know where to start digging!
Thanks @nstCactus!!! ❤️
We're on it and will try to get this resolved. In the meantime, @bjgajjar is our API Lead and can help you find things if you want to help resolve this. You can discuss here, or on our Slack:
While trying to fix this, I realised we're missing some features here.
The has filter allows the user to fetch records having at least x related records. It would be nice to have other "operators":
| operator | generated SQL |
| ----------- | ----------------------- |
| at_least | HAVING COUNT(id) >= 1 |
| up_to | HAVING COUNT(id) <= 1 |
| more_than | HAVING COUNT(id) > 1 |
| less_than | HAVING COUNT(id) < 1 |
| exactly | HAVING COUNT(id) = 1 |
The has filter syntax would have to be adapted to something like : filter[<column>][has][<operator>]=<value>. The <operator> could even be omitted and default to at_least to preserve the currently documented behavior.
@bjgajjar What do you think about this?
An interesting idea for extending the has filter. Any reason not to use a more consistent operator? We already use things like <= or lte so would those work or is there a reason to use what you have outlined?
https://docs.directus.io/api/reference.html#filter-operators
Nope, those are perfectly fine, even more if they're already used elsewhere
in the API!
On 18 June 2019 at 17:45:13, Ben Haynes ([email protected]) wrote:
An interesting idea for extending the has filter. Any reason not to use a
more consistent operator? We already use things like <= or lte so would
those work or is there a reason to use what you have outlined?
https://docs.directus.io/api/reference.html#filter-operators
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/directus/api/issues/1016?email_source=notifications&email_token=AACWMMY4HMISAAUVPSXD6MTP3D7ITA5CNFSM4HXYVI72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODX7CIJI#issuecomment-503194661,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AACWMM2MV3HK6CU7YV24BR3P3D7ITANCNFSM4HXYVI7Q
.
Fixed in #1041
Most helpful comment
While trying to fix this, I realised we're missing some features here.
The
hasfilter allows the user to fetch records having at least x related records. It would be nice to have other "operators":| operator | generated SQL |
| ----------- | ----------------------- |
|
at_least|HAVING COUNT(id) >= 1||
up_to|HAVING COUNT(id) <= 1||
more_than|HAVING COUNT(id) > 1||
less_than|HAVING COUNT(id) < 1||
exactly|HAVING COUNT(id) = 1|The has filter syntax would have to be adapted to something like :
filter[<column>][has][<operator>]=<value>. The<operator>could even be omitted and default toat_leastto preserve the currently documented behavior.@bjgajjar What do you think about this?