首页 > 解决方案 > Laravel Collection 按来自另一个集合的值过滤集合

问题描述

我正在构建自定义 Collection 类,但在过滤数据时遇到了一些问题。

我有一组财务数据 ($collection),如下所示:

App\CustomCollection {
    #items: array [
        0 => {
            +"financials": [
                0 => {
                    +"data_tag": {
                        +"tag": "revenue"
                    }
                    +"value": 1200
                }
            ]
            +"fundamental": {
                +"fiscal_year": 2018
                +"fiscal_period": "FY"
                +"start_date": "2018-01-01"
                +"end_date": "2018-12-31"
            }
            +"company": {#336
                +"id": "com_TEST1"
            }
        }
        1 => {
            +"financials": [
                0 => {
                    +"data_tag": {
                        +"tag": "revenue"
                    }
                    +"value": 300
                }
            ]
            +"fundamental": {
                +"fiscal_year": 2018
                +"fiscal_period": "Q1"
                +"end_date": "2018-03-31"
            }
            +"company": {#336
                +"name": "Test Company Inc"
            }
        }
        2 => {
            +"financials": [
                0 => {
                    +"data_tag": {
                        +"tag": "revenue"
                    }
                    +"value": 300
                }
            ]
            +"fundamental": {
                +"fiscal_year": 2018
                +"fiscal_period": "Q2"
                +"end_date": "2018-06-30"
            }
            +"company": {#336
                +"name": "Test Company Inc"
            }
        }

        ... etc to Q4
    ]
}

我可以通过以下方式成功返回我的会计年度:

$fiscalYears = $collection->where('fundamental.fiscal_period', 'FY');

回报:

App\CustomCollection {
    #items: array [
        0 => {
            +"financials": [
                0 => {
                    +"data_tag": {
                        +"tag": "revenue"
                    }
                    +"value": 1200
                }
            ]
            +"fundamental": {
                +"fiscal_year": 2018
                +"fiscal_period": "FY"
                +"start_date": "2018-01-01"
                +"end_date": "2018-12-31"
            }
            +"company": {#336
                +"id": "com_TEST1"
            }
        }
    ]
}

我也可以通过以下方式仅返回宿舍:

$fiscalYears = $collection->where('fundamental.fiscal_period', '!=', 'FY');

App\CustomCollection {
    #items: array [
        0 => {
            +"financials": [
                0 => {
                    +"data_tag": {
                        +"tag": "revenue"
                    }
                    +"value": 300
                }
            ]
            +"fundamental": {
                +"fiscal_year": 2018
                +"fiscal_period": "Q1"
                +"end_date": "2018-03-31"
            }
            +"company": {#336
                +"name": "Test Company Inc"
            }
        }
        1 => {
            +"financials": [
                0 => {
                    +"data_tag": {
                        +"tag": "revenue"
                    }
                    +"value": 300
                }
            ]
            +"fundamental": {
                +"fiscal_year": 2018
                +"fiscal_period": "Q2"
                +"end_date": "2018-06-30"
            }
            +"company": {#336
                +"name": "Test Company Inc"
            }
        }

        ... etc to Q4
    ]
}

这里一切都很好并且工作正常,但现在我想要完成的是只返回与 $fiscalYears 中的特定值匹配的季度。

这是我现在使用的:

public function quartersByFiscalYears($fiscalYears)
    {
        $quarters =  [];
        foreach ($fiscalYears as $fiscalYear) {
            $quarters[] = $this->quarters()->where('company', $fiscalYear->company)
                ->where('fundamental.end_date', '>=', $fiscalYear->fundamental->start_date)
                ->where('fundamental.end_date', '<', $fiscalYear->fundamental->end_date)
                ->values('financials');
        }

        return $quarters;
    }

上面代码中最重要的部分是它只返回 $quarter end_date >= $fiscalYear start_date 和 $quarter end_date < $fiscalYear end_date 的季度。

这可行,但它是迄今为止我收藏中最慢的“过滤器”。我有一种感觉,我认为这一切都是错误的,尽管我不确定。似乎每次 $fiscalYears 迭代时循环整个集合是一个坏主意。这可以以更快/更有效的方式完成吗?还是 foreach 在这种情况下很常见?

标签: phplaravel

解决方案


看起来结果数据中的每个“组”都包含特定公司在给定年份的季度。您可以使用雄辩的方法来完成这项工作,这比遍历四分之一要快得多。我用 40k 条记录对此进行了测试。循环大约需要 45 秒,但这需要不到一秒。

$quarters = $collection->where('fundamental.fiscal_period', '!=', 'FY');

// use `groupBy()` to organize the quarters into the right chunks
$grouped = $quarters->groupBy(function($item) {
    // concatenate the company name and fiscal year to create the "group" name
    return $item->company->name.'_'.$item->fundamental->fiscal_year;
});

// use `$fiscalYears` with `map()` and `flip()` to create a collection 
// with the same key structure as $grouped
$filter = $fiscalYears->map(function($item) {
    return $item->company->name.'_'.$item->fundamental->fiscal_year;
})->flip();

// filter the collection with `intersectByKeys()`
$result = $grouped->intersectByKeys($filter);

// If you want, replace the keys with numeric indices and convert to array
$result = $result->values()->toArray();

请注意您使用哪些雄辩的方法。如果您使用flatten(),该方法在后台使用递归循环,因此它仍然会很慢。


推荐阅读