realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.28k stars 2.14k forks source link

distinct query in Realm database #1103

Closed alexwillrock closed 6 years ago

alexwillrock commented 9 years ago

hello, realm is super fast db but very necessary distinct query from database e.g. schedules = Schedules.objectsWhere("areas.id = '(idAreas)' distict")

jpsim commented 9 years ago

We agree that this would be a useful feature and will consider adding it to Realm's querying capabilities.

In the meantime, you'll have to do this yourself:

let schedules = Schedule.allObjects()
var uniqueIDs = [String]()
var uniqueSchedules = [Schedule]()
for schedule in schedules {
  let schedule = schedule as Schedule
  let scheduleID = schedule.areas.id
  if !contains(uniqueIDs, scheduleID) {
    uniqueSchedules.append(schedule)
    uniqueIDs.append(scheduleID)
  }
}
println(uniqueSchedules) // Schedule objects with unique `areas.id` values
shmidt commented 9 years ago

+1

ajimix commented 9 years ago

+1 here :)

kunalsood commented 9 years ago

+1 This would be especially useful when number of sections in a grouped tableView is dynamic, iterating over all objects to find distinct sectionIdentifier properties after every change notification seems extremely inefficient, especially for large datasets.

It would be even better if this could be built into RLMResults or another similar class, and potentially support change/fine grain notifications in the future.

ghost commented 9 years ago

yes, something like @distinctUnionOfObjects is badly needed

stsmurf commented 9 years ago

+1 - 10,000+ records with a lot of duplicates, just need to know the distinct/unique values in a column!

anbarasu0504 commented 9 years ago

+1

marcoscurvello commented 9 years ago

+1

HiromichiYamada commented 9 years ago

+1

lukcz commented 9 years ago

+1

kevinmlong commented 9 years ago

For Swift 1.2 (due to the contains func) you could do something like this

import Foundation
import RealmSwift

extension Results {

    func uniqueValueForObject<U : Equatable>(objectKey: String, paramKey: String, type: U.Type)->[U]{

        var uniqueValues : [U] = [U]()

        for obj in self {
            let obj = obj as T
            if(obj.valueForKey(objectKey) != nil){
                let uniqueObj: Object = obj.valueForKey(objectKey) as! Object
                if(uniqueObj.valueForKey(paramKey) != nil){
                    let uniqueObjValue = uniqueObj.valueForKey(paramKey) as! U
                    if !contains(uniqueValues, uniqueObjValue) {
                        uniqueValues.append(uniqueObjValue)
                    }
                }
            }

        }
        return uniqueValues
    }

    func uniqueValue<U : Equatable>(paramKey: String, type: U.Type)->[U]{

        var uniqueValues : [U] = [U]()

        for obj in self {
            let obj = obj as T
            if(obj.valueForKey(paramKey) != nil){
                let uniqueValue = obj.valueForKey(paramKey) as! U
                if !contains(uniqueValues, uniqueValue) {
                    uniqueValues.append(uniqueValue)
                }
            }

        }
        return uniqueValues
    }

    func uniqueObject(paramKey: String)->[Object]{

        var uniqueObjects : [Object] = [Object]()

        for obj in self {
            let obj = obj as T
            if(obj.valueForKey(paramKey) != nil){
                let uniqueObj : Object = obj.valueForKey(paramKey) as! Object
                if !contains(uniqueObjects,uniqueObj) {
                    uniqueObjects.append(uniqueObj)
                }
            }

        }
        return uniqueObjects
    }

}

Then you can use it like this...

Let's say we have a two very simple models Item and SubItem

class SubItem : Object {
     dynamic var name : String = ""
}

class Item : Object {
     dynamic var name : String = ""
     dynamic var subItem : String = ""
}

To use the extension one would use:

Realm().objects(Item).uniqueValue("name", type: String.self)
Realm().objects(Item).uniqueValueForObject("subItem", paramKey: "name", type: String.self)
Realm().objects(Item).uniqueObject("subItem")
kevinmlong commented 9 years ago

