0

I've been trying to convert a mySQL query in my Rails application to use Postgres Syntax and have been pouring over the Postgres documentation, specifically looking at the differences between conditionals. The query is:

Story.select('*', 'IF (views.id is null, 0, 1) as viewed')
  .join('left join views on(
       views.story_id = stories.id and views.user_id = ?
     )', user.id)
  .group(:id)
  .order(viewed: :asc)
  .order(id: :asc)

or as a pure query:

select stories.*,
  IF (views.id is null, 0, 1) as viewed
from stories
left join views
  on(views.story_id = stories.id and views.user_id = 1)
where 1
group by stories.id
order by viewed, stories.id

It seems postgres uses 'case' instead of IF, so I tired something like this:

Story.select('*', '(case when viewid is null then 0, 1 end) as viewed')
  .join('left join views on(
       views.story_id = stories.id and view.user_id = ?
     )', user.id)
  .group(:id)
  .order(viewed: :asc)
  .order(id: :asc)

but I get a

wrong number of arguments (2 for 0..1)

error...

I've tried several variations but with no luck. I'm planning to study SQL a bit more in the near future but in the mean time, I feel somewhat lost here.

Can anyone point out the finer details here of what the different syntax might be? Thanks!

3 Answers 3

1

EXISTS(...) yields a boolean, which solves the NULL case quite elegantly:

select s.*
  , EXISTS (select 42 from views v
            where v.story_id = s.id
            and v.user_id = 1) as viewed
from stories s
where 1=1
  ;

If you want to use the viewed column in an ORDER BY clause, you'll have to push the above query into a subquery.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for this. I'd rather keep it in a single query but this taught me one approach that could avoid the conditional...
1

Based on the error:

wrong number of arguments (2 for 0..1)

Rails (no postgres) is telling you that some method is receiving two arguments but the method only supports or none arguments or only one argument.

Looking into the code, the only two methods where you are sending two arguments are select and join so what if you try:

select('*, (case when viewid is null then 0, 1 end) as viewed') instead of select('*', '(case when viewid is null then 0, 1 end) as viewed')

and (just for test because is a bad practice)

  .join("left join views on(
       views.story_id = stories.id and view.user_id = #{user.id}
     )")

instead of

  .join('left join views on(
       views.story_id = stories.id and view.user_id = ?
     )', user.id)

?

1 Comment

I believe you're onto something there. It was a rails error but I tried the above and could not get it to work. In the end I ran out of time and I used a find_by_sql method with the the postgres syntax: select stories.*, CASE WHEN views.id is null THEN 0 ELSE 1 END as viewed from stories left join views on(views.story_id = stories.id and views.user_id = 1) where true group by stories.id, views.id order by viewed, stories.id Later, I will revisit this and see if I can get it working with active-record queries.
0

The closest conversion to postgres syntax using a single query with conditional:

   select stories.*,
        CASE WHEN views.id is null THEN 0 ELSE 1 END as viewed
      from stories
      left join views
        on(views.story_id = stories.id and views.user_id = 1)
      where true
      group by stories.id, views.id
      order by viewed, stories.id

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.