在Swift中使用NSPredicate进行数据库查询

Posted on Posted in iOS, Objective-C, Swift

在Swift中使用NSPredicate进行数据库查询

简介

NSPredicate提供了一个通用的数据查询方式,有两种Predicate类型,分别是comparison 和 compound:

  • comparison predicate 使用运算符来比较两个表达式
  • compound predicate 对比两个或多个predicate的结果,或者让其他的predicate 失效.

Cocoa 里支持非常多的 predicate 类型,例如:

  • 简单的比较: grade == 7 或者 firstName like 'Mark'
  • 大小写或读音敏感的查询:name contains[cd] 'citroen'
  • 逻辑操作符:(firstName beginswith 'M') AND (lastName like 'Adderley')

你还可以根据关系创建predicate,例如:group.name matches 'work.*'ALL children.age > 12以及ANY children.age > 12根据操作创建例如:@sum.items.price < 1000.

你可以把 predicates 用在任何类上,但这个类必须是遵循 key-value-coding 的类。

NSPredicate的局限

predicate查询可能会翻译成SQL语句或xml格式或者其他格式,这取决于后端存储的类型。因此并不是所有的操作后端存储都能支持。下面是一些例子:

  • matches 关键字使用正则来进行匹配,所以是不被CoreData的 SQL 存储支持的,但是却可以用在内存中的过滤。
  • CoreData SQL 存储里每次查询只能支持一对多的操作,因此任何应用在SQL存储的predicate都只能有 ALL AND IN 中的一个操作符。
  • 不能将任意的SQL查询转换成predicate。
  • ANYKEY操作符只能用在SpotLight的查询中。
  • SpotLight中不支持关系查询。