Here's an updated version of the extension above, using Swift 2.0 (and Swift 2.0 compatible version of Realm)

@jpsim and @segiddins - thoughts on this implementation?

extension Results {

    func uniqueValueForObject<U : Equatable>(objectKey: String, paramKey: String, type: U.Type)->[U]{

        var uniqueValues : [U] = [U]()

        for obj in self {

            if let o = obj.valueForKeyPath(objectKey) {

                if let v = o.valueForKeyPath(paramKey){

                    if(!uniqueValues.contains(v as! U)){
                        uniqueValues.append(v as! U)
                    }

                }
            }

        }
        return uniqueValues
    }

    func uniqueValue<U : Equatable>(paramKey: String, type: U.Type)->[U]{

        var uniqueValues : [U] = [U]()

        for obj in self {

            if let val = obj.valueForKeyPath(paramKey) {

                if (!uniqueValues.contains(val as! U)) {
                    uniqueValues.append(val as! U)
                }

            }

        }
        return uniqueValues
    }

    func uniqueObject(paramKey: String)->[Object]{

        var uniqueObjects : [Object] = [Object]()

        for obj in self {

            if let val = obj.valueForKeyPath(paramKey) {
                let uniqueObj : Object = val as! Object
                if !uniqueObjects.contains(uniqueObj) {
                    uniqueObjects.append(uniqueObj)
                }
            }

        }
        return uniqueObjects
    }

}
apocolipse commented 9 years ago

Instead of doing all that jazz above, I took the functional programming approach:

// Query all users
let allUsers = Realm().objects(User)
// Map out the user types
let allTypes = map(allUsers) { $0.type }
// Fun part: start with empty array [], add in element to reduced array if its not already in, else add empty array
let distinctTypes = reduce(allTypes, []) { $0 + (!contains($0, $1) ? [$1] : [] )

Or for a nice one-liner

let distinctTypes = reduce(map(Realm().objects(User)) { $0.type! }, []) { $0 + (contains($0, $1) ? [] : [$1] ) }

Couple of notes here: Suffix'd map/reduce (Realm().objects(User).map { $0.type } for example) don't play nice with the Result object but the standalone map() and reduce() do just fine. I'm not sure of performance benefits over above solution but for brevity and concision, its a winner :D And finally, I'm not sure how efficiently Realm handles object/property loading, they are lazily loaded supposedly so there shouldn't be any real benefit to having the Library itself do this over using something as simple as the one-liner above (aside from indexed lookups however?) Benefits for the library with distinct queries would be more along the lines of aggregations but I'm not sure thats the route Realm is going right now.

Edit: Updated to remove need for map bringing efficiency up an order

let distinctTypes = reduce(Realm().objects(User), []) { $0 + (!contains($0, $1.type) ? [$1.type] : [] ) }
shmidt commented 9 years ago

@kevinmlong Thanks for sharing!

jpsim commented 9 years ago

or, more efficient (but stringly typed):

let distinctTypes = Set(Realm().objects(User).valueForKey("type") as! [String])
beloso commented 9 years ago

+1

ivnsch commented 8 years ago

@apocolipse it's not very efficient to use reduce to build arrays, see e.g. http://airspeedvelocity.net/2015/08/03/arrays-linked-lists-and-performance/

kexoth commented 8 years ago

I'm kinda confused here. :confounded:
I use the latest version of RealmSwift & when I try to use @distinctUnionOfObjects in valueForKeyPath I get this error:

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<RLMAccessor_v0_KXStatus 0x16574e00> valueForUndefinedKey:]: this class is not key value coding compliant for the key @distinctUnionOfObjects.'

On Realm's NSPredicate Cheatsheet it's documented as an available feature & yet here this issue is still open.

Am I doing something wrong or this feature is still not in production?

kishikawakatsumi commented 8 years ago

