What is the best way to retrieve distinct / unique values using SPQuery? - sharepoint

I have a list that looks like:
Movie Year
----- ----
Fight Club 1999
The Matrix 1999
Pulp Fiction 1994
Using CAML and the SPQuery object I need to get a distinct list of items from the Year column which will populate a drop down control.
Searching around there doesn't appear to be a way of doing this within the CAML query. I'm wondering how people have gone about achieving this?

Another way to do this is to use DataView.ToTable-Method - its first parameter is the one that makes the list distinct.
SPList movies = SPContext.Current.Web.Lists["Movies"];
SPQuery query = new SPQuery();
query.Query = "<OrderBy><FieldRef Name='Year' /></OrderBy>";
DataTable tempTbl = movies.GetItems(query).GetDataTable();
DataView v = new DataView(tempTbl);
String[] columns = {"Year"};
DataTable tbl = v.ToTable(true, columns);
You can then proceed using the DataTable tbl.

If you want to bind the distinct results to a DataSource of for example a Repeater and retain the actual item via the ItemDataBound events' e.Item.DataItem method, the DataTable way is not going to work. Instead, and besides also when not wanting to bind it to a DataSource, you could also use Linq to define the distinct values.
// Retrieve the list. NEVER use the Web.Lists["Movies"] option as in the other examples as this will enumerate every list in your SPWeb and may cause serious performance issues
var list = SPContext.Current.Web.Lists.TryGetList("Movies");
// Make sure the list was successfully retrieved
if(list == null) return;
// Retrieve all items in the list
var items = list.GetItems();
// Filter the items in the results to only retain distinct items in an 2D array
var distinctItems = (from SPListItem item in items select item["Year"]).Distinct().ToArray()
// Bind results to the repeater
Repeater.DataSource = distinctItems;
Repeater.DataBind();
Remember that since there is no CAML support for distinct queries, each sample provided on this page will retrieve ALL items from the SPList. This may be fine for smaller lists, but for lists with thousands of listitems, this will seriously be a performance killer. Unfortunately there is no more optimized way of achieving the same.

There is no DISTINCT in CAML to populate your dropdown try using something like:
foreach (SPListItem listItem in listItems)
{
if ( null == ddlYear.Items.FindByText(listItem["Year"].ToString()) )
{
ListItem ThisItem = new ListItem();
ThisItem.Text = listItem["Year"].ToString();
ThisItem.Value = listItem["Year"].ToString();
ddlYear.Items.Add(ThisItem);
}
}
Assumes your dropdown is called ddlYear.

Can you switch from SPQuery to SPSiteDataQuery? You should be able to, without any problems.
After that, you can use standard ado.net behaviour:
SPSiteDataQuery query = new SPSiteDataQuery();
/// ... populate your query here. Make sure you add Year to the ViewFields.
DataTable table = SPContext.Current.Web.GetSiteData(query);
//create a new dataview for our table
DataView view = new DataView(table);
//and finally create a new datatable with unique values on the columns specified
DataTable tableUnique = view.ToTable(true, "Year");

After coming across post after post about how this was impossible, I've finally found a way. This has been tested in SharePoint Online. Here's a function that will get you all unique values for a column. It just requires you to pass in the list Id, View Id, internal list name, and a callback function.
function getUniqueColumnValues(listid, viewid, column, _callback){
var uniqueVals = [];
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_layouts/15/filter.aspx?ListId={" + listid + "}&FieldInternalName=" + column + "&ViewId={" + viewid + "}&FilterOnly=1&Filter=1",
method: "GET",
headers: { "Accept": "application/json; odata=verbose" }
}).then(function(response) {
$(response).find('OPTION').each(function(a,b){
if ($(b)[0].value) {
uniqueVals.push($(b)[0].value);
}
});
_callback(true,uniqueVals);
},function(){
_callback(false,"Error retrieving unique column values");
});
}

