Phalcon, model bindings by field with json string or simple array string - phalcon

I want to hold array data (json data) in field "table_ids" of Table_2, eg:
Table_1
| id | name |
----------------
| 1 | title 1 |
| 2 | title 2 |
| 3 | title 3 |
| 4 | title 4 |
etc...
Table_2
| id | table_ids |
---------------------
| 1 | "{1,3}" |
| 2 | "{3,4,2}" |
And, I'm intrested how it's possible to make relation in Phalcon model of table 2, hasMany as "table1relation" to Table_1 by field "table_ids"
$this->hasMany( 'table_ids', 'Table1', 'id', array('alias' => 'table1relation' ));
so as result to have properly binded objects in Table_2
$tbl2 = Table2::find();
foreach( $id in $tbl2.table1relation ) {
echo $id->name;
};
I'm wondering is this possible ?
Thanks

First working solution is this:
class Table2 extends Model
{
....
// get all items with mapped Table1 model resulsets
public static function GetItems() {
$items = Table2::find()->filter( function( $current_item ){
if ( $current_item->table_ids != null ) {
$final = array();
$array = explode( ",", $current_item->table_ids );
foreach( $array as $key => $value ) {
$table1_item = Table1::findFirst($value);
array_push( $final, $table1_item );
}
$current_item->table_ids = $final;
} else {
$current_item->table_ids = array();
}
return $current_item;
});
return $items;
}
public function beforeSave() {
if ( is_array( $this->table_ids ) ){
$cat_value = "";
$ids = $this->table_ids;
foreach( $ids as $ids_item ) {
$cat_value .= $ids_item->id;
if ( next( $ids ) == true ) $cat_value .= ",";
}
$this->table_ids = $cat_value;
}
}
}
And now we can use methods in controller like this
// get items
$items = Table2::GetItems();
foreach( $items->table_ids as $ids ) {
echo $ids->name;
}
...
$ids = new Table1();
$ids->id = 6;
$ids->name = "title 6"
$item = new Table2();
$item->id = 10;
$item->table_ids = $ids;
// or
$item->table_ids = "1,2,3,4,5";
This work

Related

Get index of each root on level wise in tree data structure

