SQL CASE switch for parameter - sql

I have a parameter that can be 1 of 3 possible values 0, 2 or NULL (these are values for an enum 0 is Pending, 1 is UnderReview and 2 is Closed). If it is NULL it should retrieve all regardless of the Status but If it is 2 it should retrieve all the closed ones however if the value is 0 it should retrieve the ones that are pending or under review. This last one means that I want to retrieve records that have a status of either 0 or 1. I have the following code so far and tried with a CASE switch but it doesn't work.
SELECT * FROM PurchaseOrder
WHERE (CreationDate >= #StartDate AND CreationDate <= #EndDate)
AND (#POStatus IS NULL OR [Status] = #POStatus)
AND (#POStatus IS NULL OR [Status] = CASE #POStatus WHEN 0 THEN 1 END)
AND (#PurchaseOrderIdSearch IS NULL OR PurchaseOrderId LIKE #PurchaseOrderIdSearch)
AND EmployeeId = #EmployeeId
ORDER BY CreationDate DESC, PurchaseOrderId DESC
I solved it adding an if statement, I was looking for a simpler way to do it to avoid repetition, this is how it looks like now:
IF #POStatus = 0
BEGIN
SELECT * FROM PurchaseOrder
WHERE (CreationDate >= #StartDate AND CreationDate <= #EndDate)
AND (([Status] = #POStatus)
OR ([Status] = 1))
AND (#PurchaseOrderIdSearch IS NULL OR PurchaseOrderId LIKE #PurchaseOrderIdSearch)
AND EmployeeId = #EmployeeId
ORDER BY CreationDate DESC, PurchaseOrderId DESC
END
ELSE
BEGIN
SELECT * FROM PurchaseOrder
WHERE (CreationDate >= #StartDate AND CreationDate <= #EndDate)
AND (#POStatus IS NULL OR [Status] = #POStatus)
AND (#PurchaseOrderIdSearch IS NULL OR PurchaseOrderId LIKE #PurchaseOrderIdSearch)
AND EmployeeId = #EmployeeId
ORDER BY CreationDate DESC, PurchaseOrderId DESC
END

I suppose you're looking for something like
DECLARE #MyParam INT; --Try to set 0, 1 or 2
SELECT *
FROM
(
VALUES
(0),
(1),
(2)
) T(Value)
WHERE (Value = #MyParam) OR (#MyParam IS NULL);
Here is a db<>fiddle to see how it's working.
Update:
It seems like you're looking for
DECLARE #MyParam INT; --Try to set 0, 1 or 2
SELECT *
FROM
(
VALUES
(0),
(1),
(2)
) T(Value)
WHERE
(
CASE WHEN #MyParam = 0 OR #MyParam = 1 THEN 1 ELSE 0 END = 1
AND Value IN(0, 1)
)
OR
(Value = #MyParam AND #MyParam = 2)
OR
#MyParam IS NULL;
Here is a db<>fiddle

Related

SQL query to return 0 or 1 as one record from result of two queries

I have a RunHistory table, where process run result logs in. Process is either running thru a scheduler where it logs in RunInstance as Numeric, but if run manually, then it logs in 'MANUAL'
I need a query to check if the process ran today (GetDate()) regardless it was MANUAL or thru scheduler. If it ran, then query should return 1 else 0 as one single record. I have created temp table and UNION query to demo the issue.
create table #RunHistory
(
[RunId] [int] IDENTITY(1,1) NOT NULL,
[ReportDate] [date] NOT NULL,
[RunInstance] [varchar](6) NOT NULL,
[RunStartTime] [datetime] NOT NULL,
[RunEndTime] [datetime] NULL,
)
INSERT INTO #RunHistory
([ReportDate]
,[RunInstance]
,[RunStartTime]
,[RunEndTime])
VALUES
('2020-07-29'
,'1200'
,'2020-07-29 12:44:13.340'
,'2020-07-29 12:44:25.313')
INSERT INTO #RunHistory
([ReportDate]
,[RunInstance]
,[RunStartTime]
,[RunEndTime])
VALUES
('2020-07-29'
,'MANUAL'
,'2020-07-29 12:36:51.117'
,'2020-07-29 12:41:10.720')
--if both ran returing 1 then it works fine
SELECT RESULT
FROM
(
SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS RESULT
FROM #RunHistory
WHERE RunEndTime IS NOT NULL AND RunInstance IS NOT NULL
AND (ISNUMERIC(RunInstance) > 0)
AND CONVERT(TINYINT,LEFT(RunInstance,2)) >= 8
AND CONVERT(DATE,#RunHistory.ReportDate) = CONVERT(DATE,Getdate())
UNION
SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS RESULT
FROM #RunHistory
WHERE RunEndTime IS NOT NULL AND RunInstance IS NOT NULL
AND RunInstance ='MANUAL'
AND CONVERT(DATE,#RunHistory.ReportDate) = CONVERT(DATE,Getdate())
) as z
--if one of then returns 0 as result, then two records are returned. I just want if one of the records is 1, then return 1
SELECT RESULT
FROM
(
SELECT 0 AS RESULT
FROM #RunHistory
WHERE RunEndTime IS NOT NULL AND RunInstance IS NOT NULL
AND (ISNUMERIC(RunInstance) > 0)
AND CONVERT(TINYINT,LEFT(RunInstance,2)) >= 8
AND CONVERT(DATE,#RunHistory.ReportDate) = CONVERT(DATE,Getdate())
UNION
SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS RESULT
FROM #RunHistory
WHERE RunEndTime IS NOT NULL AND RunInstance IS NOT NULL
AND RunInstance ='MANUAL'
AND CONVERT(DATE,#RunHistory.ReportDate) = CONVERT(DATE,Getdate())
) as z
Don't know if I really need a UNION or somehow both conditions can be combined in one query OR an outer query is required.
You shouldn't need to restrict by RunInstance if it doesn't matter whether it was manual or not:
select case when count(1) > 0 then 1 else 0 end result
from #RunHistory
where RunEndTime is not null
and RunInstance is not null
and convert(date,ReportDate) = convert(date,getdate())
If you do need to union multiple queries, then you can get the maximum result:
select max(result)
from (
SELECT 0 AS RESULT
FROM #RunHistory
WHERE RunEndTime IS NOT NULL AND RunInstance IS NOT NULL
AND (ISNUMERIC(RunInstance) > 0)
AND CONVERT(TINYINT,LEFT(RunInstance,2)) >= 8
AND CONVERT(DATE,ReportDate) = CONVERT(DATE,Getdate())
UNION
SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS RESULT
FROM #RunHistory
WHERE RunEndTime IS NOT NULL AND RunInstance IS NOT NULL
AND RunInstance ='MANUAL'
AND CONVERT(DATE,ReportDate) = CONVERT(DATE,Getdate())
) as z

How to update table based on the values in other columns

Here is a sample below, I want to update AvailableAmt column based on the amount entered on UI.
Requirement
Update the value from the last row to the first row,
If entered 500 on UI, then the table will be like
If entered 1000 on UI, then the table will be like
Thank you for your help in advance !
Can't test it on a Sybase somewhere.
But in theory something like this might work:
DECLARE #Group VARCHAR(8) = 'a';
DECLARE #Amount INT = 1100;
UPDATE t
SET t.AvailableAmt =
CASE
WHEN q.PrevRemain > 0 AND t.AvailableAmt <= q.PrevRemain THEN 0
WHEN q.PrevRemain > 0 THEN t.AvailableAmt - q.PrevRemain
ELSE t.AvailableAmt
END
FROM YourTable t
JOIN
(
select [Group], [Row],
#Amount-(SUM(AvailableAmt) OVER (PARTITION BY [Group] ORDER BY AvailableAmt, [Row] desc) - AvailableAmt) as PrevRemain
from YourTable
where AvailableAmt > 0
and [Group] = #Group
) AS q
ON (q.[Group] = t.[Group] and q.[Row] = t.[Row]);
For a Sybase flavor that doesn't support the window function of SUM, something like this might work.
DECLARE #Group VARCHAR(8) = 'a';
DECLARE #Amount INT = 1200;
UPDATE t
SET t.AvailableAmt =
CASE
WHEN q.PrevRemain > 0 AND t.AvailableAmt <= q.PrevRemain THEN 0
WHEN q.PrevRemain > 0 THEN t.AvailableAmt - q.PrevRemain
ELSE t.AvailableAmt
END
FROM YourTable t
JOIN
(
select t1.[Group], t1.[Row],
#Amount - (SUM(t2.AvailableAmt)-t1.AvailableAmt) as PrevRemain
from YourTable t1
left join YourTable t2 on (t2.[Group] = t1.[Group] and t2.AvailableAmt <= t1.AvailableAmt and t2.[Row] >= t1.[Row])
where t1.AvailableAmt > 0
and t1.[Group] = #Group
group by t1.[Group], t1.[Row], t1.AvailableAmt
) AS q
ON (q.[Group] = t.[Group] and q.[Row] = t.[Row]);

SQL Server: is this a bug or do I have a misunderstanding?

Today I'm found a very sticky problem on SQL Server 2014.
Scenario: I want to pay awards to my customer (some pin code for cell phone operator)
In last cycle of loop T.Used = 0 condition is bypassed and is not working. I know in other conditions in that query (T.Cash < (#myAwards - #paid)) is there a mistake and I must to use T.Cash <= (#myAwards - #paid) instead of this but please focus on main question.
Why it's happened when I update Used flag to 1 (True) then in next loop it's selected while it doesn't have a valid condition (T.Used = 0)?
DECLARE #myAwards INT = 90000,
#paid INT = 0;
DECLARE #Temp TABLE
(
Id INT NOT NULL,
Pin VARCHAR(100) NOT NULL,
Cash INT NOT NULL,
[Weight] INT NULL,
Used BIT NOT NULL
)
INSERT INTO #Temp
SELECT
UPFI.Id, UPFI.PinCode,
PT.Cash, NULL, 0
FROM
dbo.UploadedPinFactorItem UPFI WITH (NOLOCK)
INNER JOIN
dbo.PinType PT WITH (NOLOCK) ON PT.ID = UPFI.PinTypeID
WHERE
PT.Cash <= #myAwards
UPDATE T
SET [Weight] = ISNULL((SELECT COUNT(TT.Id)
FROM #Temp TT
WHERE TT.Cash = T.Cash), 0) * T.Cash
FROM #Temp T
--For debug (first picture)
SELECT * FROM #Temp
DECLARE #i int = 1
DECLARE #count int = 0
SELECT #count = COUNT([Id]) FROM #Temp C WHERE C.Used = 0
WHILE (#i <= #count AND #paid < #myAwards)
BEGIN
DECLARE #nextId INT,
#nextCash INT,
#nextFlag BIT;
-- 'T.Used = 0' condition is by passed
SELECT TOP (1)
#nextId = T.Id, #nextCash = T.Cash, #nextFlag = T.Used
FROM
#Temp T
WHERE
T.Used = 0
AND T.Cash < (#myAwards - #paid)
ORDER BY
T.[Weight] DESC, T.Cash DESC, T.Id DESC
UPDATE #Temp
SET Used = 1
WHERE Id = #nextId
SET #i = #i + 1
SET #paid = #paid + #nextCash
--Show result in second picture
SELECT
#i AS 'i', #paid AS 'paid', #nextFlag AS 'flag', #nextId AS 'marked Id',*
FROM
#temp T
ORDER BY
T.[Weight] DESC, T.Cash DESC, T.Id DESC
END
SELECT 'final', #paid, *
FROM #temp T
ORDER BY T.[Weight] DESC, T.Cash DESC, T.Id DESC
Please let me to understand this is a bug or I have misunderstanding
First screenshot:
Second screenshot (result of loop):
Third screenshot (final result):
As per my comments:
This isn't a problem with the condition, the problem is with the implemented logic. After i = 4, there are no more rows where T.Used = 0 AND T.Cash < (#myAwards - #paid), that makes it so your reassigning variables gets zero rows, so they mantain the previous values.
You can test this behavior by doing:
DECLARE #A INT = 10;
SELECT #A = object_id
FROM sys.all_objects
WHERE name = 'an object that doesn''t exist'
SELECT #A;

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

SQL Server dynamic ORDER BY with mixed datatypes: is this a good idea?

I have stored procedure that returns results sorted dynamically. The parent folder (this is for content management) has a RankTypeID field that allows sorting by Rank (0), Start Date in ascending order (1), Start Date in descending order (2), and document title (3)
Rank is an integer, date is smalldatetime, and title is a nvarchar.
...
ORDER BY
Case Parent.RankTypeID
When 0 Then dbo.Folders.Rank
When 1 Then Cast(dbo.Documents.SortableDateStart As bigint)
When 2 Then (1 - Cast(dbo.Documents.SortableDateStart As bigint))
When 3 Then Cast(dbo.Documents.Title as sql_variant)
End
I set up the SortableDateStart as a computed column to take a DateStart smalldatetime column and convert it into a bigit for sorting. It takes an ISO8601 date (designed for xml usage, and also handy for sorting) and replaces the T, :, and -
(replace(replace(replace(CONVERT([varchar](16),[DateStart],(126)),'T',''),'-',''),':',''))
This is kind of ugly. Is there a better way to do this? I'm also open to better ways of handling this dynamic sorting.
Edit: Test Data Setup
DECLARE #Temp TABLE
(
[Rank] int,
[Title] nvarchar(100),
[DateStart] datetime
)
INSERT into #Temp
SELECT 1, 'title1', '1/1/2010 10:01:00AM'
UNION
SELECT 2, 'atitle1', '1/1/2010 10:03:00AM'
UNION
SELECT 3, 'title1', '1/1/2010 10:10:00AM'
UNION
SELECT 4, 'btitle1', '1/1/2010 10:04:00AM'
UNION
SELECT 10, 'title1', '1/1/2010 10:07:00AM'
UNION
SELECT 11, 'dtitle1', '1/1/2010 10:09:00AM'
UNION
SELECT 12, 'ctitle1', '1/1/2010 10:00:01AM'
UNION
SELECT 13, 'title1', '1/1/2010 10:10:00AM'
DECLARE #RankTypeID tinyint
--SET #RankTypeID = 0 -- rank
--SET #RankTypeID = 1 -- date start asc
SET #RankTypeID = 2 -- date start desc
--SET #RankTypeID = 3 -- title
SELECT
[Rank],
[DateStart],
[Title]
FROM
#Temp
ORDER BY
Case #RankTypeID
When 0 Then [Rank]
When 1 Then Cast([DateStart] As sql_variant)
When 3 Then [Title]
else null
End,
Case #RankTypeID
When 2 Then Cast([DateStart] As sql_variant)
End DESC
Try something like this
ORDER BY
Case Parent.RankTypeID
When 0 Then dbo.Folders.Rank
When 1 Then dbo.Documents.DateStart
When 3 Then Cast(dbo.Documents.Title as sql_variant)
else null
End,
case Parent.RankTypeID
when 2 Then dbo.Documents.DateStart
end desc
Update.
No, you don't need to cast anything. Here's a full solution for your test data.
order by
case #RankTypeID when 0 then [Rank] else null end,
case #RankTypeID when 1 then [DateStart] else null end,
case #RankTypeID when 2 then [DateStart] else null end desc,
case #RankTypeID when 3 then [Title] else null end
One way is to decouple the sort value and the actual ORDER BY
SELECT
col1, col2, ...
FROM
(
SELECT
col1, col2, ...,
ROW_NUMBER() OVER (ORDER BY Rank) AS RankASC,
ROW_NUMBER() OVER (ORDER BY DateStart) AS DateStartASC,
ROW_NUMBER() OVER (ORDER BY Title) AS TitleASC
FROM
MyTable
) foo
ORDER BY
Case foo.RankTypeID
When 0 Then foo.RankAsc
When 1 Then foo.DateStartAsc
When 2 Then -1 * foo.DateStartAsc
When 3 Then foo.TitleAsc
--else null needed?
End
If you want to define #SortOrder (or as a column) as 1 = ASC, -1 = DESC then you can do this
SELECT
col1, col2, ...
FROM
(
SELECT
col1, col2, ...,
ROW_NUMBER() OVER (ORDER BY Rank) AS RankOrder,
ROW_NUMBER() OVER (ORDER BY DateStart) AS DateStartOrder,
ROW_NUMBER() OVER (ORDER BY Title) AS TitleOrder
FROM
MyTable
) foo
ORDER BY
#SortOrder *
Case foo.RankTypeID
When 0 Then foo.RankOrder
When 1 Then foo.DateStartOrder
When 3 Then foo.TitleOrder
End
Another working complete solution sample is given below
--TEST DATA
DECLARE #MYTable TABLE (EmpID INT, EmpName VARCHAR(10) , JoinDate DATETIME)
INSERT INTO #MYTable VALUES (1,'E1','1/1/2001');
INSERT INTO #MYTable VALUES (2,'E2','2/2/2002');
INSERT INTO #MYTable VALUES (3,'E3','5/5/2001');
--INPUT Parameters
DECLARE #SortParam VARCHAR(MAX)
SET #SortParam = 'JoinDate'
DECLARE #SortDirection VARCHAR(MAX)
SET #SortDirection = 'DESC'
--#RankTypeID Variable
DECLARE #RankTypeID INT
--EMPNAME
IF (#SortParam = 'EmpName' AND #SortDirection = 'ASC')
BEGIN
SET #RankTypeID = 1
END
IF (#SortParam = 'EmpName' AND #SortDirection = 'DESC')
BEGIN
SET #RankTypeID = -1
END
--EmpID
IF (#SortParam = 'EmpID' AND #SortDirection = 'ASC')
BEGIN
SET #RankTypeID = 2
END
IF (#SortParam = 'EmpID' AND #SortDirection = 'DESC')
BEGIN
SET #RankTypeID = -2
END
--JoinDate
IF (#SortParam = 'JoinDate' AND #SortDirection = 'ASC')
BEGIN
SET #RankTypeID = 3
END
IF (#SortParam = 'JoinDate' AND #SortDirection = 'DESC')
BEGIN
SET #RankTypeID = -3
END
-- SELECT
SELECT *
FROM #MYTable M
ORDER BY
CASE #RankTypeID WHEN 1 then EmpName ELSE null end ASC,
CASE #RankTypeID WHEN -1 then EmpName ELSE null end DESC,
CASE #RankTypeID WHEN 2 then [EmpID] else null end ASC ,
CASE #RankTypeID WHEN -2 then [EmpID] else null end DESC ,
CASE #RankTypeID WHEN 3 then JoinDate else null end ASC,
CASE #RankTypeID WHEN -3 then JoinDate else null end DESC
--END