首页 > 解决方案 > 如何创建一个以数据库函数为源的雄辩模型?

问题描述

一些背景资料:

要创建的模型适用于 postgresql 数据库中的现有分区表。性能问题的原因是请求父表并不理想。我们创建了一个自定义数据库函数来从子表中获取数据,并根据参数通过 union all 连接结果。参数的数据由用户权限定义,因此在创建模型时就知道 id。背后的逻辑严重依赖模型和关系,因此原始查询会造成很大的麻烦。

目前我使用父表来获取数据,但请求需要 2-3 秒。调用子表时,结果低于 100 毫秒。所以我需要一种从多个子表中获取数据的可能性。

对于这个问题:

如何将现有功能设置为模型的基础?

我试图将函数名放在 DB::raw 中,但该函数仍然被转义:SELECT *FROM "partionedTableTestGetData(1,2,3)"。我需要用作模型数据源的函数。该函数直接查询子表并使用 union all 来构建结果集。

不需要写入此模型。

use Illuminate\Support\Facades\DB;

public static function boot()
{
    $oUser = Auth::user();
    $aIds = $oUser->getCustomIds();
    //some sanitation here
    $this->table = DB::raw('partionedTableTestGetData('.implode(',', $aIds ).')';
    static::addGlobalScope(new CustomScope('id-field'));
}

表的数据库模式:

- public.parent_table -> parent table
- parent_table.parent_table_123 -> child table of parent_table with all data of company with id 123
- parent_table.parent_table_321 -> child table of parent_table with all data of company with id 321
... about 600 other tables like  this ...

以下函数用于获取 child_table 数据:

create function partionedTableTestGetData(companyids text) returns SETOF parent_table
    language plpgsql
as $$
DECLARE
  aiIDs BIGINT[];
  iID BIGINT;
  sSql TEXT;
  sSqlCondition TEXT;
  bFirst BOOLEAN;
  bParent BOOLEAN;
BEGIN
  bFirst = TRUE ;
  bParent = FALSE ;
  aiIDs = string_to_array(sSzenarioIDs, ',');
  sSql = '';
  sSqlCondition = '';

  FOREACH iID IN ARRAY aiIDs
  LOOP
    IF EXISTS (SELECT table_name FROM information_schema.tables WHERE table_name='parent_table' || iID AND table_schema = 'parent_table')
    THEN
      IF bFirst IS FALSE
      THEN
        sSql = sSql || ' UNION ALL ';
      END IF;
      bFirst = FALSE;
      sSql = sSql || format('SELECT * FROM parent_table.parent_table_%s %s', iID, sSqlCondition);
    ELSE bParent = TRUE;
    END IF;
  END LOOP;

  IF bParent IS TRUE OR sSql = ''
  THEN
    IF bFirst IS FALSE
    THEN
      sSql = sSql || ' UNION ALL ';
    END IF;
    bFirst = FALSE;
    sSql = sSql || format('SELECT * FROM ONLY public.parent_table %s', sSqlCondition);
  END IF;
  RAISE NOTICE '%', sSql;

  IF sSql <> ''
  THEN RETURN QUERY EXECUTE sSql;
  END IF;

  RETURN;
END;
$$;

标签: phppostgresqleloquentmodel

解决方案


尽管感觉缓存表是处理此问题的首选方式。这应该可以解决您的问题。

$items = \DB::connection()->select(
      'SELECT * FROM partionedTableTestGetData(:ids)',
      ['ids' => implode(',', $aIds)]
);

如果您希望 Eloquent 模型被通过查询获取的数据水合,您可以使用以下内容:

Model::hydrate($items)->all() // returns Model[]

推荐阅读