How to get next records `bending` by order? - ruby-on-rails

Let say I have a model Product with columns id and sort_number.
id | sort_number (random)
1 | 325
2 | 161
3 | 58
...
147 | 500 # the biggest sort_number number is equal to the Product.count result
...
500 | 5
I want a next(n = 20) and previous(n = 20) methods for the Product instances. IE if I:
product = Product.find(43)
product.sort_number # => 490
product.next(20) # should return products with sort_number equal to (491..500) + (1..10)
How can I implement this functionality? How can I get the next record with sort_number started at 1, if there are no more records next?

OK, I will put this as an answer.
def next id, count
product = Product.find(id)
products = Product.where(
'sort_number > ? AND sort_number <= ?',
product.sort_number, product.sort_number + count
) # this will return 20 requested, unless sort_number is near 500
products |= Product.where(
'sort_number > 1 AND sort_number <= ?',
count - product.count
) if products.count < count
products
end

Related

What is the way to find the price with by the quantity in Rails?

Rails version: 7.0
PostgreSQL version: 14
What is the way to find the price by the quantity in the products table?
products table
min_quantity | max_quantity | price
1 | 4 | 200
5 | 9 | 185
10 | 24 | 175
25 | 34 | 150
35 | 999 | 100
1000 | null | 60
Expected result
3 ===> 200
50 ===> 100
2500 ===> 60
You can achieve that with a where condition checking that the given value is between min_quantity and max_quantity;
with products(min_quantity, max_quantity, price) as (
values
(1, 4, 200)
, (5, 9, 185)
, (10, 24, 175)
, (25, 34, 150)
, (35, 999, 100)
, (1000, null, 60)
)
select
price
from products
where
case
when max_quantity is null
then 3 >= min_quantity
else
3 between min_quantity and max_quantity
end
-- 200
But as you might have a null value for max_quantity when the min_quantity is 1000 then you'll need a way to handle that. So you can use a case expression to only compare the input with min_quantity.
If the same applies for min_quantity and it can hold null values, then another branch in the case expression might suffice.
As Rails doesn't have specific support for these situations, you'll be off to go with just "raw" SQL in your where;
where(<<~SQL, input: input)
where
case
when max_quantity is null
then :input > min_quantity
else
:input between min_quantity and max_quantity
end
SQL

How can I aggregated 3 measures in a line chart by an assigned day number, not date, in Tableau?

