On Thursday, December 19, 2019 at 5:29:46 PM UTC-8, Beth Skurrie wrote:
>
> Hi,
>
> Thanks to everyone who works on Sequel or helps answer Sequel questions.
>
> I have a tricky problem that I'm trying to solve involving dynamic
> queries, classes, eager loading, and performance. I've come up with a
> simplified example to explain the problem, as the domain is quite complex.
> My constraints are:
>
> * have a query which I think needs to be dynamically created, and
> therefore, I believe can't be turned into a class along the lines of class
> Foo < Sequel::Model(DB[:some_table].select(...)); end
> * I want to eager load data on to the results of that query
> * the dataset is very large so the query that I want to run takes a while
>
> Here is my contrived example.
>
> Imagine two tables, order and order_line.
>
> Order 100 has a fish and an umbrella. Order 2 has an Umbrella.
>
> Orders
> -------------------
> id | invoice_number
> -------------------
> 1 | 100
> 2 | 200
>
> Order Lines
> ----------------------------
> id | product_name | order_id
> ----------------------------
> 1 | Umbrella | 1
> 2 | Fish | 1
> 3 | Umbrella | 2
>
>
> A denormalised view of the orders and order lines looks like:
>
> -----------------------------
> invoice_number | product_name
> -----------------------------
> 100 | Umbrella
> 100 | Fish
> 200 | Umbrella
>
> The SQL for this denormalised view is:
>
> SELECT invoice_number, product_name, customer_id FROM orders LEFT OUTER
> JOIN order_lines ON (order_lines.order_id = orders.id)
>
> I want a report showing the presence, or lack of, a particular product in
> an order. If an order does not have that product, I want to see a row with
> a null in it. An example "fish" product report would look like:
>
> -----------------------------
> invoice_number | product_name
> -----------------------------
> 100 | Fish
> 200 | <null>
>
>
> The SQL for the "presence of fish" report is:
>
> SELECT invoice_number, product_name, customer_id FROM orders LEFT OUTER
> JOIN (SELECT * FROM order_lines WHERE (product_name = 'Fish')) AS
> 'fish_order_lines' ON (fish_order_lines.order_id = orders.id)
>
> Note that the left outer join must be done on an *already filtered
> dataset* to achieve the right results. If we try filtering the original
> query, after joining order_line we lose the "200/null" row.
>
> I want to make a class to represent the product report order lines,
> because I then want to do eager loading of a heap of other relationships on
> each order line. This is how I'd do it for the full denormalised view,
> without any filtering.
>
> class DenormalisedOrderLine <
> Sequel::Model(DATABASE[:orders].select(:invoice_number, :product_name,
> :customer_id).left_outer_join(:order_lines, { order_id: :id } ))
> many_to_one :customer
> end
>
> I can't do it this way for the product presence report however, because
> the left outer joined dataset needs to have the dynamic product_name filter
> applied to it *before* it's joined.
>
> I have tried dynamically creating a class on the fly to do this query:
>
> product_name = "..."
> dynamic_class
> =
> Sequel::Model(DB[:orders].select(*COLUMNS).left_outer_join(DB[:order_lines].where(product_name:
>
> product_name), { order_id: :id }))
> dynamic_class.many_to_one :customer
>
> This works like a charm, however, when the model is defined, it runs the
> query with a "limit 1" to get the definitions of the columns (I think). The
> query that actually runs (remember, the code you're seeing here is
> trivialised example) is quite an expensive query, and doing it twice makes
> a significant impact on the response time.
>
You can probably use the columns_introspection plugin to avoid the second
query:
https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/columns_introspection_rb.html
However, I think what you want is possible using a delayed evaluation
(untested):
class DenormalisedOrderLine <
Sequel::Model(DATABASE[:orders].select(:invoice_number, :product_name,
:customer_id).left_outer_join(DB[:order_lines].where(product_name:
Sequel.delay{|ds| ds.opts[:product_name]}), { order_id: :id } ))
many_to_one :customer
end
You can then do:
DenormalisedOrderLine.dataset.clone(product_name: 'Fish').eager(:customer)
I've tried messing around with `bind` but I can't see a way to turn that
> into a class definition.
>
You can't use bind (the prepared statement support) in the definition of a
model class dataset, because those are for specific queries, and a model
class dataset needs to handle different types of queries.
Thanks,
Jeremy
--
You received this message because you are subscribed to the Google Groups
"sequel-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/sequel-talk/84ae963d-255b-46f5-9609-769a30036d2c%40googlegroups.com.