Why can't i refer to a column alias in the ORDER BY using CASE?
This has to do with how a SQL dbms resolves ambiguous names.
I haven't yet tracked down this behavior in the SQL standards, but it seems to be consistent across platforms. Here's what's happening.
create table test (
col_1 integer,
col_2 integer
);
insert into test (col_1, col_2) values
(1, 3),
(2, 2),
(3, 1);
Alias "col_1" as "col_2", and use the alias in the ORDER BY clause. The dbms resolves "col_2" in the ORDER BY as an alias for "col_1", and sorts by the values in "test"."col_1".
select col_1 as col_2
from test
order by col_2;
col_2
--
1
2
3
Again, alias "col_1" as "col_2", but use an expression in the ORDER BY clause. The dbms resolves "col_2" not as an alias for "col_1", but as the column "test"."col_2". It sorts by the values in "test"."col_2".
select col_1 as col_2
from test
order by (col_2 || '');
col_2
--
3
2
1
So in your case, your query fails because the dbms wants to resolve "NewValue" in the expression as a column name in a base table. But it's not; it's a column alias.
PostgreSQL
This behavior is documented in PostgreSQL in the section Sorting Rows. Their stated rationale is to reduce ambiguity.
Note that an output column name has to stand alone, that is, it cannot be used in an expression — for example, this is not correct:
SELECT a + b AS sum, c FROM table1 ORDER BY sum + c; -- wrong
This restriction is made to reduce ambiguity. There is still ambiguity if an ORDER BY item is a simple name that could match either an output column name or a column from the table expression. The output column is used in such cases. This would only cause confusion if you use AS to rename an output column to match some other table column's name.
Documentation error in SQL Server 2008
A slightly different issue with respect to aliases in the ORDER BY clause.
If column names are aliased in the SELECT list, only the alias name can be used in the ORDER BY clause.
Unless I'm insufficiently caffeinated, that's not true at all. This statement sorts by "test"."col_1" in both SQL Server 2008 and SQL Server 2012.
select col_1 as col_2
from test
order by col_1;
Unable to ORDER BY on Column Alias when using CASE
Okay, here are some comments that I have.
1 - Please get away from old style joins. Use the INNER JOIN ON clause.
2 - There is no reason why an alias can not be used in the ORDER BY clause. Please see Itzik Ben-Gans posting on logical processing order. The SELECT arguments are processed way before the ORDER BY.
http://www.sql.co.il/books/insidetsql2008/Logical%20Query%20Processing%20Poster.pdf
3 - Last but not least, a simple example (adventureworks) that make everyone with a hire date less than 2004 as Active, everyone else is in-active. This will sort by the status column.
Good luck.
John
-- Sample database
Use AdventureWorks2012
GO
-- Sample select showing alias works fine in a order by clause.
SELECT [LoginID] as login_id,
CASE WHEN (e.HireDate < '20040101') THEN 'Active'
ELSE 'InActive' END AS emp_status
FROM [HumanResources].[Employee] as e
ORDER BY emp_status desc
GO
Since you changed your code above, here is a new answer to match. For a CASE statement on an ORDER BY you have to use the actual columns. For just a simple ORDER BY, the alias will work.
SO THE ANSWER is it ALL DEPENDS!!
Use AdventureWorks2012
GO
ALTER PROCEDURE usp_Sort_By_Column(@sort varchar(25))
AS
SELECT
[LoginID] as login_id,
CASE WHEN (e.HireDate < '20040101')
THEN 'Active' ELSE 'InActive' END AS emp_status
FROM
[HumanResources].[Employee] as e
ORDER BY
(CASE
WHEN @sort = 'ID' THEN [LoginID] ELSE
(CASE WHEN (e.HireDate < '20040101')
THEN 'Active' ELSE 'InActive' END)
END)
GO
usp_Sort_By_Column 'STATUS'
Link to ORDER BY - Books On Line ...
http://msdn.microsoft.com/en-us/library/ms188385.aspx
Best answer for your crazy query: Make a sort column that is dynamic using the variable. Just order by the first column. Cleanest answer.
Use AdventureWorks2012
GO
ALTER PROCEDURE usp_Sort_By_Column(@sort varchar(25))
AS
SELECT
(CASE
WHEN @sort = 'ID' THEN [LoginID] ELSE
(CASE WHEN (e.HireDate < '20040101')
THEN 'Active' ELSE 'InActive' END)
END) as Sort_Column,
[LoginID] as login_id,
CASE WHEN (e.HireDate < '20040101')
THEN 'Active' ELSE 'InActive' END AS emp_status
FROM
[HumanResources].[Employee] as e
ORDER BY
1
GO
usp_Sort_By_Column 'ID'
ORDER BY with a CASE statement for column with alias
Try this
ORDER BY
CASE WHEN @sortColumnName = 'LastPayCheckDate' AND @sortDirection = 'ASC'
THEN MAX(PayCheckDate) END ASC,
CASE WHEN @sortColumnName = 'LastPayCheckDate' AND @sortDirection = 'DESC'
THEN MAX(PayCheckDate) END DESC
Example
create table Test (id int, somevalue int)
insert Test values(1,1)
insert Test values(2,1)
insert Test values(3,2)
insert Test values(3,2)
insert Test values(4,2)
run this in 1 shot
declare @sortDirection char(4)
select @sortDirection = 'DESC'
select somevalue, COUNT(*)
from Test
group by somevalue
order by case when @sortDirection = 'ASC'
then COUNT(*) end asc,
case when @sortDirection = 'DESC'
then COUNT(*) end desc
select @sortDirection = 'ASC'
select somevalue, COUNT(*)
from Test
group by somevalue
order by case when @sortDirection = 'ASC'
then COUNT(*) end asc,
case when @sortDirection = 'DESC'
then COUNT(*) end desc
MSSQL CASE after ORDER BY not working with alias
You cannot use aliased columns in the ORDER BY clause because sorting logically it happens before the SELECT clause. You could use column numbers, e.g. ORDER BY 9 would sort the output by your 'Liefertermin' column, but that is considered a bad practice. Best way around it would be to put everything inside a subquery, like:
SELECT * FROM
(SELECT... -- your whole select here without the ORDER BY clause
) as t1
ORDER BY
CASE WHEN @Sort = 1 then SortDate END DESC
CASE WHEN @Sort = 2 then Liefertermin END DESC
CASE WHEN @Sort = 3 then Name END DESC
Why can't I use column aliases in the next SELECT expression?
You can use a previously created alias in the GROUP BY
or HAVING
statement but not in a SELECT
or WHERE
statement. This is because the program processes all of the SELECT
statement at the same time and doesn't know the alias' value yet.
The solution is to encapsulate the query in a subquery and then the alias is available outside.
SELECT stddev_time, max_time, avg_time, min_time, cnt,
ROUND(avg_time * cnt, 2) as slowdown
FROM (
SELECT
COALESCE(ROUND(stddev_samp(time), 2), 0) as stddev_time,
MAX(time) as max_time,
ROUND(AVG(time), 2) as avg_time,
MIN(time) as min_time,
COUNT(path) as cnt,
path
FROM
loadtime
GROUP BY
path
ORDER BY
avg_time DESC
LIMIT 10
) X;
SQL: Alias Column Name for Use in CASE Statement
I think that MySql and MsSql won't allow this because they will try to find all columns in the CASE clause as columns of the tables in the WHERE clause.
I don't know what DBMS you are talking about, but I guess you could do something like this in any DBMS:
SELECT *, CASE WHEN a = 'test' THEN 'yes' END as value FROM (
SELECT col1 as a FROM table
) q
Order by calculated column with alias inside case expression
So while you can use a calculated column in your ORDER BY
clause (but not in other clauses such as GROUP BY
), you cannot then apply further calculations or conditions - it must be used exactly as created.
There are a whole bunch of ways to solve this problem. Which approach you use will come down to some combination of:
- Which option is clearer to you as the developer
- Which option performs better
- Which option fits into your existing query better
Option 1: Repeat the logic
I don't recommend this option because it violates the DRY principle thereby making it harder to maintain and easier to make mistakes.
select top 10
S.Id as EntityId
, S.EnglishName as EntityEnglishName
, S.[Name] as EntityNativeName
, case
when @Mode = 0 then 0
when @Mode = 1 then 1
when @Mode = 2 then 2
end as ActiveStudents
from V_SchoolMinimized as S
order by
case when @Sort is null then S.Id end
, case when @Sort = 'engname' then
case
when @Mode = 0 then 0
when @Mode = 1 then 1
when @Mode = 2 then 2
end
end;
The rest of the options are sub-query variations the choice of which comes down to the comments provided as the start.
Option 2: Use a derived table sub-query
select top 10
S.Id as EntityId
, S.EnglishName as EntityEnglishName
, S.[Name] as EntityNativeName
, S.ActiveStudents
from (
select *
, case
when @Mode = 0 then 0
when @Mode = 1 then 1
when @Mode = 2 then 2
end as ActiveStudents
from V_SchoolMinimized
) as S
order by
case when @Sort is null then S.Id end
, case when @Sort = 'engname' then S.ActiveStudents end;
Option 3: Use a CTE (Common Table Expression)
with cte as (
select *
, case
when @Mode = 0 then 0
when @Mode = 1 then 1
when @Mode = 2 then 2
end as ActiveStudents
from V_SchoolMinimized
)
select top 10
S.Id as EntityId
, S.EnglishName as EntityEnglishName
, S.[Name] as EntityNativeName
, S.ActiveStudents
from cte
order by
case when @Sort is null then S.Id end
, case when @Sort = 'engname' then S.ActiveStudents end;
Option 4: Use CROSS APPLY
select top 10
S.Id as EntityId
, S.EnglishName as EntityEnglishName
, S.[Name] as EntityNativeName
, A.Students
from V_SchoolMinimized as S
cross apply (
values (
case
when @Mode = 0 then 0
when @Mode = 1 then 1
when @Mode = 2 then 2
end
)
) as A (Students)
order by
case when @Sort is null then S.Id end
, case when @Sort = 'engname' then A.Students end;
Note: I suggest keeping your table aliases nice and short, 1-2 characters where possible, occasionally 3.
Can not order by alias in SQL Server 2012
Yes, you can use it in order by, but no in the case statement.
CREATE VIEW NAME
AS
SELECT BAUSER.USER_FNM + ' ' + BAUSER.USER_LNM AS USER_FULL_NAME --, next cols
FROM CAORAC CAORAC INNER JOIN CAACCO CAACCO
ON CAORAC.ACCO_KEY = CAACCO.ACCO_KEY
INNER JOIN BAUSER BAUSER
ON CAORAC.USER_KEY = BAUSER.USER_KEY
SELECT
USER_FULL_NAME AS USER_FULL_NAME,
CASE WHEN @cOrderBy = 'cUSER_FULL_NAME ASC' THEN USER_FULL_NAME END USER_FULL_NAME_ASC ,
CASE WHEN @cOrderBy = 'cUSER_FULL_NAME DESC' THEN USER_FULL_NAME END USER_FULL_NAME_DESC
FROM NAME
ORDER
USER_FULL_NAME_ASC ASC,
USER_FULL_NAME_DESC DESC
Or you can add case into your view too. It depends on puropse but it could be candidate for table value function. Or if you prefer more complex qry you can use derived table (instead of view).
SQL not recognizing column alias in where clause
An alias can be used in a query select list to give a column a different name. You can use the alias in GROUP BY, ORDER BY, or HAVING
clauses to refer to the column.Standard SQL disallows references to column aliases in a WHERE clause. This restriction is imposed because when the WHERE clause is
evaluated, the column value may not yet have been determined.
So, the following query is illegal:
SQL> SELECT empno AS employee, deptno AS department, sal AS salary
2 FROM emp
3 WHERE employee = 7369;
WHERE employee = 7369
*
ERROR at line 3:
ORA-00904: "EMPLOYEE": invalid identifier
SQL>
The column alias is allowed in:
- GROUP BY
- ORDER BY
- HAVING
You could refer to the column alias in WHERE clause in the following cases:
- Sub-query
- Common Table Expression(CTE)
For example,
SQL> SELECT * FROM
2 (
3 SELECT empno AS employee, deptno AS department, sal AS salary
4 FROM emp
5 )
6 WHERE employee = 7369;
EMPLOYEE DEPARTMENT SALARY
---------- ---------- ----------
7369 20 800
SQL> WITH DATA AS(
2 SELECT empno AS employee, deptno AS department, sal AS salary
3 FROM emp
4 )
5 SELECT * FROM DATA
6 WHERE employee = 7369;
EMPLOYEE DEPARTMENT SALARY
---------- ---------- ----------
7369 20 800
SQL>
Referring to a Column Alias in a WHERE Clause
SELECT
logcount, logUserID, maxlogtm,
DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
WHERE ( DATEDIFF(day, maxlogtm, GETDATE() > 120)
Normally you can't refer to field aliases in the WHERE
clause. (Think of it as the entire SELECT
including aliases, is applied after the WHERE
clause.)
But, as mentioned in other answers, you can force SQL to treat SELECT
to be handled before the WHERE
clause. This is usually done with parenthesis to force logical order of operation or with a Common Table Expression (CTE):
Parenthesis/Subselect:
SELECT
*
FROM
(
SELECT
logcount, logUserID, maxlogtm,
DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
) as innerTable
WHERE daysdiff > 120
Or see Adam's answer for a CTE version of the same.
Related Topics
Calculating SQL Server Row_Number() Over() for a Derived Table
Why Is My Case Expression Non-Deterministic
SQL - Add Up All Row-Values of One Column in a Singletable
Executing Ssis Package with SQL Authentication
Oracle, Split a Time Duration Row by One Hour Period
Getting the Floor Value of a Number in SQLite
Stored Procedure, Pass Table Name as a Parameter
Insert a Select Group By:More Target Columns Than Expressions Error
Select Rownum from Salary Where Rownum=3;
Problem with MySQL Insert Max()+1
SQL Query to Select the 'Next' Record (Similar to First or Top N)
The Object Name Contains More Than the Maximum Number of Prefixes. the Maximum Is 3
Get Every Hour for a Time Range
Select * from Table or Select Id,Field1, Field2, Field3 from Table - Best Practice