I have the following function and I need to take out the SELECT part and create a separate view.
CREATE FUNCTION dbo.dbf_get_penalty_points
( #pn_seq_party_id NUMERIC(18),
#pv_penalty_points_code CHAR(1) = 'Y') -- Use 'N' for total points, otherwise will return Current Penalty Points
RETURNS NUMERIC(18,0)
AS
BEGIN
DECLARE #n_penalty_points NUMERIC(18),
#d_latest_points_date DATETIME
SELECT #d_latest_points_date = dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
SELECT #n_penalty_points = IsNull(Sum(penalty_points_amount),0)
FROM dbo.ar_penalty_point WITH(NOLOCK)
WHERE seq_party_id = #pn_seq_party_id
AND 1 = CASE
WHEN #pv_penalty_points_code = 'N' THEN 1
WHEN #pv_penalty_points_code = 'Y' AND added_date >= #d_latest_points_date AND reset_date IS NULL THEN 1
ELSE 0
END
RETURN #n_penalty_points
END
GO
SET QUOTED_IDENTIFIER OFF
GO
GRANT EXECUTE ON dbo.dbf_get_penalty_points TO standard
GO
I have tried and got this,
SELECT SUM(CASE WHEN added_date >=dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
AND reset_date IS NULL THEN 1
ELSE 0) current_points,
IsNull(Sum(penalty_points_amount),0) total_points,
seq_party_id
FROM dbo.ar_penalty_point WITH(NOLOCK)
GROUP BY seq_party_id
Now I need to get rid of
dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
From the SELECT part of the query. I am struck is there a better way to write my view ?
EDIT
The objective is to create a view that returns total_points and current_points. For better understanding refer the CREATE part following
CREATE FUNCTION dbo.dbf_get_penalty_points
( #pn_seq_party_id NUMERIC(18),
#pv_penalty_points_code CHAR(1) = 'Y') -- Use 'N' for total points, otherwise will return Current Penalty Points
Refer to -- Use 'N' for total points, otherwise will return Current Penalty Points in the comments
This is what I came up with
SELECT SUM(CASE WHEN (t.added_date >= t.target_date
AND t.reset_date IS NULL) THEN 1
ELSE 0 END) current_points,
IsNull(Sum(t.penalty_points_amount),0) total_points,
t.seq_party_id
FROM (
SELECT dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate())) as target_date,
u.reset_date, u.penalty_points_amount,u.seq_party_id,u.added_date FROM
dbo.ar_penalty_point as u ) as t GROUP BY t.seq_party_id
I want to write a query like this:
SELECT o.OrderId, MAX(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o
But this isn't how the MAX function works, right? It is an aggregate function so it expects a single parameter and then returns the MAX of all rows.
Does anyone know how to do it my way?
If you're using SQL Server 2008 (or above), then this is the better solution:
SELECT o.OrderId,
(SELECT MAX(Price)
FROM (VALUES (o.NegotiatedPrice),(o.SuggestedPrice)) AS AllPrices(Price))
FROM Order o
All credit and votes should go to Sven's answer to a related question, "SQL MAX of multiple columns?"
I say it's the "best answer" because:
It doesn't require complicating your code with UNION's, PIVOT's,
UNPIVOT's, UDF's, and crazy-long CASE statments.
It isn't plagued with the problem of handling nulls, it handles them just fine.
It's easy to swap out the "MAX" with "MIN", "AVG", or "SUM". You can use any aggregate function to find the aggregate over many different columns.
You're not limited to the names I used (i.e. "AllPrices" and "Price"). You can pick your own names to make it easier to read and understand for the next guy.
You can find multiple aggregates using SQL Server 2008's derived_tables like so: SELECT MAX(a), MAX(b) FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10) ) AS MyTable(a, b)
Can be done in one line:
-- the following expression calculates ==> max(#val1, #val2)
SELECT 0.5 * ((#val1 + #val2) + ABS(#val1 - #val2))
Edit: If you're dealing with very large numbers you'll have to convert the value variables into bigint in order to avoid an integer overflow.
You'd need to make a User-Defined Function if you wanted to have syntax similar to your example, but could you do what you want to do, inline, fairly easily with a CASE statement, as the others have said.
The UDF could be something like this:
create function dbo.InlineMax(#val1 int, #val2 int)
returns int
as
begin
if #val1 > #val2
return #val1
return isnull(#val2,#val1)
end
... and you would call it like so ...
SELECT o.OrderId, dbo.InlineMax(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o
I don't think so. I wanted this the other day. The closest I got was:
SELECT
o.OrderId,
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN o.NegotiatedPrice
ELSE o.SuggestedPrice
END
FROM Order o
Why not try IIF function (requires SQL Server 2012 and later)
IIF(a>b, a, b)
That's it.
(Extra hint: be careful about either a or b is null, as in this case the result of a>b will be false. So b will be the return result if either is null) (Also by system design, column null is not a good practice)
DECLARE #MAX INT
#MAX = (SELECT MAX(VALUE)
FROM (SELECT 1 AS VALUE UNION
SELECT 2 AS VALUE) AS T1)
In SQL Server 2012 or higher, you can use a combination of IIF and ISNULL (or COALESCE) to get the maximum of 2 values.
Even when 1 of them is NULL.
IIF(col1 >= col2, col1, ISNULL(col2, col1))
Or if you want it to return 0 when both are NULL
IIF(col1 >= col2, col1, COALESCE(col2, col1, 0))
Example snippet:
-- use table variable for testing purposes
declare #Order table
(
OrderId int primary key identity(1,1),
NegotiatedPrice decimal(10,2),
SuggestedPrice decimal(10,2)
);
-- Sample data
insert into #Order (NegotiatedPrice, SuggestedPrice) values
(0, 1),
(2, 1),
(3, null),
(null, 4);
-- Query
SELECT
o.OrderId, o.NegotiatedPrice, o.SuggestedPrice,
IIF(o.NegotiatedPrice >= o.SuggestedPrice, o.NegotiatedPrice, ISNULL(o.SuggestedPrice, o.NegotiatedPrice)) AS MaxPrice
FROM #Order o
Result:
OrderId NegotiatedPrice SuggestedPrice MaxPrice
1 0,00 1,00 1,00
2 2,00 1,00 2,00
3 3,00 NULL 3,00
4 NULL 4,00 4,00
But if one needs the maximum of multiple columns?
Then I suggest a CROSS APPLY on an aggregation of the VALUES.
Example:
SELECT t.*
, ca.[Maximum]
, ca.[Minimum], ca.[Total], ca.[Average]
FROM SomeTable t
CROSS APPLY (
SELECT
MAX(v.col) AS [Maximum],
MIN(v.col) AS [Minimum],
SUM(v.col) AS [Total],
AVG(v.col) AS [Average]
FROM (VALUES (t.Col1), (t.Col2), (t.Col3), (t.Col4)) v(col)
) ca
This has the extra benefit that this can calculate other things at the same time.
Try this. It can handle more than 2 values
SELECT Max(v) FROM (VALUES (1), (2), (3)) AS value(v)
The other answers are good, but if you have to worry about having NULL values, you may want this variant:
SELECT o.OrderId,
CASE WHEN ISNULL(o.NegotiatedPrice, o.SuggestedPrice) > ISNULL(o.SuggestedPrice, o.NegotiatedPrice)
THEN ISNULL(o.NegotiatedPrice, o.SuggestedPrice)
ELSE ISNULL(o.SuggestedPrice, o.NegotiatedPrice)
END
FROM Order o
Sub Queries can access the columns from the Outer query so you can use this approach to use aggregates such as MAX across columns. (Probably more useful when there is a greater number of columns involved though)
;WITH [Order] AS
(
SELECT 1 AS OrderId, 100 AS NegotiatedPrice, 110 AS SuggestedPrice UNION ALL
SELECT 2 AS OrderId, 1000 AS NegotiatedPrice, 50 AS SuggestedPrice
)
SELECT
o.OrderId,
(SELECT MAX(price)FROM
(SELECT o.NegotiatedPrice AS price
UNION ALL SELECT o.SuggestedPrice) d)
AS MaxPrice
FROM [Order] o
YES, THERE IS.
T-SQL (SQL Server 2022 (16.x)) now supports GREATEST/LEAST functions:
MAX/MIN as NON-aggregate function
This is now live for Azure SQL Database and SQL Managed Instance. It will roll into the next version of SQL Server.
Logical Functions - GREATEST (Transact-SQL)
This function returns the maximum value from a list of one or more expressions.
GREATEST ( expression1 [ ,...expressionN ] )
So in this case:
SELECT o.OrderId, GREATEST(o.NegotiatedPrice, o.SuggestedPrice)
FROM [Order] o;
db<>fiddle demo
SELECT o.OrderId,
--MAX(o.NegotiatedPrice, o.SuggestedPrice)
(SELECT MAX(v) FROM (VALUES (o.NegotiatedPrice), (o.SuggestedPrice)) AS value(v)) as ChoosenPrice
FROM Order o
I would go with the solution provided by kcrumley
Just modify it slightly to handle NULLs
create function dbo.HigherArgumentOrNull(#val1 int, #val2 int)
returns int
as
begin
if #val1 >= #val2
return #val1
if #val1 < #val2
return #val2
return NULL
end
EDIT
Modified after comment from Mark. As he correctly pointed out in 3 valued logic x > NULL or x < NULL should always return NULL. In other words unknown result.
SQL Server 2012 introduced IIF:
SELECT
o.OrderId,
IIF( ISNULL( o.NegotiatedPrice, 0 ) > ISNULL( o.SuggestedPrice, 0 ),
o.NegotiatedPrice,
o.SuggestedPrice
)
FROM
Order o
Handling NULLs is recommended when using IIF, because a NULL on either side of your boolean_expression will cause IIF to return the false_value (as opposed to NULL).
I probably wouldn't do it this way, as it's less efficient than the already mentioned CASE constructs - unless, perhaps, you had covering indexes for both queries. Either way, it's a useful technique for similar problems:
SELECT OrderId, MAX(Price) as Price FROM (
SELECT o.OrderId, o.NegotiatedPrice as Price FROM Order o
UNION ALL
SELECT o.OrderId, o.SuggestedPrice as Price FROM Order o
) as A
GROUP BY OrderId
Oops, I just posted a dupe of this question...
The answer is, there is no built in function like Oracle's Greatest, but you can achieve a similar result for 2 columns with a UDF, note, the use of sql_variant is quite important here.
create table #t (a int, b int)
insert #t
select 1,2 union all
select 3,4 union all
select 5,2
-- option 1 - A case statement
select case when a > b then a else b end
from #t
-- option 2 - A union statement
select a from #t where a >= b
union all
select b from #t where b > a
-- option 3 - A udf
create function dbo.GREATEST
(
#a as sql_variant,
#b as sql_variant
)
returns sql_variant
begin
declare #max sql_variant
if #a is null or #b is null return null
if #b > #a return #b
return #a
end
select dbo.GREATEST(a,b)
from #t
kristof
Posted this answer:
create table #t (id int IDENTITY(1,1), a int, b int)
insert #t
select 1,2 union all
select 3,4 union all
select 5,2
select id, max(val)
from #t
unpivot (val for col in (a, b)) as unpvt
group by id
Its as simple as this:
CREATE FUNCTION InlineMax
(
#p1 sql_variant,
#p2 sql_variant
) RETURNS sql_variant
AS
BEGIN
RETURN CASE
WHEN #p1 IS NULL AND #p2 IS NOT NULL THEN #p2
WHEN #p2 IS NULL AND #p1 IS NOT NULL THEN #p1
WHEN #p1 > #p2 THEN #p1
ELSE #p2 END
END;
You can do something like this:
select case when o.NegotiatedPrice > o.SuggestedPrice
then o.NegotiatedPrice
else o.SuggestedPrice
end
SELECT o.OrderID
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN
o.NegotiatedPrice
ELSE
o.SuggestedPrice
END AS Price
For the answer above regarding large numbers, you could do the multiplication before the addition/subtraction. It's a bit bulkier but requires no cast. (I can't speak for speed but I assume it's still pretty quick)
SELECT 0.5 * ((#val1 + #val2) +
ABS(#val1 - #val2))
Changes to
SELECT #val1*0.5+#val2*0.5 +
ABS(#val1*0.5 - #val2*0.5)
at least an alternative if you want to avoid casting.
Here's a case example that should handle nulls and will work with older versions of MSSQL. This is based on the inline function in one one of the popular examples:
case
when a >= b then a
else isnull(b,a)
end
-- Simple way without "functions" or "IF" or "CASE"
-- Query to select maximum value
SELECT o.OrderId
,(SELECT MAX(v)
FROM (VALUES (o.NegotiatedPrice), (o.SuggestedPrice)) AS value(v)) AS MaxValue
FROM Order o;
CREATE FUNCTION [dbo].[fnMax] (#p1 INT, #p2 INT)
RETURNS INT
AS BEGIN
DECLARE #Result INT
SET #p2 = COALESCE(#p2, #p1)
SELECT
#Result = (
SELECT
CASE WHEN #p1 > #p2 THEN #p1
ELSE #p2
END
)
RETURN #Result
END
Here is #Scott Langham's answer with simple NULL handling:
SELECT
o.OrderId,
CASE WHEN (o.NegotiatedPrice > o.SuggestedPrice OR o.SuggestedPrice IS NULL)
THEN o.NegotiatedPrice
ELSE o.SuggestedPrice
END As MaxPrice
FROM Order o
Here is an IIF version with NULL handling (based on of Xin's answer):
IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a > b, a, b))
The logic is as follows, if either of the values is NULL, return the one that isn't NULL (if both are NULL, a NULL is returned). Otherwise return the greater one.
Same can be done for MIN.
IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a < b, a, b))
select OrderId, (
select max([Price]) from (
select NegotiatedPrice [Price]
union all
select SuggestedPrice
) p
) from [Order]
In its simplest form...
CREATE FUNCTION fnGreatestInt (#Int1 int, #Int2 int )
RETURNS int
AS
BEGIN
IF #Int1 >= ISNULL(#Int2,#Int1)
RETURN #Int1
ELSE
RETURN #Int2
RETURN NULL --Never Hit
END
For SQL Server 2012:
SELECT
o.OrderId,
IIF( o.NegotiatedPrice >= o.SuggestedPrice,
o.NegotiatedPrice,
ISNULL(o.SuggestedPrice, o.NegiatedPrice)
)
FROM
Order o
Expanding on Xin's answer and assuming the comparison value type is INT, this approach works too:
SELECT IIF(ISNULL(#A, -2147483648) > ISNULL(#B, -2147483648), #A, #B)
This is a full test with example values:
DECLARE #A AS INT
DECLARE #B AS INT
SELECT #A = 2, #B = 1
SELECT IIF(ISNULL(#A, -2147483648) > ISNULL(#B, -2147483648), #A, #B)
-- 2
SELECT #A = 2, #B = 3
SELECT IIF(ISNULL(#A, -2147483648) > ISNULL(#B, -2147483648), #A, #B)
-- 3
SELECT #A = 2, #B = NULL
SELECT IIF(ISNULL(#A, -2147483648) > ISNULL(#B, -2147483648), #A, #B)
-- 2
SELECT #A = NULL, #B = 1
SELECT IIF(ISNULL(#A, -2147483648) > ISNULL(#B, -2147483648), #A, #B)
-- 1
In MemSQL do the following:
-- DROP FUNCTION IF EXISTS InlineMax;
DELIMITER //
CREATE FUNCTION InlineMax(val1 INT, val2 INT) RETURNS INT AS
DECLARE
val3 INT = 0;
BEGIN
IF val1 > val2 THEN
RETURN val1;
ELSE
RETURN val2;
END IF;
END //
DELIMITER ;
SELECT InlineMax(1,2) as test;