I was considering this problem earlier today, and the best solution I could think of uses the following algorithm (sorry, no code at the moment):
L is a list of known values (starts populated with the static Choice options when querying fill-in options, for example)
X is approximately the number of possible options
1. Create a query that excludes the items in L
1. Use the query to fetch X items from list (ordered as randomly as possible)
2. Add unique items to L
3. Repeat 1 - 3 until number of fetched items < X
This would reduce the total number of items returned significantly, at the cost of making more queries.
It doesn't much matter if X is entirely accurate, but the randomness is quite important. Essentially the first query is likely to include the most common options, so the second query will exclude these and is likely to include the next most common options and so on through the iterations.
In the best case, the first query includes all the options, then the second query will be empty. (X items retrieved in total, over 2 queries)
In the worst case (e.g. the query is ordered by the options we're looking for, and there are more than X items with each option) we'll make as many queries as there are options. Returning approximately X * X items in total.

Related

GUIDEWIRE : How to display the the query builder result in UI?

I am using query builder to return number of search result from the database table. Now I would like to display the result in the UI by only first three rows. How can I achieve this?
QueryAPI is lazy, when .toList(), .toTypedArray(), .toCollection(), .where(), etc occurs all resultset is retrieved (eager).
I recommend you to use this:
var limit = 3
var rs = Query.make(entity.XXX)...select()
rs.setPageSize(limit)
var paginatedRS = com.google.common.collect.Iterables.limit(rs,limit)
setPageSize method specifies how many rows will be fetch "by page"
limit method make a new iterator that have only the first (limit) rows

How to get from CouchDB only certain fields of certain documents for a single request?

For example I have a thousands of documents with same structure, for example:
{
"key_1":"value_1",
"key_2":"value_2",
"key_3":"value_3",
...
...
}
And I need to get, let's say key_1, key_3 and key_23 from some set of documents with known IDs, for example, I need to process only 5 documents while my DB contains several thousands. Each time I have a different set of keys and document IDs. Is it possible to get that information for a one request?
You can use a list function (see: this, this, and this).
Since you know the ids, you can then query _all_docs with the list function:
POST /{db}/_design/{ddoc}/_list/{func}/_all_docs?include_docs=true&columns=["key_1","key_2","key_3"]
Accept: application/json
Content-Length: {whatever}
{
"keys": [
"docid002",
"docid005"
]
}
The list function needs to look at documents, and send the appropriate JSON for each one. Not tested:
(function (head, req) {
send('{"total_rows":' + head.total_rows + ',"offset":' + head.offset + ',"rows":[');
var columns = JSON.parse(req.query.columns);
var delim = '';
var row;
while (row = getRow()) {
var doc = {};
for (var k in columns) {
doc[k] = row.doc[k];
}
row.doc = doc;
send(delim + toJSON(row));
delim = ',';
}
send(']}');
})
Whether this is a good idea, I'm not sure. If your documents are big, and bandwidth savings important, it might.
Yes, that’s possible. Your question can be broken up into two distinct problems:
Getting only a part of the document (in your example: key_1, key_3 and key_23). This can be done using a view. A view is saved into a design document. See the wiki for more info on how to create views.
Retrieving only certain documents, which are defined by their ID. When querying views, you cannot only specify a single ID (or rather key), but also an array of keys, which is what you would need here. Again, see the section on querying views in the wiki for explanations and examples.
Even though you only need a subset of values from a document, you may find that the system as a whole performs better if you just ask for the entire document then select the values you need from that result.
To only get the specific key value pairs you need to create a view that has view entries with a multipart key consisting of the doc id and doc item name, with value of the corresponding doc item.
So your map function would look something like:
function(doc){
for(var i = 1; i < doc.keysInDoc; i++){
var k = "key_"+i;
emit([doc._id, k], doc.[k]);
}
}
You can then use multi key lookup with each key being of the form ["docid12345", "key_1"], ["docid56789", "key_23"], etc.
So a query like:
http://host:5984/db/_design/design/_view/view?&keys=[["docid002","key_8"],["docid005","key_7"]]
will return
{"total_rows":84,"offset":67,"rows":[
{"id":"docid002","key":["docid002","key_8"],"value":"value d2_k8"},
{"id":"docid005","key":["docid005","key_12"],"value":"value d5_k12"}
]}

C# LINQ to objects query to change the field value based on List

I have the follwing objects
objItem (id,name,qty) - list<items>
objSel(selId) - list<int>
objSel.selId is the selected item id of objItem.
How to write the LINQ query to change item qty to 0 if the items are not selected and return objItem.
Your pseudo-code is quite confusing, but I suspect you want something like:
List<Item> items = ...;
List<int> selectedIds = ...;
foreach (var item in items.Where(x => !selectedIds.Contains(x.Id)))
{
item.Quantity = 0; // Property name adjusted for readability and convention
}
For more efficiently, use HashSet<int> for the selected IDs instead.
Note that it's not the LINQ query which performs the change - the query just gives the items which require changing. While you can abuse LINQ to change data, it's a bad idea to do so. The clue is in the word "query" - it's about asking a question. What you do with the answer to that question is a different matter.

Initiate ApexPage.StandardSetController with List causes exception being thrown in later pagination calls

I have a Paginated List displayed on the visual force page and in the backend I was using a StandardSetController to control the pagination. However, one column on the table is an aggregated field whose calculation is done in a wrapper class. Recently, I want to sort the paginated list against the calculated field. And unfortunately the calculated result cannot be done on the data model(SObject) level.
So I am thinking to passed a sorted list of SObject to the StandardSetController constructor. That is to sort the record before it has been pass into the StandardSetController.
The code is like below:
List<Job__c> jobs = new List<Job__c>();
List<Job__c> tempJobs = Database.Query(basicQuery + filterExpression);
//sort with values
List<JobWrapper> jws = createJobWrappers(tempJobs);
JobWrapper.sortBy = JobWrapper.SORTBY_CALCULATEDFIELD_ASC;
jws.sort();
for(JobWrapper jw : jws){
jobs.add(jw.JobRecord);
}
jobs = jobs.deepClone(true, true, true);
StandardSetController con = new ApexPages.StandardSetController(jobs);
con.setPageSize(10);
However after executing the last line system throw exception:Modified rows exist in the records collection!
I did not modify any rows in the controller. Could anyone help me understanding the exception?

How to create a dataview In Sharepoint with data from a join query?

I have 3 Lists in Sharepoint.
I want to create a dataview that is a join of 3 tables.
Table1 is joined with Table2 on FieldA
Table 2 is joined to Table3 on FieldB
Table1 has duplicate values in FieldA so I need to only return one value to join with Table2.
In Access my query looks like this:
SELECT DISTINCT WRK_InputWorkOrders.WorkOrder, Production1.[Part Number], Production1.[Work Order], Production1.Location, StationItems.Station, Production1.Description, Production1.Revision, WRK_InputWorkOrders.Status
FROM StationItems INNER JOIN (WRK_InputWorkOrders INNER JOIN Production1 ON WRK_InputWorkOrders.WorkOrder = Production1.[Work Order]) ON StationItems.Item = Production1.[Part Number]
WHERE (((WRK_InputWorkOrders.Status)<>"closed"));
Is there a way to write sql-like queries for dataviews?
I have Sharepoint Designer 2007 and Access.
The goal is to get a report that a user can view in Internet Explorer.
I have tried using this method. But it returns duplicate records
I found this suggestion. It suggests using an XPath Filter
not(#yourvalue = preceding-sibling::dfs:YourRepeatingRowName/#yourvalue)
But wasn't able to get it to work. I don't know what to enter as YourRepeatingRowName
I found this link. Does anyone know if it can be used to perform such a join?
Your question is more of an ADO.NET question. Unfortunately ADO.NET doesn't have an easy way to do this, which is why companies like bamboo Solutions builds theirCross List Web Part:
http://store.bamboosolutions.com/pc-42-1-cross-list-web-part.aspx
Otherwise I would attempt to use LINQ to query the tables. You might have more luck doing that.
Here is an example of a JOIN query provided by MS (I only changed the first two DataTable lines to represent filling a DataTable with an SPListItemCollection object)
DataTable orders = spListCol1.ToDataTable();
DataTable details = spListCol2.ToDataTable();
var query =
from order in orders.AsEnumerable()
join detail in details.AsEnumerable()
on order.Field<int>("SalesOrderID") equals
detail.Field<int>("SalesOrderID")
where order.Field<bool>("OnlineOrderFlag") == true
&& order.Field<DateTime>("OrderDate").Month == 8
select new
{
SalesOrderID =
order.Field<int>("SalesOrderID"),
SalesOrderDetailID =
detail.Field<int>("SalesOrderDetailID"),
OrderDate =
order.Field<DateTime>("OrderDate"),
ProductID =
detail.Field<int>("ProductID")
};
DataTable orderTable = query.CopyToDataTable();
Microsoft has a video demo and a writeup that may be just what you want:
Display data from multiple sources in a single Data View
http://office.microsoft.com/en-us/sharepointdesigner/HA103511401033.aspx
With Microsoft Office SharePoint Designer 2007, you can link two or more data sources that contain related data and then create a single Data View that displays data from those linked data sources.
you want to show the query result in SharePoint Designer? I believe, SPD has merged data sources. Look into that.
I found this third part add on
Enesys RS Data Extension lets you query (retrieve, join, merge,...) data from any SharePoint list and use the result for building "Reporting Services" reports as you would do with any other data sources. http://www.enesyssoftware.com/
I can't use it because I am currently running the basic Sharepoint version that uses the internal database.
I've done something like this, but I wasn't able to use a dataview. I ended up writing a custom web part to do it. The approach was:
Use an SPQuery object to get an SPListItemCollection for each list. Use the CAML query to restrict the items returned.
Use the SPListItemCollection object's GetDataTable() method to retrieve an ADO.NET DataTable object for each list.
Add the tables to a DataSet object.
Create relationships between the tables.
Render the data however you like, using DataList or Repeater or whatever.
Here's some code that shows the broad strokes:
protected DataTable GetDataTableFromQuery(string camlQry, SPList theList) {
SPQuery listQry = new SPQuery();
listQry.Query = camlQry;
SPListItemCollection listItems = theList.GetItems(listQry);
return listItems.GetDataTable();
}
protected void BuildDataSet() {
// get SPList objects for the lists in questions ... left as an exercise for the dev -- call them list1, list2, and list3
string camlQry = "the CAML necessary to retreive the ites from list1";
DataTable table1 = GetDataTable(camlQry, list1);
table1.TableName = "Table1";
camlQry = "the CAML necessary to retreive the ites from list2";
DataTable table2 = GetDataTable(camlQry, list2);
table1.TableName = "Table2";
camlQry = "the CAML necessary to retreive the ites from list3";
DataTable table3 = GetDataTable(camlQry, list3);
table1.TableName = "Table3";
// now build the DataSet
DataSet ds = new DataSet();
ds.Tables.Add(table1);
ds.Tables.Add(table2);
ds.Tables.Add(table3);
ds.Relations.Add("Table1_2", ds.Tables["Table1"].Columns["FieldA"], ds.Tables["Table2"].Columns["FieldA"]);
ds.Relations.Add("Table2_3", ds.Tables["Table2"].Columns["FieldB"], ds.Tables["Table3"].Columns["FieldB"]);
// now you can do something with these, like store them in the web part class and bind them to repeaters in the web part's Render() method
}

Resources