I have table that has 5 boolean columns.
on_stock | paid | received_payment | on_the_way | received
How to create an index for this table? Should I do it? I want to optimize a query like this:
SELECT "order".* FROM "order" INNER JOIN "seller_users" ON "order"."seller_foreign_id" = "seller_users"."google_id"
WHERE(((("order"."on_stock" <> true AND "order"."on_the_way" <> true ) AND "order"."paid" <> true ) AND "order"."received_payment" <> true ) AND "order"."received" <> true ) AND ("seller_users"."google_id" = 'lala#gmail.com' )
ORDER BY "order"."updated_at" DESC ;
When I try add this index - nothing happens. This index is not used.
add_index :order, [:on_stock, :on_the_way, :paid, :received_payment, :received], :name => "state_index"
If I add separate index for each columns - nothing happens too.
EXPLAIN ANALYZE output:
http://explain.depesz.com/s/FS2
Your table has a total of 8 rows, an index is not needed in this case. It is way faster to test each of this 8 rows against the where clause than to use an index here.
Related
I have models as follows:
class QuestionResult
belongs_to :test_result
belongs_to :test
scope :answered -> { where("answered = ?",true) }
scope :unanswered, -> { where("answered IS NULL OR answered != ?",true) }
end
class TestResult
belongs_to :test
has_many :question_results
end
Now I need these values:
test_result.question_results.answered.count
test_result.question_results.unanswered.count
test_result.review_questions.count
review_questions is a column in test_results table.
So I need the value of count of review_questions in the test_results table and values of the counts of scopes: answered and unanswered in the associated question_results table.
There is no column unanswered in the question_results table. It is only a scope but there is a column with the name answered.
At present, I am querying these values as shown above.
Is there any way that I can do the same in a single query?
Update 1
I am able to combine the first two queries into a single query without using scopes in query as below:
test_result.question_results.group(:answered).count
How can I combine test_result.review_questions.count query into the updated query?
Update 2
SELECT
test_results.id,
sum(if (question_results.answered = 1, 1, 0)) as answered,
sum(if (question_results.answered = 0 or question_results.answered is null, 1, 0)) as unanswered,
test_results.review_questions
FROM test_results
INNER JOIN question_results ON question_results.section_result_id = test_results.id
WHERE test_results.id = test.test_result.last.id;
This is the working sql query. What is its equivalent rails activerecord query?
I don't know what you mean with _value of count of review_questions in the test_results_. If it's a column in the table itself why are you going to count it?
About the answered and unanswered scopes, without CTE you can use sub-queries:
SELECT
test_results.id,
(SELECT COUNT(*)
FROM question_results q
WHERE q.answered = true
AND q.test_result_id = test_results.id) AS total_answered,
(SELECT COUNT(*)
FROM question_results q
WHERE q.answered = false
OR q.answered IS NULL
AND q.test_result_id = test_results.id) AS total_unanswered
FROM test_results
WHERE test_results.id = id
The further you can go using Rails with this is storing each sub-select and pass it within the ActiveRecord select method:
answered = '(SELECT COUNT(*) FROM question_results q WHERE q.answered = true AND q.test_result_id = test_results.id) AS answered'
unanswered = '(SELECT COUNT(*) FROM question_results q WHERE q.answered = false OR q.answered IS NULL AND q.test_result_id = test_results.id) AS unanswered'
TestResult.select(:id, answered, unanswered).where(id: id).as_json
As seen in the discussion, you can just "convert" the raw SQL you're using with ActiveRecord methods (sadly, not completely):
TestResult
.select(
:id,
:review_questions,
'SUM(IF(question_results.answered = true, 1, 0)) AS answered',
'SUM(IF(question_results.answered = true OR question_results.answered IS NULL, 1, 0)) AS unanswered')
.joins(:question_results)
.group(:id)
.as_json
This is one option.
answered_count = test_result.question_results.answered.count
unanswered_count = TestResult.count - answered_count
Also, I'd recommend removing the belongs_to :test on QuestionResult. TestResult already has a foreign key reference to :test, so the QuestionResult's association with both the :test and the :test_result is redundant.
Or,
belongs_to :test, :through => :test_result
can get rid of the redundant foreign key as well
I have a model Category that has_many Pendencies. I would like to create a scope that order the categories by the amount of Pendencies that has active = true without excluding active = false.
What I have so far is:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).order('COUNT(pendencies.id) DESC')}
This will order it by number of pendencies, but I want to order by pendencies that has active = true.
Another try was:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).where('pendencies.active = ?', true).order('COUNT(pendencies.id) DESC')}
This will order by number of pendencies that has pendencies.active = true, but will exclude the pendencies.active = false.
Thank you for your help.
I guess you want to sort by the amount of active pendencies without ignoring categories that have no active pendencies.
That would be something like:
scope :order_by_pendencies, -> {
active_count_q = Pendency.
group(:category_id).
where(active: true).
select(:category_id, "COUNT(*) AS count")
joins("LEFT JOIN (#{active_count_q.to_sql}) AS ac ON ac.category_id = id").
order("ac.count DESC")
}
The equivalent SQL query:
SELECT *, ac.count
FROM categories
LEFT JOIN (
SELECT category_id, COUNT(*) AS count
FROM pendencies
GROUP BY category_id
WHERE active = true
) AS ac ON ac.category_id = id
ORDER BY ac.count DESC
Note that if there are no active pendencies for a category, the count will be null and will be added to the end of the list.
A similar subquery could be added to sort additionally by the total amount of pendencies...
C# answer as requested:
method() {
....OrderBy((category) => category.Count(pendencies.Where((pendency) => pendency.Active))
}
Or in straight SQL:
SELECT category.id, ..., ActivePendnecies
FROM (SELECT category.id, ..., count(pendency) ActivePendnecies
FROM category
LEFT JOIN pendency ON category.id = pendency.id AND pendnecy.Active = 1
GROUP BY category.id, ...) P
ORDER BY ActivePendnecies;
We have to output ActivePendnecies in SQL even if the code will throw it out because otherwise the optimizer is within its rights to throw out the ORDER BY.
For now I developed the following (it's working, but I believe that it's not the best way):
scope :order_by_pendencies, -> { scoped = Category.left_joins(:pendencies)
.group(:id)
.order('COUNT(pendencies.id) DESC')
.where('pendencies.active = ?', true)
all = Category.all
(scoped + all).uniq}
Been banging away and would like some help
After running a lot of mathematical functions on rails and finding spurious results I decided to climb down to do it in the DB to find it's all very do-able for me however as soon as I get to the second function I have the following errors:
PG::UndefinedFunction: ERROR: function updown(integer) does not exist
LINE 3: SELECT "updown" (lag(updown) over (order by id)) table_nam...
^ HINT: No function matches the given name and argument types.
Migration:
class CreateSmoothings < ActiveRecord::Migration
def change
create_table :smoothings do |t|
t.decimal :firstsmprice, :precision => 8, :scale => 6
t.decimal :finalsmprice, :precision => 8, :scale => 6
t.decimal :firstsmdelta, :precision => 8, :scale => 6
t.decimal :finalsmdelta, :precision => 8, :scale => 6
t.integer :updown, :null => false, :default => 0
t.integer :posture
t.integer :step
t.references :usdzar, index: true, foreign_key: true
t.references :smoothval, index: true, foreign_key: true
t.timestamps null: false
end
add_index :smoothings, :finalsmprice
end
end
I have an update controller that sets the data in smoothings from another table and this is when the trigger is fired. The first trigger works like a charm. This is the controller action and then the trigger and function.
# POST /smoothings works and does the insert of all data to the smoothings table
def create
# Do the 1st and 2nd SMOOTHINGS
# Get the row with the smoothval_id parameter to use these
sv = Smoothval.find(smoothing_params[:smoothval_id])
fval = sv.fval
sval = sv.sval
svid = sv.id
toremove = Smoothing.where(:smoothval_id => sv.id)
toremove.destroy_all
# For every entry in the table do:
collection = Usdzar.all
collection.each_with_index do |usdzar, i|
# Declare the previous variables required for id = 1 as
# the calculation cannot be done as it has no previous id
# I am sure this can be refactored
if usdzar.id == 1
prevprice = usdzar.price
prevfirstsmprice = usdzar.price
prevfinalsmprice = usdzar.price
else
prevprice = collection[i-1].price
prevfirstsmprice = Smoothing.last.firstsmprice
prevfinalsmprice = Smoothing.last.finalsmprice
end
# Do the smoothing calcs for the first smoothing using fval (first value)
firstsmprice = (((fval * usdzar.delta)/100) + prevprice)
# Find the difference of the current and previous of the first smoothing
firstsmdelta = firstsmprice - prevfirstsmprice
# Do the final smooting with the fval
finalsmprice = ((sval * firstsmdelta)/100) + prevfirstsmprice
# Find the differance of the current and previous of the second smoothing
finalsmdelta = finalsmprice - prevfinalsmprice
# Create the rows at end of iteration
Smoothing.create!(
usdzar_id: usdzar.id,
smoothval_id: sv.id,
firstsmprice: firstsmprice,
firstsmdelta: firstsmdelta,
finalsmprice: finalsmprice,
finalsmdelta: finalsmdelta
)
end
redirect_to smoothings_url, notice: 'Old smoothings removed and new smoothings successfully created.'
The trigger function and trigger that works :-)
CREATE TRIGGER a_smoothings_trigger
AFTER INSERT
ON smoothings
FOR EACH ROW
EXECUTE PROCEDURE make_updowns_trg();
CREATE FUNCTION make_updowns_trg()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
update smoothings
set updown = case when (finalsmdelta < 0) then 1
when (finalsmdelta > 0 ) then 0
else 2 end;
RETURN NULL;
END
$BODY$;
This (the above) runs well.
What I am trying to do next is extend the function by another trigger and function (knowing now that they run in alphabetical order
The trigger/function is meant to take the "updown" (being 0, 1 or 2: up, down or same is the enum in rails) and:
If updown and next updown are the same then update as 2 (in between)
If the updown is greater than next "updown" update as 1 (high)
If the updown is less then next "updown" update as 0 (low)
CREATE FUNCTION public.make_high_lows_trg()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100.0
VOLATILE NOT LEAKPROOF
AS $BODY$
BEGIN
WITH next_updown AS
(SELECT updown (lag(updown) over (order by id)) table_name FROM smoothings
ORDER BY id DESC)
update smoothings
set posture = case when updown < next_updown then 1
when updown > next_updown then 0
else 2 end;
RETURN NULL;
END
$BODY$;
CREATE TRIGGER b_smoothings_trigger
AFTER INSERT
ON public.smoothings
FOR EACH ROW
EXECUTE PROCEDURE public.make_high_lows_trg();
Model for the controller:
class Smoothing < ActiveRecord::Base
enum updown: [:up, :down, :same]
enum posture: [:high, :low, :inbetween]
belongs_to :usdzar
belongs_to :smoothval
end
I had this all running in the controller as my model method coding really sucks and got it all running however after testing found the data to be very unreliable. Thanks in advance for the help. I am really astounded by the number of functions in Postgres and when I came across the window function thought it would be appropriate but can get over the hurdle. I know that (from the psql docs) you cannot put the lag/lead windowing into the update part of the function but have an issue obviously with how to do the sql block correctly
This query runs after running the first but how do I update the table on this:
with myUpdowns as (
Select id, updown, Lag(updown) over (order by id desc) next_updown from smoothings
)
select *
from myUpdowns
where updown < next_updown;
AND now I got one high to update correctly: I am now trying to do the full spread of high. low and inbetween (0, 1 and 2). If you have any suggestions I would love to hear them. The updated trigger that adds the high to the table looks as follows:
CREATE FUNCTION make_high_lows_trg()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
UPDATE smoothings
SET posture = 0
FROM (
Select id, updown, Lag(updown) over (order by id desc) next_updown from smoothings
) as highs
WHERE highs.updown < highs.next_updown
AND smoothings.id = highs.id;
RETURN NULL;
END
$BODY$;
The Trigger:
CREATE TRIGGER b_smoothings_trigger
AFTER INSERT
ON smoothings
FOR EACH ROW
EXECUTE PROCEDURE make_high_lows_trg();
Hmm, now how to do low and inbetween, actually just the high and low are good
I am generating the condition for an ActiveRecord where as follows:
query = {:status => status}
if (limit)
query[:limit] = #vals['limit'].to_i
end
if (offset && limit)
query[:offset] = (offset - 1) * limit
end
rows = Review.all(query)
This works just fine. I filter on 'status' of a review and I fill in limit and offset if passed in. Problem is now that I need to add a check for 'not null' on the reviews content field.
I.E.
AND review.content != '' && review.content != nil
I have read you can do something like
Review.were("review <> ''")
Which by itself works but I am not sure how to incorporate that into my above command. Or change the above command to work with a where statement rather than an 'all' statement.
I would write that code something like
query = Review.where("status = ?", status).where("review <> '' AND review IS NOT NULL")
if limit.present?
query = query.limit(limit)
if offset.present?
query = query.offset((offset - 1) * limit)
end
end
rows = query.all
rails query object does lazy evaluation, so you can build up the query, no sql is issued to the database until you begin to iterate over the rows
alternate to .where("review <> '' AND review IS NOT NULL")
.where("COALESCE(review, '') <> ''")
Basically I have a table of objects, each of these objects has one particular field that is a number. I'm trying to see if any of these numerical entries match up and I can't think of a way to do it. I thought possibly a double for loop, one loop iterating through the table, the other decrementing, but won't this at some point lead to two values being compared twice? I'm worried that it may appear to work on the surface but actually have subtle errors. This is how I pictured the code:
for i = #table, 1, -1 do
for j = 1, #table do
if( table[i].n == table[j].n ) then
table.insert(table2, table[i])
table.insert(table2, table[j])
end
end
end
I want to insert the selected objects, as tables, into another pre made one without any duplicates.
Let the outer loop run over the table, and let the inner loop always start one element ahead of the outer one - this avoids double counting and comparing objects with themselves. Also, if you call the table you want to examine table that will probably hide the table library in which you want to access insert. So let's say you call your input table t:
for i = 1, #t do
for j = i+1, #t do
if( t[i].n == t[j].n ) then
table.insert(table2, t[i])
table.insert(table2, t[j])
end
end
end
Still, if three or more elements have the same value n you will add some of them multiple times. You could use another table to remember which elements you've already inserted:
local done = {}
for i = 1, #t do
for j = i+1, #t do
if( t[i].n == t[j].n ) then
if not done[i] then
table.insert(table2, t[i])
done[i] = true
end
if not done[j] then
table.insert(table2, t[j])
done[j] = true
end
end
end
end
I admit this isn't really elegant, but it's getting late over here, and my brain refuses to think of a neater approach.
EDIT: In fact... using another table, you can reduce this to a single loop. When you encounter a new n you add a new value at n as the key into your helper table - the value will be that t[i] you were just analysing. If you encounter an n that already is in the table, you take that saved element and the current one and add them both to your target list - you also replace the element in the auxiliary table with true or something that's not a table:
local temp = {}
for i = 1, #t do
local n = t[i].n
if not temp[n] then
temp[n] = t[i]
else
if type(temp[n]) == "table" then
table.insert(table2, temp[n])
temp[n] = true
end
table.insert(table2, t[i])
end
end