创建Predicate

  1. 使用格式化的字符串创建

    可以使用NSPredicate的类方法predicateWithFormat 从字符串创建一个predicate对象:

    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"(lastName like[cd] %@) AND (birthday > %@)", lastNameSearchString, birthdaySearchDate];
    

    示例代码中的like[cd]操作符表示大小写(c 表示case-insensitive,)发音(d 表示 diacritic-insensitive)不敏感。

  2. 字符串常量、变量和通配符

    字符串常量必须用引号包裹起来,用单引号或双引号都是可以的,如果你用%@来进行参数替换(例如firstName like %@),引号会自动添加,如果你用的是字符串常量,那你必须自己用引号包裹起来:

    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"lastName like[c] 'S*'"];
    // 或者
    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"lastName like[c] \"S*\""];
    

    在使用通配符时必须考虑引号的问题,你应该把通配符添加到一个预定义的变量中来进行参数替换:

    NSString* prefix = @"prefix";
    NSString* suffix = @"suffix";
    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"SELF like[c] %@", [[prefix stringByAppendingString: @"*"] stringByAppendingString: suffix]];
    BOOL ok = [predicate evaluateWithObject: @"prefixxxxxxxsuffix"];
    

    下面则是一个错误的例子:

    predicate = [NSPredicate predicateWithFormat: @"SELF like[c] %@*%@", prefix, suffix];
    ok = [predicate evaluateWithFormat: @"prefixxxxxsuffix"];
    
  3. 布尔值

    用下面的方法可以来测试布尔值是否相等:

    let aBool = true
    
    let anotherPredicate = NSPredicate(format: "anAttribute == %@", NSNumber(bool: aBool))
    NSPredicate(format: "anAttribute == YES")
    
  4. 动态属性名

    当通过参数替换时,因为string变量会被双引号包裹,所以不能用%@来指定动态属性:

    NSString* attribute = @"firstName";
    NSString* attributeValue = @"Adam";
    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"%@ like %@", attributeName, attributeValue];
    

    如果需要指定一个动态属性,可以使用%K:

    predicate = [NSPredicate predicateWithFormat: @"%K like %@", attributeName, attributeValue];
    
  5. 直接用代码来创建的NSPredicate

    Objective-C:

    NSExpression *lhs = [NSExpression expressionForKeyPath:@"revenue"];
    
    NSExpression *greaterThanRhs = [NSExpression expressionForConstantValue:[NSNumber numberWithInt:1000000]];
    NSPredicate *greaterThanPredicate = [NSComparisonPredicate
       predicateWithLeftExpression:lhs
       rightExpression:greaterThanRhs
       modifier:NSDirectPredicateModifier
       type:NSGreaterThanOrEqualToPredicateOperatorType
       options:0];
    
    NSExpression *lessThanRhs = [NSExpression expressionForConstantValue:[NSNumber numberWithInt:100000000]];
    NSPredicate *lessThanPredicate = [NSComparisonPredicate
       predicateWithLeftExpression:lhs
       rightExpression:lessThanRhs
       modifier:NSDirectPredicateModifier
       type:NSLessThanPredicateOperatorType
       options:0];
    
    NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
       @[greaterThanPredicate, lessThanPredicate]];
    

    Swift:

    let lhs = NSExpression(forKeyPath: "revenue")
    
    let greaterThanRhs = NSExpression(forConstantValue: "张三")
    let greaterPredicate = NSComparisonPredicate(leftExpression: lhs, rightExpression: greaterThanRhs, modifier: .DirectPredicateModifier, type: .BeginsWithPredicateOperatorType, options: .CaseInsensitivePredicateOption)
    
    let lessThanRhs = NSExpression(forConstantValue: 1000000000000)
    let lessThanPredicate = NSComparisonPredicate(leftExpression: lhs, rightExpression: lessThanRhs, modifier: .DirectPredicateModifier, type: .LessThanOrEqualToPredicateOperatorType, options: .CaseInsensitivePredicateOption)
    
    let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [greaterPredicate, lessThanPredicate])
    
  6. 使用模板来创建predicate

    Objective-C:

    NSPredicate* predicateTemplate = [NSPredicate predicateWithFormat: @"lastName like[c] $LAST_NAME"];
    NSPredicate* predicate = [predicateTemplate predicateWithSubstitutionVariables: [NSDictionary dictionaryWithObject:@"Turner" forKey:@"LAST_NAME"]];
    

    Swift:

    let predicateTemplate = NSPredicate(format: "lastName like[c]  $LAST_NAME")
    
    let new = predicateTemplate.predicateWithSubstitutionVariables(["LAST_NAME":"turner"])
    
  7. 使用Predicate

    使用NSPredicate的evaluateWithObject方法并传入要查询的数据,这个方法会返回一个布尔值:

    Objective-C:

    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"SELF in %@",@[@"Stig", @"Shaffig", @"Chris"]];
    BOOL result = [predicate evaluateWithObject: @"Shaffig"];
    

    Swift:

    let predicate = NSPredicate(format: "SELF in %@", ["Stig", "Shaffig", "Chris"]) // SELF IN {"Stig", "Shaffig", "Chris"}
    let result = predicate.evaluateWithObject("Stig") // true
    

    在数组中使用Predicate

    Objective-C:

    NSMutableArray* array = [NSMutableArray arrayWithObjects: @"Nick", @"Ben", @"Adam", @"Melissa", nil];
    
    NSPredicate* sPredicate - [NSPredicate predicateWithFormat: @"SELF contains[c] 'e'"];
    [array filterUsingPredicate: sPredicate];
    

    在predicate中传入key-path

    NSString* departmentName = ...;
    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"department.name like %@", departmentName];
    

    如果你使用的是一对多的关系,那么predicate的构造会有些许不同:

    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"ANY employees.firtName like 'Matthew'"];
    
    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"ANY employees.salary > %f", salary];
    

    使用空值

    comparison predicate不会匹配任何的null值,除非nil或者NSNull,也就是($value == nil)。看一下下面的例子:

    NSString* firstName = @"Ben";
    
           NSArray* array = @[@{@"lastName" : @"Turner"},
                              @{@"firstName" : @"Ben", @"lastName" : @"Ballard",
                                @"birthday" : [NSDate dateWithString: @"1972-03-24 10:45:32 +0600"]}
                              ];
           NSPredicate* predicate = [NSPredicate predicateWithFormat:@"firstName like %@", firstName];
           NSArray* filteredArray = [array filteredArrayUsingPredicate:predicate];
           NSLog(@"%@", filteredArray);
    

    predicate 匹配了包含firstName 键的值,却没匹配上不包含这个键的值,下面的例子是同样的道理:

    NSDate* referenceDate = [NSDate dateWithTimeIntervalSince1970:0];
           predicate = [NSPredicate predicateWithFormat:@"birthday > %@", referenceDate];
           filteredArray = [array filteredArrayUsingPredicate:predicate];
           NSLog(@"%@", filteredArray); // (
           // { birthday = "1972-03-24 04:45:32 +0000";
            // firstName = Ben;
            //lastName = Ballard; })
    

    如果你想要匹配null值,你就得添加一条额外的对比:

    predicate = [NSPredicate predicateWithFormat:@"(firstName == %@) || (firstName == nil)"];
           filteredArray = [array filteredArrayUsingPredicate:predicate];
           NSLog(@"%@", filteredArray); 
    

    在 CoreData 上使用predicate

    下面的例子展示了在CoreData中使用NSPredicate进行查询:

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
           NSEntityDescription *entity = [[NSEntityDescription entityForName: @"Employee"] inManagedObjectContext: managedObjectContext];
           [request setEntity: entity];
           NSNumber *salaryLimit = @2000;
           NSPredicate *predicate = [NSPredicate predicateWithFormat:@"salary > %@", salaryLimit];
           [request setPredicate: predicate];
           NSError *error;
           NSArray *array = [managedObjectContext executeFetchRequest: request error: &error];
    

发表评论

电子邮件地址不会被公开。 必填项已用*标注