Orderby with a string that contains part numbers and part letters - sql-server

I am using MSSQL ADO.NET and I need to sort order by employer code numercially although each order code has 2X letters in front of it.
For example:
HP1234
HP1233
HP1236
Essentially I am trying to seperate the numbers from the letters and order by either asc or desc just with the numbers to give some order. I wondered if anyone can advise on a good approach to do this withing an orderby clause.

SELECT *
FROM YourTable
ORDER BY CONVERT(int, RIGHT(employer_code, LEN(employer_code) - 2))

Note I did not add any erro checking, but this would help if the pattern changed etc. Even though this is C#, you can convert the code to VB.
void Main()
{
var arrayOfString = new string []{"HP1234","HP1233","HP1236"};
var pat =#"(?<name>\d+)";
Regex r = new Regex(pat, RegexOptions.IgnoreCase);
var orderedItems = arrayOfString.OrderBy(x=>{
var match = r.Match(x);
return match.Groups["name"].Value;
});
foreach (var item in orderedItems)
{
Console.WriteLine(item);
}
}
VB Code
Private Sub Main()
Dim arrayOfString = New String() {"HP1234", "HP1233", "HP1236"}
Dim pat = "(?<name>\d+)"
Dim r As New Regex(pat, RegexOptions.IgnoreCase)
Dim orderedItems = arrayOfString.OrderBy(Function(x) GetSortOrder(x,r))
For Each item As string In orderedItems
Console.WriteLine(item)
Next
End Sub
Function GetSortOrder(x , r)
Dim match = r.Match(x)
Return match.Groups("name").Value
End Function

Related

How do I sort an array of different times?

