Suppose I have 2 tables
1. Artical(ID,Description,PubDate)
2. ArticalMedia(ID,ArticalID,MediaURL)
Now I want to fetch 2 tables within stored procedure.
Table1: Top 5 Latest news
Table2: All Media's of Top 5 news selected in Table1
I know we can achieve this using #Temp tables. I this only & best way? Or do we have any other method to achieve same thing?
Simple 2 select statements might lead to wrong data, plesae see following example:
select top 5 * from Artical order by PubDate desc
retuns Artical's : 5,4,3,2,1
select * from ArticalMedia where ArticalID in (select top 5 ID from Artical order by PubDate desc)
can return Medias of 6,5,4,3,2. cause new Artical might be inserted in database, after first select & before second select.
Get the TOP 5 records and then join them to the ArticleMedia table:
SELECT *
FROM
(
SELECT TOP 5 ID,Description,PubDate
FROM Artical
ORDER BY PubDate DESC
) DS
INNER JOIN ArticleMedia AM
ON DS.[Id] = AM.[id]
Try this optimized query with light weight execution plan:
SELECT A.*,AM.*
FROM ArticalMedia AS AM INNER JOIN
Article AS A ON AM.ArticleID = A.ID
WHERE (AM.ArticleID IN
(SELECT TOP (5) ID
FROM Article
ORDER BY PubDate DESC))
ORDER BY A.PubDate DESC
Edit 2
Create Table valued function in SQL fn_split:
CREATE FUNCTION [dbo].[fn_Split](#sText varchar(8000), #sDelim varchar(20) = ' ')
RETURNS #retArray TABLE (idx smallint Primary Key, value varchar(8000))
AS
BEGIN
DECLARE #idx smallint,
#value varchar(8000),
#bcontinue bit,
#iStrike smallint,
#iDelimlength tinyint
IF #sDelim = 'Space'
BEGIN
SET #sDelim = ' '
END
SET #idx = 0
SET #sText = LTrim(RTrim(#sText))
SET #iDelimlength = DATALENGTH(#sDelim)
SET #bcontinue = 1
IF NOT ((#iDelimlength = 0) or (#sDelim = 'Empty'))
BEGIN
WHILE #bcontinue = 1
BEGIN
--If you can find the delimiter in the text, retrieve the first element and
--insert it with its index into the return table.
IF CHARINDEX(#sDelim, #sText)>0
BEGIN
SET #value = SUBSTRING(#sText,1, CHARINDEX(#sDelim,#sText)-1)
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
--Trim the element and its delimiter from the front of the string.
--Increment the index and loop.
SET #iStrike = DATALENGTH(#value) + #iDelimlength
SET #idx = #idx + 1
SET #sText = LTrim(Right(#sText,DATALENGTH(#sText) - #iStrike))
END
ELSE
BEGIN
--If you can’t find the delimiter in the text, #sText is the last value in
--#retArray.
SET #value = #sText
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
--Exit the WHILE loop.
SET #bcontinue = 0
END
END
END
ELSE
BEGIN
WHILE #bcontinue=1
BEGIN
--If the delimiter is an empty string, check for remaining text
--instead of a delimiter. Insert the first character into the
--retArray table. Trim the character from the front of the string.
--Increment the index and loop.
IF DATALENGTH(#sText)>1
BEGIN
SET #value = SUBSTRING(#sText,1,1)
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
SET #idx = #idx+1
SET #sText = SUBSTRING(#sText,2,DATALENGTH(#sText)-1)
END
ELSE
BEGIN
--One character remains.
--Insert the character, and exit the WHILE loop.
INSERT #retArray (idx, value)
VALUES (#idx, #sText)
SET #bcontinue = 0
END
END
END
RETURN
END
Then the query will be:
DECLARE #tmp nvarchar(max);
select #tmp = stuff((select top 5 ', ', cast(id as nvarchar(max))
FROM Article
ORDER BY PubDate DESC
for xml path ('')
), 1, 2, '');
SELECT A.*,AM.*
FROM ArticalMedia AS AM INNER JOIN
Article AS A ON AM.ArticleID = A.ID
WHERE (AM.ArticleID IN (select value from dbo.fn_split(#tmp,',')))
ORDER BY A.PubDate DESC
Use a CTE to keep it simple:
with Top5Artical as (
select top (5) Artical.ID as ArticleID
from Artical
order by Artical.PubDate desc
),
insert into #Table1
select Top5.ArticalID,
Art.Description,
Art.PubDate
from Top5Artical as Top5
inner join Artical as Art
on Top5.ArticalID = Art.ID
order by Top5.PubDate desc;
insert into #Table2
select Top5.ArticalID,
ArtMedia.ID,
ArtMedia.URL
from Top5Artical as Top5
inner join ArticalMedia as ArtMedia
on Top5.ArticalID = ArtMedia.ArticalID
order by Top5.PubDate desc;
select * from #Table1;
select * from #Table2;
I am using SQL Server, the column is a VARCHAR(50) and I want to sort it like this:
1A
1B
2
2
3
4A
4B
4C
5A
5B
5C
5N
14 Draft
21
22A
22B
23A
23B
23C
23D
23E
25
26
FR01584
MISC
What I have so far is:
Select *
From viewASD
ORDER BY
Case When IsNumeric(LEFT(asdNumNew,1)) = 1
Then CASE When IsNumeric(asdNumNew) = 1
Then Right(Replicate('0',20) + asdNumNew + '0', 20)
Else Right(Replicate('0',20) + asdNumNew, 20)
END
When IsNumeric(LEFT(asdNumNew,1)) = 0
Then Left(asdNumNew + Replicate('',21), 20)
End
But this SQL statement puts '14 Draft' right after '26'.
Could someone help? Thanks
Your WHERE statement is... oddly complex.
It looks like you want to sort by any leading numeric digits in integer order, and then sort by the remainder. If so, you should do that as separate clauses, rather than trying to do it all in one. The specific issue you're having is that you're only allowing for a single-digit number, instead of two or more. (And there's No such thing as two.)
Here's your fix, along with a SQLFiddle, using two separate calculated columns tests for your ORDER BY. (Note that this assumes the numeric portion of asdNumNew will fit in a T-SQL int. If not, you'll need to adjust the CAST and the maximum value on the first ELSE.)
SELECT * FROM viewASD
ORDER BY
CASE
WHEN ISNUMERIC(asdNumNew)=1
THEN CAST(asdNumNew as int)
WHEN PATINDEX('%[^0-9]%',asdNumNew) > 1
THEN CAST(
LEFT(
asdNumNew,
PATINDEX('%[^0-9]%',asdNumNew) - 1
) as int)
ELSE 2147483648
END,
CASE
WHEN ISNUMERIC(asdNumNew)=1
THEN NULL
WHEN PATINDEX('%[^0-9]%',asdNumNew) > 1
THEN SUBSTRING(
asdNumNew,
PATINDEX('%[^0-9]%',asdNumNew) ,
50
)
ELSE asdNumNew
END
If all numbers within the string are reasonably small, say not exceeding 10 digits,
you may expand all the numbers in the string to be exactly 10 digits:
123A -> 0000000123A
S4 -> S0000000004
A3B89 -> A0000000003B0000000089
and so on and then sort them
-- Expand all numbers within S by zeros to be MaxLen
create function [dbo].ExpandNumbers(#S VarChar(4000), #maxlen integer) returns VarChar(4000)
as
begin
declare #result VarChar(4000);
declare #buffer VarChar(4000);
declare #Ch Char;
declare #i integer;
set #buffer = '';
set #result = '';
set #i = 1;
while (#i <= len(#S))
begin
set #Ch = substring(#S, #i, 1);
if ((#Ch >= '0') and (#Ch <= '9'))
set #buffer = #buffer + #Ch
else
begin
if (len(#buffer) > 0)
set #result = #result + right(replicate('0', #maxlen) + #buffer, #maxlen);
set #buffer = '';
set #result = #result + #Ch;
end;
set #i = #i + 1;
end;
if (len(#buffer) > 0)
set #result = #result + right(replicate('0', #maxlen) + #buffer, #maxlen);
return #result;
end;
-- Final query is
select *
from viewASD
order by [dbo].ExpandNumbers(asdNumNew)
I had something similar, but with the possibility of dashes as leading characters as well as trailing spaces. This code worked for me.
SELECT
my_column,
PATINDEX('%[^0-9]%',my_column) AS first_alpha_position,
CONVERT(INT,
CASE
WHEN PATINDEX('%[^0-9]%',my_column) = 0 OR PATINDEX('-%',my_column) = 1
THEN ABS(my_column)
ELSE SUBSTRING(my_column,1,PATINDEX('%[^0-9]%',my_column) -1)
END) AS numeric_value,
LTRIM(
SUBSTRING(my_column,PATINDEX('%[^0-9]%',my_column),LEN(my_column)-PATINDEX('%[^0-9]%',my_column)+1)
) AS alpha_chars
FROM my_table
ORDER BY numeric_value,alpha_chars
TRY THIS
DECLARE #t table (Number nvarchar(20))
INSERT INTO #t
SELECT 'L010'
UNION ALL SELECT 'L011'
UNION ALL SELECT 'L011'
UNION ALL SELECT 'L001'
UNION ALL SELECT 'L012'
UNION ALL SELECT '18'
UNION ALL SELECT '8'
UNION ALL SELECT '17'
UNION ALL SELECT 'B004'
UNION ALL SELECT 'B006'
UNION ALL SELECT 'B008'
UNION ALL SELECT 'B018'
UNION ALL SELECT 'UG001'
UNION ALL SELECT 'UG011'
UNION ALL SELECT 'G001'
UNION ALL SELECT 'G002'
UNION ALL SELECT 'G011';
SELECT Number
FROM #t
ORDER BY
CAST
(
SUBSTRING
(
Number
, 1
, CASE
WHEN patindex('%[^0-9]%',Number) > 0 THEN patindex('%[^0-9]%',Number) - 1
ELSE LEN(Number) END
) AS int
)
, Number
What worked for me is I split up the numeric and the alpha parts and then sorted based on the Alpha, then the Numeric:
CREATE FUNCTION [admin].[GetUnitNumberAsIntFunc](#UnitNumber varchar(20))
RETURNS int
BEGIN
DECLARE #intPosition int
SET #intPosition = PATINDEX('%[^0-9]%', #UnitNumber)
WHILE #intNumber > 0
BEGIN
SET #UnitNumber = STUFF(#UnitNumber, #intNumber, 1, '')
SET #intPosition = PATINDEX('%[^0-9]%', #UnitNumber)
END
RETURN ISNULL(#UnitNumber,9999)
END;
CREATE FUNCTION [admin].[GetUnitNumberAsStrFunc](#UnitNumber varchar(20))
RETURNS varchar(20)
BEGIN
DECLARE #intPosition int
SET #intPosition = PATINDEX('%[0-9]%', #UnitNumber)
SET #UnitNumber = STUFF(#UnitNumber, #intPosition, 6, '')
RETURN ISNULL(#UnitNumber,9999)
END;
When trying to compare software versions 5.12 to 5.8, version 5.12 is newer, however mathematically 5.12 is less than 5.8. How would I compare the two versions so that a newer version returns 'Y'?
SELECT CASE WHEN 5.12 > 5.8 THEN 'Y' ELSE 'N' END
Possible Solutions
Add a 0 after the decimal in 5.8 so that it compares 5.08 to 5.12, however it seems like this would require a bit of code.
Simply compare values after the decimal (ie. 12 > 8), however this fails when the version rolls to 6.0.
Use reverse logic and assume that if 5.12 is less than 5.8 to return 'Y'. I believe this would fail when the version rolls to 6.0.
You could use hierarchyid
Which you can use by putting a / at the end and start of the string and casting it
e.g.
SELECT CASE WHEN cast('/5.12/' as hierarchyid) > cast('/5.8/' as hierarchyid) THEN 'Y' ELSE 'N' END
That returns a Y
declare #v1 varchar(100) = '5.12'
declare #v2 varchar(100) = '5.8'
select
case
when CONVERT(int, LEFT(#v1, CHARINDEX('.', #v1)-1)) < CONVERT(int, LEFT(#v2, CHARINDEX('.', #v2)-1)) then 'v2 is newer'
when CONVERT(int, LEFT(#v1, CHARINDEX('.', #v1)-1)) > CONVERT(int, LEFT(#v2, CHARINDEX('.', #v2)-1)) then 'v1 is newer'
when CONVERT(int, substring(#v1, CHARINDEX('.', #v1)+1, LEN(#v1))) < CONVERT(int, substring(#v2, CHARINDEX('.', #v2)+1, LEN(#v1))) then 'v2 is newer'
when CONVERT(int, substring(#v1, CHARINDEX('.', #v1)+1, LEN(#v1))) > CONVERT(int, substring(#v2, CHARINDEX('.', #v2)+1, LEN(#v1))) then 'v1 is newer'
else 'same!'
end
There was a very good solution from a duplicate question here:
How to compare SQL strings that hold version numbers like .NET System.Version class?
After playing with the query for a while, I learned that it was not able to compare the last part when there are 4 or more parts (say, if the version number was 1.2.3.4, it would always treat the last one as 0). I have fixed that issue as well as came up with another function to compare two version numbers.
CREATE Function [dbo].[VersionNthPart](#version as nvarchar(max), #part as int) returns int as
Begin
Declare
#ret as int = null,
#start as int = 1,
#end as int = 0,
#partsFound as int = 0,
#terminate as bit = 0
if #version is not null
Begin
Set #ret = 0
while #partsFound < #part
Begin
Set #end = charindex('.', #version, #start)
If #end = 0 -- did not find the dot. Either it was last part or the part was missing.
begin
if #part - #partsFound > 1 -- also this isn't the last part so it must bail early.
begin
set #terminate = 1
end
Set #partsFound = #part
SET #end = len(#version) + 1; -- get the full length so that it can grab the whole of the final part.
end
else
begin
SET #partsFound = #partsFound + 1
end
If #partsFound = #part and #terminate = 0
begin
Set #ret = Convert(int, substring(#version, #start, #end - #start))
end
Else
begin
Set #start = #end + 1
end
End
End
return #ret
End
GO
CREATE FUNCTION [dbo].[CompareVersionNumbers]
(
#Source nvarchar(max),
#Target nvarchar(max),
#Parts int = 4
)
RETURNS INT
AS
BEGIN
/*
-1 : target has higher version number (later version)
0 : same
1 : source has higher version number (later version)
*/
DECLARE #ReturnValue as int = 0;
DECLARE #PartIndex as int = 1;
DECLARE #SourcePartValue as int = 0;
DECLARE #TargetPartValue as int = 0;
WHILE (#PartIndex <= #Parts AND #ReturnValue = 0)
BEGIN
SET #SourcePartValue = [dbo].[VersionNthPart](#Source, #PartIndex);
SET #TargetPartValue = [dbo].[VersionNthPart](#Target, #PartIndex);
IF #SourcePartValue > #TargetPartValue
SET #ReturnValue = 1
ELSE IF #SourcePartValue < #TargetPartValue
SET #ReturnValue = -1
SET #PartIndex = #PartIndex + 1;
END
RETURN #ReturnValue
END
Usage/Test case:
declare #Source as nvarchar(100) = '4.9.21.018'
declare #Target as nvarchar(100) = '4.9.21.180'
SELECT [dbo].[CompareVersionNumbers](#Source, #Target, DEFAULT) -- default version parts are 4
SET #Source = '1.0.4.1'
SET #Target = '1.0.1.8'
SELECT [dbo].[CompareVersionNumbers](#Source, #Target, 4) -- typing out # of version parts also works
SELECT [dbo].[CompareVersionNumbers](#Source, #Target, 2) -- comparing only 2 parts should be the same
SET #Target = '1.0.4.1.5'
SELECT [dbo].[CompareVersionNumbers](#Source, #Target, 4) -- only comparing up to parts 4 so they are the same
SELECT [dbo].[CompareVersionNumbers](#Source, #Target, 5) -- now comparing 5th part which should indicate that the target has higher version number
I recommend to create a SQL CLR function:
public partial class UserDefinedFunctions
{
[SqlFunction(Name = "CompareVersion")]
public static bool CompareVersion(SqlString x, SqlString y)
{
return Version.Parse(x) > Version.Parse(y);
}
}
Notes:
SqlString has explicit cast to string.
Pass full version string as of a.b.c.d
I encountered this when trying to filter SQL rows based on semantic versioning. My solution was a bit different, in that I wanted to store configuration rows tagged with a semantic version number and then select rows compatible with a running version of our software.
Assumptions:
My software will include a configuration setting containing the current version number
Data-driven configuration rows will include a min version number
I need to be able to select configuration rows where min <= current.
Examples:
Version 1.0.0 should include: 1.0.0, 1.0.0-*, 1.0.0-beta.1
Version 1.0.0 should exclude: 1.0.1, 1.1.0, 2.0.0
Version 1.1.0-beta.2 should include: 1.0.0, 1.0.1, 1.1.0-beta.1, 1.1.0-beta.2
Version 1.1.0-beta.2 should exclude: 1.1.0, 1.1.1, 1.2.0, 2.0.0, 1.1.1-beta.1
The MSSQL UDF is:
CREATE FUNCTION [dbo].[SemanticVersion] (
#Version nvarchar(50)
)
RETURNS nvarchar(255)
AS
BEGIN
DECLARE #hyphen int = CHARINDEX('-', #version)
SET #Version = REPLACE(#Version, '*', ' ')
DECLARE
#left nvarchar(50) = CASE #hyphen WHEN 0 THEN #version ELSE SUBSTRING(#version, 1, #hyphen-1) END,
#right nvarchar(50) = CASE #hyphen WHEN 0 THEN NULL ELSE SUBSTRING(#version, #hyphen+1, 50) END,
#normalized nvarchar(255) = '',
#buffer int = 8
WHILE CHARINDEX('.', #left) > 0 BEGIN
SET #normalized = #normalized + CASE ISNUMERIC(LEFT(#left, CHARINDEX('.', #left)-1))
WHEN 0 THEN LEFT(#left, CHARINDEX('.', #left)-1)
WHEN 1 THEN REPLACE(STR(LEFT(#left, CHARINDEX('.', #left)-1), #buffer), SPACE(1), '0')
END + '.'
SET #left = SUBSTRING(#left, CHARINDEX('.', #left)+1, 50)
END
SET #normalized = #normalized + CASE ISNUMERIC(#left)
WHEN 0 THEN #left
WHEN 1 THEN REPLACE(STR(#left, #buffer), SPACE(1), '0')
END
SET #normalized = #normalized + '-'
IF (#right IS NOT NULL) BEGIN
WHILE CHARINDEX('.', #right) > 0 BEGIN
SET #normalized = #normalized + CASE ISNUMERIC(LEFT(#right, CHARINDEX('.', #right)-1))
WHEN 0 THEN LEFT(#right, CHARINDEX('.', #right)-1)
WHEN 1 THEN REPLACE(STR(LEFT(#right, CHARINDEX('.', #right)-1), #buffer), SPACE(1), '0')
END + '.'
SET #right = SUBSTRING(#right, CHARINDEX('.', #right)+1, 50)
END
SET #normalized = #normalized + CASE ISNUMERIC(#right)
WHEN 0 THEN #right
WHEN 1 THEN REPLACE(STR(#right, #buffer), SPACE(1), '0')
END
END ELSE
SET #normalized = #normalized + 'zzzzzzzzzz'
RETURN #normalized
END
SQL tests include:
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-alpha') < dbo.SemanticVersion('1.0.0-alpha.1') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-alpha.1') < dbo.SemanticVersion('1.0.0-alpha.beta') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-alpha.beta') < dbo.SemanticVersion('1.0.0-beta') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-beta') < dbo.SemanticVersion('1.0.0-beta.2') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-beta.2') < dbo.SemanticVersion('1.0.0-beta.11') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-beta.11') < dbo.SemanticVersion('1.0.0-rc.1') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-rc.1') < dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.0-*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.1-*') > dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.0.1-*') <= dbo.SemanticVersion('1.0.1') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.1.*') > dbo.SemanticVersion('1.0.9') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.1.*') <= dbo.SemanticVersion('1.2.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.*') <= dbo.SemanticVersion('2.0.0') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('1.*') > dbo.SemanticVersion('0.9.9-beta-219') THEN 'Success' ELSE 'Failure' END
SELECT CASE WHEN dbo.SemanticVersion('*') <= dbo.SemanticVersion('0.0.1-alpha-1') THEN 'Success' ELSE 'Failure' END
Two steps, first compare the left of the decimal point and after that compare the right.
Possible solution:
declare #v1 varchar(100) = '5.12'
declare #v2 varchar(100) = '5.8'
select case
when CONVERT(int, LEFT(#v1, CHARINDEX('.', #v1)-1)) < CONVERT(int, LEFT(#v2, CHARINDEX('.', #v2)-1)) then 'v2 is newer'
when CONVERT(int, LEFT(#v1, CHARINDEX('.', #v1)-1)) > CONVERT(int, LEFT(#v2, CHARINDEX('.', #v2)-1)) then 'v1 is newer'
when CONVERT(int, RIGHT(#v1, LEN(#v1) - CHARINDEX('.', #v1))) < CONVERT(int, RIGHT(#v2, LEN(#v2) - CHARINDEX('.', #v2))) then 'v2 is newer'
when CONVERT(int, RIGHT(#v1, LEN(#v1) - CHARINDEX('.', #v1))) > CONVERT(int, RIGHT(#v2, LEN(#v2) - CHARINDEX('.', #v2))) then 'v1 is newer'
else 'same!' end as 'Version Test'
Do not store in a string what is not a string. Alternative is creating your own data type (in C# - allowed for some time) that stored the versions as a sequence of bytes and implements proper comparison logic.
As suggested by AF you can compare the int part and then the decimal part .Apart from all the answers given there is one more way to do it using parsename .You could try something like this
case when cast(#var as int)>cast(#var2 as int) then 'Y'
when cast(PARSENAME(#var,1) as int) > cast(PARSENAME(#var2,1) as int) THEN 'Y'
Declare #var float
Declare #var2 float
set #var=5.14
set #var2=5.8
Select case when cast(#var as int)>cast(#var2 as int) then 'Y'
when cast(PARSENAME(#var,1) as int)> cast(PARSENAME(#var2,1) as int) THEN 'Y'
else 'N' END
You don't say so in the question, but your comment under Tomtom's answer suggests you are storing the version numbers as [decimals][d]. I guess that you have a table like this:
CREATE TABLE ReleaseHistory (
VersionNumber DECIMAL(6,3) NOT NULL
);
GO
INSERT INTO ReleaseHistory (
VersionNumber
)
VALUES
(5.12),
(5.8),
(12.34),
(3.14),
(0.78),
(1.0);
GO
The following query is an attempt to rank versions by the order in which they would be released:
SELECT
VersionNumber,
RANK() OVER (ORDER BY VersionNumber) AS ReleaseOrder
FROM ReleaseHistory;
It produces the following result set:
VersionNumber ReleaseOrder
--------------------------------------- --------------------
0.780 1
1.000 2
3.140 3
5.120 4
5.800 5
12.340 6
This is not what we expect. Version 5.8 was released before version 5.12!
Split the version number into its major and minor components to rank the version numbers properly. One way to do this is to convert the decimal value to a string and split on the period. The T-SQL syntax for this is ugly (the language is not designed for string processing):
WITH VersionStrings AS (
SELECT CAST(VersionNumber AS VARCHAR(6)) AS VersionString
FROM ReleaseHistory
),
VersionNumberComponents AS (
SELECT
CAST(SUBSTRING(VersionString, 1, CHARINDEX('.', VersionString) - 1) AS INT) AS MajorVersionNumber,
CAST(SUBSTRING(VersionString, CHARINDEX('.', VersionString) + 1, LEN(VersionString) - CHARINDEX('.', VersionString)) AS INT) AS MinorVersionNumber
FROM VersionStrings
)
SELECT
CAST(MajorVersionNumber AS VARCHAR(3)) + '.' + CAST(MinorVersionNumber AS VARCHAR(3)) AS VersionString,
RANK() OVER (ORDER BY MajorVersionNumber, MinorVersionNumber) AS ReleaseOrder
FROM VersionNumberComponents;
But it provides the expected result:
VersionString ReleaseOrder
------------- --------------------
0.780 1
1.0 2
3.140 3
5.120 4
5.800 5
12.340 6
As Tomtom replied, decimal is a not a good type to store a version number. It would be better to store the version number in two positive integer columns, one containing the major version number and the other containing the minor version number.
This is based on SeanW's answer but this solution allows for the following format [major].[minor].[build]. It maybe used for SQL 2K and when cursor is not an option.
declare #v1 varchar(100) = '1.4.020'
declare #v2 varchar(100) = '1.4.003'
declare #v1_dot1_pos smallint /*position - 1st version - 1st dot */
declare #v1_dot2_pos smallint /*position - 1st version - 2nd dot */
declare #v2_dot1_pos smallint /*position - 2nd version - 1st dot */
declare #v2_dot2_pos smallint /*position - 2nd version - 2nd dot */
-------------------------------------------------
-- get the pos of the first and second dots
-------------------------------------------------
SELECT
#v1_dot1_pos=CHARINDEX('.', #v1),
#v2_dot1_pos=CHARINDEX('.', #v2),
#v1_dot2_pos=charindex( '.', #v1, charindex( '.', #v1 ) + 1 ),
#v2_dot2_pos=charindex( '.', #v2, charindex( '.', #v2 ) + 1 )
-------------------------------------------------
-- break down the parts
-------------------------------------------------
DECLARE #v1_major int, #v2_major int
DECLARE #v1_minor int, #v2_minor int
DECLARE #v1_build int, #v2_build int
SELECT
#v1_major = CONVERT(int,LEFT(#v1,#v1_dot1_pos-1)),
#v1_minor = CONVERT(int,SUBSTRING(#v1,#v1_dot1_pos+1,(#v1_dot2_pos-#v1_dot1_pos)-1)),
#v1_build = CONVERT(int,RIGHT(#v1,(LEN(#v1)-#v1_dot2_pos))),
#v2_major = CONVERT(int,LEFT(#v2,#v2_dot1_pos-1)),
#v2_minor = CONVERT(int,SUBSTRING(#v2,#v2_dot1_pos+1,(#v2_dot2_pos-#v2_dot1_pos)-1)),
#v2_build = CONVERT(int,RIGHT(#v2,(LEN(#v2)-#v2_dot2_pos)))
-------------------------------------------------
-- return the difference
-------------------------------------------------
SELECT
Case
WHEN #v1_major < #v2_major then 'v2 is newer'
WHEN #v1_major > #v2_major then 'v1 is newer'
WHEN #v1_minor < #v2_minor then 'v2 is newer'
WHEN #v1_minor > #v2_minor then 'v1 is newer'
WHEN #v1_build < #v2_build then 'v2 is newer'
WHEN #v1_build > #v2_build then 'v1 is newer'
ELSE '!Same'
END
The solution that was implemented:
CREATE FUNCTION [dbo].[version_compare]
(
#v1 VARCHAR(5), #v2 VARCHAR(5)
)
RETURNS tinyint
AS
BEGIN
DECLARE #v1_int tinyint, #v1_frc tinyint,
#v2_int tinyint, #v2_frc tinyint,
#ResultVar tinyint
SET #ResultVar = 0
SET #v1_int = CONVERT(tinyint, LEFT(#v1, CHARINDEX('.', #v1) - 1))
SET #v1_frc = CONVERT(tinyint, RIGHT(#v1, LEN(#v1) - CHARINDEX('.', #v1)))
SET #v2_int = CONVERT(tinyint, LEFT(#v2, CHARINDEX('.', #v2) - 1))
SET #v2_frc = CONVERT(tinyint, RIGHT(#v2, LEN(#v2) - CHARINDEX('.', #v2)))
SELECT #ResultVar = CASE
WHEN #v2_int > #v1_int THEN 2
WHEN #v1_int > #v2_int THEN 1
WHEN #v2_frc > #v1_frc THEN 2
WHEN #v1_frc > #v2_frc THEN 1
ELSE 0 END
-- Return the result of the function
RETURN #ResultVar
END
GO
This recursive query would convert any '.'-separated version numbers into comparable strings left-padding each element to 10 characters thus allowing to compare versions with or without build number and accommodating for non-numeric characters:
WITH cte (VersionNumber) AS (
SELECT '1.23.456' UNION ALL
SELECT '2.3' UNION ALL
SELECT '0.alpha-3'
),
parsed (VersionNumber, Padded) AS (
SELECT
CAST(SUBSTRING(VersionNumber, CHARINDEX('.', VersionNumber) + 1, LEN(VersionNumber)) + '.' AS NVARCHAR(MAX)),
CAST(RIGHT(REPLICATE('0', 10) + LEFT(VersionNumber, CHARINDEX('.', VersionNumber) - 1), 10) AS NVARCHAR(MAX))
FROM cte
UNION ALL
SELECT
SUBSTRING(VersionNumber, CHARINDEX('.', VersionNumber) + 1, LEN(VersionNumber)),
Padded + RIGHT(REPLICATE('0', 10) + LEFT(VersionNumber, CHARINDEX('.', VersionNumber) - 1), 10)
FROM parsed WHERE CHARINDEX('.', VersionNumber) > 0
)
SELECT Padded
FROM parsed
WHERE VersionNumber = ''
ORDER BY Padded;
Padded
------------------------------
0000000000000alpha-3
000000000100000000230000000456
00000000020000000003
I have created (with inspiration from Eva Lacy (above)), this function:
CREATE or alter function dbo.IsVersionNewerThan
(
#Source nvarchar(max),
#Target nvarchar(max)
)
RETURNS table
as
/*
-1 : target has higher version number (later version)
0 : same
1 : source has higher version number (later version)
test harness:
; WITH tmp
AS
(
SELECT '1.0.0.5' AS Version
UNION ALL SELECT '0.0.0.0'
UNION ALL SELECT '1.5.0.6'
UNION ALL SELECT '2.0.0'
UNION ALL SELECT '2.0.0.0'
UNION ALL SELECT '2.0.1.1'
UNION ALL SELECT '15.15.1323.22'
UNION ALL SELECT '15.15.622.55'
)
SELECT tmp.version, isGreather from tmp
outer apply (select * from dbo.IsVersionNewerThan(tmp.Version, '2.0.0.0')) as IsG
*/
return (
select CASE
when cast('/' + #Source + '/' as hierarchyid) > cast('/' + #Target + '/' as hierarchyid) THEN 1
when #Source = #Target then 0
else -1
end as IsGreather
)
go
The test script is included as a comment.
It works, as long as you do not have versions like '1.5.06.2' (note the zero).
SQL Server thinks this function has is_inlineable = 1, which bodes well for the performance.
Then my SQL code can look like this:
declare #version varchar(10) = '2.30.1.12'
set #version = '2.30.1.1'
if exists(select * from dbo.IsVersionNewerThan(#version,'2.30.1.12') where IsGreather >= 0)
BEGIN
print 'yes'
end
else print 'no'
Here is what I did by modifying some code I found on StackOverflow and writing some myself. This is version 1 of the code so please let me know what you think. Usage examples and test cases are in the code comments.
First create this function if not using SQL 2016 or greater and you do not have access to STRING_SPLIT:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: modified from https://stackoverflow.com/questions/10914576/t-sql-split-string/42000063#42000063
-- =============================================
CREATE FUNCTION [dbo].[SplitStringToRows]
(
#List VARCHAR(4000)
, #Delimiter VARCHAR(50)
)
RETURNS TABLE
AS
RETURN
(
--For testing
-- SELECT * FROM SplitStringToRows ('1.0.123','.')
-- DECLARE #List VARCHAR(MAX) = '1.0.123', #Delimiter VARCHAR(50) = '.';
WITH Casted AS
(
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(#List,#Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Index]
, x.value(N'.',N'nvarchar(max)') AS Part
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)
)
Then create this function:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Soenhay
-- Create date: 7/1/2017
-- Description: Returns -1 if VersionStringA is less than VersionStringB.
-- Returns 0 if VersionStringA equals VersionStringB.
-- Returns 1 if VersionSTringA is greater than VersionStringB.
-- =============================================
CREATE FUNCTION dbo.CompareVersionStrings
(
#VersionStringA VARCHAR(50)
,#VersionStringB VARCHAR(50)
)
RETURNS TABLE
AS
RETURN
(
--CurrentVersion should be of the form:
--major.minor[.build[.revision]]
--This is the same as the versioning system used in c#.
--For applications the build and revision numbers will by dynamically set based on the current date and time of the build.
--Example: [assembly: AssemblyFileVersion("1.123.*")]//http://stackoverflow.com/questions/15505841/the-version-specified-for-the-file-version-is-not-in-the-normal-major-minor-b
--Each component should be between 0 and 65534 ( UInt16.MaxValue - 1 )
--Max version number would be 65534.65534.65534.65534
--For Testing
-- SELECT * FROM dbo.CompareVersionStrings('', '')
-- SELECT * FROM dbo.CompareVersionStrings('asdf.asdf', 'asdf.asdf') --returns 0
-- SELECT * FROM dbo.CompareVersionStrings('asdf', 'fdas') --returns -1
-- SELECT * FROM dbo.CompareVersionStrings('zasdf', 'fdas') --returns 1
-- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.1.123.123') --Should return -1
-- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.0.123.123') --Should return 0
-- SELECT * FROM dbo.CompareVersionStrings('1.1.123.123', '1.0.123.123') --Should return 1
-- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.0.124.123') --Should return -1
-- SELECT * FROM dbo.CompareVersionStrings('1.0.124.123', '1.0.123.123') --Should return 1
-- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.0.123.124') --Should return -1
-- SELECT * FROM dbo.CompareVersionStrings('1.0.123.124', '1.0.123.123') --Should return 1
-- SELECT * FROM dbo.CompareVersionStrings('1.0', '1.1') --Should return -1
-- SELECT * FROM dbo.CompareVersionStrings('1.0', '1.0') --Should return 0
-- SELECT * FROM dbo.CompareVersionStrings('1.1', '1.0') --Should return 1
-- Declare #VersionStringA VARCHAR(50) = '' ,#VersionStringB VARCHAR(50) = '' ;
-- Declare #VersionStringA VARCHAR(50) = '1.0.123.123' ,#VersionStringB VARCHAR(50) = '1.1.123.123' ;
-- Declare #VersionStringA VARCHAR(50) = '1.1.123.123' ,#VersionStringB VARCHAR(50) = '1.1.123.123' ;
-- Declare #VersionStringA VARCHAR(50) = '1.2.123.123' ,#VersionStringB VARCHAR(50) = '1.1.123.123' ;
-- Declare #VersionStringA VARCHAR(50) = '1.1.123' ,#VersionStringB VARCHAR(50) = '1.1.123.123' ;
-- Declare #VersionStringA VARCHAR(50) = '1.1.123.123' ,#VersionStringB VARCHAR(50) = '1.1.123' ;
-- Declare #VersionStringA VARCHAR(50) = '1.1' ,#VersionStringB VARCHAR(50) = '1.1' ;
-- Declare #VersionStringA VARCHAR(50) = '1.2' ,#VersionStringB VARCHAR(50) = '1.1' ;
-- Declare #VersionStringA VARCHAR(50) = '1.1' ,#VersionStringB VARCHAR(50) = '1.2' ;
WITH
Indexes AS
(
SELECT 1 AS [Index]
, 'major' AS Name
UNION
SELECT 2
, 'minor'
UNION
SELECT 3
, 'build'
UNION
SELECT 4
, 'revision'
)
, SplitA AS
(
SELECT * FROM dbo.SplitStringToRows(#VersionStringA, '.')
)
, SplitB AS
(
SELECT * FROM dbo.SplitStringToRows(#VersionStringB, '.')
)
SELECT
CASE WHEN major = 0 THEN
CASE WHEN minor = 0 THEN
CASE WHEN build = 0 THEN
CASE WHEN revision = 0 THEN 0
ELSE revision END
ELSE build END
ELSE minor END
ELSE major END AS Compare
FROM
(
SELECT
MAX(CASE WHEN [Index] = 1 THEN Compare ELSE NULL END) AS major
,MAX(CASE WHEN [Index] = 2 THEN Compare ELSE NULL END) AS minor
,MAX(CASE WHEN [Index] = 3 THEN Compare ELSE NULL END) AS build
,MAX(CASE WHEN [Index] = 4 THEN Compare ELSE NULL END) AS revision
FROM(
SELECT [Index], Name,
CASE WHEN A = B THEN 0
WHEN A < B THEN -1
WHEN A > B THEN 1
END AS Compare
FROM
(
SELECT
i.[Index]
,i.Name
,ISNULL(a.Part, 0) AS A
,ISNULL(b.Part, 0) AS B
FROM Indexes i
LEFT JOIN SplitA a
ON a.[Index] = i.[Index]
LEFT JOIN SplitB b
ON b.[Index] = i.[Index]
) q1
) q2
) q3
)
GO
I'll give you the most shortest answer of this.
with cte as (
select 7.11 as ver
union all
select 7.6
)
select top 1 ver from cte
order by parsename(ver, 2), parsename(cast(ver as float), 1)
Maybe converting build number to a value can help to understand the hierarchy between build versions.
DECLARE #version VARCHAR(25), #dot1 AS TINYINT, #dot2 AS TINYINT, #dot3 AS TINYINT, #MaxPower AS TINYINT, #Value AS BIGINT
SELECT #version = CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR) --'14.0.1000.169' --'10.50.1600'
SELECT #dot1 = CHARINDEX('.', #version, 1)
SELECT #dot2 = CHARINDEX('.', #version, #dot1 + 1)
SELECT #dot3 = CHARINDEX('.', #version, #dot2 + 1)
SELECT #dot3 = CASE
WHEN #dot3 = 0 THEN LEN(#version) + 1
ELSE #dot3
END
SELECT #MaxPower = MAX(DotColumn) FROM (VALUES (#dot1-1), (#dot2-#dot1-1), (#dot3-#dot2-1)) AS DotTable(DotColumn)
SELECT #Value = POWER(10, #MaxPower)
--SELECT #version, #dot1, #dot2, #dot3, #MaxPower, #Value
SELECT
-- #version AS [Build],
CAST(LEFT(#version, #dot1-1) AS INT) * POWER(#Value, 3) +
CAST(SUBSTRING(#version, #dot1+1, #dot2-#dot1-1) AS INT) * POWER(#Value, 2) +
CAST(SUBSTRING(#version, #dot2+1, #dot3-#dot2-1) AS INT) * #Value +
CASE
WHEN #dot3 = LEN(#version)+1 THEN CAST(0 AS INT)
ELSE CAST(SUBSTRING(#version, #dot3+1, LEN(#version)-#dot3) AS INT)
END AS [Value]
Ispired from #Sean answer, since I needed it for 4 parts, I wrote this (and it is easily modulable for more, comment on function in end of code):
CREATE OR REPLACE FUNCTION compareversions(v1 text,v2 text)
RETURNS smallint
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $$
declare res int;
-- Set parts into variables (for now part 1 to 4 are used)
-- IMPORTANT: if you want to add part(s) think to add:
-- - Setting of part(s) to 0 in "Convert all empty or null parts to 0" below
-- - Proper tests in select/case below
-- IMPORTANT: do not use CAST here since it will lead to syntax error if a version or part is empty
-- v1
declare v1_1 text := split_part(v1, '.', 1);
declare v1_2 text := split_part(v1, '.', 2);
declare v1_3 text := split_part(v1, '.', 3);
declare v1_4 text := split_part(v1, '.', 4);
-- v2
declare v2_1 text := split_part(v2, '.', 1);
declare v2_2 text := split_part(v2, '.', 2);
declare v2_3 text := split_part(v2, '.', 3);
declare v2_4 text := split_part(v2, '.', 4);
begin
-- Convert all empty or null parts to 0
-- v1
if v1_1 = '' or v1_1 is null then v1_1 = '0'; end if;
if v1_2 = '' or v1_2 is null then v1_2 = '0'; end if;
if v1_3 = '' or v1_3 is null then v1_3 = '0'; end if;
if v1_4 = '' or v1_4 is null then v1_4 = '0'; end if;
-- v2
if v2_1 = '' or v2_1 is null then v2_1 = '0'; end if;
if v2_2 = '' or v2_2 is null then v2_2 = '0'; end if;
if v2_3 = '' or v2_3 is null then v2_3 = '0'; end if;
if v2_4 = '' or v2_4 is null then v2_4 = '0'; end if;
select
case
-------------
-- Compare first part:
-- - If v1_1 is inferior to v2_1 return -1 (v1 < v2),
-- - If v1_1 is superior to v2_1 return 1 (v1 > v2).
when CAST(v1_1 as int) < cast(v2_1 as int) then -1
when CAST(v1_1 as int) > cast(v2_1 as int) then 1
-------------
-------------
-- v1_1 is equal to v2_1, compare second part:
-- - If v1_2 is inferior to v2_2 return -1 (v1 < v2),
-- - If v1_2 is superior to v2_2 return 1 (v1 > v2).
when CAST(v1_2 as int) < cast(v2_2 as int) then -1
when CAST(v1_2 as int) > cast(v2_2 as int) then 1
-------------
-------------
-- v1_1 is equal to v2_1 and v1_2 is equal to v2_2, compare third part:
-- - If v1_3 is inferior to v2_3 return -1 (v1 < v2),
-- - If v1_3 is superior to v2_3 return 1 (v1 > v2).
when CAST(v1_3 as int) < cast(v2_3 as int) then -1
when CAST(v1_3 as int) > cast(v2_3 as int) then 1
-------------
-------------
-- Etc..., continuing with fourth part:
when CAST(v1_4 as int) < cast(v2_4 as int) then -1
when CAST(v1_4 as int) > cast(v2_4 as int) then 1
-------------
-- All parts are equals, meaning v1 == v2, return 0
else 0
end
into res;
return res;
end;
$$;
;
COMMENT ON FUNCTION compareversions(v1 text,v2 text)
IS 'Function to compare 2 versions as strings, versions can have from 1 to 4 parts (e.g. "1", "2.3", "3.4.5", "5.6.78.9") but it is easy to add a part.
A version having less than 4 parts is considered having its last part(s) set to 0, i.e. "2.3" is considered as "2.3.0.0" so that comparing "1.2.3" to "1.2.3.0" returns "equal"). Indeed we consider first part is always major, second minor, etc ... whatever the number of part for any version.
Function returns:
- -1 when v1 < v2
- 1 when v1 > v2
- 0 when v1 = v2
And, according to return value:
- To compare if v1 < v2 check compareversions(v1, v2) == -1
- To compare if v1 > v2 check compareversions(v1, v2) == 1
- To compare if v1 == v2 check compareversions(v1, v2) == 0
- To compare if v1 <= v2 check compareversions(v1, v2) <= 0
- To compare if v1 >= v2 check compareversions(v1, v2) >= 0'
;
With this you can also for example compare a version "1.2" with "1.2.1" (will return -1, v1 < v2) as "1.2" will be considered as "1.2.0", it is not an usual check but in case during time a digit is added to version a "1.2" will actually be considered equal to "1.2.0".
And it's also easily modulable for another version format, for X.Y-Z for example, v1_1, etc... will be (not tested but you got the idea):
-- v1_1 = X
declare v1_1 text := split_part(v1, '.', 1);
-- tmp = Y-Z
declare tmp text := split_part(v1, '.', 2);
-- v1_2 = Y
declare v1_2 text := split_part(tmp, '-', 1);
-- v1_3 = Z
declare v1_3 text := split_part(tmp, '-', 2);
-- do the same for v2
#MartinSmith answer works best for up-to 5 decimals but if more than that (which might be rare). Here is what I could have done:
DECLARE #AppVersion1 VARCHAR(20) = '2.7.2.2.3.1'
DECLARE #AppVersion2 VARCHAR(20) = '2.7.2.2.4'
DECLARE #V1 AS INT = CASE WHEN LEN(#AppVersion1) < LEN(#AppVersion2) THEN CAST(REPLACE(#AppVersion2,'.','') AS INT) ELSE CAST(REPLACE(#AppVersion1,'.','') AS INT) END;
DECLARE #V2 AS INT = CASE WHEN LEN(#AppVersion1) < LEN(#AppVersion2) THEN CAST(REPLACE(#AppVersion1,'.','') AS INT) ELSE CAST(REPLACE(#AppVersion2,'.','') AS INT) END;
IF(LEN(#V2)< LEN(#V1))
BEGIN
SET #V2 = CAST( LTRIM(CAST(#V2 AS VARCHAR)) + ISNULL(REPLICATE('0',LEN(#V1)-LEN(#V2)),'') AS INT);
END;
SELECT CASE WHEN #V1 > #V2 THEN 'Y' ELSE 'N' END
I tried searching around, but I couldn't find anything that would help me out.
I'm trying to do this in SQL:
declare #locationType varchar(50);
declare #locationID int;
SELECT column1, column2
FROM viewWhatever
WHERE
CASE #locationType
WHEN 'location' THEN account_location = #locationID
WHEN 'area' THEN xxx_location_area = #locationID
WHEN 'division' THEN xxx_location_division = #locationID
I know that I shouldn't have to put '= #locationID' at the end of each one, but I can't get the syntax even close to being correct. SQL keeps complaining about my '=' on the first WHEN line...
How can I do this?
declare #locationType varchar(50);
declare #locationID int;
SELECT column1, column2
FROM viewWhatever
WHERE
#locationID =
CASE #locationType
WHEN 'location' THEN account_location
WHEN 'area' THEN xxx_location_area
WHEN 'division' THEN xxx_location_division
END
without a case statement...
SELECT column1, column2
FROM viewWhatever
WHERE
(#locationType = 'location' AND account_location = #locationID)
OR
(#locationType = 'area' AND xxx_location_area = #locationID)
OR
(#locationType = 'division' AND xxx_location_division = #locationID)
Here you go.
SELECT
column1,
column2
FROM
viewWhatever
WHERE
CASE
WHEN #locationType = 'location' AND account_location = #locationID THEN 1
WHEN #locationType = 'area' AND xxx_location_area = #locationID THEN 1
WHEN #locationType = 'division' AND xxx_location_division = #locationID THEN 1
ELSE 0
END = 1
I'd say this is an indicator of a flawed table structure. Perhaps the different location types should be separated in different tables, enabling you to do much richer querying and also avoid having superfluous columns around.
If you're unable to change the structure, something like the below might work:
SELECT
*
FROM
Test
WHERE
Account_Location = (
CASE LocationType
WHEN 'location' THEN #locationID
ELSE Account_Location
END
)
AND
Account_Location_Area = (
CASE LocationType
WHEN 'area' THEN #locationID
ELSE Account_Location_Area
END
)
And so forth... We can't change the structure of the query on the fly, but we can override it by making the predicates equal themselves out.
EDIT: The above suggestions are of course much better, just ignore mine.
The problem with this is that when the SQL engine goes to evaluate the expression, it checks the FROM portion to pull the proper tables, and then the WHERE portion to provide some base criteria, so it cannot properly evaluate a dynamic condition on which column to check against.
You can use a WHERE clause when you're checking the WHERE criteria in the predicate, such as
WHERE account_location = CASE #locationType
WHEN 'business' THEN 45
WHEN 'area' THEN 52
END
so in your particular case, you're going to need put the query into a stored procedure or create three separate queries.
OR operator can be alternative of case when in where condition
ALTER PROCEDURE [dbo].[RPT_340bClinicDrugInventorySummary]
-- Add the parameters for the stored procedure here
#ClinicId BIGINT = 0,
#selecttype int,
#selectedValue varchar (50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT
drugstock_drugname.n_cur_bal,drugname.cdrugname,clinic.cclinicname
FROM drugstock_drugname
INNER JOIN drugname ON drugstock_drugname.drugnameid_FK = drugname.drugnameid_PK
INNER JOIN drugstock_drugndc ON drugname.drugnameid_PK = drugstock_drugndc.drugnameid_FK
INNER JOIN drugndc ON drugstock_drugndc.drugndcid_FK = drugndc.drugid_PK
LEFT JOIN clinic ON drugstock_drugname.clinicid_FK = clinic.clinicid_PK
WHERE (#ClinicId = 0 AND 1 = 1)
OR (#ClinicId != 0 AND drugstock_drugname.clinicid_FK = #ClinicId)
-- Alternative Case When You can use OR
AND ((#selecttype = 1 AND 1 = 1)
OR (#selecttype = 2 AND drugname.drugnameid_PK = #selectedValue)
OR (#selecttype = 3 AND drugndc.drugid_PK = #selectedValue)
OR (#selecttype = 4 AND drugname.cdrugclass = 'C2')
OR (#selecttype = 5 AND LEFT(drugname.cdrugclass, 1) = 'C'))
ORDER BY clinic.cclinicname, drugname.cdrugname
END
Please try this query.
Answer To above post:
select #msgID, account_id
from viewMailAccountsHeirachy
where
CASE #smartLocationType
WHEN 'store' THEN account_location
WHEN 'area' THEN xxx_location_area
WHEN 'division' THEN xxx_location_division
WHEN 'company' THEN xxx_location_company
END = #smartLocation
Try this:
WHERE (
#smartLocationType IS NULL
OR account_location = (
CASE
WHEN #smartLocationType IS NOT NULL
THEN #smartLocationType
ELSE account_location
END
)
)
CREATE PROCEDURE [dbo].[Temp_Proc_Select_City]
#StateId INT
AS
BEGIN
SELECT * FROM tbl_City
WHERE
#StateID = CASE WHEN ISNULL(#StateId,0) = 0 THEN 0 ELSE StateId END ORDER BY CityName
END
Try this query, it's very easy and useful: Its ready to execute!
USE tempdb
GO
IF NOT OBJECT_ID('Tempdb..Contacts') IS NULL
DROP TABLE Contacts
CREATE TABLE Contacts(ID INT, FirstName VARCHAR(100), LastName VARCHAR(100))
INSERT INTO Contacts (ID, FirstName, LastName)
SELECT 1, 'Omid', 'Karami'
UNION ALL
SELECT 2, 'Alen', 'Fars'
UNION ALL
SELECT 3, 'Sharon', 'b'
UNION ALL
SELECT 4, 'Poja', 'Kar'
UNION ALL
SELECT 5, 'Ryan', 'Lasr'
GO
DECLARE #FirstName VARCHAR(100)
SET #FirstName = 'Omid'
DECLARE #LastName VARCHAR(100)
SET #LastName = ''
SELECT FirstName, LastName
FROM Contacts
WHERE
FirstName = CASE
WHEN LEN(#FirstName) > 0 THEN #FirstName
ELSE FirstName
END
AND
LastName = CASE
WHEN LEN(#LastName) > 0 THEN #LastName
ELSE LastName
END
GO
In general you can manage case of different where conditions in this way
SELECT *
FROM viewWhatever
WHERE 1=(CASE <case column or variable>
WHEN '<value1>' THEN IIF(<where condition 1>,1,0)
WHEN '<value2>' THEN IIF(<where condition 2>,1,0)
ELSE IIF(<else condition>,1,0)
END)
Case Statement in SQL Server Example
Syntax
CASE [ expression ]
WHEN condition_1 THEN result_1
WHEN condition_2 THEN result_2
...
WHEN condition_n THEN result_n
ELSE result
END
Example
SELECT contact_id,
CASE website_id
WHEN 1 THEN 'TechOnTheNet.com'
WHEN 2 THEN 'CheckYourMath.com'
ELSE 'BigActivities.com'
END
FROM contacts;
OR
SELECT contact_id,
CASE
WHEN website_id = 1 THEN 'TechOnTheNet.com'
WHEN website_id = 2 THEN 'CheckYourMath.com'
ELSE 'BigActivities.com'
END
FROM contacts;
This worked for me.
CREATE TABLE PER_CAL ( CAL_YEAR INT, CAL_PER INT )
INSERT INTO PER_CAL( CAL_YEAR, CAL_PER ) VALUES ( 20,1 ), ( 20,2 ), ( 20,3 ), ( 20,4 ), ( 20,5 ), ( 20,6 ), ( 20,7 ), ( 20,8 ), ( 20,9 ), ( 20,10 ), ( 20,11 ), ( 20,12 ),
( 99,1 ), ( 99,2 ), ( 99,3 ), ( 99,4 ), ( 99,5 ), ( 99,6 ), ( 99,7 ), ( 99,8 ), ( 99,9 ), ( 99,10 ), ( 99,11 ), ( 99,12 )
The 4 digit century is determined by the rule, if the year is 50 or more, the century is 1900, otherwise 2000.
Given two 6 digit periods that mark the start and end period, like a quarter, return the rows that fall in that range.
-- 1st quarter of 2020
SELECT * FROM PER_CAL WHERE (( CASE WHEN CAL_YEAR > 50 THEN 1900 ELSE 2000 END + CAL_YEAR ) * 100 + CAL_PER ) BETWEEN 202001 AND 202003
-- 4th quarter of 1999
SELECT * FROM PER_CAL WHERE (( CASE WHEN CAL_YEAR > 50 THEN 1900 ELSE 2000 END + CAL_YEAR ) * 100 + CAL_PER ) BETWEEN 199910 AND 199912
Try this query. Its very easy to understand:
CREATE TABLE PersonsDetail(FirstName nvarchar(20), LastName nvarchar(20), GenderID int);
GO
INSERT INTO PersonsDetail VALUES(N'Gourav', N'Bhatia', 2),
(N'Ramesh', N'Kumar', 1),
(N'Ram', N'Lal', 2),
(N'Sunil', N'Kumar', 3),
(N'Sunny', N'Sehgal', 1),
(N'Malkeet', N'Shaoul', 3),
(N'Jassy', N'Sohal', 2);
GO
SELECT FirstName, LastName, Gender =
CASE GenderID
WHEN 1 THEN 'Male'
WHEN 2 THEN 'Female'
ELSE 'Unknown'
END
FROM PersonsDetail