I want to split my strings in Oracle based on length with space as a delimiter.
For example,
`MY_STRING="Before continuing, turn off the top title display without changing its definition:"`
My output should be
`STRING1="Before continuing, turn off the"`
`STRING2="top title display without changing"`
`STRING3="its definition:"`
The strings should be a maximum of 35 characters in length. The words after position 105 can be ignored.
It colud be done with a stored function :
create or replace FUNCTION get_part(p_value IN VARCHAR2, part in number)
RETURN VARCHAR2
IS temp VARCHAR2(1000);
BEGIN
temp := p_value;
FOR i IN 1 .. (part-1) LOOP
if (Length(temp) <35) then
return '';
ELSE
FOR j in REVERSE 1 .. 35 LOOP
if SUBSTR(temp,j,1) = ' ' then
temp := SUBSTR(temp,j+1);
EXIT;
end if;
END LOOP;
temp := SUBSTR(temp,36);
end if;
END LOOP;
if (Length(temp) <=35) then
return temp;
else
FOR j in reverse 1 .. 35 LOOP
if SUBSTR(temp,j,1) = ' ' then
return SUBSTR(temp,1,j-1);
end if;
END LOOP;
return SUBSTR(temp,1,35);
end if;
END;
usage:
select
get_part(string_value,1),
get_part(string_value,2),
get_part(string_value,3) from ( select 'Before continuing, turn off the top title display without changing its definition:' string_value from dual)
It surely will fail if there are more than 35 chars without space, i'll leave that to you
EDIT: now it should split hard after 35 chars if there are no spaces
You have tagged C# so i chose the language to answer you. I hope it helps. It perfectly splits the text.
Regarding the rules, in your case it splits the text in 3 parts.
This is the Outcome :
STR1 :"Before continuing, turn off the"
STR2 :" top title display without changing"
STR3 :" its definition:"
static void Main(string[] args)
{
const string txt = "Before continuing, turn off the top title display without changing its definition:";
var txtArr = txt.ToCharArray();
var counter = 0;
var stringList = new List<string>();
var str = string.Empty;
for (var i = 0; i < txt.Count(); i++)
{
counter++;
if (counter == 35)
{
while (txtArr[i].ToString() != " ")
{
i--;
str = str.Remove(i);
}
stringList.Add(str);
str = string.Empty;
counter = 0;
}
str = str + txtArr[i];
}
stringList.Add(str);
}
This is how I implemented the algorithm in ORACLE (PL/SQL). Ignore the error and look at the output. It returns 3 lines and works properly. Now write some extra code and modify it as you want. The error does not seem important and I have no idea what the reason is.
declare
--
txt nvarchar2(1000):='Before continuing, turn off the top title display without changing its definition:';
charc nvarchar2(1):='';
TYPE txtArrTyp IS VARRAY(1000) OF NVARCHAR2(1);
txtArr txtArrTyp :=txtArrTyp();
--
str nvarchar2(35):='';
cntr number:=0;
j number:=0;
lent number:=0;
begin
--
lent:=LENGTHB(txt);
--
for i In 1 ..lent
loop
if(txt is null )then
dbms_output.put_line('SHIT');
end if;
charc := SUBSTR(txt,i,1);
txtArr.extend;
txtArr(i):=charc;
end loop;
--
While(j>=1 or j<=lent)
loop
j:=j+1;
cntr :=cntr+1;
if(cntr = 35) then
while(txtArr(j)<>' ')
loop
j:=j-1;
end loop;
str:=substr(str,0,j);
dbms_output.put_line(str);
str:=null;
cntr:=0;
end if;
str := str || txtArr(j);
end loop;
dbms_output.put_line(str);
end;
Related
I've some integration tests written in C# code using a Oracle Database. The test project has a CreateDatabase.sql that contains the DDL to create the entire database in each test execution.
When I had only sequences and tables, I was splitting the content of this file in ";" char and executing each create statement separately, but now I've some functions and their statements contains some ; chars in it, so I can't use this approach anymore.
I've checked on .NET / Oracle: How to execute a script with DDL statements programmatically question, but it did not help.
1) If I try to execute the entire file content in a single OracleCommand, I get an error ORA-00911: invalid character because of the ; chars
2) If I try to wrap the file content in a "begin {0} end;" I get an error PLS-00103: Encountered the symbol "CREATE" when expecting one of the following: ...
3) I could try to parse the SQL file and put each statement inside a EXECUTE IMMEDIATE, but it will be harder...
Is there another option?
I'm using the Oracle.DataAccess version 4.112.3.0 to execute the commands.
EDIT
#kevinsky ask for a script, here it is a simplified example... the entire script create hundreds of objects...
CREATE SEQUENCE SQ_ARAN_SQ_ARQUIVO_ANEXO;
CREATE OR REPLACE FUNCTION UFC_SPSW_DISP_COMPOSICAO(p_id_composicao IN NUMBER) RETURN NUMBER
IS
retorno NUMBER:= 0;
numeroItens NUMBER;
temDefinicao BOOLEAN := false;
CURSOR item_cur is
select idc.itdc_sq_item_definicao_composi, idc.insu_sq_insumo, idc.comp_sq_composicao, idc.itdc_nr_coeficiente, idc.COMP_DS_COMPOSICAO, idc.comp_sq_composicao_pai
from item_definicao_composicao idc;
BEGIN
FOR item_rec IN item_cur LOOP
temDefinicao := true;
IF (item_rec.itdc_nr_coeficiente is null) THEN
RETURN null;
ELSE
IF (item_rec.insu_sq_insumo is null) THEN
numeroItens := UFC_SPSW_DISP_COMPOSICAO(nvl(item_rec.comp_sq_composicao_pai, item_rec.comp_sq_composicao));
else
retorno := retorno + 1;
END IF;
END IF;
END LOOP;
IF (temDefinicao = false) THEN
RETURN 0;
END IF;
RETURN retorno;
END;
CREATE SEQUENCE SQ_CALC_SQ_CALCULO;
I had the same prolem and solved.
Use both 'BEGIN END' and 'EXECUTE IMMEDIATE'.
This is my test (success case)
begin
EXECUTE IMMEDIATE 'create or replace procedure SP_JHKIM2 IS
begin
dbms_output.put_line(''ABC'');
end;';
end;
Here is an idea. Make the front slash / (alone on a separate line) your new standard way of terminating every statement in your script instead of relying on the semi colon. For instance, your sample script could become:
CREATE SEQUENCE SQ_ARAN_SQ_ARQUIVO_ANEXO
/
CREATE OR REPLACE FUNCTION UFC_SPSW_DISP_COMPOSICAO(p_id_composicao IN NUMBER) RETURN NUMBER
IS
retorno NUMBER:= 0;
numeroItens NUMBER;
temDefinicao BOOLEAN := false;
CURSOR item_cur is
select idc.itdc_sq_item_definicao_composi, idc.insu_sq_insumo, idc.comp_sq_composicao, idc.itdc_nr_coeficiente, idc.COMP_DS_COMPOSICAO, idc.comp_sq_composicao_pai
from item_definicao_composicao idc;
BEGIN
FOR item_rec IN item_cur LOOP
temDefinicao := true;
IF (item_rec.itdc_nr_coeficiente is null) THEN
RETURN null;
ELSE
IF (item_rec.insu_sq_insumo is null) THEN
numeroItens := UFC_SPSW_DISP_COMPOSICAO(nvl(item_rec.comp_sq_composicao_pai, item_rec.comp_sq_composicao));
else
retorno := retorno + 1;
END IF;
END IF;
END LOOP;
IF (temDefinicao = false) THEN
RETURN 0;
END IF;
RETURN retorno;
END;
/
CREATE SEQUENCE SQ_CALC_SQ_CALCULO
/
By using the /, your script remains perfectly valid if you wish to run it using SQL*Plus. But it now has the advantage that it becomes trivial to parse by statement in C# so that you can execute each statement separately without the semi colon problems.
I've used this technique in the past and it has worked well.
(Relevant reading in case you are not familiar with the use of the slash in Oracle SQL scripts: When do I need to use a semicolon vs a slash in Oracle SQL?.)
I split only on ; that was followed by some reserved words (or in the end of the file) using regex lookahead assertion.
Ex:
var statements = Regex.Split(
fileContent,
#"\s*;\s*(?=(?:CREATE|ALTER|DROP|RENAME|TRUNCATE)\s|\s*$)",
RegexOptions.IgnoreCase);
Suppose I have a MySQL table of one column: "Message". It is of type TEXT.
I now want to query all rows, but the text can be large (not extremely large but large) and I only want to get a summary of them. For example the result can be populated into a list.
Is there a way to trim the text to a specific length (say, 10 characters), and add ellipsis if the text is trimmed?
For example:
Message
-----------
12345678901234
1234567890
12345
12345678901
Query result:
1234567...
1234567890
12345
1234567...
Thanks!
select case when length(message) > 7
then concat(substring(message, 1, 7), '...')
else message end as adapted_message
from ...
to test/confirm:
SELECT CASE WHEN LENGTH('1234567890') > 7
THEN CONCAT(SUBSTRING('1234567890', 1, 7), '...')
ELSE '1234567890' END AS adapted_message
UNION
SELECT CASE WHEN LENGTH('12345') > 7
THEN CONCAT(SUBSTRING('12345', 1, 7), '...')
ELSE '12345' END AS adapted_message
Here's a simple one line solution:
IF(CHAR_LENGTH(message) > 10, CONCAT(LEFT(message, 7),"..."), message)
or...
SELECT CONCAT(LEFT(message, 7), IF(LENGTH(message)>7, "…", ""))
FROM table
You can declare a new ELLIPSIS function in order to make your query readable:
DELIMITER //
CREATE FUNCTION ELLIPSIS ( str TEXT, max_length INT )
RETURNS TEXT
BEGIN
DECLARE str_out TEXT;
IF LENGTH(str) <= max_length THEN
SET str_out = str;
ELSE
SET str_out = CONCAT(SUBSTR(str, 1, max_length-3), '...');
END IF;
RETURN str_out;
END; //
DELIMITER ;
Then you simply do:
SELECT ELLIPSIS(Message, 10);
Have a look at the MySQL string functions, documented here. You should be able to use some combination of substring and concat to achieve your desired behaviour.
My approach:
Let x be the maximum number of characters to display (therefore x + 3 dots will be the longest string displayed)
You always want LEFT(field,x)
If LENGTH(field) > x + 3, append 3 dots
Otherwise if LENGTH(field) > x, append the remainder of field
SELECT CONCAT(
LEFT(field,x),
IF(LENGTH(field) > x+3,
'...',
IF(LENGTH(field) > x,
MID(field,x+1,LENGTH(field)),
''
)
)
) FROM table
288007 327920 374740 000368 044575 082865 680798
717374 755879 811106 855460 920577 953515 996819 ......
I have a string containing thousands of 6-digit numbers and I want to extract the Nth numbers after Nth number with the help of regular expression.
Let say I need to extract Three numbers after the 4th number then The result should be 044575 082865 680798.
another example If I need to extract 2 numbers after the 10th number then the result should be 855460 920577.
I don't know is this possible with regex, I think FOR EACH statement may be use in my case.
I am only able to extract each six digits number with the code below.
Dim NumberMatchCollection As MatchCollection = Regex.Matches("String containing numbers", "(?<!\d)\d{6}(?!\d)")
For Each NumberMatch As Match In NumberMatchCollection
Dim ItemNumber As String = NumberMatch.Value
Next
Edited:
I can not guarantee that every separator character will be a single space, a double space, a tab or something else. I can just guarantee that the number length always will be 6 which will be separated by space(s) or tab(s).
Wouldn't this be simpler using maths?
Three numbers after the 4th number, is chars (7 * 4) + (7 * 3)
To expand on my comment. This assume that the actual data are divided equaly.
If each number have 6 digits with a space in between. Then the position of the 4th number will be (6+1)*4 and if you want 3 numbers than you just need to fetch (6+1)*3 amount of characters.
Dim str As String
str = "288007 327920 374740 000368 044575 082865 680798 717374 755879 811106 855460 920577 953515 996819"
Dim startingNumber As Integer = 4
Dim amountToFetch As Integer = 3
' 7 = [size of each number] + [delimiter length]
' 7 = 6 + 1
Console.WriteLine(str.Substring(7 * startingNumber, 7 * amountToFetch))
Console.ReadLine()
If you would like a regex and c# solution, the following code does the 3 numbers after 4th number example.
var st = #"288007 327920 374740 000368 044575 082865 680798
717374 755879 811106 855460 920577 953515 996819";
var pattern = #"^(\d+\s+){4}((?<x>\d+)\s+){3}";
var matches = Regex.Matches(st,pattern,RegexOptions.Singleline);
foreach (Capture m in matches[0].Groups["x"].Captures)
Console.WriteLine("value={0}", m.Value);
(Edit: removed one group per comment below)
You can .Split() the string and use LINQ extension methods on the resulting array:
// some test data...
var rand = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 10000; i++)
{
sb.Append(i.ToString("000000") + ((rand.Next(5)==1) ? " ": "\t"));
}
string s = sb.ToString();
string portion = string.Join(" ", s.Split(new [] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries).Skip(10).Take(3));
Console.WriteLine(portion); // outputs "000011 000012 000013"
Note: for the first number you would .Skip(0).
But if your string is in the rigid format you show (asuming the variable numbers of spaces are typos, thanks #ErikE), Coenraad's method of calculating where the start of the required string is and how many characters to take would be more efficient. I'll leave it to Coenraad to expand on that answer as it would not be fair to possibly take the points.
I tried and tried to make the regex method be consistently fast, but I found it depended strongly on which numbers you want to retrieve:
For anyone wanting to test that, I put a default Chart on a Form and used this code:
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Windows.Forms.DataVisualization
Imports System.Windows.Forms.DataVisualization.Charting
Public Class Form1
Sub DoStuff()
Dim ser1 As New Series With {.Name = "String.Split"}
Dim ser2 As New Series With {.Name = "RegEx"}
Dim sb As New StringBuilder()
For i As Integer = 1 To 10000
sb.Append(i.ToString("000000") + " ")
Next
Dim s As String = sb.ToString()
Dim sw As New Stopwatch()
Dim itemsToTake As Integer = 50
For firstItem = 1 To 9000 Step 100
sw.Restart()
Dim portion As String = String.Join(" ", s.Split({" "c}, StringSplitOptions.RemoveEmptyEntries).Skip(firstItem - 1).Take(itemsToTake))
sw.Stop()
ser1.Points.AddXY(firstItem -1, sw.ElapsedTicks)
Dim pattern = "^(?:\d+\s+){" + (firstItem - 1).ToString() + "}((\d+)\s+){" + itemsToTake.ToString() + "}"
Dim re = New Regex(pattern)
sw.Restart()
Dim matches = re.Matches(s)
Dim cs = matches(0).Groups(0).Captures
sw.Stop()
ser2.Points.AddXY(firstItem - 1, sw.ElapsedTicks)
Next
Chart1.Series.Clear()
Chart1.Series.Add(ser1)
Chart1.Series(0).ChartType = SeriesChartType.Line
Chart1.Series.Add(ser2)
Chart1.Series(1).ChartType = SeriesChartType.Line
Chart1.ChartAreas(0).AxisX.IsMarginVisible = False
Chart1.ChartAreas(0).AxisX.Title = "First item to retrieve"
Chart1.ChartAreas(0).AxisY.Title = "Time taken"
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DoStuff()
End Sub
End Class
I know that this looks like a noob question, but I don't know what's going on that I keep getting wrong values when using the .substring. I have used it in java like this
text = "stackoverflow"
text1 = text.substring(start, end);
where start can be any number < text.length and it works perfectly. Example
text1 = text.substring(9, text.length);
Should give me "flow" but if I try that exact code in Visual Basic it gives me the full text "stackoverflow" so... what am I doing wrong?
Here's my code for it:
TextBox2.Text = bin.Substring(9, text.length)
The second parameter is not end position, but length of desired output string:
'Declaration
Public Function Substring ( _
startIndex As Integer, _
length As Integer _
) As String
This should return "flow":
TextBox2.Text = bin.Substring(9,4)
In Visual Studio I can jump from/to opening/closing brace with the Control+] shortcut.
Is there a shortcut that will allow me to delete both braces at once (maybe with a macro/extension)?
e.g.
foo = ( 1 + bar() + 2 );
When I am on the first opening brace I would like to delete it and its matching brace to get
foo = 1 + bar() + 2;
There isn't an inherent way to do this with Visual Studio. You would need to implement a macro in order for this.
If you choose the macro route you'll want to get familiar with the Edit.GoToBrace command. This is the command which will jump you from the current to the matching brace. Note it will actually dump you after the matching brace so you may need to look backwards one character to find the element to delete.
The best way to implement this as a macro is to
Save the current caret position
Execute Edit.GoToBrace
Delete the brace to the left of the caret
Delete the brace at the original caret position
Make a macro to press Ctrl+] twice and then backspace, then Ctrl+minus and a delete.
The Ctrl+minus moves the cursor back in time.
It's not quite as simple as JaredPar suggested but I'm no Macro expert either.
This works for (), {} and []
Sub DeleteMatchingBrace()
Dim sel As TextSelection = DTE.ActiveDocument.Selection
Dim ap As VirtualPoint = sel.ActivePoint
If (sel.Text() <> "") Then Exit Sub
' reposition
DTE.ExecuteCommand("Edit.GoToBrace") : DTE.ExecuteCommand("Edit.GoToBrace")
If (ap.DisplayColumn <= ap.LineLength) Then sel.CharRight(True)
Dim c As String = sel.Text
Dim isRight As Boolean = False
If (c <> "(" And c <> "[" And c <> "{") Then
sel.CharLeft(True, 1 + IIf(c = "", 0, 1))
c = sel.Text
sel.CharRight()
If (c <> ")" And c <> "]" And c <> "}") Then Exit Sub
isRight = True
End If
Dim line = ap.Line
Dim pos = ap.DisplayColumn
DTE.ExecuteCommand("Edit.GoToBrace")
If (isRight) Then sel.CharRight(True) Else sel.CharLeft(True)
sel.Text = ""
If (isRight And line = ap.Line) Then pos = pos - 1
sel.MoveToDisplayColumn(line, pos)
sel.CharLeft(True)
sel.Text = ""
End Sub
Then add a shortcut to this macro in VS.