Order by calculated column with alias inside case expression - sql

I've got a problem with my order by clause when using a calculated column with an alias as below:
This order by works without any problem
declare #Mode int = 1
declare #Sort nvarchar(max) = 'engname'
select top 10 School.Id as EntityId,
School.EnglishName as EntityEnglishName,
School.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 School
Order By ActiveStudents
The following query has an error:
Invalid column name 'ActiveStudents'
declare #Mode int = 1
declare #Sort nvarchar(max) = 'engname'
select top 10 School.Id as EntityId,
School.EnglishName as EntityEnglishName,
School.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 School
Order By
case when #Sort is null then School.Id end,
case when #Sort = 'engname' then ActiveStudents end
How can I use ActiveStudents within the conditional order by clause as shown?

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.

Related

SQL Conversion failed when converting row number

So I've been trying to add an Dynamic Row Number column with out using Dynamic SQL. However when I try I get an error 'Conversion failed when converting character string to smalldatetime data type.'
I Know the Error is coming from in the functions So if you want to just look at the switch case in the function that is the problem, but here is the stored procedure just in case you need to see it.
I have a store procedure which looks like this:
ALTER PROCEDURE [dbo].[MMS_EdgateMainQueue]
-- Add the parameters for the stored procedure here
#OrderByColumnID int = 3,
#Skip int = 0,
#Take int = 0,
#Descending bit = 1,
#ResultCount INT OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #UrlTitlePrefix varchar(2080) = '<a href="/Title/PageByExtTitleID?ActionName=Edgate&ExtTitleID='
Declare #UrlProducerPrefix varchar(2080) = '<a href="/Producers/ByExtVendorID?ActionName=Details&ExtVendorID='
Declare #Urlmidfix varchar(100) = '">'
Declare #UrlPostFix varchar(100) = '</a>'
SELECT TOP (#Take)
[row_numb],
#UrlTitlePrefix + ExtTitleID + #Urlmidfix + ExtTitleID + #UrlPostFix as [Item #],
f.Title as Name,
#UrlProducerPrefix + f.ExtVendorID + #Urlmidfix + f.DisplayName + #UrlPostFix as Producer,
f.Created as Created,
isnull(f.Academic, '') as Academic,
isnull(f.Sears,'') as Sears,
isnull(f.Editor, '') as Editor,
CONVERT(INT, f.[Copy]) AS Copy,
f.[Segment],
CONVERT(INT, f.[Taxonomy]) AS Taxonomy,
f.[Priority]
FROM EdgateNewTitlesInnerQuery(#OrderByColumnID, #Descending) as f
Where f.[row_numb] Between ((#Skip * #Take) + 1) and ((#Skip + 1) * #Take) order by f.[row_numb]
END
And The Inner Function Looks Like:
ALTER FUNCTION [dbo].[EdgateNewTitlesInnerQuery]
(
#OrderByColumnID int,
#Descending bit
)
RETURNS TABLE
AS
RETURN
(
SELECT DISTINCT
v.ExtVendorID,
t.ID,
t.ExtTitleID,
t.Title,
v.DisplayName,
t.Created,
ecs.Title as [Academic],
ssub.Title as [Sears],
etw.EditorName as [Editor],
etw.CopyDone AS [Copy],
etw.SegmentsStatus as [Segment],
etw.TaxonomyDone AS [Taxonomy],
CASE WHEN wft.[Priority] is null THEN 0 ELSE wft.[Priority] END as [Priority],
--row_number() OVER (ORDER BY t.Created DESC) AS [row_number]
row_number() OVER (ORDER BY
CASE #OrderByColumnID WHEN 0 THEN t.ExtTitleID
WHEN 1 THEN t.Title
WHEN 2 THEN v.DisplayName
WHEN 3 THEN t.Created
WHEN 4 THEN ecs.Title
WHEN 5 THEN ssub.Title
WHEN 6 THEN etw.EditorName
WHEN 7 THEN etw.CopyDone
WHEN 8 THEN etw.SegmentsStatus
WHEN 9 THEN etw.TaxonomyDone
WHEN 10 THEN CASE WHEN wft.[Priority] is null THEN 0 ELSE wft.[Priority] END
ELSE t.Created
END DESC ) AS [row_numb]
FROM [Title] t
join EdgateTitleWorkflow etw on etw.FK_TitleID = t.ID
join Vendor v on v.ExtVendorID = t.ProducerID
join CollectionItem i on i.TitleID = t.ID and i.CollectionID = 16
left join [EdgateSuggestedAcademicSubject] esas on esas.FK_TitleID = t.ID and esas.isPrimary = 1
left join EC_Subject ecs on ecs.ID = esas.FK_SubjectID
left join [FMGSuggestedSears] fss on fss.FK_TitleID = t.ID and fss.isPrimary = 1
left join [FMGSearsSubjects] ssub on ssub.ID = fss.SearsSubjectID and ssub.ParentID is null
left join [WorkFlow_Tracker] wft on wft.TitleID = t.ID
where (etw.CopyDone = 0 or etw.TaxonomyDone = 0 or etw.SegmentsStatus = 0)
)
I've tried passing this in as a string originally but it just didn't sort at all. So I was looking at similar problems and tried this solution Here
but my switch Case is now throwing a Conversion Error. Does anyone have an Idea on how to fix this?
The problem is that case is an expression that returns one type, defined during compilation. You can fix this with a separate case for each key. I think this is the statement you want:
row_number() OVER (ORDER BY (CASE WHEN #OrderByColumnID = 0 THEN t.ExtTitleID END),
(CASE WHEN #OrderByColumnID = 1 THEN t.Title END),
(CASE WHEN #OrderByColumnID = 2 THEN v.DisplayName END),
(CASE WHEN #OrderByColumnID = 3 THEN t.Created END),
(CASE WHEN #OrderByColumnID = 4 THEN ecs.Title END),
(CASE WHEN #OrderByColumnID = 5 THEN ssub.Title END),
(CASE WHEN #OrderByColumnID = 6 THEN etw.EditorName END),
(CASE WHEN #OrderByColumnID = 7 THEN etw.CopyDone END),
(CASE WHEN #OrderByColumnID = 8 THEN etw.SegmentsStatus END),
(CASE WHEN #OrderByColumnID = 9 THEN etw.TaxonomyDone END),
(CASE WHEN #OrderByColumnID = 10 THEN COALESCE(wft.[Priority], 0) END)
t.Created DESC
)

T-SQL Case Condition in Where Clause

i trying to do this query where i have a where clause. The problem is that i need to use inside the where condition the operator IN but i can´t figured out
what i missing.
someone can give a hand pls?
here is my query
DECLARE #OP INT = 1
SELECT * FROM Table
WHERE
Table.[status] IN (CASE WHEN #OP = 1 THEN (5,6) ELSE (12) END)
There's no need for a case statement.
DECLARE #OP INT = 1;
SELECT * FROM Table
WHERE (#OP = 1 AND Table.[status] IN (5,6))
OR (#OP !=1 AND Table.[status] IN (12))
Try:
DECLARE #OP INT = 1
SELECT * FROM TABLE
WHERE
((#OP = 1 AND TABLE.[status] IN (5,6)) OR (#OP <> 1 AND TABLE.[status] = 12))
Another option:
DECLARE #OP INT = 1
SELECT * FROM Table
WHERE
1 = (CASE WHEN #OP = 1 CASE WHEN Table.[status] IN (5,6) THEN 1 END
ELSE CASE WHEN Table.[status] = 12 THEN 1 END
END)
Having nested case statements may help the query plan take advantage of case statement short circuiting. You could also do it like this without the nested cases:
DECLARE #OP INT = 1
SELECT * FROM Table
WHERE
1 = (CASE WHEN #OP = 1 AND Table.[status] IN (5,6) THEN 1
WHEN #OP <> 1 AND Table.[status] = 12 THEN 1
END)

Less expensive query?

I have a stored procedure that returns an integer 1 or 0 depending on specific criteria. It currently uses three select statements and it will be used heavily by multiple users across multiple locations. There has to be a more efficient way of doing this.
In short the query checks first to see if all checklist items on an order are completed (a separate table), then it checks to see if a field named BreakOutGuest (a bit field) is a 1 or 0. Depending on that result it checks to see if the total guest count is greater than 0 and the order total is zero. It returns the one or zero on all this criteria. Is there a more efficient way to do this? A temp table so I only have to hit the actual tables once? Below is the code.
#ORDERID INT
AS
BEGIN
DECLARE #AUTO_CLOSE INT
SET NOCOUNT ON;
--If all checklist items are marked complete move on, if not set #AUTO_CLOSE=0
IF NOT EXISTS(SELECT ORDERID FROM dbo.orderchecklistitems WHERE OrderID=#ORDERID AND CompletedON IS NULL)
BEGIN
--if BreakOutGuestFees is 1 only sum Guest_Count_1 + Guest_Count_2
IF EXISTS(SELECT * FROM dbo.Orders WHERE (GuestCount_1 + GuestCount_2)>1 AND OrderTotal=0 AND BreakoutGuestFees=1)
BEGIN
SET #AUTO_CLOSE=1
END
ELSE
SET #AUTO_CLOSE=0
--if BreakOutGuestFees is 0 only consider Guest_Count_1
IF EXISTS(SELECT * FROM dbo.Orders WHERE (GuestCount_1)>1 AND OrderTotal=0 AND BreakoutGuestFees=0)
BEGIN
SET #AUTO_CLOSE=1
END
ELSE
SET #AUTO_CLOSE=0
END
ELSE
SET #AUTO_CLOSE=0
END
If am not wrong you can combine two if clause into single if clause by using AND , OR logic. Try this.
IF NOT EXISTS(SELECT ORDERID
FROM dbo.orderchecklistitems
WHERE OrderID = #ORDERID
AND CompletedON IS NULL)
BEGIN
IF EXISTS(SELECT *
FROM dbo.Orders
WHERE ( ( GuestCount_1 + GuestCount_2 > 1
AND BreakoutGuestFees = 1 )
OR ( BreakoutGuestFees = 0
AND GuestCount_1 > 1 ) )
AND OrderTotal = 0
AND OrderID = #ORDERID)
SET #AUTO_CLOSE=1
ELSE
SET #AUTO_CLOSE=0
END
ELSE
SET #AUTO_CLOSE=0
You can perform your selection check with only one query
SELECT
(SELECT sum(1) FROM dual WHERE EXISTS (SELECT ORDERID FROM dbo.orderchecklistitems WHERE OrderID=#ORDERID AND CompletedON IS NULL)),
(SELECT sum(1) FROM dual WHERE EXISTS (SELECT 1 FROM dbo.Orders WHERE (GuestCount_1 + GuestCount_2)>1 AND OrderTotal=0 AND BreakoutGuestFees=1)),
(SELECT sum(1) FROM dual WHERE EXISTS (SELECT 1 FROM dbo.Orders WHERE (GuestCount_1)>1 AND OrderTotal=0 AND BreakoutGuestFees=0))
INTO
result1, result2, result3
from dual
then check results
DELCARE #AUTO_CLOSE INT = 0
IF NOT EXISTS(SELECT ORDERID
FROM dbo.orderchecklistitems
WHERE OrderID = #ORDERID
AND CompletedON IS NULL)
BEGIN
SET #AUTO_CLOSE =
(
SELECT
CASE
WHEN (GuestCount_1 + GuestCount_2 > 1) AND BreakoutGuestFees = 0 THEN 1
WHEN (GuestCount_1 > 1 ) AND BreakoutGuestFees = 1 THEN 1
ELSE 0 END
FROM dbo.orders
WHERE OrderTotal = 0 AND OrderID = #orderID
)
END

Query improvement in where clause

I have the following query:
DECLARE #MyTable TABLE
(
[ID] INT ,
[Col1] INT ,
[Col2] INT
)
INSERT INTO #MyTable
SELECT 1 ,
2 ,
1
UNION
SELECT 1 ,
2 ,
3
UNION
SELECT 2 ,
2 ,
3
UNION
SELECT 2 ,
2 ,
3
UNION
SELECT 3 ,
2 ,
3
UNION
SELECT 3 ,
2 ,
1
DECLARE #ID INT
SET #ID = 1
SELECT *
FROM #MyTable
WHERE ( Col1 = ( CASE WHEN [ID] = #ID THEN 2
END )
OR [Col2] = ( CASE WHEN [ID] != #ID THEN 1
END )
)
WHEN [ID] = #ID I want to match Col1 with constant value equals to 2 and when [ID] != #ID I want to match Col2 with constant value equals to 1. Can the above query be improve so that [ID] equality check can be done only once in the above query, something like this:
SELECT *
FROM #MyTable
WHERE
if([ID] = #ID)
Col1=2
ELSE
[Col2]=1
Is this the logic that you want?
where id = #id and col1 = 2 or
id <> #id and col2 = 1
I don't know why you are concerned about the performance of such a clause. You can do what you want with a case statement:
where 1 = (case when id = #id
then (case when col1 = 2 then 1 end)
else col2 = 1
end)
But this is a needless "optimization". It is not even clear that the nested case statements would be any faster than the first version. Such simple operations are really, really fast on modern computers. And, what slows databases down is the processing of large volumes of data (in general), not such simple operations.
Perhaps just:
Select *
From #MyTable
Where ((id = #id and col1 = 2) or (id <> #id and col2 = 1))

How to use between operator with case statement in SQL Server 2005

How to use BETWEEN operator with CASE statement
#FromEmpAge
#ToempAge
select * from Emp
where EmpAge between
case when ToempAge0 then #FromEmpAge and #ToempAge end
How to do this?
Not sure if it works - but if it did, you would have to make sure to properly finish your CASE statement first!
SELECT
(list of columns)
FROM
dbo.Emp
WHERE
EmpAge BETWEEN
CASE (some column) | this is the first value
WHEN ToEmpAge0 THEN #FromEmpAge | for the BETWEEN ....
END | close the CASE
AND #ToempAge | this is the second value
The CASE needs to have at least one WHEN....THEN.... and then an END to "close" then CASE - only after that, you can keep on going.....
But again: not sure if this will work, even with the "right" syntax.....
I'm not sure about what you mean with ToEmpAge0. Anyway, marc_s has answered your question on how to use CASE statement within a BETWEEN operator, that will work if you put a proper literal value after the WHEN.
You can also use the CASE statement for the second operand of the BETWEEN operator.
I will assume that ToEmpAge is a column of INT:
SELECT
(list of columns)
FROM
dbo.Emp
WHERE
EmpAge BETWEEN
CASE ToEmpAge
WHEN 0 THEN #FromEmpAge
WHEN 1 THEN #FromEmpAge+10 --or whatever
END
AND
CASE ToEmpAge
WHEN 0 THEN #ToEmpAge
WHEN 1 THEN #ToEmpAge+5
END
SELECT *
FROM CTE
WHERE RN BETWEEN case #Excel when 0 then (#StartRow - cast(#NumberOfRows AS int)) else #NumberOfRows end
AND case #Excel when 0 then (#StartRow - 1) else #PageIndex end
DECLARE #StartRow INT
SET #StartRow = ( cast(#PageIndex as int) * cast(#NumberOfRows as int) ) + 1 ;
WITH CTE
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY CASE
WHEN #SortColumnName = 'ID'
AND #SortOrderBy = 'asc'
THEN sod.ID
END ASC, CASE
WHEN #SortColumnName = 'ID'
AND #SortOrderBy = 'desc'
THEN sod.ID
END DESC, CASE
WHEN #SortColumnName = 'MessageText'
AND #SortOrderBy = 'asc'
THEN MessageText
END ASC, CASE
WHEN #SortColumnName = 'MessageText'
AND #SortOrderBy = 'desc'
THEN MessageText
END DESC, CASE
WHEN #SortColumnName = 'TO'
AND #SortOrderBy = 'desc'
THEN [TO]
END DESC
) AS RN ,
[SendedDate]
,[UserID]
,sod.[ID]
,[SmsOutboxID]
,[MessageID]
,[FolderID]
,[From]
,[TO]
,[MessageText]
,[SendedType]
,[SendedStatus]
,[IsDelete]
,[NumberOfMessage]
,[MessageType]
,[PricesbySMS]
,[Sended]
FROM SMS_Outbox so inner join SMS_OutboxDetails sod on so.ID = sod.SmsOutboxID where so.UserID = #UserID and sod.SendedType = #Type and sod.IsDelete = 0)
SELECT *
FROM CTE
WHERE RN BETWEEN case #Excel when 0 then (#StartRow - cast(#NumberOfRows AS int)) else #NumberOfRows end
AND case #Excel when 0 then (#StartRow - 1) else #PageIndex end
Write a query that will display the employee's name with the first letter capitalized and all other letters lowercase and department no. For all employees whose second character of their name is between ‘A’ and ‘M’, Give each column an appropriate label.
I tried something that did not work. My work code is:
SELECT INITCAP(ename), deptno
FROM emp
WHERE ename LIKE BETWEEN ('_A%' AND '_M%');