【问题标题】:How to create an eloquent model with a database function as source?如何创建一个以数据库函数为源的雄辩模型?
【发布时间】:2020-10-15 16:28:23
【问题描述】:

一些背景资料:

要创建的模型适用于 postgresql 数据库中现有的 partitioned 表。性能问题的原因是请求父表并不理想。我们创建了一个自定义数据库函数来从子表中获取数据,并根据参数通过 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;
$$;

【问题讨论】:

  • 用你的函数创建一个实际的第二个表不是更容易(例如,使用数据库触发器保持同步),然后像你一样在 Eloquent 中使用第二个表作为数据源有桌子吗?
  • 我不确定你的意思。我认为您建议为数据创建一种缓存表。问题是使用的 ID 取决于用户权限。因此,如果权限被修改,我将不得不为每个用户创建一个自己的表集并创建新的表。那太复杂了。如何处理参数?
  • 是的,这就是我的建议。您能否仅将用户 ID /“自定义 ID”添加到缓存表中的数据集 - 正如您所说的那样 - 缓存表并让您的应用程序代码只让用户访问他应该有权访问的记录?我知道这并不能真正回答你的问题,我的意思是它可能被认为太复杂了
  • 访问数据是一个性能问题。数据库必须搜索所有子表。我的习惯在桌子上。我能想到的唯一选择是创建一个自定义模型来合并所有查询
  • 您是否为要查询的属性添加了索引?函数会比查询产生更快的结果,这听起来有点奇怪。如果您没有(!)有适当的临时缓存(如其他表),则更是如此。您能否将您的数据库架构添加到问题中,以便我们可以从那里开始?

标签: php postgresql eloquent model


【解决方案1】:

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

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

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

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

【讨论】:

  • 感谢您的回答。问题是每个模型都有一个全局范围的权限检查。它们包含一些连接和位置。我不想复制逻辑。有没有办法将范围应用于此解决方案?
  • 不,在这种情况下,无法使用 Eloquent 范围进行查询过滤。这是因为起点是原始查询而不是 Elquent 模型。模型上的关系中的连接等确实照常工作。
猜你喜欢
  • 2018-11-11
  • 2014-12-05
  • 1970-01-01
  • 1970-01-01
  • 2022-08-05
  • 1970-01-01
  • 2018-08-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多