I have following query to split the range of numbers in parts. For ex: i have range of 200 numbers and i have to group the numbers to 95 But Result is coming in reverse order.I Have attached expected result
declare #MinValue bigint = 1
declare #MaxValue bigint = 200;
declare #RowsPerGroup bigint =95
declare #RowsPerGroup1 bigint =(#RowsPerGroup-1)
;with src(val,rm) as (
select #MaxValue, (#MaxValue - #RowsPerGroup1) union all
select rm-1, case when rm-1 > #MinValue + #RowsPerGroup1 then rm-1 - #RowsPerGroup1 else #MinValue end from src where rm-1 >= #MinValue
)
select rm as 'Start', val as 'End',[Difference]=(val-rm)+1 from src order by rm asc
option(maxrecursion 0)
Current Result:
Start End Difference
1 10 10
11 105 95
106 200 95
Expected Result:
Pls let me know where am doing wrong
My variant:
DECLARE
#MinValue bigint = 1,
#MaxValue bigint = 200,
#RowsPerGroup bigint = 95 -- 300
;WITH cte AS(
SELECT #MinValue [Start],IIF(#RowsPerGroup>#MaxValue,#MaxValue,#RowsPerGroup) [End]
UNION ALL
SELECT [End]+1,IIF([End]+#RowsPerGroup>#MaxValue,#MaxValue,[End]+#RowsPerGroup)
FROM cte
WHERE [End]<#MaxValue
)
SELECT [Start],[End],[End]-[Start]+1 [Difference]
FROM cte
You can see the reason in the first line of the common table expression:
select #MaxValue, (#MaxValue - #RowsPerGroup1)
This inserts (200,106) intto src. The second select then counts down from existing rows. To adapt the CTE, exchange minimums with maximums (including for ranges), invert arithmetic, reverse comparisons and any other related exchanges:
select #MinValue, (#MinValue + #RowsPerGroup1) union all
select val+1, case
when val+1 < #MaxValue - #RowsPerGroup1
then val+1 + #RowsPerGroup1
else #MaxValue
end
from src
where val+1 <= #MaxValue
This particular statement can be simplified in parts:
select #MinValue, (#MinValue + #RowsPerGroup - 1) union all
select val + 1, case
when val + #RowsPerGroup < #MaxValue
then val + #RowsPerGroup
else #MaxValue
end
from src
where val < #MaxValue
Related
How do I perform recursive division in SQL?
I know the basic premise is
declare #i = 0
declare #testValue bigint = FLOOR(#num/#dem)
while #testValue>0
begin
set #num = #num - #testValue*#dem
set #testValue = FLOOR(#num/#dem)
set #i = #i + 1
end
, but I do not know how to do this in an efficient manner.
Your query can be rewritten as the following:
Declare #Num BigInt = 10000,
#Dem BigInt = 50
;With Cte As
(
Select Floor(#num / #dem) TestValue, 1 As Iteration
Union All
Select Floor((TestValue * #Dem) / #Dem) As TestValue, Iteration + 1
From Cte
Where TestValue > 0
)
Select *
From Cte
But there's a problem with the design of it. It multiplies the value back to the division result, and divides again, resulting in an infinite loop.
I believe this is what you were trying to do:
Declare #Num BigInt = 10000,
#Dem BigInt = 50
;With Cte As
(
Select Floor(#num / #dem) TestValue, 1 As Iteration
Union All
Select Floor(TestValue / #Dem) As TestValue, Iteration + 1
From Cte
Where TestValue > 0
)
Select *
From Cte
Which, in this case gives the following results:
TestValue Iteration
200 1
4 2
0 3
I want is to select Armstrong numbers from the list below list I have searched of solution of this question bu unable to find in SQL-Server:
Numbers
121
113
423
153
541
371
I am sure most of you know what's the Armstrong number and how to calculate though I am describing is for the simplicity : sum of the cubes of its digits is equal to the number itself i.e.
1*1*1 + 5*5*5 + 3*3*3 = 153
3*3*3 + 7*7*7 + 1*1*1 = 371
Please help me on this as I am also trying but seeking for quick solution. It will be very helpful to me. Thanks in advance.
Obviously static processing during each query is not correct approach but we can create function like this and
create function dbo.IsArmstrongNumber(#n int)
returns int as
begin
declare #retValue int = 0
declare #sum int = 0
declare #num int = #n
while #num > 0
begin
set #sum += (#num%10) * (#num%10) * (#num%10)
set #num = #num/10
end
IF #sum = #n
set #retValue = 1
return #retValue
end
Pre-processing and selecting in IN clause is better
select * from #Numbers where dbo.IsArmstrongNumber(n) = 1
select 153 x into #temp;
insert #temp values(371);
insert #temp values(541);
with cte as (select x, substring(cast(x as nvarchar(40)) ,1,1) as u, 1 as N FROM #temp
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),3)) = x
drop table #temp;
here is the mark 2 - you can change the #ORDER to explore power of 4,5 etc
declare #order int = 3;
declare #limit int = 50000;
with nos as (select 1 no
union all
select no + 1 from nos where no < #limit),
cte as (select no as x, substring(cast(no as nvarchar(40)) ,1,1) as u, 1 as N FROM nos
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),#order)) = x
option (maxrecursion 0);
This is a quick mod to my sum of digits UDF
Declare #Table table (Numbers int)
Insert into #Table values
(121),
(113),
(423),
(153),
(541),
(371)
Select * from #Table where [dbo].[udf-Stat-Is-Armstrong](Numbers)=1
Returns
Numbers
153
371
The UDF
CREATE Function [dbo].[udf-Stat-Is-Armstrong](#Val bigint)
Returns Bit
As
Begin
Declare #RetVal as bigint
Declare #LenInp as bigint = len(cast(#Val as varchar(25)))
;with i AS (
Select #Val / 10 n, #Val % 10 d
Union ALL
Select n / 10, n % 10
From i
Where n > 0
)
Select #RetVal = IIF(SUM(power(d,#LenInp))=#Val,1,0) FROM i;
Return #RetVal
End
You can use the following to find Armstrong numbers using Sql functions:
WITH Numbers AS(
SELECT 0 AS number UNION ALL SELECT number + 1 FROM Numbers WHERE number < 10000)
SELECT number AS ArmstrongNumber FROM Numbers
WHERE
number = POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),1,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),2,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),3,1),0),3)
OPTION(MAXRECURSION 0)
I am trying to create a while loop in SQL and it seems kind of complex. Here's what I need it to achieve:
Iterate through a single VARCHAR string (ex. '123')
If the nth character is in an even position in the string (ex. 2nd, 4th .... letter in the string), it must be added(SUM) to a base variable (Let's assume #z)
If the nth character is in an odd position in the string (ex. 1st, 3rd .... letter in the string), it must be multiplied by 2. If this newly generated value (Let's assume #y) is less than 10, it must be added(SUM) to the base variable (Still the same assumed #z). If #y is greater than 10, we need to subtract 9 from #y before adding(SUM) it to #z
After iterating through the entire string, this should return a numeric value generated by the above process.
Here is what I've done so far, but I'm stuck now (Needless to say, this code does not work yet, but I think I'm heading in the right direction):
DECLARE #x varchar(20) = '12345'
DECLARE #p int = len(#x)
WHILE #p > 0
SELECT #x =
stuff(#x, #p, 1,
case when CONVERT(INT,substring(#x, #p, 1)) % 2 = 0 then CONVERT(INT, #x) + CONVERT(INT,substring(#x, #p, 1))
end), #p -= 1
RETURN #x;
PS. The input will always be 100% numeric values, but it is formatted as VARCHAR when I recieve it.
UPDATE
The expected result for the sample string is 15
You can do this without using a loop. Here is a solution using Tally Table:
DECLARE #x VARCHAR(20) = '12345'
DECLARE #z INT = 0 -- base value
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
CteTally(N) AS(
SELECT TOP(LEN(#x)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
),
CteChars(N, num) AS(
SELECT
t.N, CAST(SUBSTRING(#x, t.N, 1) AS INT)
FROM CteTally t
WHERE t.N <= LEN(#x)
)
SELECT
SUM(
CASE
WHEN N % 2 = 0 THEN num
WHEN num * 2 < 10 THEN num * 2
ELSE (num * 2) - 9
END
) + #z
FROM CteChars
The CTEs up to CteTally generates a list of number from 1 to LEN(#x). CteChars breaks #x character by character into separate rows. Then the final SELECT does a SUM based on the conditions.
OUTPUT : 15
Check below if it helps you
DECLARE #x varchar(20) = '12345'
DECLARE #p int = 1
DECLARE #result bigint=0;
DECLARE #tempval int =0;
WHILE #p <= len(#x)
BEGIN
SET #tempval = CONVERT(INT,substring(#x, #p, 1));
if(#p%2 = 1)
BEGIN
SET #tempval = #tempval * 2;
IF(#tempval >= 10) SET #tempval = #tempval - 9;
END
SET #result = #result + #tempval;
SET #p = #p + 1;
END;
PRINT #result;--This is the result
RETURN #x;
DECLARE #x INT = 12345
DECLARE #p int = len(#x)
DECLARE #z INT =0
PRINT #p%2
SET #x=#x/10
PRINT #x
WHILE #p > 0
BEGIN
IF(#p%2 = 0)
BEGIN
SET #z=#z+#x%10
SET #p=#p-1
SET #x=#x/10
END
ELSE
BEGIN
SET #z=#z+(2*(#x%10))
SET #p=#p-1
SET #x=#x/10
IF(#x>=10)
BEGIN
SET #x=(#x/10+#x%10)
END
END
END
SELECT #z
The while loop does not seem necessary here.
This can be achieved with a CTE that will split the string and a case statement:
DECLARE #x varchar(20) = '12345';
with split(id, v) as (
select 0, cast(0 as tinyint)
union all
select id+1, cast(SUBSTRING(#x, id+1, 1) as tinyint)
From split
Where id+1 <= len(#x)
)
Select Result = SUM(
Case When id % 2 = 0 then v
When v < 5 then v*2
Else (v*2)-9
End
)
From split
output = 15
I am trying to speed up this recursive UNION ALL as shown below, but I cannot think how to do it. Maybe a while loop but I am not sure. The movement data is stored as one long string of encoded movement data and the script recursively calls the select statement to parse/extract this data and then it is all casted.
I would really like to understand more about speeding up recursive union all's or finding another way. I don't believe indexing is a problem so this is not really a possible solution.
"RouteData" is the long string that is parsed by fixed length intervals.
Here is a sample of the encoded data:
ScenarioPID : 3
LegID :1
RoutePart : 0x0000000000000000000100000000000000000000000000
RouteData : 0x40323AAAAAAAAAAB00013FA6FFD663CCA3310000001F00403 ... (goes on)
cnt : 37
sequence : 1
StartTime : 8828
The final output data looks like this for one track.
ScenarioPID LegID sequence TrackID Offset TimeOffset Length StartTime
3 1 1 1 0 0 6300 8828
3 1 2 1 0.0449 31 6300 8828
3 1 3 1 0.8942 325 6300 8828
3 1 4 1 0.9736 356 6300 8828
3 1 5 1 1 369 6300 8828
USE nss_demo;
DECLARE #scenario1 INT;
DECLARE #DAY_START INT;
DECLARE #DAY_END INT;
DECLARE #TRAIN_TYPE VARCHAR(50);
DECLARE #TRACK_TYPE VARCHAR(50);
SET #scenario1 = 3;
SET #DAY_START = 0;
SET #DAY_END = 7;
SET #TRAIN_TYPE = 'Empty Train';
SET #TRACK_TYPE = 'East Track';
DECLARE #KM_START INT;
DECLARE #KM_END INT;
SET #KM_START = 0;
SET #KM_END = 200;
WITH movement
AS (SELECT m.scenariopid,
m.legid,
Substring(routedata, 1, 23) AS RoutePart,
Substring(routedata, 24, Len(routedata) - 23) AS RouteData,
Len(routedata) / 23 - 1 AS cnt,
1 AS sequence,
m.starttime
FROM output.movement m
WHERE scenariopid = #scenario1
AND m.starttime BETWEEN ( #DAY_START * 86400 ) AND
( #DAY_END * 86400 )
UNION ALL
SELECT scenariopid,
legid,
Substring(m1.routedata, 1, 23) AS RoutePart
,
Substring(m1.routedata, 24,
Len(m1.routedata) - 23) AS RouteData,
Len(m1.routedata) / 23 - 1 AS cnt,
sequence + 1 AS sequence,
m1.starttime
FROM movement m1
WHERE m1.cnt > 0),
casttable
AS (SELECT tt.scenariopid,
tt.legid,
tt.sequence,
tt.trackid,
tt.offset,
tt.timeoffset,
tr.[length],
tt.starttime
FROM (SELECT scenariopid,
legid,
sequence,
Cast(trackidbin AS SMALLINT) AS TrackID,
Sign(Cast(offsetbin AS BIGINT)) *
( 1.0 +
( Cast(offsetbin AS BIGINT) & 0x000FFFFFFFFFFFFF ) *
Power(Cast(2 AS FLOAT), -52) )
*
Power(Cast(2 AS FLOAT), ( Cast(offsetbin AS BIGINT) &
0x7ff0000000000000
) /
0x0010000000000000
- 1023) AS Offset,
Cast(timebin AS INT) AS TimeOffset,
starttime AS StartTime
FROM (SELECT legid,
scenariopid,
sequence,
Substring(routepart, 9, 2) AS TrackIDBin,
Substring(routepart, 11, 8) AS OffsetBin,
Substring(routepart, 19, 4) AS TimeBin,
starttime
FROM movement) t) tt
INNER JOIN input.track tr
ON tr.trackid = tt.trackid
AND tr.scenariopid = tt.scenariopid)
SELECT *
FROM casttable
ORDER BY legid,
sequence
OPTION (maxrecursion 20000)
Use a Numbers Table (zero-based assumed below) to create CTE movement like this:
WITH movement
AS (SELECT m.scenariopid,
m.legid,
Substring(routedata, n.N*23 + 1, 23) AS RoutePart,
n.N AS cnt,
-- 1 AS sequence, -- use a row_number function here instead, as per your vendor.
m.starttime
FROM output.movement m
JOIN Numbers n
on n < Len(routedata) / 23
WHERE scenariopid = #scenario1
AND m.starttime BETWEEN ( #DAY_START * 86400 ) AND
( #DAY_END * 86400 )
),
-- etc.
If you don't have a static Numbers Table, my answer here demonstrates how to create one dynamically in a CTE.
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;