How can I use ramda's sequence to traverse a dictionary?
Given the following dictionary
cars = {color: ['yellow', 'red'], year: [2017], model: ['coup', 'sedan']}
I'd like to produce the traversed result
all_cars = [
{color: 'yellow', year: 2017, model: 'coup'},
{color: 'yellow', year: 2017, model: 'sedan'},
{color: 'red', year: 2017, model: 'coup'},
{color: 'red', year: 2017, model: 'sedan'}
]
Using R.sequence results in a list of an empty list
R.sequence(R.of, cars)
[[]]
If I traverse a list instead of a dictionary it produces the correct cartesian product, but the results are (of course) lists instead of dictionaries.
R.sequence(R.of, [['yellow', 'red'], [2017], ['coup', 'sedan']])
[["yellow", 2017, "coup"], ["yellow", 2017, "sedan"], ["red", 2017, "coup"], ["red", 2017, "sedan"]]
I can think of two ways to do this, one involving sequence, and the other not.
This one uses your sequence(of) call above:
const convert = lift(map)(
compose(zipObj, keys),
compose(sequence(of), values)
)
const all_cars = convert(cars);
The other was built by my usual technique of keep changing the output with one transformation after another until I get to what I want:
const combine = pipe(
toPairs,
map(apply(useWith(map, [objOf, identity]))),
reduce(xprod, [[]]),
map(flatten),
map(mergeAll)
)
const all_cars = combine(cars)
This might be made a little clearer by introducing a crossproduct across multiple lists:
const xproduct = reduce(pipe(xprod, map(unnest)), [[]])
const combine = pipe(
toPairs,
map(apply(useWith(map, [objOf, identity]))),
xproduct,
map(mergeAll)
)
The second version is what I came up with on first trying the problem. Then when I looked to see what you'd tried, I got that first version. That first version looks cleaner to me, although most of the individual steps in the second one are trivial. But since there is one non-trivial step in there, the first one seems an overall winner.
You can see the first or the second on the Ramda REPL.
Related
I have 2 tables contractPoint and contractPointHistory
ContractPointHistory
ContractPoint
I would like to get contractPoint where point will be subtracted by pointChange. For example: ContractPoint -> id: 3, point: 5
ContractPointHistory has contractPointId: 3 and pointChange: -5. So after manipulating point in contractPoint should be 0
I wrote this code, but it works just for getRawMany(), not for getMany()
const contractPoints = await getRepository(ContractPoint).createQueryBuilder('contractPoint')
.addSelect('"contractPoint".point + COALESCE((SELECT SUM(cpHistory.point_change) FROM contract_point_history AS cpHistory WHERE cpHistory.contract_point_id = contractPoint.id), 0) AS points')
.andWhere('EXTRACT(YEAR FROM contractPoint.validFrom) = :year', { year })
.andWhere('contractPoint.contractId = :contractId', { contractId })
.orderBy('contractPoint.grantedAt', OrderByDirection.Desc)
.getMany();
The method getMany can be used to select all attributes of an entity. However, if one wants to select some specific attributes of an entity then one needs to use getRawMany.
As per the documentation -
There are two types of results you can get using select query builder:
entities or raw results. Most of the time, you need to select real
entities from your database, for example, users. For this purpose, you
use getOne and getMany. But sometimes you need to select some specific
data, let's say the sum of all user photos. This data is not an
entity, it's called raw data. To get raw data, you use getRawOne and
getRawMany
From this, we can conclude that the query which you want to generate can not be made using getMany method.
I am receiving a list of ID's. Most of these already exist in a table. I need to find which ID's are NOT in the table. This question has nothing to do with joins.
My API will receive a list of IDs, such as: [1, 2, 3, 4, 5]
Let's say there are three records in the table: [2, 3, 4]
The result I'm looking for is the array: [1, 5]
Our SQL brains jump quickly to something like the following, but clearly that's not what we need:
select * from widgets where id not in [list]
We don't need the records not in the list, we need the part of the list not in the records!
My fallback is to retrieve all records in the list and subtract from the list, something like this:
existing_ids = Widget.where(id: id_list).pluck(:id)
new_ids = id_list - existing_ids
That will work...but feels heavy-handed. Particularly if id_list has 100,000 records, and the table has 99,999 of those records.
I've searched around, and the only similar result is ID from list that is not in a table ... which did not find a viable solution.
Is there any way to do this in a single SQL query? (Bonus points for an ActiveRecord solution!)
To compare the lists to each other, either the input list needs to go into the database or the list of existing ids needs to come out of the database. The latter you already tried and didn't like, so here's an alternative
SELECT "id" FROM unnest('{1,2,3,4,5}'::integer[]) AS "id" WHERE "id" NOT IN (SELECT "id" FROM "widgets");
Not sure about performance.
Depending how many records are in your database, the simplest thing might just be to select all of the IDs and then drop the duplicates in Ruby.
from_api = [1,2,3,4,5]
existing = Widgets.pluck(:id) # => [2,3,4]
from_api.difference(existing) # => [1,5]
Obviously, if you have a substantial dataset, this will be less than optimal.
This should work.
from_api = [1,2,3,4,5]
existing = Widgets.order(:id).ids # => [2,3,4]
new_ids = []
from_api.each{ |n| new_ids << n unless existing.include? n }
new_ids # => [1,5]
or
from_api = [1,2,3,4,5]
existing = Widgets.order(:id).ids # => [2,3,4]
from_api.map{ |n| n == existing.first ? (nil if existing = existing.drop(1)) : n }.compac # => [1,5]
Balancing the complexity (to the current and future developers) of unset approach, I decided for my project that the simpler approach was warranted. While I didn't profile performance, I believe any gains would be minimal, if any.
Here is the solution I ended up with:
class Widget < ApplicationRecord
def self.absent(names)
uniq_names = names.uniq
uniq_names - where(name: uniq_names).pluck(:name)
end
end
And tests:
describe '.absent' do
subject { described_class.absent(names) }
let!(:widget1) { create(:widget, name: 'old-1') }
let!(:widget2) { create(:widget, name: 'old-2') }
let(:names) { %w[new-2 old-2 new-1 old-1 new-1 old-1] }
it { is_expected.to eq %w[new-2 new-1] }
end
I'm working on a kanban board using Postgres as my database. Normally I would use MongoDB for something like this, but I'm trying to practice and improve my understanding of relational databases.
So I have boards, which can have many lanes, and lanes can have many cards. Boards, lanes, and cards are 3 tables in the database. Lanes have a boardId which is a board.id value, cards have a laneId which is a lane.id value.
I want to query for a board by its id, but also have it include an array of its lanes. Then, I want each of the lanes to include an array of its cards. Like this:
{
id:123
title: 'Board title',
lanes: [{id: 0, title: 'Lane title', cards: [{id: 0, text: 'Card text'}]}, {id: 1, title: 'Lane title', cards: [{id: 1, text: 'Card text'}, {id: 2, text: 'Card text'}]}]
}
I have the first part down with a query that gets a board, then creates an array of lanes. Not sure if this is the 'right' way to do it, but here it is, with an example looking for a board with id '123':
select "boards"."id" as "boardId", "boards"."title" as "boardTitle", ARRAY_AGG(json_build_object('id', lanes.id, 'title', lanes.title)) as lanes from "boards" inner join "lanes" on "boards"."id" = "lanes"."boardId" and "boards"."id" = 123 group by "boards"."id"
But I'm not sure how I would get the cards to be included as a cards array for each element in the lanes array. My guess is that I could add another join like "cards" on "lanes"."id" = "cards"."laneId"... but then I don't know how I would include the cards for each lane in the json_build_object.
It would be best to accept json from the database in order to put rows inside of arrays.
I would build this up using CTEs to make it clear as possible:
with agg_lanes as (
select lane_id as id, jsonb_agg(cards) as "cards"
from cards
group by lane_id
), agg_boards as (
select b.id, b.title, jsonb_agg(a) as "lanes"
from agg_lanes a
join lanes l on l.id = a.id
join boards b on b.id = l.board_id
group by b.id, b.title
)
select to_json(a)
from agg_boards a
;
Working fiddle here.
I want to build an ElasticSearch query where I query multiple fields, but one of the words in the query MUST match one of the fields.
For instance, suppose I query for "holiday party food", I want it to return all documents that have at least 1 of the terms in the title, and the rest in the html_source.
If a document has:
title: Holiday, html_source: party food => MATCH
title: Party, html_source: food holiday => MATCH
title: Food, html_source: holiday party => MATCH
title: RANDOM TITLE, html_source: holiday party food => NO MATCH.
This is what I have constructed so far, and it matches documents that have all the terms in the html_source, but NOT in the title, which is not what I want.
{
"query":{
"query_string":{
"query":"holiday party food",
"default_operator":"AND",
"fields":[
"title","html_source"
]
}
},
"sort":[
{
"total_shares":"desc"
}
]
}
Here is a query syntax version that I think will do want you want:
+(
(+title:holiday +(body:party body:food -body:holiday))
(+title:party +(body:holiday body:food -body:party)
(+title:food +(body:holiday body:party -body:food))
)
If this does not work, I think it points you in the right direction. The trick in this kind of query is to enumerate all of the choices explicitly.
I'm trying to get the number of calls by commercial week. I've written the sql:
select strftime('%W', date_time_origination) as week, count(*)
from calls
group by week;
I then tried to run this in rails, firstly using this:
calls_by_week = Call.select("strftime('%W',date_time_origination) as week, count(*) as total_number_of_calls").group("week")
This returned a collection of objects with the correct data but repeated multiple times (json representation):
[{ "total_number_of_calls": 5435, "week": 39 },.....]
Given that it should have returned one object, I tried changing the rails query structure:
calls_by_week = Call.select("strftime('%W', date_time_origination) as week").group("strftime('%W', date_time_origination)").count
This time, the query returned one hash, minus the named params:
{ "31": 3123, "32": 1231,... }
I'd like to have the data, in json, presented as follows:
{ "week": 31, "total_number_of_calls": 12412,.....}
Is there a way of eliminating the duplication produced by the first query?
First: if you use Call.select(...) rails will try to instanciate Call objects. What you want are not Call objects, just hashes. You can use Call.connection.execute("SELECT .... blah blah").to_enum.to_a and use the resulting array of arrays.
But to convert your result to hash form, use
returned_hash = { "31": 3123, "32": 1231,... }
what_you_want = returned_hash.collect do |k,v|
{"week" => k.to_i, "total_number_of_calls" => v.to_i}
end
Another way is to use .uniq applied to the query, it will still build Call objects, but at least they will not have repeated data.