SQL Order By with case when - sql

I am trying to understand the ORDER BY with CASE WHEN.
My aim is to understand it fundamentally for that I had different use cases created
My base table is as below
| Name |
|--------|
| BPM |
| BXR |
| Others |
| XZA |
| XYZ |
| PQR |
| ABC |
Query 1: Basic ORDER BY
SELECT *
FROM City
ORDER BY Name
Query 1 Result:Gave correct output as below(Name column sorted in ascending order)
| Name |
|--------|
| ABC |
| BPM |
| BXR |
| Others |
| PQR |
| XYZ |
| XZA |
Query 2: I want Others at last
SELECT *
FROM City
ORDER BY CASE
WHEN Name = 'Others' THEN 1
ELSE 0
END
Query 2 Result: I got partially correct result.I got Others at last but other names I expected it to be in ascending order.They actually appear the way they are in base table.
| Name |
|--------|
| BPM |
| BXR |
| XZA |
| XYZ |
| PQR |
| ABC |
| Others |
I am also not getting what does 0 and 1 actually mean in the ORDER BY statement.
Query 3: I want BXR and Others at last.
SELECT *
FROM City
ORDER BY CASE
WHEN Name = 'BXR' THEN 1
WHEN Name = 'Others' THEN 2
ELSE 0
END
Query 3 Result: I got partially correct result.I got 'Others' and 'BXR' at last but other Name are not in alphabetical order.Same as seen in Query 2.Here also I am not understanding the significance of 0, 1,2
| Name |
|--------|
| BPM |
| XZA |
| XYZ |
| PQR |
| ABC |
| BXR |
| Others |
Query 4: I want Others and PQR at top.
SELECT *
FROM City
ORDER BY CASE
WHEN Name = 'PQR' THEN 0
WHEN Name = 'Others' THEN 1
ELSE 2
END
QUery 4 Result: I get PQR and Others at top but the remaining names are not in aplhabetical order.
| Name |
|--------|
| PQR |
| Others |
| BPM |
| BXR |
| XZA |
| XYZ |
| ABC |
My assumption about 0, 1, 2 is that they are just numbers deciding the "order" in which a record should be.
(The record having 0 should be kept first and if all other records have 1 then should be sorted alphabetically)
(If there are '0', '1','2', in record with 0 should be first, record with 1 should be second all other record having 2 should be sorted alphabetically)
Correct me if I am wrong with this
SQLFiddle

You need to add name also in order by
DEMO
SELECT *
FROM City
ORDER BY CASE
WHEN Name = 'PQR' THEN 0
WHEN Name = 'Others' THEN 1
ELSE 2
END,name
OUTPUT:
**Name**
PQR
Others
ABC
BPM
BXR
XYZ
XZA

We can also ORDER BY using FIELD:
SELECT *
FROM City
ORDER BY FIELD(Name, 'Others', 'PQR') DESC, name;
Demo
The behavior of FIELD is such that it will return 1 for Others, 2 for PQR, and 0 for any other name. So, we use a descending order to ensure that PQR appears first, followed by Others, followed all other names.

You may keep the name column in the else case as :
SELECT *
FROM City
ORDER BY CASE
WHEN Name = 'PQR' THEN 0
WHEN Name = 'Others' THEN 1
ELSE Name
END
since always numbers has precedence over alphabets.
SQL Fiddle Demo

Related

How can I subtract two row's values within same column using sql query in access?

(query access)
This is the table structure:
+-----+--------+--------+
| id | name | sub1 |
+-----+--------+--------+
| 1 | ABC | 6.27% |
| 2 | ABC | 7.47% |
| 3 | PQR | 3.39% |
| 4 | PQR | 2.21% |
+-----+--------+--------+
I want to subtract Sub1
Output should be:
+-----+--------+---------+------------------------------------+
| id | name | sub1 | |
+-----+--------+---------+------------------------------------+
| 1 | ABC | 6.27% | 0 First Rec no need Subtract |
| 2 | ABC | 7.47% | 1.2% <=(7.47-6.27) |
| 3 | PQR | 3.39% | 0 First Rec no need Subtract |
| 4 | PQR | 2.21% | -1.18% <=(2.21-3.39) |
+-----+--------+---------+------------------------------------+
Thank you so much.
If you can guarantee consecutive id values, then the following presents an alternative:
select t.*, nz(t.sub1-u.sub1,0) as sub2
from YourTable t left join YourTable u on t.name = u.name and t.id = u.id+1
Change YourTable to the name of your table.
This is painful, but you can do:
select t.*,
(select top 1 t2.sub1
from t as t2
where t2.name = t.name and t2.id < t.id
order by t2.id desc
) as prev_sub1
from t;
This gives the previous value or NULL for the first row. You can just use - for the subtraction.
An index on (name, id) would help a bit with performance. However, if you can upgrade to a better database, you can then just use lag().

Returning rows with the same ID but exclude some on second column