Hey I am working on tree data structure. I want to know can we get index of each node in level wise. I below diagram represent how I want the value + index. Level A or B represent node value and index value represent index value
Node
| | |
Level A -> 1 2 3
index value-> 0 1 2
| | | | | |
| | | | | |
Leve B-> 4 5 6 7 8 9
index value-> 0 1 2 3 4 5
....// more level
How can we achieved index in each level wise. I am adding my logic how I am adding value in each level wise. Could you someone suggest how can I achieve this?
var baseNode: LevelIndex = LevelIndex()
var defaultId = "1234"
fun main() {
val list = getUnSortedDataListForLevel()
val tempHashMap: MutableMap<String, LevelIndex> = mutableMapOf()
list.forEach { levelClass ->
levelClass.levelA?.let { levelA ->
val levelOneTempHashMapNode = tempHashMap["level_a${levelA}"]
if (levelOneTempHashMapNode != null) {
if (defaultId == levelClass.id && levelOneTempHashMapNode is LevelOne) {
levelOneTempHashMapNode.defaultValue = true
}
return#let
}
val tempNode = LevelOne().apply {
value = levelA
if (defaultId == levelClass.id) {
defaultValue = true
}
}
baseNode.children.add(tempNode)
tempHashMap["level_a${levelA}"] = tempNode
}
levelClass.levelB?.let { levelB ->
val levelTwoTempHashMapNode = tempHashMap["level_a${levelClass.levelA}_level_b${levelB}"]
if (levelTwoTempHashMapNode != null) {
if (defaultId == levelClass.id && levelOneTempHashMapNode is LevelTwo) {
levelTwoTempHashMapNode.defaultValue = true
}
return#let
}
val tempNode = LevelTwo().apply {
value = levelB
if (defaultId == levelClass.id) {
defaultValue = true
}
}
val parent =
tempHashMap["level_a${levelClass.levelA}"] ?: baseNode
parent.children.add(tempNode)
tempHashMap["level_a${levelClass.levelA}_level_b${levelB}"] =
tempNode
}
levelClass.levelC?.let { levelC ->
val tempNode = LevelThree().apply {
value = levelC
if (defaultId == levelClass.id) {
defaultValue = true
}
}
val parent =
tempHashMap["level_a${levelClass.levelA}_level_b${levelClass.levelB}"]
?: baseNode
parent.children.add(tempNode)
}
}
}
open class LevelIndex(
var value: String? = null,
var children: MutableList<LevelIndex> = arrayListOf()
)
class LevelOne : LevelIndex() {
var defaultValue: Boolean? = false
}
class LevelTwo : LevelIndex() {
var defaultValue: Boolean? = false
}
class LevelThree : LevelIndex() {
var defaultValue: Boolean = false
}
UPDATE
I want index value by root level because, I have one id, I want to match that combination with that id, if that value is present then I am storing that value b true, and need to find that index value.
Node
| | |
Level A -> 1 2 3
index value-> 0 1 2
default value-> false true false
| | | | | |
| | | | | |
Leve B-> 4 5 6 7 8 9
index value-> 0 1 2 3 4 5
default value->false false true false false false
....// more level
So, Level A I'll get index 1.
For Level B I'll get index 2
I'd create a list to put the nodes at each level in order. You can recursively collect them from your tree.
val nodesByLevel = List(3) { mutableListOf<LevelIndex>() }
fun collectNodes(parent: LevelIndex) {
for (child in parent.children) {
val listIndex = when (child) {
is LevelOne -> 0
is LevelTwo -> 1
is LevelThree -> 2
// I made LevelIndex a sealed class. Otherwise you would need an else branch here.
}
nodesByLevel[listIndex] += child
collectNodes(child)
}
}
collectNodes(baseNode)
Now nodesByLevel contains three lists containing all the nodes in each layer in order.
If you just need the String values, you could change that mutableList to use a String type and use += child.value ?: "" instead, although I would make value non-nullable (so you don't need ?: ""), because what use is a node with no value?
Edit
I would move defaultValue up into the parent class so you don't have to cast the nodes to be able to read it. And I'm going to treat is as non-nullable.
sealed class LevelIndex(
var value: String = "",
val children: MutableList<LevelIndex> = arrayListOf()
var isDefault: Boolean = false
)
Then if you want to do something with the items based on their indices:
for ((layerNumber, layerList) in nodesByLevel.withIndex()) {
for((nodeIndexInLayer, node) in layerList) {
val selectedIndexForThisLayer = TODO() //with layerNumber
node.isDefault = nodeIndexInLayer == selectedIndexForThisLayer
}
}

Merge several time-series datasets into one

Given several sets of time-stamped data, how can one merge them into one?
Suppose, I have a dataset represented by the following data structure (Kotlin):
data class Data(
val ax: Double?,
val ay: Double?,
val az: Double?,
val timestamp: Long
)
ax, ay, az - accelerations over the respective axes
timestamp - unix timestamp
Now, I got three datasets: Ax, Ay, Az. Each dataset has two non-null fields: the timestamp and the acceleration over its' own axis.
Ax:
+-----+------+------+-----------+
| ax | ay | az | timestamp |
+-----+------+------+-----------+
| 0.0 | null | null | 0 |
| 0.1 | null | null | 50 |
| 0.2 | null | null | 100 |
+-----+------+------+-----------+
Ay:
+------+-----+------+-----------+
| ax | ay | az | timestamp |
+------+-----+------+-----------+
| null | 1.0 | null | 10 |
| null | 1.1 | null | 20 |
| null | 1.2 | null | 30 |
+------+-----+------+-----------+
Az:
+------+------+-----+-----------+
| ax | ay | az | timestamp |
+------+------+-----+-----------+
| null | null | 2.0 | 20 |
| null | null | 2.1 | 40 |
| null | null | 2.2 | 60 |
+------+------+-----+-----------+
What the algorithm would produce is:
+------+------+------+-----------+
| ax | ay | az | timestamp |
+------+------+------+-----------+
| 0.0 | null | null | 0 |
| 0.0 | 1.0 | null | 10 |
| 0.0 | 1.1 | 2.0 | 20 |
| 0.0 | 1.2 | 2.0 | 30 |
| 0.0 | 1.2 | 2.1 | 40 |
| 0.1 | 1.2 | 2.1 | 50 |
| 0.1 | 1.2 | 2.2 | 60 |
| 0.2 | 1.2 | 2.2 | 100 |
+------+------+------+-----------+
So in order to merge three datasets into one, I:
Put Ax, Ay and Az into one list:
val united: List<Data> = arrayListOf<Data>()
united.addAll(Ax)
united.addAll(Ay)
united.addAll(Az)
Sort the resulting list by timestamp:
united.sortBy { it.timestamp }
Copy unchanged values down the stream:
var tempAx: Double? = null
var tempAy: Double? = null
var tempAz: Double? = null
for (i in 1 until united.size) {
val curr = united[i]
val prev = united[i-1]
if (curr.ax == null) {
if (prev.ax != null) {
curr.ax = prev.ax
tempAx = prev.ax
}
else curr.ax = tempAx
}
if (curr.ay == null) {
if (prev.ay != null) {
curr.ay = prev.ay
tempAy = prev.ay
}
else curr.ay = tempAy
}
if (curr.az == null) {
if (prev.az != null) {
curr.az = prev.az
tempAz = prev.az
}
else curr.az = tempAz
}
}
Remove duplicated rows (with the same timestamp):
return united.distinctBy { it.timestamp }
The above method could be improved by merging two lists at a time, I could perhaps create a function for that.
Is there a more elegant solution to this problem? Any thoughts? Thanks.
I assume that your Data rather contains vars instead of vals (otherwise your code wouldn't work). The following is a rewrite of your function using grouped timestamps and a method, that either extracts the interested property or returns the last known value for the given property otherwise.
// your tempdata containing the default (starting) values:
val tempData = Data(0.0, 0.0, 0.0, 0L)
fun extract(dataList: List<Data>, prop: KMutableProperty1<Data, Double?>) =
// find the first non null value for the given property
dataList.firstOrNull { prop(it) != null }
// extract that property
?.let(prop)
// set the extracted value in our tempData so that it can reused if a null value is retrieved in future
?.also { prop.set(tempData, it) }
// if the above didn't return a value, use the last one set into tempData
?: prop(tempData)
val mergedData = /* your united.addAll */ (Ax + Ay + Az)
.groupBy { it.timestamp }
// your sort by timestamp
.toSortedMap()
.map {(timestamp, dataList) ->
Data(extract(dataList, Data::ax),
extract(dataList, Data::ay),
extract(dataList, Data::az),
timestamp
)
It's rather hard to come up with a better approach as your main condition (defaulting to the last resolved value) will actually force you to have your dataset sorted and to hold a (or several) temporary variable(s).
However, the benefits of this version in contrast to yours are the following:
don't bother about the indices
less duplicated code
no need to remove any duplicates from the returned list (no need to distinctBy)
while the extract-method itself might be complex, the usage of it is more readable
Maybe by refactoring the extract the whole gets more readable too.
As you also said, that you want it to be easily portable to Java, here a possible Java rewrite:
Map<Long, List<Data>> unitedList = Stream.concat(Stream.concat(Ax.stream(), Ay.stream()), Az.stream())
.collect(Collectors.groupingBy(Data::getTimestamp));
List<Data> mergedData = unitedList.keySet().stream().sorted()
.map(key -> {
List<Data> dataList = unitedList.get(key);
return new Data(extract(dataList, Data::getAx, Data::setAx),
extract(dataList, Data::getAy, Data::setAy),
extract(dataList, Data::getAz, Data::setAz),
key);
}).collect(Collectors.toList());
and the extract could then look like:
Double extract(List<Data> dataList, Function<Data, Double> getter, BiConsumer<Data, Double> setter) {
Optional<Double> relevantProperty = dataList.stream()
.map(getter)
.filter(Objects::nonNull)
.findFirst();
if (relevantProperty.isPresent()) {
setter.accept(tempData, relevantProperty.get());
return relevantProperty.get();
} else {
return getter.apply(tempData);
}
}
Basically the same mechanism.
So at the moment I am using this solution:
data class Data(
var ax: Double?,
var ay: Double?,
var az: Double?,
val timestamp: Long
)
fun mergeDatasets(Ax: List<Data>, Ay: List<Data>, Az: List<Data>): List<Data> {
val united = mutableListOf<Data>()
united.addAll(Ax)
united.addAll(Ay)
united.addAll(Az)
united.sortBy { it.timestamp }
var tempAx: Double? = null
var tempAy: Double? = null
var tempAz: Double? = null
for (i in 1 until united.size) {
val curr = united[i]
val prev = united[i-1]
if (curr.ax == null) {
if (prev.ax != null) {
curr.ax = prev.ax
tempAx = prev.ax
}
else curr.ax = tempAx
}
if (curr.ay == null) {
if (prev.ay != null) {
curr.ay = prev.ay
tempAy = prev.ay
}
else curr.ay = tempAy
}
if (curr.az == null) {
if (prev.az != null) {
curr.az = prev.az
tempAz = prev.az
}
else curr.az = tempAz
}
if (curr.timestamp == prev.timestamp) {
prev.ax = curr.ax
prev.ay = curr.ay
prev.az = curr.az
}
}
return united.distinctBy { it.timestamp }
}

Retrieve values from a table without using filters

I have two tables
Estatus table
+----+----------+
| ID | Nombre |
+----+----------+
| 16 | ACTIVE |
| 2 | DISABLED |
+----+----------+
TipoCarga Table
+----+----------+--------------------+-----------+
| ID | Nombre | Descripcion | EstatusID |
+----+----------+--------------------+-----------+
| 1 | Active | first descriptio | 16 |
| 2 | Disabled | second description | 2 |
+----+----------+--------------------+-----------+
and I have custom entity to list and charge like:
IEnumerable<T> Listar(Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "");
T Cargar(Expression<Func<T, bool>> filter,
string includeProperties = "");
if I do something like
var claseid = _claseService.Cargar(c => c.Nombre.Equals("TIPOS DE CARGA")).ID;
var estatusid = _estatusService.Cargar(e => e.Nombre.Equals("ACTIVE") && e.ClaseID.Equals(claseid)).ID;
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
It load values, but only values who have Nombre.Equals("ACTIVO"), and I want to get all Nombre values
So first I try to get a list of all Estatus like:
//I don´t need this line because I dont wannt to filte by clase
//var claseid = _claseService.Cargar(c => c.Nombre.Equals("TIPOS DE CARGA")).ID;
//get list of all estatus
var estatusid = _estatusService.Listar();
then use this list and use to retrieve value "Nombre" depending of EstatusID of table TipoCarga like:
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
Problem is var estatusid = _estatusService.Listar(); get 0 values, and I don´t know why it don´t get values, can someone help ? Regards
Methods implementations:
public virtual IEnumerable<T> Listar(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = _dbset;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).AsEnumerable<T>();
}
else
{
return query.AsEnumerable<T>();
}
}
public virtual T Cargar(
Expression<Func<T, bool>> filter,
string includeProperties = "")
{
IQueryable<T> query = _dbset;
query = query.AsNoTracking().Where(filter);
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
return query.FirstOrDefault();
}
UPDATE:
If I use .Tolist as Ivan Stoev comment it return all values of Estatus but now it pass to:
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
then it load foreach like:
if (tc != null)
{
List<TipoCargaViewModel> tiposcargas = new List<TipoCargaViewModel>();
foreach (var item in tc)
{
var carga = new TipoCargaViewModel()
{
ID = item.ID,
Nombre = item.Nombre,
Descripcion = item.Descripcion,
EstatusID = item.EstatusID,
NombreEstatus = item.Estatus.Nombre
};
tiposcargas.Add(carga);
}
But when it try to pass foreach it throw an exception:
Unable to create a constant value of type 'Project.Model.Estatus'.
Only primitive types or enumeration types are supported in this
context.

RavenDB: How can I properly index a cartesian product in a map-reduce?

This question is a spin-off of RavenDB: Why do I get null-values for fields in this multi-map/reduce index?, but I realized, the problem was another.
Consider my extremely simplified domain, rewritten to a movie rental store scenario for abstraction:
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
It's a text-book many-to-many example.
The index I want to create is this:
For a given user, give me a list of every movie in the database (filtering/search left out for the moment) along with an integer describing how many times (or zero) the user has rented this movie.
Basically like this:
Users:
| Id |
|--------|
| John |
| Lizzie |
| Albert |
Movies:
| Id |
|--------------|
| Robocop |
| Notting Hill |
| Inception |
MovieRentals:
| Id | UserId | MovieId |
|-----------|--------|--------------|
| rental-00 | John | Robocop |
| rental-01 | John | Notting Hill |
| rental-02 | John | Notting Hill |
| rental-03 | Lizzie | Robocop |
| rental-04 | Lizzie | Robocop |
| rental-05 | Lizzie | Inception |
Ideally, I want an index to query, that would look like this:
| UserId | MovieId | RentalCount |
|--------|--------------|-------------|
| John | Robocop | 1 |
| John | Notting Hill | 2 |
| John | Inception | 0 |
| Lizzie | Robocop | 2 |
| Lizzie | Notting Hill | 0 |
| Lizzie | Inception | 1 |
| Albert | Robocop | 0 |
| Albert | Notting Hill | 0 |
| Albert | Inception | 0 |
Or declaratively:
I always want a full list of all the movies (eventually I will add filtering/searching) - even when providing a user that has never rented a single movie yet
I want a count of the rentals for each user, just the integer
I want to be able to sort by the rental-count - i.e. show the most-rented movies for a given user at the top of the list
However, I can't find a way to make the "cross-join" above and save it in the index. Instead, I initially thought I got it right with this maneuver below, but it does not allow me to sort (see failing test):
{"Not supported computation: x.UserRentalCounts.SingleOrDefault(rentalCount => (rentalCount.UserId == value(UnitTestProject2.MovieRentalTests+<>c__DisplayClass0_0).user_john.Id)).Count. You cannot use computation in RavenDB queries (only simple member expressions are allowed)."}
My question is basically: how can I - or can I at all - index so, that my requirements are fulfilled?
Below is my mentioned example, that does not fulfill my requirements, but that's where I am right now. It uses the following packages (VS2015):
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net461" />
<package id="NUnit" version="3.5.0" targetFramework="net461" />
<package id="RavenDB.Client" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Database" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Tests.Helpers" version="3.5.2" targetFramework="net461" />
</packages>
MovieRentalTests.cs
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Client.Indexes;
using Raven.Client.Linq;
using Raven.Tests.Helpers;
namespace UnitTestProject2
{
[TestFixture]
public class MovieRentalTests : RavenTestBase
{
[Test]
public void DoSomeTests()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
//Test-data
var user_john = new User { Id = "John" };
var user_lizzie = new User { Id = "Lizzie" };
var user_albert = new User { Id = "Albert" };
var movie_robocop = new Movie { Id = "Robocop" };
var movie_nottingHill = new Movie { Id = "Notting Hill" };
var movie_inception = new Movie { Id = "Inception" };
var rentals = new List<MovieRental>
{
new MovieRental {Id = "rental-00", UserId = user_john.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-01", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-02", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-03", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-04", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-05", UserId = user_lizzie.Id, MovieId = movie_inception.Id}
};
//Init index
new Movies_WithRentalsByUsersCount().Execute(store);
//Insert test-data in db
using (var session = store.OpenSession())
{
session.Store(user_john);
session.Store(user_lizzie);
session.Store(user_albert);
session.Store(movie_robocop);
session.Store(movie_nottingHill);
session.Store(movie_inception);
foreach (var rental in rentals)
{
session.Store(rental);
}
session.SaveChanges();
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
}
//Test of correct rental-counts for users
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.ToList();
var robocopWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_robocop.Id);
Assert.AreEqual(1, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(2, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
var nottingHillWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_nottingHill.Id);
Assert.AreEqual(2, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
}
// Test that you for a given user can sort the movies by view-count
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.OrderByDescending(x => x.UserRentalCounts.SingleOrDefault(rentalCount => rentalCount.UserId == user_john.Id).Count)
.ToList();
Assert.AreEqual(movie_nottingHill.Id, allMoviesWithRentalCounts[0].MovieId);
Assert.AreEqual(movie_robocop.Id, allMoviesWithRentalCounts[1].MovieId);
Assert.AreEqual(movie_inception.Id, allMoviesWithRentalCounts[2].MovieId);
}
}
}
public class Movies_WithRentalsByUsersCount :
AbstractMultiMapIndexCreationTask<Movies_WithRentalsByUsersCount.ReducedResult>
{
public Movies_WithRentalsByUsersCount()
{
AddMap<MovieRental>(rentals =>
from r in rentals
select new ReducedResult
{
MovieId = r.MovieId,
UserRentalCounts = new[] { new UserRentalCount { UserId = r.UserId, Count = 1 } }
});
AddMap<Movie>(movies =>
from m in movies
select new ReducedResult
{
MovieId = m.Id,
UserRentalCounts = new[] { new UserRentalCount { UserId = null, Count = 0 } }
});
Reduce = results =>
from result in results
group result by result.MovieId
into g
select new
{
MovieId = g.Key,
UserRentalCounts = (
from userRentalCount in g.SelectMany(x => x.UserRentalCounts)
group userRentalCount by userRentalCount.UserId
into subGroup
select new UserRentalCount { UserId = subGroup.Key, Count = subGroup.Sum(b => b.Count) })
.ToArray()
};
}
public class ReducedResult
{
public string MovieId { get; set; }
public UserRentalCount[] UserRentalCounts { get; set; }
}
public class UserRentalCount
{
public string UserId { get; set; }
public int Count { get; set; }
}
}
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
}
}
Since your requirement says "for a given user", if you really are looking only for a single user, you can do this with a Multi-Map index. Use the Movies table itself to produce the baseline zero-count records and then map in the actual MovieRentals records for the user on top of that.
If you really need it for all users crossed with all movies, I don't believe there is a way to do this cleanly with RavenDB as this would be considered reporting which is noted as one of the sour spots for RavenDB.
Here are some options if you really want to try to do this with RavenDB:
1) Create dummy records in the DB for every user and every movie and use those in your index with a 0 count. Whenever a movie or user is added/updated/deleted, update the dummy records accordingly.
2) Generate the zero-count records yourself in memory on request and merge that data with the data that RavenDB gives you back for the non-zero counts. Query for all users, query for all movies, create the baseline zero-count records, then do the actual query for non-zero counts and layer that on top. Finally, apply paging/filtering/sorting logic.
3) Use the SQL replication bundle to replicate the Users, Movies, and MovieRental tables out to SQL and use SQL for this "reporting" query.

