Is there a "nice" way to create a CAML query for SharePoint that does something like this?
SELECT *
FROM table
WHERE Id IN (3, 12, ...)
Or am I stuck with a nightmare of nested <Or> nodes?
EDIT: This was my solution to generate the <Or> nodes.
/// Simulates a SQL 'Where In' clause in CAML
/// </summary>
/// <param name="columnType">Specifies the data type for the value contained by the field.</param>
/// <returns>Nested 'Or' elements portion of CAML query</returns>
public static string CamlIn<T>(string internalFieldName, string columnType, T[] values)
{
XDocument doc = new XDocument();
XElement prev = null;
int index = 0;
while (index < values.Length)
{
XElement element =
new XElement("Or",
new XElement("Eq",
new XElement("FieldRef",
new XAttribute("Name", internalFieldName)),
new XElement("Value",
new XAttribute("Type", columnType),
values[index++].ToString())));
if (index == values.Length - 1)
{
element.AddFirst(
new XElement("Eq",
new XElement("FieldRef",
new XAttribute("Name", internalFieldName)),
new XElement("Value",
new XAttribute("Type", columnType),
values[index++].ToString())));
}
if (prev != null)
prev.AddFirst(element);
else
doc.Add(element);
prev = element;
}
return doc.ToString(SaveOptions.DisableFormatting);
}
Usage:
int[] ids = new int[] { 1, 2, 4, 5 };
string query = string.Format("<Where>{0}</Where>", CamlIn("SomeColumn", "Number", ids));
Output:
<Where>
<Or>
<Or>
<Or>
<Eq>
<FieldRef Name=\"SomeColumn\" />
<Value Type=\"Number\">5</Value>
</Eq>
<Eq>
<FieldRef Name=\"SomeColumn\" />
<Value Type=\"Number\">4</Value>
</Eq>
</Or>
<Eq>
<FieldRef Name=\"SomeColumn\" />
<Value Type=\"Number\">2</Value>
</Eq>
</Or>
<Eq>
<FieldRef Name=\"SomeColumn\" />
<Value Type=\"Number\">1</Value>
</Eq>
</Or>
</Where>
Also made this overload for working with Lookup Fields a bit easier
/// <summary>
/// Simulates a SQL 'Where In' clause in CAML
/// </summary>
/// <param name="lookupId">Specify whether to use the Lookup column's Id or Value.</param>
/// <returns>Nested 'Or' elements portion of CAML query</returns>
public static string CamlIn<T>(string internalFieldName, bool lookupId, T[] values)
{
XDocument doc = new XDocument();
XElement prev = null;
int index = 0;
while (index < values.Length)
{
XElement element =
new XElement("Or",
new XElement("Eq",
new XElement("FieldRef",
new XAttribute("Name", internalFieldName),
lookupId ? new XAttribute("LookupId", "TRUE") : null),
new XElement("Value",
new XAttribute("Type", "Lookup"),
values[index++].ToString())));
if (index == values.Length - 1)
{
element.AddFirst(
new XElement("Eq",
new XElement("FieldRef",
new XAttribute("Name", internalFieldName),
lookupId ? new XAttribute("LookupId", "TRUE") : null),
new XElement("Value",
new XAttribute("Type", "Lookup"),
values[index++].ToString())));
}
if (prev != null)
prev.AddFirst(element);
else
doc.Add(element);
prev = element;
}
if (values.Length == 1)
{
XElement newRoot = doc.Descendants("Eq").Single();
doc.RemoveNodes();
doc.Add(newRoot);
}
return doc.ToString(SaveOptions.DisableFormatting);
}
For those using Sharepoint 2010, there is an IN element available:
http://msdn.microsoft.com/en-us/library/ff625761.aspx
Here's a working example:
SPQuery locationsQuery = new SPQuery();
locationsQuery.Query = string.Concat("<Where>",
"<In>",
"<FieldRef Name='ID' />",
"<Values>",
"<Value Type='Number'>6</Value>",
"<Value Type='Number'>7</Value>",
"<Value Type='Number'>8</Value>",
"</Values>",
"</In>",
"</Where>");
NO, you'll need to deal with nested OR tags; these are supported query instructions on CAML
Maybe CAML.NET can help you in your quest.
FullTextSqlQuery
It is possible to search MOSS using SQL statements, using the FullTextSqlQuery class. I have no experience of using this class personally. These articles may be of use:
http://blogit.create.pt/blogs/ricardocosta/archive/2007/06/15/How-to-use-FullTextSqlQuery-to-search-in-WSS.aspx
http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2008/03/06/how-to-use-the-moss-enterprise-search-fulltextsqlquery-class.aspx
YACAMLQT
Alternatively, there is also YACAMLQT (Yet Another CAML Query Tool) which allows you to create SharePoint CAML queries using a T-SQL syntax.
LINQ to SharePoint
If you are up to speed with LINQ, then the LINQ to SharePoint project provides a tool to query SharePoint lists using the LINQ syntax. Please note, this tool is still in the alpha testing phase, so it may not be production ready.
U2U CAML Query Builder
If you are working with CAML queries, I would recommend using the U2U CAML Query Builder for SharePoint (2003 and 2007) tool to build up your CAML queries. The tool allows you to build up your query string, and to execute it against the target list, using a point-and-click interface, as shown below.
(source: u2u.net)
Of the above four methods, I can recommend the U2U CAML Query Builder, having used it almost daily over the last 6 months. It also appears to be the most widely used CAML tool in the SharePoint community .
Note, if you are building the CAML queries in code, then I recommend that you take a look at the CAML.NET project on CodePlex, which provides "a set of .NET language-based tools for creating dynamic, reusable CAML query components".
I faced a similar thing and ultimately had to create a recursive algorithm to generate the nested OR structure. Here is my algorithm
var DynamicQuery = '<Query><Where>{{DYNAMICSTRING}}</Where></Query>';
var OneOR = '<Or><Eq><FieldRef Name="IMEI" /><Value Type="Text">{{SearchValue}}</Value></Eq>{{DYNAMICSTRING}}</Or>';
var TwoOr = '<Or><Eq><FieldRef Name="IMEI" /><Value Type="Text">{{SearchValue}}</Value></Eq><Eq><FieldRef Name="IMEI" /><Value Type="Text">{{SearchValue}}</Value></Eq></Or>';
var OnlyEq = '<Eq><FieldRef Name="IMEI" /><Value Type="Text">{{SearchValue}}</Value></Eq>';
function generateAdvancedInQuery(x){
if(x.length == 1)
return OnlyEq.replace('{{SearchValue}}',x[0]);
else if(x.length == 2)
return TwoOr.replace('{{SearchValue}}',x[0]).replace('{{SearchValue}}',x[1]);
else
return OneOR.replace('{{SearchValue}}',x[x.length-1]).replace('{{DYNAMICSTRING}}',generateAdvancedInQuery(x.splice(0,x.length-1) ) );
}
x = ['438753234098792','438753234098793','438753234098794','438753234098795','438753234098796','438753234098797','438753234098798'];
var Caml = DynamicQuery.replace('{{DYNAMICSTRING}}',generateAdvancedInQuery(x) )
This generates the XML caml query as
<Query>
<Where>
<Or>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098798</Value>
</Eq>
<Or>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098797</Value>
</Eq>
<Or>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098796</Value>
</Eq>
<Or>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098795</Value>
</Eq>
<Or>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098794</Value>
</Eq>
<Or>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098792</Value>
</Eq>
<Eq>
<FieldRef Name="IMEI" />
<Value Type="Text">438753234098793</Value>
</Eq>
</Or>
</Or>
</Or>
</Or>
</Or>
</Or>
</Where>
</Query>
Related
I have a SharePoint page which shows new employees from an employee list. I uses the start date of the employee and displays them for 30 days. We have outsourced 2 departments and managers do not want to see them as new employees since they are from a third party firm. Below is the code I tried but can not get to work. I have moved the 'And' up to include all three filter and I get an error '(soap:ServerException of t.SharePoint.SoapServer.SoapServerException' was thrown. Cannot complete this action. Please try again.0x80004005)'.
I move the 'And' near the 'Neq' and they get ignored. I am trying to keep the 30 days for start date and exclude the two departments.
`<Query>
<OrderBy>
<FieldRef Name="Full_x0020_Name"/>
</OrderBy>
<Where>
<And>
<Geq>
<FieldRef Name="StartDate"/>
<Value Type="DateTime">
<Today OffsetDays="-30"/>
</Value>
</Geq>
<Neq>
<FieldRef Name="Department"/>
<Value Type="Text">G4S Security</Value>
</Neq>
<Neq>
<FieldRef Name="Department"/>
<Value Type="Text">Budd Group</Value>
</Neq>
</And>
</Where>
</Query>`
I found a solution, there can be only two within an And. This is the code I changed below.
'<Where>
<And>
<And>
<Geq>
<FieldRef Name="StartDate"/>
<Value Type="DateTime">
<Today OffsetDays="-30"/>
</Value>
</Geq>
<Neq>
<FieldRef Name="Department"/>
<Value Type="Text">G4S Security</Value>
</Neq>
</And>
<Neq>
<FieldRef Name="Department"/>
<Value Type="Text">Budd Group</Value>
</Neq>
</And>
</Where>'
https://msdn.microsoft.com/en-us/library/office/ms196939(v=office.14).aspx
This element can be nested inside other And and Or elements. The server supports unlimited complicated queries. However, any given And element can have only two conjuncts; that is, only two child elements. If you need to conjoin three or more conditions, you must nest the And elements [...]
<Where>
<And>
<And>
<Geq>
<FieldRef Name="StartDate"/>
<Value Type="DateTime">
<Today OffsetDays="-30"/>
</Value>
</Geq>
<Neq>
<FieldRef Name="Department"/>
<Value Type="Text">G4S Security</Value>
</Neq>
</And>
<Neq>
<FieldRef Name="Department"/>
<Value Type="Text">Budd Group</Value>
</Neq>
</And>
</Where>
There is also the issue of the query method itself.
If you are using the method "query" in the "list" class, then you cannot use the <Query></Query> tags in your CAML. The reason is the method already wraps your query with this tag automatically and adding it breaks the query.
See my code for example:
Private Function GenerateCamlForCurrentUserMessages(list As SPList, isActiveOnly As Boolean) As SPQuery
Dim qResult As SPQuery
Dim sCaml As String = ""
Dim web As SPWeb = SPContext.Current.Web
If isActiveOnly Then
sCaml = "<Where><And><Eq><FieldRef Name='Author' /><Value Type='User'>" & web.CurrentUser.Name.ToString() & "</Value></Eq><Eq><FieldRef Name='IsActive' /><Value Type='Boolean'>1</Value></Eq></And></Where>"
Else
sCaml = "<Where><Eq><FieldRef Name='Author' /><Value Type='User'>" & web.CurrentUser.Name.ToString() & "</Value></Eq></Where>"
End If
qResult = New SPQuery()
'sCaml = sCaml & list.DefaultView.Query
qResult.Query = sCaml
Return qResult
End Function
I have Sitecore items with a treelist property referring to other items (with different a template).
My goal is to find item A that contains item B in the treelist property using the ContentSearch api (lucene).
I've added the treelist property to my index:
<indexConfigurations>
<defaultLuceneIndexConfiguration type="Sitecore.ContentSearch.LuceneProvider.LuceneIndexConfiguration, Sitecore.ContentSearch.LuceneProvider">
<fieldMap type="Sitecore.ContentSearch.FieldMap, Sitecore.ContentSearch">
<fieldNames hint="raw:AddFieldByFieldName">
<field patch:before="field[0]" fieldName="TreelistProperty" storageType="YES" indexType="UNTOKENIZED" vectorType="NO" boost="1f" type="System.String"
settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider">
<analyzer type="Sitecore.ContentSearch.LuceneProvider.Analyzers.LowerCaseKeywordAnalyzer, Sitecore.ContentSearch.LuceneProvider" />
</field>
</fieldNames>
</fieldMap>
</defaultLuceneIndexConfiguration>
</indexConfigurations>
I would expect that lucene stores the treelist property as a concatenation of guids.
Assuming this is correct and my index is populated my query looks like this:
master = Sitecore.ContentSearch.ContentSearchManager.GetIndex("sitecore_master_index");
using (var context = master.CreateSearchContext())
{
var results = context.GetQueryable<SearchResultItem>()
.Where(x => x["TreelistProperty"].Contains("{456-41414-my-guid-here-1516}"))
.GetResults();
var hits = results.Hits.ToArray();
}
This returns nothing. Where did it go wrong?
You should normalize your guid, like this:
var master = Sitecore.ContentSearch.ContentSearchManager.GetIndex("sitecore_master_index");
using (var context = master.CreateSearchContext())
{
Sitecore.Data.ID myId = ID.Parse("{456-41414-my-guid-here-1516}");
string normalizedID = Sitecore.ContentSearch.Utilities.IdHelper.NormalizeGuid(myId );
var results = context.GetQueryable<SearchResultItem>()
.Where(x => x["TreelistProperty"].Contains(normalizedID))
.GetResults();
var hits = results.Hits.ToArray();
}
I think there is a typo in your index configuration, can you try
indexType="UN_TOKENIZED"
You can also investigate what values are in your lucene indexes using luke
http://www.sitecore.net/en-gb/learn/blogs/technical-blogs/getting-to-know-sitecore/posts/2013/06/using-luke-to-understand-sitecore-7-search.aspx
I think the guid values are stored without the braces and dashes by default. Try converting to ToShortId() before the comparison.
I'm trying following CAML Query but it's not working correctly instead of returning 2 documents it's returning all .
<Where><And><Or><Eq><FieldRef Name='Participant' /><Value Type='User'>Project1, test</Value></Eq><Eq><FieldRef Name='Participant' /><Value Type='User'>Project2, test</Value></Eq></Or><Eq><FieldRef Name='Department' /><Value Type='Text'>Positioning</Value></Eq></And></Where>
I've tried testing in CAML Query builder but don't find any option to add two OR with one AND conditions like above.
My full query building code is,
strQuery1 = "<Where><And>";
strQuery1 +="<Or>";
foreach (string p in ParticipantNames)
{
strQuery1 += "<Eq><FieldRef Name='Participant' /><Value Type='User'>" + p + "</Value></Eq>";
}
strQuery1 +="</Or>";
strQuery1 += "<Eq><FieldRef Name='Department' /><Value Type='Text'>" + dept + "</Value></Eq></And></Where>";
Have you checked how much values you are getting in 'ParticipantNames' variable?
because And , Or conditions in CAML we can specify only 2 conditions, please check how many times it is looping?
<Where>
<And>
<Or>
<Or>
<Eq><FieldRef Name='Participant' /><Value Type='User'>Project1, test</Value></Eq>
<Eq><FieldRef Name='Participant' /><Value Type='User'>Project2, test</Value></Eq>
</Or>
<Eq><FieldRef Name='Participant' /><Value Type='User'>Project3, test</Value></Eq>
</Or>
<Eq><FieldRef Name='Department' /><Value Type='Text'>Positioning</Value></Eq>
</And>
</Where>
I have the following CAML query to programmatically filter a list:
<Query>
<Where>
<And>
<Geq><FieldRef Name='notificationExpires' /><Value IncludeTimeValue='TRUE' Type='DateTime'>2011-03-30T00:00:00Z</Value></Geq>
<Or>
<Membership Type='CurrentUserGroups'><FieldRef Name='notificationTargetRoles'/></Membership>
<Contains><FieldRef Name='notificationTargetRoles'/><Value Type='User'>MyDomain\administrator</Value></Contains>
</Or>
</And>
</Where>
</Query>
When I execute this exact same query (running under the same account) in the U2U CAML Query Builder I get filtered users, groups and dates.
When apply exact the same filter in code, the Groups do not appear in the results.
What can be the reason that the group filter (CurrentGroups) does not return any results?
The code for applying the filter is:
SPListItemCollection items = null;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite elevatedSite = new SPSite(theSiteName))
{
using (SPWeb elevatedWeb = elevatedSite.OpenWeb())
{
SPList alertList = elevatedWeb.Lists[theListName];
SPQuery query = new SPQuery();
query.Query = "<Where><And><Geq><FieldRef Name='notificationExpires' /><Value IncludeTimeValue='TRUE' Type='DateTime'>2011-03-30T00:00:00Z</Value></Geq> <Or><Membership Type='CurrentUserGroups'><FieldRef Name='notificationTargetRoles'/></Membership><Contains><FieldRef Name='notificationTargetRoles'/><Value Type='User'>BSFLMK\administrator</Value></Contains></Or></And></Where>";
items = alertList.GetItems(query);
}
}
});
It turns out that removing the "SPSecurity.RunWithElevatedPrivileges(delegate()" part corrected the problem
Am looking for the same target mentioned in Daivd Hill post, but still can not get the custom content type my query is like;
string dateString = (DateTime.Now.Add(new TimeSpan(-5, 0, 0, 0, 0))).ToString("yyyy-MM-ddThh:mm:ssZ");
string q= String.Format("Where And"+"Eq FieldRef Name='ContentTypeId'/" + "Value Type='Text'0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D08/Value /Eq"+
"Eq FieldRef Name='Created'/" +
"Value Type='DateTime'{0} /Value /Eq ", dateString+ " /And /Where");
SPSiteDataQuery query = new SPSiteDataQuery();
query.Query= q;
After that what should I do -> where should I execute the query against , as am trying to get against the content type , most of the examples I found was about built-in lists like task announcement..etc
in the query am trying to get the specific custom content type based on its ID that is created for ex 5 days ago
Thanks if u have any idea for help, or can u give the example of what ur solution look like thanks Azo
Something like this worked well for me, but I was querying the list.
string dateInCorrectFormat = SPUtility.CreateISO8601DateTimeFromSystemDateTimeDateTime.Now.AddDays(-5));
SPQuery query = new SPQuery();
//Query below
query.Query = String.Format( .... , dateInCorrectFormat);
SPListItemCollection items = list.GetItems(query);
<Where>
<And>
<BeginsWith>
<FieldRef Name='ContentTypeId' />
<Value Type='ContentTypeId'>0x0100XCustomContentypeIdHereX</Value>
</BeginsWith>
<Lt>
<FieldRef Name='Created'/>
<Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>
</Lt>
</And>
</Where>
Hope this helps.