I've seen similar questions about but not quite hitting the nail on the head for what I need. Lets say I have a table.
+-----+-------+
| ID | Value |
+-----+-------+
| 123 | 1 |
| 123 | 2 |
| 123 | 3 |
| 456 | 1 |
| 456 | 2 |
| 456 | 4 |
| 789 | 1 |
| 789 | 2 |
+-----+-------+
I want to return DISTINCT IDs but exclude those that have a certain value. For example lets say I don't want any IDs that have a 3 as a value. My results should look like.
+-----+
| ID |
+-----+
| 456 |
| 789 |
+-----+
I hope this makes sense. If more information is needed please ask and if this has been answered before please point me in the right direction. Thanks.
You can use group by and having:
select id
from t
group by id
having sum(case when value = 3 then 1 else 0 end) = 0;
The having clause counts the number of "3"s for each id. The = 0 returns only returns groups where the count is 0 (i.e. there are no "3"s).
You can use not exists :
select distinct t.id
from table t
where not exists (select 1 from table t1 where t1.id = t.id and t1.value = 3);
Try this:
select id from tablename
group by id
having (case when value=3 then 1 else 0 end)=0
You can also use EXCEPT for comparing following two data sets that will give the desired result set
select distinct Id from ValuesTbl
except
select Id from ValuesTbl where Value = 3

Remove dulicate rows using SQL

I want to know if there is a way to remove duplicate values from a table. The key 'distinct' will fetch us the unique rows however if one value differs in a column, it wont. so just wanted to know if this can be achieved by any means. Hope the below example will help.
For example : In the below table there are two entries for Emp_ID 1234 with two different priorities. my output should consider the higher priority row alone. Is it possible?
My table
+---------+------+--------+-------+
| Employee_ID| priority | gender |
+------------+-----------+--------+
| 1234 | 1 | F |
| 1234 | 10 | F |
| 5678 | 2 | M |
| 5678 | 25 | M |
| 9101 | 45 | F |
+------------+-----------+--------+
Output
+---------+------+--------+-------+
| Employee_ID| priority | gender |
+------------+-----------+--------+
| 1234 | 1 | F |
| 5678 | 2 | M |
| 9101 | 45 | F |
+------------+-----------+--------+
DELETE
FROM Table t
WHERE EXISTS ( SELECT Employee_ID FROM Table WHERE Employee_ID = t.Employee_ID AND priority < t.Priority)
That is if you really want to remove them from the table. The Exists part can also be used in a select query to leave the values in the Original table.
SELECT *
FROM Table t
WHERE NOT EXISTS (SELECT Employee_ID FROM Table WHERE Employee_ID = t.Employee_ID AND priority > t.Priority)
select Employee_ID,max(priority) as priority,gender
from table
group by Employee_ID,gender

How to Order by enum in Oracle DB

I want to order by a string column where that column is an enumeration. For example:
+----+--------+----------------------+
| ID | NAME | STATUS |
+----+--------+----------------------+
| 1 | Serdar | ACTIVE |
| 2 | John | DEACTIVE |
| 3 | Jerry | WAITING_FOR_APPROVAL |
| 4 | Jessie | REJECTED |
+----+--------+----------------------+
I want to order by STATUS. It should sort the results such that the first result must have STATUS = WAITING_FOR_APPROVAL, then ACTIVE, then DEACTIVE and then REJECTED.
Is there any way to do that in SQL? Is there something like Comparator in java?
You can enumerate the values in a CASE statement and order by that
SELECT id, name, status
FROM your_table
ORDER BY (CASE status
WHEN 'WAITING_FOR_APPROVAL' THEN 1
WHEN 'ACTIVE' THEN 2
WHEN 'DEACTIVE' THEN 3
WHEN 'REJECTED' THEN 4
ELSE 5
END)

Select rows appearing after a row with a given ID when sorted by criteria unrelated to the ID

Given the data in the table "people":
+----+-------+
| id | name |
+----+-------+
| 1 | Jane |
| 2 | Joe |
| 4 | John |
| 5 | Alice |
| 6 | Bob |
+----+-------+
And the order:
SELECT * FROM people ORDER BY name
... which would return:
+----+-------+
| id | name |
+----+-------+
| 5 | Alice |
| 6 | Bob |
| 1 | Jane |
| 2 | Joe |
| 4 | John |
+----+-------+
How could one write a query--including the order above--which would return only rows after the one with a given id, e.g., if given an id of 1, it would return:
+----+-------+
| id | name |
+----+-------+
| 2 | Joe |
| 4 | John |
+----+-------+
To be clear, the id is variable and not known before hand.
An approach using commonly supported SQL would be great, but I'm using PostgreSQL 9.2 and ActiveRecord 3.2 if they have anything additional of use, e.g., OVER() and ROW_NUMBER().
[Edit] I'd previously showed the wrong desired result set, including the row with the given id. But, the result set, as described in the question, should only include rows after the given ID.
select *
from people
where
name >= (
select name
from people
where id = 1
)
and id != 1
order by name
So far the simplest approach I've found for a situation where precision is needed, e.g., no missing or duplicate results across multiple calls with varying values for ID is to combine window functions and CTEs, as in:
WITH ordered_people AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY name) AS n
FROM people
ORDER BY name
)
SELECT *
FROM ordered_people
WHERE n > (SELECT n FROM ordered_people WHERE id = 1)
ORDER BY name
;