Given the following string (ignore double quotes):
"I101G2.2 OZ 001 0002200 L Y 0010000 "
I need to find the starting position of each substring and it's length. For example, using the string above, the first substring is I101G2.2
starts at position 0 and is 8 characters in length. Bear in mind that each space character (" ") should be considered a substring, always one character in length. So, the output should be something like:
**Sub-String Value**,**Starting Position**, **Length**
I101G2.2,0,8
{space},9,1
OZ,10,2
{space},12,1
{space},13,1
001,14,3
{space},17,1
0002200,18,7
{space},25,1
L,26,1
{space},27,1
{space},28,1
{space},29,1
{space},30,1
{space},31,1
{space},32,1
{space},33,1
{space},34,1
Y,35,1
{space},36,1
{space},37,1
{space},38,1
{space},39,1
{space},40,1
{space},41,1
{space},42,1
{space},43,1
0010000,44,7
etc...
We have a production system written in Cobol that outputs information in the above string format. I have a table that maps positions and string lengths to a column in another table. So, the idea is to get the position and length of the string and compare to the mapping table to determine what table column the belongs in. For instance
0002200 is the item class because it's at position 18, and is 7 characters in length.
Thanks in advance!
I'm impressed you still got Cobol running, my mom used to code stuff in Cobol... In the 80s!
Here's my try (it handles all spaces even those at the end of the string):
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
GO
CREATE TABLE #t (str VARCHAR(100))
INSERT INTO #t
VALUES ('I101G2.2 OZ 001 0002200 L Y 0010000')
;WITH cte AS(
SELECT cast(CASE WHEN charindex(' ', t.str) = 1 THEN ' ' ELSE LEFT(str, charindex(' ', str)) END AS varchar(100)) str
, 0 AS startPosition
, CASE WHEN charindex(' ', t.str) = 1 THEN 1 ELSE charindex(' ', t.str) END AS nextIndex
, stuff(str, 1,charindex(' ', t.str)-1, '') AS strLeft
FROM #t t
--
UNION ALL
SELECT cast(CASE WHEN charindex(' ', t.strLeft) = 1 THEN ' ' WHEN charindex(' ', t.strleft) = 0 THEN strLeft ELSE LEFT(strLeft, charindex(' ', strLeft)) END AS varchar(100))
, startPosition + nextIndex
, CASE WHEN charindex(' ', t.strLeft) = 1 THEN 1 ELSE charindex(' ', t.strLeft) END
, stuff(strLeft, 1,charindex(' ', t.strLeft) + CASE WHEN charindex(' ', t.strleft) <> 1 THEN -1 ELSE 0 END, '')
FROM CTE t
WHERE datalength(strLeft) > 0
)
SELECT str, startPosition, datalength(str) AS length
FROM cte
OPTION(maxrecursion 0);
To do this in c#, you can simply walk the string in a loop. As we encounter non-space characters, we can append them to a StringBuilder. When we encounter a space, we would then first add the characters we've captured so far, along with their length and starting position, to a list of strings (each representing a substring), clear the StringBuilder for the next substring, and then add the space character and it's position and length. At the end of the loop, if our StringBuilder has a substring in it, add it to the list and then return the list.
One bug in your original example is that the first substring started at position 0 and had 8 characters. This means that the next substring should start at position 8, but your example shows it starting at 9.
Here's one way to do it:
public static List<string> GetParts(string input)
{
if (input == null) return null;
var result = new List<string>();
if (input.Length == 0) return result;
var currentPart = new StringBuilder();
for (int i = 0; i < input.Length; i++)
{
if (input[i] == ' ')
{
if (currentPart.Length > 0)
{
var part = currentPart.ToString();
result.Add($"{part},{i - part.Length},{part.Length}");
currentPart.Clear();
}
result.Add($"{{space}},{i},1");
}
else
{
currentPart.Append(input[i]);
}
}
if (currentPart.Length > 0)
{
var part = currentPart.ToString();
result.Add($"{part},{input.Length - part.Length - 1},{part.Length}");
}
return result;
}
And an example calling it:
static void Main(string[] args)
{
var input =
"I101G2.2 OZ 001 0002200 L Y 0010000 ";
var parts = GetParts(input);
parts.ForEach(Console.WriteLine);
Console.Write("\n\nDone. Press any key to exit...");
Console.ReadKey();
}
I'm not sure of your exact requirements here, but this is a pretty standard string split with an ordinal type of situation.
Give this a whirl:
SET ANSI_PADDING ON
DECLARE #ThatsALongOne NVARCHAR(MAX) = 'I101G2.2 OZ 001 0002200 L Y 0010000 ';
DECLARE #WhaddaYaWant NVARCHAR(10) = ' ';
SET #ThatsALongOne+='.'
;WITH cte AS
(
SELECT #ThatsALongOne AS TheWholeSheBang,
CHARINDEX(#WhaddaYaWant,#ThatsALongOne)-1 AS CI, LEN(#ThatsALongOne) AS LEN,
CASE WHEN LEFT(LEFT(#ThatsALongOne ,CHARINDEX(#WhaddaYaWant,#ThatsALongOne)),1) = #WhaddaYaWant THEN #WhaddaYaWant ELSE LEFT(#ThatsALongOne ,CHARINDEX(#WhaddaYaWant,#ThatsALongOne)) END AS This,
CASE WHEN LEFT(LEFT(#ThatsALongOne ,CHARINDEX(#WhaddaYaWant,#ThatsALongOne)),1) = #WhaddaYaWant THEN RIGHT(#ThatsALongOne, LEN(#ThatsALongOne) -CHARINDEX(#WhaddaYaWant,#ThatsALongOne )) ELSE RIGHT(#ThatsALongOne, 1+ LEN(#ThatsALongOne) -CHARINDEX(#WhaddaYaWant,#ThatsALongOne )) END AS WhatsLeft
, 1 AS Incidence
, CAST(0 AS BIGINT) AS Start
UNION ALL
SELECT #ThatsALongOne AS TheWholeSheBang,
CASE WHEN LEFT(LEFT(WhatsLeft ,CHARINDEX(#WhaddaYaWant,WhatsLeft)),1) = #WhaddaYaWant THEN 1 ELSE LEN(LEFT(WhatsLeft ,CHARINDEX(#WhaddaYaWant,WhatsLeft))) END AS CI,
LEN(WhatsLeft) AS LEN,
CASE WHEN LEFT(LEFT(WhatsLeft ,CHARINDEX(#WhaddaYaWant,WhatsLeft)),1) = #WhaddaYaWant THEN #WhaddaYaWant ELSE LEFT(WhatsLeft ,CHARINDEX(#WhaddaYaWant,WhatsLeft)) END AS This,
CASE WHEN LEFT(LEFT(WhatsLeft ,CHARINDEX(#WhaddaYaWant,WhatsLeft)),1) = #WhaddaYaWant THEN RIGHT(#ThatsALongOne, LEN(WhatsLeft) -CHARINDEX(#WhaddaYaWant,WhatsLeft )) ELSE RIGHT(WhatsLeft, 1+ LEN(WhatsLeft) -CHARINDEX(#WhaddaYaWant,WhatsLeft )) END AS WhatsLeft
, Incidence + 1 AS Incidence
, CASE WHEN LEFT(LEFT(WhatsLeft ,CHARINDEX(#WhaddaYaWant,WhatsLeft)),1) = #WhaddaYaWant THEN 1 ELSE CHARINDEX(#WhaddaYaWant,WhatsLeft) END
FROM cte
WHERE WhatsLeft <> '.'
)
SELECT This AS SubString, SUM(CI) OVER (PARTITION BY cte.TheWholeSheBang ORDER BY cte.Incidence)-CI AS StartingPosition, CI AS Length
FROM cte
SubString StartingPosition Length
I101G2.2 0 8
8 1
OZ 9 2
11 1
12 1
001 13 3
..
My first idea was to make an array/list that has values assigned to each character.
So for example:
array[0] =' 0'
array[10] = 'A'
[...]
Then code would pick a random number y between [0,x] for slot 1.
For next slot [0,(x-y)] etc. When y <= 0 then fill rest of the slots with '0'.
Would that be enough for a simple voucher code generator? (It's not my decision to make encryption with this rule)
I am worried that sum of 9 is quite low for 6 character code, letters won't be used at all since they all have value over 9.
To prevent situation like this:
540000, 630000, 180000
Should I make chance of '0' to appear more?
What do you guys think about it?
Maybe you could also suggest some other way of doing this.
#Edit
Examples:
112320 = 1+1+2+3+2+0 = 9 Valid code, sum equals 9
000900 = 0+0+0+9+0+0 = 9 Valid code, sum equals 9
003015 = 0+0+3+0+1+5 = 9 Valid code, sum equals 9
A0012B = 10+0+0+1+2+11 = 24 Invalid code
Let's say that the function Rand(n) creates a random integer number that can go from 0 up to n (n included), then you can do the following:
Sum = 0;
A[0] = Rand(9);
Sum += A[0];
A[1] = Rand(9 - Sum);
Sum += A[1];
A[2] = Rand(9 - Sum);
Sum += A[2];
...
I just wrote this down very quickly, I didn't check the boundaries, but such an algorithm should do the trick.
I have some data stored in SQL Server like this :
A1
A2
A3
1A
2A
3A
How can sort it?
I tried this query:
select name
from Analyze_Table
group by name
order by CONVERT(INT, LEFT(name, PATINDEX('%[^0-9]%', name+'z')-1)),name
but this only sorts by the first number and after alphabetic and doesn't sort alphabetic and after number values
I've tried to sort on just the numeric portion of the name, excluding the first or last character. Then if there are 2 with the same number, they will sort again, e.g. 23 and 23A. This should give you the output you were looking for
select name
from Analyze_Table
group by name
order by case
when isnumeric(name) = 1 then cast(name as int)
when isnumeric(left(name, 1)) = 0 and isnumeric(right(name, 1)) = 0 then cast(substring(name, 2, len(name)-2) as int)
when isnumeric(left(name, 1)) = 0 then cast(right(name, len(name)-1) as int)
when isnumeric(right(name, 1)) = 0 then cast(left(name, len(name)-1) as int) end
,name
If you want the names that start with numbers first, then is this what you what?
order by (case when name like '[0-9]%' then 1 else 2 end),
name
I am looking for a way that would allow me to have a column that I would simply manually input a number and then the list would sort itself based off this list, this would be using c# as the language for the listing and it's ms SQL
I don't have much information, but if anyone wants to know anything else feel free to ask and I will try and answer to my best ability.
They are currently stored as strings.
The thing that causes it to get thrown out is because some of the lists contains ranges but some contain fractions, both which are displayed the same I.E 1/2 can mean 1-2 or 1/2(half)
All of the SQL connection is done using NHibernate.
The reason for not simply sorting it normally, is that the list is ordered using fractions currently and the list works fine, however when it gets over 1, it seems to break and throws all of them to the bottom of the list I.E:
Example of how I would like this to work:
I would have a column in my database named "DisplayOrder" or something along those lines.
Database row says "1", this would be the first item in a list to appear.
Database row says "8", this would be the 8th item in the list to appear.
I've put together a quick function to parse your fractions and convert them to floats, which can then be used for comparison and sorting. You may need more in the "sanitation" step, possibly normalizing whitespace, removing other characters, etc., and may have to check the divisor for zero (only you know your data).
create function dbo.FractionToFloat(#fraction varchar(100))
returns float
as
begin
declare #input varchar(100)
, #output float
, #whole int
, #dividend int
, #divisor int
-- Sanitize input
select #input = ltrim(rtrim(replace(replace(#fraction, '''', ''), '"', '')))
select #whole = cast(case
when charindex('/', #input) = 0 then #input
when charindex(' ', #input) = 0 then '0'
else left(#input, charindex(' ', #input) - 1)
end as int)
select #dividend = cast(case
when charindex('/', #input) = 0 then '0'
when charindex(' ', #input) = 0 then left(#input, charindex('/', #input) - 1)
else substring(#input, charindex(' ', #input) + 1, charindex('/', #input) - charindex(' ', #input) - 1)
end as int)
select #divisor = cast(case
when charindex('/', #input) = 0 then '1'
else right(#input, charindex('/', reverse(#input)) - 1)
end as int)
select #output = cast(#whole as float) + (cast(#dividend as float) / cast(#divisor as float))
return #output
end
This way, you can simply order by the function's output like so:
select *
from MyTable
order by dbo.FractionToFloat(MyFractionColumn)
I wouldn't normally suggest rolling your own parser that you have to maintain as the data changes, but this seems simple enough on the surface, and probably better than manually maintaining an ordinal column. Also, if you had to compare various units (feet to inches, minutes to seconds), then this gets more complicated. I'm removing anything that looks like a unit in your demo data.
The sorting result seems perfectly normal, since I really think that the database doesn't understand what "1 1/4" means.
Which type has your database field? Text/varchar/string I guess?
Maybe you should create a stored proc to convert the fraction values to numeric ones, an then call
SELECT field1, field2, ParseAndConvertProc(DisplayOrder) as disp_order ORDER by disp_order
Ignoring for the moment that 1/2 can mean between 1 and 2, you can deal with the madness and try to get some order out of it.
CREATE TABLE #Fractions (
Frac varchar(15)
);
INSERT #Fractions VALUES
('1 1/2'), ('1 1/4'), ('1"'), ('1/2'), ('1/4'), ('1/8'),
('2 1/2'), ('2"'), ('3"'), ('3/4'), ('3/8'), ('4"');
WITH Canonical AS (
SELECT
Frac,
F,
CharIndex(' ', F) S,
CharIndex('/', F) D
FROM
(SELECT Frac, Replace(Frac, '"', '') F FROM #Fractions) F
WHERE
F NOT LIKE '%[^0-9 /]%'
), Parts AS (
SELECT
Frac,
Convert(int, Left(F, CASE WHEN D = 0 THEN Len(F) ELSE S END)) W,
Convert(int, CASE WHEN D > 0 THEN Substring(F, S + 1, D - S - 1) ELSE '' END) N,
Convert(int, CASE WHEN D > 0 THEN Substring(F, D + 1, 2147483647) ELSE '1' END) D
FROM Canonical
)
SELECT
Frac,
W + N * 1.0 / D Calc
FROM Parts
ORDER BY Calc;
DROP TABLE #Fractions;
But I don't recommend this. Use my query as a base to get the correct value, and switch to using decimal values.
Note you also have to search your data for characters this code didn't account for, using the pattern match above with LIKE instead of NOT LIKE. Then remove them or account for them somehow.
If what you said is true that 1/2 can mean two things, andyou can tell the difference between them, then you can do something about it. Store the range in two columns. If they are the same then there is no range.
If you can't tell the difference between the two meanings then you're just majorly messed up and may as well quit your job and go race sled dogs in Alaska.
I have a application that I save this in the database:
FromLetter ToLetter
AAA AAZ
ABC MNL
what I need is to search like this AAC and returns record 1 and FBC and return record 2.
Is the same functionality if instead of letter I save dates. I need to do the same query.
I am using SQL Server and Entity Framework, any Idea how to do this?
Should be pretty straight forward. Here is a Linq to Entities solution, ignoring case:
Entity Framework/Linq solution strings:
string yourValue = somevalue;
var result = (from r in db.ExampleTable
where String.Compare(yourValue, r.FromLetter, true) == 1
&& String.Compare(yourValue, r.ToLetter, true) == -1
select r).First();
Dates:
DateTime yourValue = somevalue;
var result = (from r in db.ExampleTable
where yourValue >= r.FromDate
&& yourValue <= r.ToDate
select r).First();
I think it would be much easier to represent the FromLetter and ToLetter attributes using an integer. Especially if the length of the string is always just 3 - you can simply encode the number as:
(((letter1 - 'A') * 26 + (letter2 - 'A')) * 26) + (letter3 - 'A')
This will give you a number between 0 and 26^3 that represents the tripple and can be easily converted back to the string (using modulo and division as when converting numbers between numeric bases). This number fits into Int32 comfortably (up to 6 letters).
Searching for a string within a specified range would then be a simple search for an integer within a numeric range (which is easy to do and efficient).
Genius solution given by.... bunglestink
I wasted plenty of time in researching implementation of "between" clause for string in EF. This is helpful.