So I have an array of various timeslots, but I want to sort out the times from the earliest ones to the latest ones, however, I also want to delete any elements within the array that has the time value of 12:00am.
Appointment(0) = #10:45:00 AM#
Appointment(1) = #12:00:00 AM# 'My actual has up 80 elements of different timeslots but I'm using 3 elements as an example
Appointment(2) = #12:00:00 AM#
Is there anyone who can help me with this problem?
Using a string array is not a good idea if that's what you used just use a list of DateTime and the Sort method.
Dim list As List(of DateTime) = new List(of DateTime)
For Each val As String In Appointment
list.Add(val.replace(" ",""))
Next
list.Sort(New Comparison(Of Date)(Function(x As Date, y As Date) y.CompareTo(x)))
Nothing in .Net for removing specific items from array, but it can be "simplified" a bit with LINQ:
Dim Appointment = {#10:45#, #12AM#, #12AM#}
Appointment = (From a In Appointment Where a <> #12AM# Order By a).ToArray
or
Appointment = Appointment.Except({#12AM#}).ToArray
Array.Sort(Appointment)
A bit more efficient alternative can be to use generic collection like List or SortedSet instead:
Dim list = New List(Of Date) From {#10:45#, #12AM#} ' or Dim list = Appointment.ToList
list.Add(#12AM#)
list.RemoveAll(Function(a) a = #12AM#)
list.Sort()

How do i pass values from view model to controller without use of list?

Continuing from my previous question, i pass the id value from 'Lookup' controller to another controller method 'Charges'.
Function Charges(id As String) As ActionResult
Dim Patient As New LookupVM()
Patient.GetHistory(paid)
Return View(Patient)
End Function
In the view model, i have the GetHistory(id) list method, and listProcedure is the list holding the values that i want to pass back to the controller to be displayed in the view.
Public Property listProcedure As List(Of LookupVM)
Public Function GetHistory(id As String) As List(Of LookupVM)
Using db As New DbContext()
' SQL query
'====================
Dim idd = New SqlParameter("#id", "%" & id & "%")
Dim query As String = "select p.id id, pv.pvid pvid
from patient p
join patient_visit pv on p.id = pv.id
where p.paid like #id"
Dim Results = db.Database.SqlQuery(Of LookupVM)(query, idd).ToList()
For Each item In Results
Dim pvid = New SqlParameter("#pvid", "%" & item.pvid & "%")
Dim query2 As String = "select po.remarks remarks, po.entereddt entereddt, po.Consultant Consultant
from procedure_order po
join procedures p on po.pdid = p.pdid
where po.pvid like #pvid
order by po.entereddt desc"
Dim Results2 = db.Database.SqlQuery(Of LookupVM)(query2, pvid).ToList()
For Each item2 In Results2
Dim ProcedureList2 As New LookupVM()
ProcedureList2.remarks = item2.remarks
ProcedureList2.entereddt = item2.entereddt
ProcedureList2.ForConsultant = item2.ForConsultant
listProcedure.Add(ProcedureList2)
Next
Next
Return listProcedure
End Using
End Function
I'm wondering if i'm able to pass those values to the controller through something else other than a list. if possible, what could it be? Because if its a list, i can't display them on the page without the use of foreach loop.
Another question is, is my code conceptually correct or wrong? Because when i run, it took me 5 minutes to display a list of 72 values which involves the code above. I think it took way too long. Is there any way to make it load faster?
About the List, there is nothing wrong with doing For Each in a View. If it is needed then it is needed, and no reason to avoid it.
Some query tips (it is hard to do better for lack of details):
If your paid and pvid columns are INT, then do not query them using LIKE #id but using = #id (without % in it). Even if they are (N)VARCHAR then using LIKE would need to be justified and explicit. If done by accident then output will often be wrong.
LIKE '%value%' causes indexes to be ignored, possibly leading to bad performance.
Make sure that you have indexes on the columns that you use to JOIN on.
Try combining the two queries into one, to limit the number of .NET/DB roundtrips.

data to structure in vb.net

I have this structure in Visual Basic .net
Private Structure queueT
Public name As String
Public parent As String
Public limitat As Integer
Public maxlimit As Integer
And this is the data I get, which I have stored in a one dimensional String array (just pasting 3):
!re=.id=*10000B0=name=Up-PBX=parent=Up=packet-mark=pack_pbx=limit-at=256000=queue=PCQ_Up=priority=1=max-limit=512000=burst-limit=0=burst-threshold=0=burst-time=00:00:00=invalid=true=disabled=true=comment=PBX
!re=.id=*10000C7=name=Down_Mauro=parent=Down=packet-mark==limit-at=315000=priority=8=max-limit=5000000=burst-limit=0=burst-threshold=0=burst-time=00:00:00=invalid=false=disabled=true
!re=.id=*10000C8=name=Down_Mauro_dom=parent=Down_Mauro=packet-mark=pack_Mauro_dom=limit-at=40000=queue=PCQ_Down=priority=2=max-limit=400000=burst-limit=0=burst-threshold=0=burst-time=00:00:00=invalid=false=disabled=true
I need to store the information in my structure so it looks like this:
queueT.name = UP-PBX
queueT.parent = UP
queueT.limitat = 256000
queueT.maxlimit = 512000
I only need the information mentioned above, not the rest. How can I do that?
Thanks!!
It looks like it's a key/value pairing after the first value. From your sample something like this could work.
Private Function queueTParse(item As String) As queueT
Dim queueValues = item.Split("=")
Dim queueTItem = New queueT
For i As Integer = 1 To queueValues.Length - 1 Step 2
Select Case queueValues(i)
Case "name"
queueTItem.name = queueValues(i + 1)
Case "parent"
queueTItem.parent = queueValues(i + 1)
Case "limit-at"
queueTItem.limitat = queueValues(i + 1)
Case "max-limit"
queueTItem.maxlimit = queueValues(i + 1)
End Select
Next
Return queueTItem
End Function

Parse XML with Linq

I have the following XML document which I would like to parse into a DataSet.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Response Status="OK">
<Item>
<Field Name="ID">767147519</Field>
<Field Name="Name">Music</Field>
<Field Name="Path">Family\Music</Field>
<Field Name="Type">Playlist</Field>
</Item>
</Response>
I am wanting to get the attribute values for ID, Name, and Path.
The following is what I have attempted:
Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Item") Select c
For Each result In name
Dim str1 = result.Attribute("ID").Value 'Returns Nothing and causes a validation error
Dim str2 = result.Value ' Returns all the attribute values in one long string (ie "767147519MusicFamilyPlaylist")
Next
Any help would be greatly appreciated.
Thanks,
Matt
EDIT:
Following one of the answers below, I have been attempting to implement an anonymous type in my Linq, however I keep encountering the error
Object reference not set to an
instance of an object.
My updated code is as follows:
Dim name = From c In loaded.Descendants("Item") Select c Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
Dim Id As String = String.Empty
For Each result In name
Id = result.sID
Next
I think this error means that the attribute ("ID") cannot be located, so I have attempted several variations of this with similar results.
Is anyone able to identify where I am going wrong and point me in the right direction.
Thanks,
Matt
You can use XPath:
Dim data = From item In loaded.Descendants("Item")
Select
ID = item.XPathSelectElement("Field[#Name='ID']").Value,
Name = item.XPathSelectElement("Field[#Name='Name']").Value,
Path = item.XPathSelectElement("Field[#Name='Path']").Value,
Type = item.XPathSelectElement("Field[#Name='Type']").Value
(Be sure to import the System.Xml.XPath namespace)
Or to add it directly to a DataTable:
Dim dt As New DataTable()
dt.Columns.Add("ID")
dt.Columns.Add("Name")
dt.Columns.Add("Path")
dt.Columns.Add("Type")
For Each item In loaded.Descendants("Item")
dt.Rows.Add(
item.XPathSelectElement("Field[#Name='ID']").Value,
item.XPathSelectElement("Field[#Name='Name']").Value,
item.XPathSelectElement("Field[#Name='Path']").Value,
item.XPathSelectElement("Field[#Name='Type']").Value
)
Next
Another one solution with anonymous types:
var doc = XDocument.Load("c:\\test");
var list = doc.Root
.Elements("Item")
.Select(item =>
new
{
Id = item.Elements("Field").Where(e => e.Attribute("Name").Value == "ID").Select(e => e.Value).FirstOrDefault(),
Path = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Path").Select(e => e.Value).FirstOrDefault(),
Name = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Name").Select(e => e.Value).FirstOrDefault(),
})
.ToArray();
foreach (var item in list)
{
var id = item.Id;
var name = item.Name;
}
Ugly expression inside new operator can be shorted with next anonymous function:
Func<XElement, string, string> getAttrValue = (node, attrName) =>
{
return node.Elements("Field")
.Where(e => e.Attribute("Name").Value == attrName)
.Select(e => e.Value)
.FirstOrDefault();
};
Then new operator looks like:
new
{
Id = getAttrValue(item, "ID"),
Path = getAttrValue(item, "Path"),
Name = getAttrValue(item, "Name"),
}
Here is my attempt at solution to your problem. I just noticed that you wish to go with as much LINQ as possible so I've structured my LINQ query accordingly. Please note result type (for "IDs") will be IEnumerable() i.e. you will need to run a for each loop on it to get individual ids even with a single item:
Dim loaded As XDocument = XDocument.Load(uriString)
Dim IDs = From items In loaded.Descendants("Item") _
Let fields = items.Descendants("Field") _
From field In fields _
Where field.Attribute("Name").Value = "ID" _
Select field.Value
On a side note: For future reference, if you run into C# anonymous type "var" in examples, the equivalent in vb is plain dim like in my query above (without the 'as type' part).
Hope this helps.
Maverik
Use XPath and save everyone the headaches?
XmlDocument xml = new XmlDocument();
xml.Load(xmlSource);
string id = xml.SelectSingleNode("/Response/Item/Field[#Name='ID']").InnerText;
string name = xml.SelectSingleNode("/Response/Item/Field[#Name='Name']").InnerText;
string path = xml.SelectSingleNode("/Response/Item/Field[#Name='Path']").InnerText;
I am wanting to get the attribute values for ID, Name, and Path.
If you don't mind using something else than XDocument i'd just use a XmlDocument:
XmlDocument doc = new XmlDocument();
doc.Load(new XmlTextReader("XData.xml"));
XmlNodeList items = doc.GetElementsByTagName("Item");
foreach (XmlElement item in items.Cast<XmlElement>())
{
XmlElement[] fields = item.GetElementsByTagName("Field").Cast<XmlElement>().ToArray();
string id = (from s in fields where s.Attributes["Name"].InnerText == "ID" select s).First().InnerText;
string name = (from s in fields where s.Attributes["Name"].InnerText == "Name" select s).First().InnerText;
string path = (from s in fields where s.Attributes["Name"].InnerText == "Path" select s).First().InnerText;
//Do stuff with data.
}
Performance-wise this might be abysmal. You could also have a loop on the Fields and then use a switch on the Name-Attribute so you don't check the same field more than once. Why would you need any linq for this anyway?
XmlDocument doc = new XmlDocument();
doc.Load(new XmlTextReader("XData.xml"));
XmlNodeList items = doc.GetElementsByTagName("Item");
foreach (XmlElement item in items.Cast<XmlElement>())
{
foreach (XmlNode field in item.GetElementsByTagName("Field"))
{
string name = field.Attributes["Name"].InnerText;
switch (name)
{
case "ID":
string id = field.InnerText;
//Do stuff with data.
break;
case "Path":
string path = field.InnerText;
//Do stuff with data.
break;
case "Name":
string name = field.InnerText;
//Do stuff with data.
break;
default:
break;
}
}
}
Your linq query returns all the Item elements in the document:
Dim name = From c In loaded.Descendants("Item") Select c
The code that follows is trying to obtain an 'ID' attribute from the 'Item' element:
Dim str1 = result.Attribute("ID").Value
However, the 'ID' attribute is on a 'Field' child element.
What you need is the following:
// find all the Item elements
var items = loaded.Descendants("Item");
foreach(var item in items)
{
// find all the Field child elements
var fields = item.Descendants("Field");
// find the field element which has an ID attribute, and obtain the element value
string id = fields.Where(field => field.Attribute("ID")!=null)
.Single()
.Value;
// etc ...
}
A Simple solution is
var result = doc.Root.Descendants(XName.Get("Item")).Select(x => x.Descendants(XName.Get("Field")));
foreach (var v in result)
{
string id = v.Single(x => x.Attribute(XName.Get("Name")).Value == "ID").Value;
string name = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Name").Value;
string path = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Path").Value;
string type = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Type").Value;
}
It can be easily converted in to vb code.
Here is a generic solution that handles all fields with different field names in several items. It saves the result in one table containing all distinct field names as column names.
Module Module1
Function createRow(ByVal table As DataTable, ByVal item As XElement) As DataRow
Dim row As DataRow = table.NewRow
Dim fields = item.Descendants("Field")
For Each field In fields
row.SetField(field.Attribute("Name").Value, field.Value)
Next
Return row
End Function
Sub Main()
Dim doc = XDocument.Load("XMLFile1.xml")
Dim items = doc.Descendants("Item")
Dim columnNames = From attr In items.Descendants("Field").Attributes("Name") Select attr.Value
Dim columns = From name In columnNames.Distinct() Select New DataColumn(name)
Dim dataSet As DataSet = New DataSet()
Dim table As DataTable = New DataTable()
dataSet.Tables.Add(table)
table.Columns.AddRange(columns.ToArray())
Dim rows = From item In items Select createRow(table, item)
For Each row In rows
table.Rows.Add(row)
Next
' TODO Handle Table
End Sub
End Module
I tried to use as much Linq as possible, but Linq is a bit inflexible when it comes to handling nested elements recursively.
Heres the sample xml file I've used:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Response Status="OK">
<Item>
<Field Name="ID">767147519</Field>
<Field Name="Name">Music</Field>
<Field Name="Path">Family\Music</Field>
<Field Name="Type">Playlist</Field>
</Item>
<Item>
<Field Name="ID">123</Field>
<Field Name="Name">ABC</Field>
<Field Name="RandomFieldName">Other Value</Field>
<Field Name="Type">FooBar</Field>
</Item>
</Response>
And the result:
ID Name Path Type RandomFieldName
767147519 Music Family\Music Playlist
123 ABC FooBar Other Value
After some further research and with the assistance of parts from the answers provided, I have come up with the following, which returns the information that I am after.
Dim Query = From items In loaded.Descendants("Item") _
Let sID = ( From q In items.Descendants("Field") _
Where q.Attribute("Name").Value = "ID" ) _
Let sName = ( From r In items.Descendants("Field") _
Where r.Attribute("Name").Value = "Name" ) _
Let sPath = ( From s In items.Descendants("Field") _
Where s.Attribute("Name").Value = "Path" ) _
Where (Ctype(sPath.Value,String) Like "Family\*") _
Select pId=sID.Value, pName=sName.Value, pPath = sPath.Value
If this can be improved in any way to enable better performance, please let me know.
Thank you all for your assistance, while no one answer was able to entirely solve the problem I was able to learn a great deal about Linq through everyones assistance.
Matt
I hope you expected something like this short answer and not another implementation:
Dim items = From c In loaded.Descendants("Item") Select c (...)
Ok so far nothing should run into any trouble. The variable name 'name' was a bit confusing, so I changed it to 'items'.
The second part contains the error:
Dim items = (...) Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
The following works because there is an Attribute called Name, although the result is 'ID' what shurely wasn't expected:
c.Element("Field").Attribute("Name").Value
Here comes the error:
c.Attribute("ID").Value.FirstOrDefault
c is the XmlNode '< Item > ... < / Item >' and it does not have any attributes, thus the result of c.Attribute("ID") is null.
I guess you wanted something like the following:
Dim loaded = XDocument.Load("XMLFile1.xml")
Dim items = From item In loaded.Descendants("Item") Select _
sID = (From field In item.Descendants("Field") _
Where field.Attribute("Name") = "ID" _
Select field.Value).FirstOrDefault() _
, _
sName = (From field In item.Descendants("Field") _
Where field.Attribute("Name") = "Name" _
Select field.Value).FirstOrDefault()
There are a few errors in your code:
You should get the Descendents that have the XName equal to Field instead of to Item
Dim name = From c In loaded.Descendants("Field") Select c
The attribute you are after is called Name, not ID
Dim str1 = result.Attribute("Name").Value
At the first iteration of your for each str1 will be "ID", the next one it will be "Name", etc.
Total code:
Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Field") Select c
For Each result In name
Dim str1 = result.Attribute("Name").Value 'Returns "ID"
Dim str2 = result.Value ' Returns "767147519"
Next
There's another way to fix this problem. Transform this XML into the format that the DataSet wants, and then load it using DataSet.ReadXml. This is something of a pain if you don't know XSLT. But it's really important to know XSLT if you work with XML.
The XSLT you'd need is pretty simple. Start with the XSLT identity transform. Then add a template that transforms the Response and Item elements into the format that the DataSet expects:
<xsl:template match="Response">
<MyDataSetName>
<xsl:apply-templates select="Item"/>
</MyDataSetName>
</xsl:template>
<xsl:template match="Item">
<MyDataTableName>
<xsl:apply-templates select="Field[#Name='ID' or #Name='Name' or #Name='Path']"/>
</MyDataTableName>
</xsl:template>
<xsl:template match="Field">
<xsl:element name="{#Name}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
That will change your XML to a document that looks like this:
<MyDataSetName>
<MyDataTableName>
<ID>767147519</ID>
<Name>Music</Name>
<Path>Family\Music</Path>
</MyDataTableName>
</MyDataSetName>
...and you can just feed that to DataSet.ReadXml.
Edit:
I should point out, since it's not obvious unless you do this a lot, that one effect of this is that the amount of C# code that you need to create and populate the DataSet is minimal:
private DataSet GetDataSet(string inputFilename, string transformFilename)
{
StringBuilder sb = new StringBuilder();
using (XmlReader xr = XmlReader.Create(inputFilename))
using (XmlWriter xw = XmlWriter.Create(new StringWriter(sb)))
{
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(transformFilename);
xslt.Transform(xr, xw);
}
using (StringReader sr = new StringReader(sb.ToString()))
{
DataSet ds = new DataSet();
ds.ReadXml(sr);
return ds;
}
}
It's also reusable. You can use this method to populate as many different DataSets from as many different possible input formats as you need; you just need to write a transform for each format.

VBA. Array for search or replace

Need to found any symbol of array.
For example:
replace(string,[a,b,c,e,f,g],"a1b2c3d4e567");
result = "1234567"
How do it ?
If your goal is to remove all non-numeric characters, the following will work:
' Added reference for Microsoft VBScript Regular Expressions 5.5
Const s As String = "a1b2c3d4e567"
Dim regex2 As New RegExp
Dim a As String
regex2.Global = True
regex2.Pattern = "[^0-9]"
Dim a As String = regex2.Replace(s, "")
MsgBox (a) ' Outputs 1234567
If you are looking for specific characters, change the pattern.
AFAIK you are going to have to do this by consecutive calls to replace
result = "a1b2c3d4e567"
result = replace(result,"a","")
result = replace(result,"b","")
result = replace(result,"c","")
etc

Resources