Compare two strings with version numbers in them - c#

I need to compare 2 strings in SQL that are in the following format:
name_1.2.3
Can someone explain how to compare them correctly? How do you compare non-int value?
I had an issue in the past when you compare strings with numbers: the comparison is done literally. This gets me into problems like where "name_1.5.3" is "bigger" than "name_1.20.3", because it probably compares char by char, but that's not what I'm trying to achieve.
Some SQL or C# example will be really helpful.
Part of my code in C#:
for (int i = 0; i < PDCs.Count(); i++)
{
if (foundInstallOrder)
{
string justNumbers = new String(PDCs[i].Where(Char.IsDigit).ToArray());
a = Convert.ToInt32(justNumbers);
if (projPDCs.Count() <= i)
{
b = 0;
}
else
{
justNumbers = new String(projPDCs[i].Where(Char.IsDigit).ToArray());
b = Convert.ToInt32(justNumbers);
}
if (a > b )//compare the currentPDC with the projfound, if result is 1 => that means current is within range
{
foundInstallOrder = true;
arrayInstallOrder.Add(lines);
break;
}
else if (a == b) //numbers are equal
{
foundInstallOrder = true;
}
else //lower
{
foundInstallOrder = false;
break;
}
}
}
As you can see this is too complex, it is working but I'm trying to find a better solution.

Although I do suggest that you keep those numbers in a separate column in the table for easier/faster querying, you can also solve this by adding regular expression support to SQL server and use it in your query.
SETUP
Create a .NET Class Library, named 'SqlExtensions', containing this code:
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;
namespace SqlExtensions
{
public static partial class UserDefinedFunctions
{
[SqlFunction]
public static SqlString RegexGroup(SqlString input, SqlString pattern, SqlString name)
{
Regex regex = new Regex(pattern.Value, RegexOptions.Singleline);
Match match = regex.Match(input.Value);
return match.Success ? new SqlString(match.Groups[name.Value].Value) : SqlString.Null;
}
}
}
Build in Release.
Then, add the class library to SQL server using SQL Server Management Studio:
Connect to database server
Expand Databases
Expand the database you're working on
Expand Programmability
Right-click Assemblies
Add new assembly by browsing to the Class Library DLL created above
Add the Scalar-valued function to the database using SQL Server Management Studio:
Select the database you're working on
Click New Query and execute this query:
CREATE FUNCTION [dbo].[RegexGroup] (#input nvarchar(max), #pattern nvarchar(max), #name nvarchar(max))
RETURNS nvarchar(max)
AS EXTERNAL NAME [SqlExtensions].[SqlExtensions.UserDefinedFunctions].[RegexGroup];
If .NET execution is not enabled, execute a new query:
Select the database you're working on
Click New Query and execute this query
sp_configure #configname=clr_enabled, #configvalue=1;
GO
RECONFIGURE
GO
QUERY
SELECT
*,
dbo.RegexGroup(Name, '^(?<n>[^_]*)_', 'n') AS n,
CONVERT(INT, dbo.RegexGroup(Name, '_(?<major>[0-9]+)\.', 'major')) AS Major,
CONVERT(INT, dbo.RegexGroup(Name, '\.(?<minor>[0-9]+)\.', 'minor')) AS Minor,
CONVERT(INT, dbo.RegexGroup(Name, '\.(?<build>[0-9]+)$', 'build')) AS Build
FROM dbo.Table_1;
RESULT
name_1.5.3 name 1 5 3
name_1.20.3 name 1 20 3
name_5.19.2 name 5 19 2
name_0.6.3 name 0 6 3
name_1.20.2 name 1 20 2

Tokenise the string using String.split('.') and then int.parse each "part string". This allows you to check major, minor, and build as required.

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
List<NameVersion> lst = new List<NameVersion>();
lst.Add(new NameVersion("name_1.2.3"));
lst.Add(new NameVersion("name_1.20.3"));
lst.Sort(delegate(NameVersion x, NameVersion y)
{
int i = x.a.CompareTo(y.a);
if (i == 0)
i = x.b.CompareTo(y.b);
if (i == 0)
i = x.c.CompareTo(y.c);
return i;
});
}
}
public class NameVersion
{
public string Name = "";
public int a, b, c;
public NameVersion(string text)
{
Name = text;
string[] sa = text.Split('_');
string[] sb = sa[1].Split('.');
a = Convert.ToInt32(sb[0]);
b = Convert.ToInt32(sb[1]);
c = Convert.ToInt32(sb[2]);
}
}

Related

Trying to create user defined function `LIKE` using SQLite

