Transcribe the SQL to Arel - ruby-on-rails

Could you help me with this case?? I need to transcribe the SQL below to Arel:
select sum(X.l_c_v_case) as case
from fc_low F1,
TABLE (pg_fg001.fmjc(f1.rowid,trunc(SYSDATE))) V1
WHERE V1.fic = ? and v1.fiu = ?
AND V1.fst IN ('A', 'B', 'C')
AND NVL(V1.ftm,0) NOT IN (701, 711, 721, 731)
Thanks.

I am not sure how this query is working at the moment due to 2 issues:
You have not defined the multipart identifier X.l_c_v_case; and
CASE is a keyword so I anticipate this would cause a syntax error
That being said the AST does not care from an assembly standpoint so we can convert your query to Arel as follows: (this does not mean the query will work when executed See Above Comments)
# Table Definitions
# If these are rails models you could use ModelName#arel_table instead
f1 = Arel::Table.new('fc_low',as: 'f1')
x = Arel::Table.new('X')
# Fake V1 table so we can use the Object like a table reference
faux_v1 = Arel::Table.new('V1')
# Define the table definition for V1
v1_table_def = Arel::Nodes::NamedFunction.new('TABLE',[
Arel::Nodes::NamedFunction.new('pg_fg001.fmjc',[
f1[:rowid],
Arel::Nodes::NamedFunction.new('TRUNC',[Arel.sql('SYSDATE')])
])
]).as(faux_v1.table_name)
# Build a Select Manager with the query conditions
# I substituted 1 and 2 for your ?
query = Arel::SelectManager.new([f1,v1_table_def])
.project(x[:l_c_v_case].sum.as('case')
.where(
faux_v1[:fic].eq(1).and(
faux_v1[:fiu].eq(2)
).and(
faux_v1[:fst].in(['A','B','C'])
).and(
Arel::Nodes::NamedFunction.new('NVL',[faux_v1[:ftm],0]).not_in([701,711,721,731])
)
)
Output of query.to_sql
SELECT
SUM("X"."l_c_v_case") AS case
FROM
"fc_low" "f1",
TABLE(pg_fg001.fmjc("f1"."rowid", TRUNC(SYSDATE))) AS V1
WHERE
"V1"."fic" = 1
AND "V1"."fiu" = 2
AND "V1"."fst" IN ('A', 'B', 'C')
AND NVL("V1"."ftm", 0) NOT IN (701, 711, 721, 731)

Related

Rails 4 bulk updating array of models

I have an array of ActiveRecord model and I want to renumber one column and bulk update them.
Code looks like this:
rules = subject.email_rules.order(:number)
rules.each_with_index do |rule, index|
rule.number = index + 1
end
EmailRule.update(rules.map(&:id), rules.map { |r| { number: r.number } })
But this creates N SQL statements and I would like 1, is there a way to do it?
Assuming you are using postgres you can use row_number and the somewhat strange looking UPDATE/FROM construct. This is the basic version:
UPDATE email_rules target
SET number = src.idx
FROM (
SELECT
email_rules.id,
row_number() OVER () as idx
FROM email_rules
) src
WHERE src.id = target.id
You might need to scope this on a subject and of course include the order by number which could look like this:
UPDATE email_rules target
SET number = src.idx
FROM (
SELECT
email_rules.id,
row_number() OVER (partition by subject_id) as idx
FROM email_rules
ORDER BY number ASC
) src
WHERE src.id = target.id
(assuming subject_id is the foreign key that associates subjects/email_rules)
One alternative to you is to put all interaction in a transaction and it will at least make one single commit at the end, making it way faster.
ActiveRecord::Base.transaction do
...
end

How to combine 3 SQL request into one and order it Rails

I'm creating filter for my Point model on Ruby on Rails app. App uses ActiveAdmin+Ransacker for filters. I wrote 3 methods to filter the Point:
def self.filter_by_customer_bonus(bonus_id)
Point.joins(:customer).where('customers.bonus_id = ?', bonus_id)
end
def self.filter_by_classificator_bonus(bonus_id)
Point.joins(:setting).where('settings.bonus_id = ?', bonus_id)
end
def self.filter_by_bonus(bonus_id)
Point.where(bonus_id: bonus_id)
end
Everything works fine, but I need to merge the result of 3 methods to one array. When The Points.count (on production server for example) > 1000000 it works too slow, and I need to merge all of them to one method. The problem is that I need to order the final merged array this way:
Result array should start with result of first method here, the next adding the second method result, and then third the same way.
Is it possible to move this 3 sqls into 1 to make it work faster and order it as I write before?
For example my Points are [1,2,3,4,5,6,7,8,9,10]
Result of first = [1,2,3]
Result of second = [2,3,4]
Result of third = [5,6,7]
After merge I should get [1,2,3,4,5,6,7] but it should be with the result of 1 method, not 3+merge. Hope you understand me :)
UPDATE:
The result of the first answer:
Point Load (8.0ms) SELECT "points".* FROM "points" INNER JOIN "customers" ON "customers"."number" = "points"."customer_number" INNER JOIN "managers" ON "managers"."code" = "points"."tp" INNER JOIN "settings" ON "settings"."classificator_id" = "managers"."classificator_id" WHERE "points"."bonus_id" = $1 AND "customers"."bonus_id" = $2 AND "settings"."bonus_id" = $3 [["bonus_id", 2], ["bonus_id", 2], ["bonus_id", 2]]
It return an empty array.
You can union these using or (documentation):
def self.filter_trifecta(bonus_id)
(
filter_by_customer_bonus(bonus_id)
).or(
filter_by_classificator_bonus(bonus_id)
).or(
filter_by_bonus(bonus_id)
)
end
Note: you might have to hoist those joins up to the first condition — I'm not sure of or will handle those forks well as-is.
Below gives you all the results in a single query. if you have indexes on the foreign keys used here it should be able to handle million records:
The one provided earlier does an AND on all 3 queries, thats why you had zero results, you need union, below should work. (Note: If you are using rails 5, there is active record syntax for union, which the first commenter provided.)
Updated:
Point.from(
"(#{Point.joins(:customer).where(customers: {bonus_id: bonus_id).to_sql}
UNION
#{Point.joins(:setting).where(settings: {bonus_id: bonus_id}).to_sql}
UNION
#{Point.where(bonus_id: bonus_id).to_sql})
AS points")
Instead you can also use your 3 methods like below:
Point.from("(#{Point.filter_by_customer_bonus(bonus_id).to_sql}
UNION
#{Point.filter_by_classificator_bonus(bonus_id).to_sql}
UNION
#{Point.filter_by_bonus(bonus_id).to_sql}
) as points")

How can you specify Oracle SQL functions such as trunc() in Rails select functions?

I have the below query written in a Rails controller which is querying an existing Oracle DB and it works fine.
#total_sales_volume = PaymentTransaction.joins(:settlement)
#total_sales_volume = #total_sales_volume.where("payment_transaction.transaction_status = 'S'
and payment_settlement.settlement_type = 'D'
and trunc(payment_settlement.transaction_date) > sysdate - 30")
However, I want to apply Oracle's trunc() function on the payment_settlement.transaction_date column. So I modified the above query and tried this:
#total_sales_volume = PaymentTransaction.joins(:settlement)
#total_sales_volume = #total_sales_volume.where("payment_transaction.transaction_status = 'S'
and payment_settlement.settlement_type = 'D'
and trunc(payment_settlement.transaction_date) > sysdate - 30").select("trunc(payment_settlement.transaction_date), payment_transaction.transaction_amount")
But no luck :( as I do not see any change in the actual query that gets fired. Here is the actual query that gets fired to Oracle (found it from the rails server logs)
SELECT
SUM("PAYMENT_TRANSACTION"."TRANSACTION_AMOUNT") AS sum_transaction_amount, transaction_date AS transaction_date
FROM
"PAYMENT_TRANSACTION" INNER JOIN "PAYMENT_SETTLEMENT" ON "PAYMENT_SETTLEMENT"."PAYMENT_TRANSACTION_ID" = "PAYMENT_TRANSACTION"."PAYMENT_TRANSACTION_ID"
WHERE
(payment_transaction.transaction_status = 'S'
and payment_settlement.settlement_type = 'D'
and trunc(payment_settlement.transaction_date) > sysdate - 30)
GROUP BY
transaction_date
Is applying Oracle SQL functions such as trunc() supported in ActiveRecord select functions?

Postgresql error with Rails 3 using order("RANDOM()")

Im trying to query my db for records that are similar to the currently viewed record (based on taggings), which I have working but I would like to randomize the order.
my development environment is mysql so I would do something like:
#tattoos = Tattoo.tagged_with(tags, :any => true).order("RAND()").limit(6)
which works, but my production environment is heroku which is using postgresql so I tried using this:
#tattoos = Tattoo.tagged_with(tags, :any => true).order("RANDOM()").limit(6)
but I get the following error:
ActionView::Template::Error (PGError: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
SELECT DISTINCT tattoos.* FROM "tattoos" JOIN taggings
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477 ON
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.taggable_id = tattoos.id AND
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.taggable_type = 'Tattoo' WHERE
(tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.tag_id = 3 OR
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.tag_id = 4 OR
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.tag_id = 5 OR
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.tag_id = 24 OR
tattoos_taggings_color_fantasy_newschool_nerdy_tv_477.tag_id = 205) ORDER BY RANDOM() LIMIT 6):
After analyzing the query more closely, I have to correct my first draft. The query would require a DISTINCT or GROUP BY the way it is.
The (possibly) duplicate tattoos.* come from first joining to (possibly) multiple rows in the table taggings. Your query engine then tries to get rid of such duplicates again by using DISTINCT - in a syntactically illegal way.
DISTINCT basically sorts the resulting rows by the resulting columns from left to right and picks the first for each set of duplicates. That's why the leftmost ORDER BY column have to match the SELECT list.
MySQL is more permissive and allows the non-standard use of DISTINCT, but PostgreSQL throws an error.
ORMs often produce ineffective SQL statements (they are just crutches after all). However, if you use appropriate PostgreSQL libraries, such an illegal statement shouldn't be produced to begin with. I am no Ruby expert, but something's fishy here.
The query is also very ugly and inefficient.
There are several ways to fix it. For instance:
SELECT *
FROM (<query without ORDER BY and LIMIT>) x
ORDER BY RANDOM()
LIMIT 6
Or, better yet, rewrite the query with this faster, cleaner alternative doing the same:
SELECT ta.*
FROM tattoos ta
WHERE EXISTS (
SELECT 1
FROM taggings t
WHERE t.taggable_id = ta .id
AND t.taggable_type = 'Tattoo'
AND t.tag_id IN (3, 4, 5, 24, 205)
)
ORDER BY RANDOM()
LIMIT 6;
You'll have to implement it in Ruby yourself.
not sure about the random, as it should work.
But take a note of http://railsforum.com/viewtopic.php?id=36581
which has code that might suit you
/lib/agnostic_random.rb
module AgnosticRandom
def random
case DB_ADAPTER
when "mysql" then "RAND()"
when "postgresql" then "RANDOM()"
end
end
end
/initializers/extend_ar.rb (name doesn't matter)
ActiveRecord::Base.extend AgnosticRandom

SQL query symbol "*="

What does the symbol "*=" stand for in a SELECT statement? How does this affect the performance? Can we replace it with JOINS?
Thanks
STR
In some database systems,
SELECT ... FROM a, b WHERE a.field1 *= b.field2
is an old syntax for a LEFT OUTER JOIN:
SELECT ... FROM a LEFT JOIN b ON a.field1 = b.field2
Microsoft SQL Server, for example, considers the *= syntax to be deprecated since version 2005.
So yes, you can replace *= with a JOIN. In fact, you should. (I doubt that it affects performance in any relevant way, but your queries might stop working in newer versions of your database engine.)
In some implementations of SQL, a SELECT statement that has a assignment operator(=) can be used to create the relationship between a column heading and the expression that defines the values for the column.
So an example might be:
SELECT name = 'renamed_column_name'
FROM users
More Info:
Unfortunately the = operator can mean both assign and equality.
For assignment:
DECLARE #Counter INT;
SET #Counter = 1;
For equality:
The = is a equality operator states that the left side must equal the right side.
The could mean a value must equal the result returned by a subquery, or a variable must equal a literal, no matter the case... a = b means that a and b have to have the same value.
SELECT * FROM users LEFT JOIN posts ON users.id = posts.user_id

Resources