I can't figure out how to attach the .csv or workbook. Can someone explain?
I feel like I am overcomplicating this. I have a simple line chart, comparing 2 SUM values, in 3 measures, across an assigned day number. I've attached a .csv with sample data & tableau wbk. If I need to provide anything else, please let me know.
The viz also needs to by dynamic, so they can toggle between months past using a parameter [Selected Month] to toggle between months; in this example September is selected.
The [Contributing Day#] number is a field in the data provided by the user, not a calculation. I cannot use the workday feature, because weekend dates and holidays are sometimes considered a [Contributing Day#].
A date is matched to a Contributing_Day#. I can't compare date to date, they want to see it across Contributing_Day# instead.
I need to aggregate daily for the smaller number of days; in this example I would want to exclude Contributing_Day# 6 & 7 from the viz since one month only went to 5.
It needs to be MTD minus today, meaning if only Contributing_Day# <= 4 has passed, all 3 measures would null after Contributing_Day# 4
And if that wasn't enough, I need to exclude a dimension [Role] = 'Role' string type as well.
Measure_1 = SUM(Value_Sum) for [Selected Month]
Measure_2 = SUM(Value_Sum) for [Selected Month] - 1
Measure_3 = SUM(Value_Goal) for [Selected Month]
September Date | [Contributing_Day#] | Measure_1 | Measure_3
9/1/22 | 1 | 105 | 96
9/2/22 | 2 | 112 | 92
9/6/22 | 3 | 129 | 99
9/7/22 | 4 | 108 | 100
9/8/22 | 5 | 108 | 98
August Date | [Contributing_Day#] | Measure_2
8/1/22 | 1 | 105
8/2/22 | 2 | 112
8/3/22 | 3 | 129
8/4/22 | 4 | 108
8/5/22 | 5 | 108
8/8/22 | 6 | 112
8/9/22 | 7 | 92
Here is what I have tried that was almost working but not, and a screen shot of my visual when I have September selected and October.
Measure_1
{Fixed [Date],[Role] :SUM(IF MONTH([Date]) = MONTH(TODAY())AND DAY ([Date]) < DAY(TODAY())AND
[Role] <> 'Role' THEN [Value_1] END) }
Measure_2
{Fixed [Date],[Role] :
SUM(
IF ISNULL([Measure_1]) THEN NULL
ELSEIF MONTH([Date]) = MONTH(TODAY())-1
AND DAY ([Date]) < DAY(TODAY())
AND [Role] <> 'Role'
THEN [Value_1] END) }
Measure_3
{Fixed [Date],[Role] :
SUM(
IF MONTH([Date]) = MONTH(TODAY())
AND DAY ([Date]) < DAY(TODAY())
AND [Role] <> 'Role'
THEN [Value_2] END) }
Other calculations I've tried...
SUM(
{ FIXED [Date] :
MAX(IF DATEPART('year',[Date]) = DATEPART('year',[Select Month])
AND DATEPART('month',[Date]) <= DATEPART('month',[Select Month])
AND [Contributing_Day#] = 1
THEN 1 ELSE NULL END)
})
COUNTD(IF DATEPART('year',[Date]) = DATEPART('year',[Select Month])
AND [Contributing_Day#] = 1
THEN [Date] ELSE NULL END)
[% of Month Complete]
would be count of [Contributing_Day#] past / total count of [Contributing_Day#]
But I haven't been able to get that to work
[Role Flag]
{Fixed [Date],[Role] :
SUM(IF [Role] <> 'Role' THEN 0 ELSE 1 END) }
Measure_1
{Fixed [Date],[Role] : IF [% of Month Complete] = 1 THEN
SUM([Value_1]) ELSEIF MAX(MONTH([Date])) = MAX(MONTH([Select Month]))
AND MAX(DAY([Date])) < MAX(DAY([Select Month])) AND MAX([Role Flag])
<> 1 THEN SUM([Value_1]) END }
Measure_2
{Fixed [Date],[Role] :
IF MAX([Measure_1]) = 0 THEN NULL
ELSEIF [% of Month Complete] = 1
AND MAX(MONTH([Date])) = MAX(MONTH([Select Month]))-1
AND MAX([Role Flag]) <> 1
THEN SUM([Value_1])
ELSEIF MAX(MONTH([Date])) = MAX(MONTH([Select Month]))-1
AND MAX(DAY([Date])) < MAX(DAY([Select Month]))
AND MAX([Role Flag]) <> 1
THEN SUM([Value_1]) END }

Ruby aggregate function with model

I have a product model in my ruby on rails application which has name,endorsement and expense attributes.
I need to write a query that list all records, but for every record I need to calculate endorsement-expense as income value. That seems to be ok. However, I need to sum all of the incomes consequtively as well.
For example my records are like these:
Name Endorsement Expense
X 100 25
Y 20 17
X 60 55
T 178 78
I need to list those values as:
Name Endorsement Expense Income Total Income
X 100 25 75 75
Y 20 17 3 78
X 60 55 5 83
T 178 78 100 183
How can I do that ?
Thanks.
rows = Product.select('name,endorsement,expense, (endorsement-expense) as income')
total_income = 0
rows.each do |row|
total_income += row.income
puts "#{row.name}, #{row.endorsement}, #{row.expense}, #{row.income}, #{total_income}"
end
You can also do it with a total income for each product:
products = Product.select("(endorsement - expense) AS 'income', name, endorsement, created_at, null as total")
products.each do |p|
p.total = Product.select("sum(endorsement) - sum(expense) AS 'total'").where(name: p.name).where("created_at <= ?", p.created_at).group('name').last.total
end
You can apply inject function to accumulate total income.
It will look something like this:
products.inject{|sum, product| sum += (product.endorsement - product.expense }

strange result when summing up count("reviews".id) + count("comments".id) as my_count

In a query below I count how many reviews and comments publication have. I summing up reviews and comments like that: .select('"publications".*, count("reviews".id) + count("comments".id) as my_count').
Assuming publication have 3 reviews and 3 comments in sum would be 6, however my_count always shows bigger number. What is happening behind the curtain and how to make it count normally?
Publication.joins(:reviews, :comments)
.select('"publications".*, count("reviews".id) + count("comments".id) as my_count')
.group('"publications".id')
.order("my_count DESC")
The generated SQL probably looks like this:
SELECT publications.id, COUNT(reviews.id) + COUNT(comments.id) AS my_count
FROM publications p
INNER JOIN reviews r ON p.id = r.publication_id
INNER JOIN comments c ON p.id = c.publication_id
GROUP BY p.id
ORDER BY my_count DESC
Let's get rid of grouping for a moment and see what's going on on the following input:
publications: [{ id: 1 }],
reviews: [{ publication_id: 1, id: 1 }, { publication_id: 1, id: 2 },{ publication_id: 1, id: 3 }]
comments: [{ publication_id: 1, id: 10 }, { publication_id: 1, id: 20 }]
So there are are 3 reviews and 2 comments. However, this query will return 6 rows:
SELECT *
FROM publications p
INNER JOIN reviews r ON p.id = review.publication_id
INNER JOIN comments c ON p.id = comment.publication_id
publication.id | review.id | comment.id
1 | 1 | 10
1 | 2 | 10
1 | 3 | 10
1 | 1 | 20
1 | 2 | 20
1 | 3 | 20
And, when you group it, it will return 6+6 = 12 as a total count.
One possible workaround is to do COUNT(DISTINCT reviews.id) + COUNT(DISTINCT comments.id). Probably, this is not the best solution in terms of performance.
As DNNX points out, this occurs because you have fallen into what is called a "Chasm trap" -- the row count is inflated because of the multiple one-to-many joins.
You could try this as a shortterm alternative:
Publication.select('"publications".*,
coalesce((select count(*) from reviews r where r.publication_id = pubications.id),0)
+ coalesce((select count(*) from comments c where c.publication_id = pubications.id),0) as my_count')
.order("my_count DESC")
However I'd suggest you place counter caches on Publication for reviews and comments.

ActiveRecord/Postgres: PGError must appear in the GROUP BY clause or be used in an aggregate function

In my model I have a couple of queries that can be used (and re-used) one after another. One of those should aggregate amounts. This works fine on SQLite and throws an error on Postgres:
ActiveRecord::StatementInvalid (PGError: ERROR: column "entries.date" must appear in the GROUP BY clause or be used in an aggregate function
: SELECT sum(case when joint = false then amount else amount / 2 end) as total, sum(case when joint = false then amount else 0 end) as sum_personal, sum(case when joint = true and user_id = 1 then amount / 2 else 0 end) as sum_user_joint, sum(case when joint = true and user_id = 2 then amount / 2 else 0 end) as sum_partner_joint FROM "entries" WHERE (1 = entries.user_id OR (2 = entries.user_id AND entries.joint = 't')) AND ('2011-04-01' <= entries.date AND entries.date <= '2011-04-30') AND (amount_calc > 0 AND compensation = 'f') ORDER BY date asc)
Relevant part of Model.rb
# all entries of one month
def self.all_entries_month(year, month, user_id, partner_id)
mydate = Date.new(year, month, 1)
where(':user_id = entries.user_id OR (:partner_id = entries.user_id AND entries.joint = :true)', {
:user_id => user_id,
:partner_id => partner_id,
:true => true
}).
where(':first_day <= entries.date AND entries.date <= :last_day', {
:first_day => mydate,
:last_day => mydate.at_end_of_month
})
end
def self.income
where('amount_calc > 0 AND compensation = ?', false)
end
def self.cost
where('amount_calc <= 0 AND compensation = ?', false)
end
def self.order_by_date
order('date asc')
end
# group by tag and build sum of groups named group_sum
def self.group_by_tag(order)
group('tag').
select('tag, ' +
'sum(case when joint = "f" then amount else amount / 2 end) as tag_sum'
).
order('tag_sum ' + order)
end
def self.multiple_sums(user_id, partner_id)
case ActiveRecord::Base.connection.adapter_name
when 'SQLite'
select('sum(case when joint = "f" then amount else amount / 2 end) as total, ' +
'sum(case when joint = "f" then amount else 0 end) as sum_personal, ' +
'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount / 2 else 0 end) as sum_user_joint, ' +
'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount / 2 else 0 end) as sum_partner_joint '
)
when 'PostgreSQL'
select('sum(case when joint = false then amount else amount / 2 end) as total, ' +
'sum(case when joint = false then amount else 0 end) as sum_personal, ' +
'sum(case when joint = true and user_id = ' + user_id.to_s + ' then amount / 2 else 0 end) as sum_user_joint, ' +
'sum(case when joint = true and user_id = ' + partner_id.to_s + ' then amount / 2 else 0 end) as sum_partner_joint '
)
else
raise 'Query not implemented for this DB adapter'
end
end
Controller
# get all entries of given month
#cost = Entry.all_entries_month(#year, #month, current_user.id, current_partner.id).cost
# group cost by categories
#group_cost = #cost.group_by_tag('asc')
# still need to sort by date
#cost = #cost.order_by_date
#calc_cost = #cost.multiple_sums(current_user.id, current_partner.id)[0]
How can I change my query multiple_sums without breaking the other queries? Or do I need to implement multiple_sums from ground without using the existing ones?
Remove order by clause, which is useless as far as I can see anyway because you're grouping into single row.
And please - reformat your queries so that they will be visible and readable.

Resources