I am (trying to) using the sqlite-net-pcl package version 1.8.116, to create a user defined function in C#.
I tried to start simple by creating a function to reverse a string, like REVERSE
When i try the query: SELECT 1,'abc','def',
I am expecting the output to be: 1:abc:fed
But the output is: 0:abc:System.Char[], So I must have done something wrong...
Can anyone give a tip? because I am pretty clueless right now... 😉
My code:
SQLiteConnection db = new SQLiteConnection("test.db");
// https://sqlite.org/c3ref/create_function.html
sqlite3 dbhandle = db.Handle;
string function = "myReverse";
int narg = 1;
int eTextRep = SQLitePCL.raw.SQLITE_UTF8 | SQLitePCL.raw.SQLITE_DETERMINISTIC;
int ok = SQLitePCL.raw.sqlite3_create_function(dbhandle, function, narg, eTextRep, myReverse);
//Console.WriteLine($"OK:{ok}");
SQLiteCommand cmd = db.CreateCommand("SELECT 1 as id, 'abc' as t, myReverse('def') as revt");
var result = cmd.ExecuteQuery<Record>();
Console.WriteLine($"{result[0].id}:{result[0].t}:{result[0].revt}");
and:
static void myReverse(sqlite3_context ctx, object user_data, sqlite3_value[] args)
{
SQLitePCL.utf8z utf8z = raw.sqlite3_value_text(args[0]);
string input = utf8z.utf8_to_string();
//Console.WriteLine($"INPUT:{input}"); // This shows INPUT:def
char[] charArray = input.ToCharArray();
Array.Reverse(charArray);
//raw.sqlite3_result_text(ctx, utf8z.FromString(charArray.ToString())); // see: comments...
raw.sqlite3_result_text(ctx, utf8z.FromString(new string(charArray)));
}
Record:
public class Record
{
public int id { get; set; }
public string t { get; set; }
public string revt { get; set; }
}
Above was my first try, finally I would like to have an implementation of the function LIKE, like is mentioned in paragraph 5 "The infix LIKE operator is implemented by calling the application-defined SQL functions"
Currently returning a string is working, but I am looking for something like raw.sqlite3_result_boolean, or raw.sqlite3_result_logical, because the return value for the function LIKE is a boolean.

