Using boolean expression in order by clause - sql

I have an order by clause that looks like:
( user_id <> ? ), rating DESC, title
Where ? is replaced with the current user's id.
On postgresql this gives me the ordering I'm looking for i.e. by current user, then highest rating, then title (alphabetically).
However on MySQL I get an unclear order current user is neither first nor last, nor is it by rating or title.
Is my only option for cross database compatibility to replace this quick and dirty boolean expression with a CASE WHEN .. THEN .. ELSE .. END statement?
Edit: Thanks all for the assistance, it is as correctly pointed out by Chaos and Chad Birch the case that the problem lies elsewhere (specifically that I'm using the results of the above query as input into the next - then acting surprised that the order of the first is lost ;)

MySQL has no real notion of booleans, and simply maps TRUE and FALSE to the numeric values 1 and 0 repectively.
In this case user_id <> ? will return 0 for the majority of the rows in your table and 1 for the other rows. The default sort order is ASC, meaning in all likelihood the rows you want are at the bottom of your result set (0/FALSE come before 1/TRUE). Try modifying your query to accommodate this.
( user_id <> ? ) DESC, rating DESC, title
Assuming this is indeed the issue, cross-database compatibility can be achieved with ease.
IF(user = ?, 0, 1), rating DESC, title

You could try doing a
select (user_id <> ?), user_id
to see that you are getting the right true/false values showing up.

I tested several variations on this in mysql and they all worked correctly (the way you're expecting). I suppose your problem has to be somewhere other than the query. To verify for yourself, I suggest running an equivalent query directly from mysql client.

Related

Rails/SQL - How to filter by distinct value in a group

I'm working in Rails, but an answer in SQL is equally helpful. Let's say I have a table of Users and a table of Purchases. I want to find the Users who have only ever bought Item A. I was hoping to use a query along the lines of:
User.joins(:purchases).group(:id).having("DISTINCT(item) = 'A'").pluck(:id)
This is a simplification of the question I need to answer, but this grouping issue is my main roadblock. For that reason, I'm hoping for an answer that is logically very similar, as other workarounds would likely not apply.
Does this work in Rails?
User.joins(:purchases).group(:id).having("MIN(item) = MAX(item) AND MIN(item) = 'A'").pluck(:id)
This phrase as: there is only one distinct value (since MIN() and MAX() are equal), that is 'A'.
Alternatively:
having("MAX(CASE WHEN item <> 'A' THEN 1 ELSE 0 END) = 0")
Which would stand for: no other value than 'A'.
In having you can use only aggregate functions (e.g. having count(id) > 2) or expressions on columns you did the grouping on e.g. having("id > 1").
So depending on your db you may try to find an aggregate function that identifies existence of item in the grouping per id.
For PostgreSql that would be something like (haven't tested):
...
GROUP BY id
HAVING 'A' = ANY(ARRAY_AGG(item))

Two Level Grouping in SQL

Need Help on this SQL query..
Table has just 3 columns - Name, Check(Yes / No), Post (true /false)
The result gets ordered by Check and then further grouped by Post value....
Simply - All Records with Check Yes and Post true first, then those with Check Yes and Post false. And so on those with Check No and Post true and at last those with Check No and Post false
May be Simpler then it sounds but just can't get this one to work :)
Well the actual table schema is larger then this dummy table but we can assume all these columns to be nvarchars
From my understanding of the question, this seem to be what you want.
SELECT *
FROM dummyTable
ORDER BY [Check] DESC,
[Post] DESC
Will work with BIT and VARCHAR, because T > F AND Y > N.
In case it is VARCHAR with denormalize data, a good idea would be to use
ORDER BY UPPER([Check]) DESC,
UPPER([Post]) DESC
to avoid bad result due to case sensitivity (T < f AND Y < n)

Selecting top n Oracle records with ROWNUM still valid in subquery?

I have the following FireBird query:
update hrs h
set h.plan_week_id=
(select first 1 c.plan_week_id from calendar c
where c.calendar_id=h.calendar_id)
where coalesce(h.calendar_id,0) <> 0
(Intention: For records in hrs with a (non-zero) calendar_id
take calendar.plan_week_id and put it in hrs.plan_week_id)
The trick to select the first record in Oracle is to use WHERE ROWNUM=1, and if understand correctly I do not have to use ROWNUM in a separate outer query because I 'only' match ROWNUM=1 - thanks SO for suggesting Questions that may already have your answer ;-)
This would make it
update hrs h
set h.plan_week_id=
(select c.plan_week_id from calendar c
where (c.calendar_id=h.calendar_id) and (rownum=1))
where coalesce(h.calendar_id,0) <> 0
I'm actually using the 'first record' together with the selection of only one field to guarantee that I get one value back which can be put into h.plan_week_id.
Question: Will the above query work under Oracle as intended?
Right now, I do not have a filled Oracle DB at hand to run the query on.
Like Nicholas Krasnov said, you can test it in SQL Fiddle.
But if you ever find yourself about to use where rownum = 1 in a subquery, alarm bells should go off, because in 90% of the cases you are doing something wrong. Very rarely will you need a random value. Only when all selected values are the same, a rownum = 1 is valid.
In this case I expect calendar_id to be a primary key in calendar. Therefor each record in hrs can only have 1 plan_week_id selected per record. So the where rownum = 1 is not required.
And to answer your question: Yes, it will run just fine. Though the brackets around each where clause are also not required and in fact only confusing (me).

designedly big value in SQL

Ok, I have to put null values last. The query should run under Oracle and MySQL.
I've already came up with
ORDER BY
CASE WHEN some_table.ord IS NULL THEN 9999999999 ELSE some_table.ord END
I should use value > max(some_table.ord) instead of 9999999999.
I think subquery to determine this value is too ugly here.
If this was C++ I can use some macro like INT_MAX for this purpose. Can you name its cross-DBMS SQL twin?
UPDATE
the question is if can I put something .. beautiful instead of 9999999999, so that query will work both in Oracle and MySQL,
not how to put null values last
Use an extra column for the null flag:
order by
case when some_table.ord is null then 2 else 1 end ,
some_table.ord
Or, if you have enough knowledge of the values that this column can take, just hard-code a number that is larger than anything in there:
order by coalesce(some_table.ord, 9999999999)
In Oracle, it's simply
ORDER BY some_table.ord NULLS LAST
Something like the following might work:
SELECT S.VAL1, S.VAL2, S.VAL3, COALESCE(S.ORD, O.MAX_ORD+1) AS ORD
FROM SOME_TABLE S,
(SELECT MAX(ORDER) AS MAX_ORD FROM SOME_TABLE) O
WHERE S.whatever = whichever AND
S.something <> something_else
ORDER BY ORD
Not sure if MySQL allows sub-queries in the FROM list. The idea here is to avoid the use of a magic value to handle the NULL case.
Share and enjoy.

changing sorting criteria after the first result

I am selecting from a database of news articles, and I'd prefer to do it all in one query if possible. In the results, I need a sorting criteria that applies ONLY to the first result.
In my case, the first result must have an image, but the others should be sorted without caring about their image status.
Is this something I can do with some sort of conditionals or user variables in a MySQL query?
Even if you manage to find a query that looks like one query, it is going to be logicaly two queries. Have a look at MySQL UNION if you really must make it one query (but it will still be 2 logical queries). You can union the image in the first with a limit of 1 and the rest in the second.
Something like this ensures an article with an image on the top.
SELECT
id,
title,
newsdate,
article
FROM
news
ORDER BY
CASE WHEN HasImage = 'Y' THEN 0 ELSE 1 END,
newsdate DESC
Unless you define "the first result" closer, of course. This query prefers articles with images, articles without will appear at the end.
Another variant (thanks to le dorfier, who deleted his answer for some reason) would be this:
SELECT
id,
title,
newsdate,
article
FROM
news
ORDER BY
CASE WHEN id = (
SELECT MIN(id) FROM news WHERE HasImage = 'Y'
) THEN 0 ELSE 1 END,
newsdate DESC
This sorts the earliest (assuming MIN(id) means "earliest") article with an image to the top.
I don't think it's possible, as it's effectively 2 queries (the first query the table has to get sorted for, and the second unordered), so you might as well use 2 queries with a LIMIT 1 in the first.