I'm trying to work out how to sort a collection of items nested within the data structure returned from a pull pattern, particularly a pull that uses a reverse lookup. Here's the pull pattern I'm working with:
[:db/id
{:batch/formula [:db/id :formula/name]}
:batch/doses
:batch/date
{:batch-item/_batch [:db/id
{:batch-item/nutrient [:nutrient/name
{:nutrient/category [:category/sort-order]}]}
:batch-item/weight
:batch-item/complete?]}]
The :batch-item/_batch bit returns a rather large collection and I want to sort it by :category/sort-order and :nutrient/name@nando you can use #specter with something like (transform [(walker :batch-item/nutrient) :batch-item/nutrient] (partial sort-by :nutrient/category) (d/pull ...))
So I would wrap the pull in a specter transform? With a query that returns a flat structure, I'd use
(sort-by (juxt :sort-order :nutrient-name)
(d/q ...
#specter will help you to "find some sub-structure and transform it without change anything outside it'
once you find what you want to transform (the second argument, know as 'path'. On the example: find a map with this key, and 'enter' this key)
in this case the transform function will not by sort-by :nutrient/category
, but something like #(sort-by (fn [el] ((juxt ..) el)) %)
the path is [(walker :batch-item/nutrient) :batch-item/nutrient ALL]
TLDR; #datomic do not do anything about sorting
Thanks @souenzzo , will look into #specter next.
This is mega important, not just for enterprise, but for any datomic cloud user who wants to preserve their sanity. The sooner the better Stuart. This is huge deal.
Does datomic query syntax allow for group-by? I wanted to group some datums by date and then count them.
I've been looking at the clojure core function group-by for this https://clojuredocs.org/clojure.core/group-by
Has it worked ? I will try it as well .. see what happens .. good idea
I haven't incorporated it into the app I'm working on, but it certainly worked in the REPL
(defn group-nutrients-by-category
[v]
(group-by :category-name v))
I've got a datomic query that returns nutrients, and each of these have a category, such as Vitamins, Minerals, Amino Acids, Plant Extracts. I just tried the above, using (group-nutrients-by-category (find-all-nutrients))
and it worked perfectly, as expected.right, that is doing group-by after the query .. i meant in the query itself ..
It is my understanding that sorting and grouping is done with clojure functions rather than datalog query syntax.
i reckon so. there are some other aggregate function like max, min count, but not group-by or sort-by that are built in
Have you tried to sort the results of a query yet?
oh sure, tis easy
https://docs.datomic.com/cloud/query/query-data-reference.html#aggregate-example
^^^
Is this not what you mean when you say group-by?
@dansudol '[:find ?date (count ?e) :where [?e :entity/date ?date]]
Have you looked at https://docs.datomic.com/cloud/query/query-data-reference.html#aggregate-example
yes, that is pretty close to the query i need Joe, interesting .. i guess if that does the same as group by ( i am reading the examples now ) then that does it .. i am going for something a big more complicated ( count by date range ) but if this works as grouping by date then i am super close to what i want
Are the date ranges contiguous and non-overlapping?
yes
beginning ->end of a month , so finding items whose dates are in that range and counting them up, where let's say the range is a year, so each month, wanted the count of the items ( that have date field on them )
What datomic system are you using?
cloud
If I'm understanding the difference correctly, using group-by will return all records, while using count in an aggregate query will return a single record for each date.
you can't use group-by in the query though, just to operate on the returned data , but the last part is right i reckon
i guess the count by date is kinda grouping dates in a way so there is the element of group by there
'[:find ?month (count ?e)
:where
[(java.time.ZoneId/of "UTC") ?UTC]
[?e :entity/date ?date]
[(.toInstant ^Date ?date) ?inst]
[(.atZone ^Instant ?inst ?UTC) ?inst-in-zone]
[(.getMonthValue ^ZonedDateTime ?inst-in-zone) ?month]
Consider the above a sketch, written in slack, untested, likely need to add a few things.
that is pretty hillarious Joe, nifty idea , i will hack around it
The instant
type in datomic is a java.util.Date
, so if you want to use the nice .getMonthValue
method you'll need some combination of that.
There are several other things you could do like make a custom query function to do all the gnarly time conversion stuff in an isolated way. https://docs.datomic.com/cloud/query/query-data-reference.html#deploying
Other than that time conversion stuff, this is a pretty trivial query, right?
It's basically:
'[:find ?month (count ?e)
:where
[?e :entity/date ?date]
[(my.ions/date->month ?date) ?month]]
(You might need to use :with ?month
in that query, I'd have to think about it...)
pretty much, your idea is good .. me like
interesting @lanejo01 .. this works, very nice ( i made my own database function as you suggested ) slick !
Great to hear!