How to properly get index of a custom class list <CustomClass> and check if the list value in the selected index is equal to int [c#]

I have created a custom class list which is filled from the result of a query. Specifically, me query returns (int, timestamp) 2 columns -> 2 values.
public class SucessfulCompletion
{
public int Result;
public string Timestampvalue;
public SucessfulCompletion(int result, string timestamp) => (Result, Timestampvalue) = (result, timestamp);
}
public List<SucessfulCompletion> SuccessfulCalculationsTimestamp(string connectionstring)
{
List<SucessfulCompletion> QueryListResult = new List<SucessfulCompletion>();
using (SqlConnection sqlConnection = new SqlConnection(connectionstring))
{
var query_table_timestamp = (
#"SELECT CASE
WHEN t.STATUS = 'SUCCESS' AND t.STATUS_DESCRIPTION = 'ALL QUERIES COMPLETED SUCCESSFULLY' THEN 1
ELSE 0
END SuccessfulCompletion, t.TIMESTAMP
FROM (SELECT TOP (1) l.TIMESTAMP, l.STATUS, l.STATUS_DESCRIPTION
FROM LOG_DETAILS l
ORDER BY 1 DESC) t");
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand(query_table_timestamp, sqlConnection))
{
using (SqlDataReader reader = sqlCommand.ExecuteReader())
{
while (reader.Read())
{
QueryListResult.AddRange(new List<SucessfulCompletion>
{
new SucessfulCompletion(reader.GetInt32(0), reader.GetDateTime(1).ToString())
});
}
reader.Close();
}
sqlCommand.Cancel();
}
}
return QueryListResult;
}
The code to create the custom class list was taken from this SO question
So the QueryListResult would be like [1, "2020-10-04 HH:MM:SS"]
Now I want to make an if statement to check if the first index of the QueryListResult is ether 0 or 1.
List<SucessfulCompletion> reportsucessfulcompletion = new List<SucessfulCompletion>();
reportsucessfulcompletion = SuccessfulCalculationsTimestamp(SQLServerConnectionDetails());
if (reportsucessfulcompletion[0]=1) //my problem is here
{
//Enable is only if successful
PreviewCalculationsButton.IsEnabled = true;
PreviewReportButton.IsEnabled = true;
//add textbox of success
SQLSuccessfulTextCalculations.Text = String.Format("Completed On: {0}", reportsucessfulcompletion[1]);
}
else
{
//add textbox of fail
SQLFailedTextCalculations.Text = String.Format("Failed On: {0}", reportsucessfulcompletion[1]);
}
In the if statement I get an error
Cannot implicitly convert type 'int' to 'TestEnvironment.MainWindow.SucessfulCompletion'
I know it may be a silly question for someone experienced with C# but I am a newbie, so I would appreciate your help. Please inform me in the comments if the theme of the question is a duplicate one I will close the question.
You're comparing an jnstance of your class to a number.
These are different things.
And one equal sign sets rather than compares
Try
If ( reportsucessfulcompletion[0].Result == 1)
You should make these properties rather than variables.
I also recommend Dapper as a "micro" orm very close to the ado metal, but which saves a fair bit of coding whilst implementing best practice for you.

Using DacFX how do I comment out a statement in a TsqlFragment/TSqlScript

If I have a TqlFragment or TsqlScript with a set of statements, how do I comment out a specific statement using DacFX? There is no CommentStatement or anything like that. How do I replace that statement in the syntax tree with a commented version of it?
I know this could be accomplished with pure text editing or regex but at this time I am using the DacFx visitor pattern to scan for certain statements. So I need to remain using that constraint.
As you have found out there there is no script dom statement for a comment, what we do have is a token stream which is like a statement but at a slightly lower level.
I'm not 100% sure what it is you are after, but if I assume that you have a script and want to comment a statement out then it is possible like this. You said that you needed to stay within the visitor pattern but a comment won't be returned by a visitor so this should do what you want and you can either re-parse the script again or just leave it as sql and do what you like with it:
static void Main(string[] args)
{
var sqlText = #"
create procedure something
as
select 100;
select 200
exec sp_who2;
";
var sql = new StringReader(sqlText);
var parser = new TSql140Parser(false);
IList<ParseError> errors;
var script = parser.Parse(sql, out errors);
var visitor = new visitor();
script.Accept(visitor);
TSqlParserToken startComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "/*");
TSqlParserToken endComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "*/");
var newScriptSql = "";
for (var i = 0; i < script.LastTokenIndex; i++)
{
if (i == visitor.Statement.FirstTokenIndex)
{
newScriptSql += startComment.Text;
}
if (i == visitor.Statement.LastTokenIndex)
{
newScriptSql += endComment.Text;
}
newScriptSql += script.ScriptTokenStream[i].Text;
}
script = parser.Parse(new StringReader(newScriptSql), out errors);
Console.WriteLine(newScriptSql);
}
class visitor : TSqlFragmentVisitor
{
public ExecuteStatement Statement;
public override void Visit(ExecuteStatement s)
{
Statement = s;
}
}
}
What this does is to pass the procedure above (put it in a code file or something) and uses the visitor pattern to find the exec, once we have that we take a note of where the statement started and ended in the script token stream, then we iterate through the tokens turning them into t-sql, when we get to our statement we insert a startComment, after the statement we insert an endToken (note it is before the ; so you might want to add some extra logic to handle that).
I tried just altering the ScriptTokenStream of the original script but even though it allows you, if you do anything with it then it is ignored so consider it immutable (unless i'm missing something). This doesn't work as far as I can tell but would make the roundtrip to text/parse unnessesary if it did:
/* - changes the token stream but if you use a generator on script, they don't appear
script.ScriptTokenStream.Insert(visitor.Statement.LastTokenIndex, endComment);
script.ScriptTokenStream.Insert(visitor.Statement.FirstTokenIndex, startComment);
*/
Hope it helps!
ed
Ed, I re-worked your answer code a bit.
modified the visitor to handle multiple statements
Changed the code to loop backwards through the visitor statements so the positions would not get mangled
Revised the code to insert the comment tokens
Observations:
As you deduced the sql generator happily strips out comments when trying to write out the token stream.
Adding the comment in this manner excludes the semi colon at the end of the lines from the comment.
Code:
class Program
{
static void Main(string[] args)
{
var sqlText = #"
create procedure something
as
select 100;
select 200
exec sp_who;
exec sp_who2;
";
var sql = new StringReader(sqlText);
var parser = new TSql140Parser(false);
var script = parser.Parse(sql, out IList<ParseError> errors);
var visitor = new ExecVisitor();
script.Accept(visitor);
TSqlParserToken startComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "/*");
TSqlParserToken endComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "*/");
var newScriptTokenStream = new List<TSqlParserToken>(script.ScriptTokenStream);
for(var i = visitor.Statements.Count - 1; i >= 0; i--)
{
var stmt = visitor.Statements[i];
newScriptTokenStream.Insert(stmt.LastTokenIndex, endComment);
newScriptTokenStream.Insert(stmt.FirstTokenIndex, startComment);
}
var newFragment = parser.Parse(newScriptTokenStream, out errors);
Console.WriteLine(GetScript(newFragment.ScriptTokenStream));
}
private static string GetScript(IList<TSqlParserToken> tokenStream)
{
var sb = new StringBuilder();
foreach(var t in tokenStream)
{
sb.Append(t.Text);
}
return sb.ToString();
}
}
class ExecVisitor : TSqlFragmentVisitor
{
public IList<ExecuteStatement> Statements { get; set; } = new List<ExecuteStatement>();
public override void Visit(ExecuteStatement s)
{
Statements.Add(s);
}
}
Results:
create procedure something
as
select 100;
select 200
/*exec sp_who*/;
/*exec sp_who2*/;