Get the data 'less common as possible' from a collection

Starting from a table like this:
| Code | Year |
---------------
| A01 | 2001 |
| A01 | 2002 |
| B01 | 2002 |
| C01 | 2003 |
I have to arrive to this:
| Code | Year |
---------------
| A01 | 2001 |
| B01 | 2002 |
| C01 | 2003 |
I have to group the first column (Code) and from the second (Year) I have to get the data 'less common as possible' compared to all the other records. I try to explain this with the example: for the code 'A01' I have 2 years: '2001' and '2002'. I have to take '2001' because it's the one that not recurs in the other records. In the case that there aren't available values 'Year' that not recurs in the other records, it's good to take whatever value.
The data are in the form of array in memory and to interact with them I'm useng some LINQ queries.
Thank you in advance!
Pileggi
I'm sorry for making it in C#. Hope you will not have problems to convert it to VB.NET.
var filteredItems = items
.Select(cod => cod.Code).Distinct()
.Select(cod => items.OrderBy(itm => itm.Year).First(itm => itm.Code == cod))
.ToList();
Test code:
public class Item
{
public string Code { get; set; }
public string Year { get; set; }
}
public static void Main(string[] args)
{
var items =
new List<Item>
{
new Item{ Code = "A01", Year = "2001" },
new Item{ Code = "A01", Year = "2002" },
new Item{ Code = "B01", Year = "2002" },
new Item{ Code = "C01", Year = "2003" },
};
var filteredItems = items
.Select(cod => cod.Code).Distinct()
.Select(cod => items.OrderBy(itm => itm.Year).First(itm => itm.Code == cod))
.ToList();
}
Here is the right answer (to be compared with Alex Aza's one : filteredItemsAlexAza and filteredItemsSsithra give different results since the less common data is not also the minimum one anymore - here 2005 instead of 2001 for A01)
class Program
{
public static void Main(string[] args)
{
var items = new List<Item>
{
new Item { Code = "A01", Year = "2005" },
new Item { Code = "A01", Year = "2002" },
new Item { Code = "B01", Year = "2002" },
new Item { Code = "C01", Year = "2003" },
};
var filteredItemsAlexAza = items.Select(cod => cod.Code).Distinct().Select(cod => items.OrderBy(itm => itm.Year).First(itm => itm.Code == cod)).ToList();
var filteredItemsSsithra = items
.Select(item => new { Item = item, NbItemsWithSameYear = items.Where(i => i.Year == item.Year).Count() })
.GroupBy(ano => ano.Item.Code)
.Select(group => group.OrderBy(ano => ano.NbItemsWithSameYear).First().Item)
.ToList();
}
public class Item
{
public string Code { get; set; }
public string Year { get; set; }
}
}
You can google for "Concordance source code" in your preferred implementation language.
Sorry but the proposed solution absolutely doesn't fullfill Pileggi's initial requirements.
"The less common value as possible" has become "the min value".
Both match in this precise case, which gives the illusion that Alex Aza's answer is right but it is just a coincidence.