@kexoth We don't support @distinctUnionOfObjects in queries yet, everything with the pink dot next to it is what we currently support.

kexoth commented 8 years ago

@kishikawakatsumi this is the fastest response on Github Issues ever!! :+1: Thanks for the info, I missed that detail in the documentation, now I saw that :sweat_smile:

ivnsch commented 8 years ago

+1

jpsim commented 8 years ago

Support for this has been added in Realm's core, so it's no longer blocked. We do need to implement it in cocoa though, probably via @distinctUnionOfObjects.

Kirow commented 8 years ago

Any info about when it will be implemented to realm-cocoa?

jpsim commented 8 years ago

We've made lots of progress in core on this recently, but no one's actively working on exposing it via the Objective-C or Swift APIs. So no timeline we can share.

timothyrobb commented 8 years ago

+1 for this.

The number of times I've used Realm on a project then regretted it when somewhat fundamental tools like this are lacking... It's a bit grating :) Love the work you guys are doing though, just probably worthwhile waiting another half year or so next time.

JaNd3r commented 8 years ago

+1

Started moving an app from CoreData to RealmSwift and the first query to convert uses request.returnDistinctResults on a table with approximately 100k+ rows. ;)

TThana commented 8 years ago

+9

markjarecki commented 8 years ago

+1

cbess commented 8 years ago

+1

torcelly commented 8 years ago

+1

jpsim commented 8 years ago

This needs a detailed API design to be proposed before we can move forward

torcelly commented 8 years ago

In this category the functionality is implemented in a simple way at high level. Until the guys from Realm implement the functionality at low level I don't think it’s a bad solution.:

https://github.com/torcelly/RLMObjectDistinct

JadenGeller commented 8 years ago

@torcelly It's important to note that that solution loses the lazy-loading functionality of RLMResult since it iterates over all objects in the results.

torcelly commented 8 years ago

Totally agree. The performance isn't optimal and the value returned it's an NSArray . As I mentioned before, it's a temporal solution until the Realm guys add the feature. However it could be valid in some cases and the measure times is good even for a huge RLMResults count.

Add your comment to Readme. Thanks man!

JadenGeller commented 8 years ago

You could also improve performance from O(n^2) to O(n) by making uniqueIDs an NSMutableSet instead of an NSMutableArray. 🙂

torcelly commented 8 years ago

It would be a great solution to improve the performance. However several methods add sort and we won't be able to access to the indexes. I could return NSMutableSet in the methods without sort but I don't know if the category lose consistency. Thoughts?

JadenGeller commented 8 years ago

uniqueIDs is never returned, so this shouldn't be an issue, no? uniqueArray would not change type.

torcelly commented 8 years ago

My mistake! you're right. I thought you were talking about uniqueArray. Changed and committed. Thanks for the feedback.

tstanik commented 8 years ago

+1

bgnlordhelmut commented 8 years ago

+1

chml commented 8 years ago

Any updates?

de-dan commented 7 years ago

+1

VladasZ commented 7 years ago

+1

moritzmoritz commented 7 years ago

+1

kimdv commented 7 years ago

+1

Akaren13 commented 7 years ago

+1

ghost commented 7 years ago

+1

ghost commented 7 years ago

Any updates? This seems to be a dead end, as RBQ or realm results controller don't work for swift 3!

jpsim commented 7 years ago

Work on this is ongoing here: realm/realm-object-store#235.

However, we still need to design how this feature should be exposed in our Objective-C and Swift APIs. Unfortunately, NSPredicate's @distinctUnionOfObjects isn't appropriate as that only returns the unique values of the property being "uniqued" (e.g. @distinctUnionOfObjects.name returning an array of unique names, not one object for each unique name).

A new method on Results (e.g. extension Results { func distinct(property: String) -> Self }) is an obvious way to expose this, but like I said, we haven't discussed it internally and no one from the community has stepped up to make a proposal.

quiet324 commented 7 years ago

+9