How to handle SQL key-value tables from my application?

So I have a simple key-value table BillTypes in my SQL Server. It looks like this:
+----+-------------+
| Id | Description |
+----+-------------+
| 1 | OK |
| 2 | Missing |
| 3 | Maximum |
| 4 | Minimum |
| 5 | Error |
+----+-------------+
The table holds different types of bill states. I have another table Items with a foreign key BillType, referring to Id in the previous table.
+----------+------+--------+
| IdItem | Cash |BillType|
+----------+------+--------+
| * | * | * |
| * | * | * |
| * | * | * |
| ... |... | ... |
+----------+------+--------+
Then, in my application I get data from the second table via a stored procedure. In this sp, I am not sure if should select the BillType as it is, a tinyint, or use an innerjoin to return the description instead. The thing is, my application has some logic depending on the BillType, i.e. rows will be processed differently depending on the BillType. Should my logic depend on the Id or the description??
In code, should I have something like this?
DataTable dt = ...//call stored procedure that selects the BillType id
foreach(Datarow r in dt)
{
if(r["BillType"] == 3) ... //use logic
}
or this?
DataTable dt = ...//call stored procedure that selects the BillType description
foreach(Datarow r in dt)
{
if(r["BillType"] == "Maximum") ... //use logic
}
I am currently using the second way, with descriptions, because I think it makes code more readable. I am however worried about the possibility that someone might change the descriptions and break my application logic. On the other hand, using ids would be less readable(though i could work around this with an enum) and would be invulnerable to description changes, but still if someone changed an id, things would break.
To be honest, we don't expec anyone to mess around with the first table, since we made it specially for this application. Still, I am new to SQL and I am trying to learn the best practices of application-SQL interaction, so I would like to know how experienced people do it
Please consider this a comment rather than an answer (I don't yet have enough rep to comment).
I prefer the latter (as you are doing), where I rely on the description.
If your IDs are SQL indentity fields (which auto-increment) Consider the following scenario:
You build your app and database in DEV environment
The production deployment gets back out and re-run
Now, rather than IDs 1,2,3 you have 4,5,6
Generally speaking, it makes more sense to rely on IDs instead of values. After all, IDs should be unique, whereas values can have duplicates/change. Although you are not expecting any changes to the BillTypes table, I wouldn't use the string values, as you suggested.
Since updates/deletes/inserts can still happen in the BillTypes table and you want to be prepared as well as have readable code, the easiest thing to do would be if you define your own enum as such:
public Enum BillTypesEnum
{
OK = 1,
Missing = 2,
Maximum = 3,
Minimum = 4,
Error = 5
};
If something happens and you need to add something to the database, you do it and then change the Enum. It would also work if you delete the "Error" entry and insert "Average". You would not get Average=5 but probably Average=6 or something higher. The enum structure lets you define the values with no problem.
Then you can use it all over your code, not just in this single instance. Your code is modified below:
DataTable dt = ...//call stored procedure that selects the BillType id
foreach(Datarow r in dt)
{
if(r["BillType"] == (int)BillTypesEnum.Maximum) ... //use logic
}
This makes the code more readable then before, you dont have to hard-code the values all over your code but just in one place. And if it happens,that you will not be changing the database, then it's just a plus.
Another option would be to do the spGetAllBillTypes (select * from tblBillTypes) and then internally create a dictionary.
Dictionary<string,int> dictBillTypes = new Dictionary<string,int>();
int = billTypeId
string = billTypeText
When you retrieve your data from the child table (the one with the foreign key), you retrieve your billtypetext.
You can then do it like this:
DataTable dt = ...//call stored procedure that selects the BillType id
foreach(Datarow r in dt)
{
if(r["BillType"] == dictBillTypes["Maximum"]) ... //use logic
}
...but this is still an incomplete solution, because here, you rely on two things:
1. that the billTypeText will not change (if it does, you have to change it in the code)
2. that the billTypeText will not have duplicates (otherwise you will get an exception about duplicate key value in the dictionary)
Another option is to turn it around:
Dictionary<int,string> dict = new Dictionary<int,string>();
and do the search based on the value instead of the key. But that makes the code less readable and you dont really gain a lot from this.
If you are worried about the ID changing, I would recommend creating an Abstract Class to hold the ID values. This can be referenced like an Enum, and it will look up the ID on the first call, and cache the result:
public abstract class BillType
{
private static readonly string c_ok = "ok";
private static readonly string c_missing = "missing";
private static readonly string c_maximum = "maximum";
private static readonly string c_minimum = "minumum";
private static readonly string c_error = "error";
private static int? _ok = null;
private static int? _missing = null;
private static int? _maximum = null;
private static int? _minimum = null;
private static int? _error = null;
public static int OK
{
get
{
if (_ok == null)
_ok = GetBillTypeID(c_ok);
return (int)_ok;
}
}
public static int Missing
{
get
{
if (_missing == null)
_missing = GetBillTypeID(c_missing);
return (int)_missing;
}
}
public static int Maximum
{
get
{
if (_maximum == null)
_maximum = GetBillTypeID(c_maximum);
return (int)_maximum;
}
}
public static int Minimum
{
get
{
if (_minimum == null)
_minimum = GetBillTypeID(c_minimum);
return (int)_minimum;
}
}
public static int Error
{
get
{
if (_error == null)
_error = GetBillTypeID(c_error);
return (int)_error;
}
}
private static int GetBillTypeID(string identifier)
{
using (SqlConnection conn = new SqlConnection("your connection string")
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("spGetBillTypeId", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("Description", identifier);
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
}
}
I would also have a Stored Procedure on your database to do a lookup for that ID:
Create Procedure spGetBillTypeId (#Description Varchar (20))
As Begin
Select Id
From BillTypes
Where Description = #Description
End
With this, you can simply call BillType.Missing and it will pull the Id of missing, cache it, and return 2.
(Note, most of this was copy/paste, so mind random errors I didn't catch).
If you don't expect the values to change, however, and you are okay with making code changes if that does happen, you can simply make an Enum:
public enum BillType
{
OK = 1,
Missing = 2,
Maximum = 3,
Minimum = 4,
Error = 5
}

How can I return an int value from a SQLite query?

I've got this SQLite method:
internal static bool DataContainsAnyLocationInfo()
{
int latCount = 0;
using (var db = new SQLite.SQLiteConnection(App.DBPath))
{
string sql = "SELECT COUNT(Id) FROM PhotraxBaseData WHERE latitude > 0.0";
latCount = db.Query<PhotraxBaseData>(sql);
}
return (latCount > 0);
}
...that won't compile due to: "Cannot implicitly convert type 'System.Collections.Generic.List' to 'int'"
I just want an int back, such as 0 or 1 or 42, not an entire List.
I do have sqlite-net installed, if there's a way to do it with that that would be easier, not excluding a LINQy way, if that'll work.
UPDATE
Maybe I'd be better off doing it this way instead:
internal static bool DataContainsAnyLocationInfo(List<PhotraxBaseData> _pbd)
{
bool locInfoFound = false;
foreach (PhotraxBaseData pbd in _pbd)
{
if (pbd.latitude > 0.0)
{
locInfoFound = true;
break;
}
}
return locInfoFound;
}
UPDATE 2
John Woo's code compiles, but my SQL must be bad, because, although I have many records in PhotraxBaseData where latitude is greater than 0, the function returns false.
And when I run either of these queries directly in LINQPad4:
SELECT COUNT(Id) FROM PhotraxBaseData WHERE latitude > 0.0
SELECT COUNT(Id) FROM PhotraxBaseData WHERE (latitude > 0.0)
I get:
) or end of expression expected
; expected
Note: latitude is a double.
UPDATE 3
So now I've got two ways that work (my non-database-touching code above, and John Woo's "ExecuteScalar" code), but I don't know which one is preferable. They are both faster than a famished hummingbird zipping toward newly-discovered nectar.
from their documentation, you can use this code:
latCount = db.ExecuteScalar<int>(sql);
since it will return a row other than a list. I don't know why you are getting that error but your query will run on SQLFiddle.
SQLFiddle Demo

Categories

Resources