1

有这样一个数据库:

在此处输入图像描述

还有这样一个存储函数:

CREATE FUNCTION fetch_mentor(direction_type_list_in text, education_type_list_in text, name_in text, city_id_in integer) RETURNS json
    LANGUAGE plpgsql
AS
$$
DECLARE
    direction_type_list_inner INT[];
    education_type_list_inner INT[];
BEGIN
    direction_type_list_inner = STRING_TO_ARRAY(direction_type_list_in, ',');
    education_type_list_inner = STRING_TO_ARRAY(education_type_list_in, ',');

    RETURN (SELECT JSON_AGG(rows)
                FROM (SELECT m.id                                     AS "ID"
                           , FORMAT('%s %s', m.firstname, m.lastname) AS "fullName"
                           , m.photo_url                              AS "photoURL"
                           , m.video_url                              AS "videoURL"
                           , (SELECT JSON_AGG(mc.name)
                                  FROM mentors.mentor_competence mc
                                  WHERE mc.mentor_id = m.id)          AS "competenceList"
                           , (SELECT JSON_AGG(el.name)
                                  FROM mentors.mentor_employment            me
                                           INNER JOIN lists.employment_list el ON el.id = me.employment_id
                                  WHERE me.mentor_id = m.id)          AS "competenceList"
                           , m.certified                              AS "certified"
                           , cl.display_name                          AS "cityName"
                           , (SELECT JSON_AGG(dtl.display_name)
                                  FROM mentors.mentor_direction_type            mdt
                                           INNER JOIN lists.direction_type_list dtl ON mdt.direction_type_id = dtl.id
                                  WHERE mdt.mentor_id = m.id)            "directionList"
                           , (SELECT JSON_AGG(etl.display_name)
                                  FROM mentors.mentor_education_type            met
                                           INNER JOIN lists.education_type_list etl ON met.education_type_id = etl.id
                                  WHERE met.mentor_id = m.id)            "educationList"
                          FROM mentors.mentor                m
                                   LEFT JOIN lists.city_list cl ON m.city_id = cl.id
                          WHERE approved = TRUE
                            AND (direction_type_list_in ISNULL OR 
                                 m.id IN (SELECT m.id
                                              FROM mentors.mentor_direction_type mdt
                                              WHERE mdt.direction_type_id = ANY (direction_type_list_inner)))
                            AND (education_type_list_inner ISNULL OR
                                 m.id IN (SELECT m.id
                                              FROM mentors.mentor_education_type met
                                              WHERE met.education_type_id = ANY (education_type_list_inner)))
                            AND (name_in ISNULL OR
                                 (m.firstname LIKE FORMAT('%%%s%%', $3) OR m.lastname LIKE FORMAT('%%%s%%', $3)))
                            AND (city_id_in ISNULL OR m.city_id = city_id_in)
                     ) rows);
END;
$$;

ALTER FUNCTION fetch_mentor(TEXT, TEXT, TEXT, INTEGER) OWNER TO postgres;

它返回这样一个json:

[
    {
        "ID": 3,
        "fullName": "fsafd 413",
        "photoURL": "sadf",
        "videoURL": "dsa",
        "competenceList": [
            "a",
            "s",
            "f"
        ],
        "employmentList": [
            "a",
            "b",
            "c"
        ],
        "certified": false,
        "cityName": null,
        "directionList": [
            "x",
            "z"
        ],
        "educationList": [
            "offline"
        ]
    }
]

以下参数进入输入:

type FetchMentorsParams struct {
    DirectionTypeList []int   `json:"directionTypeList"`
    EducationTypeList []int   `json:"educationTypeList"`
    MentorName        *string `json:"mentorName"`
}

所有过滤器都是可选的。也就是说,如果没有过滤器,则输出所有记录。例如,如果 ,Direction Type List = [1,2]则只应返回表中mentor_direction_type的导师 ID 在 where 列中的那些记录direction_type_id = 1 or 2。等等。但是有没有一种方法可以在没有存储函数的情况下形成一个查询来抵抗 SQL 注入?如果您执行类似的操作len(direction Type List) != 0,然后在 for 循环中添加子查询,则该查询将容易受到 sql 注入的攻击。

总的来说,尝试在一个查询中完成所有事情有多好?我看到了一个选项:在主查询之前,查询 tomentor_direction_type和 tomentor_education_type然后做where mentor_id in (the result of querys).

进行诸如 - 之类的查询是正常的(SELECT JSON_AGG(el.name) FROM mentors.mentor_employment me INNER JOIN lists.employment_list el ON el.id = me.employment_id WHERE me.mentor_id = m.id) AS "competenceList",我不确定它是否已优化,但我没有看到任何其他选项。

我正在使用 Go、SQLX、PGX。

4

1 回答 1

1

分解你的问题。

功能

还有这样一个存储函数:

尽管plpgsql从其他编程语言编写函数似乎是合乎逻辑的,但在 Postgresql 中,这些函数有一个警告,即规划器将无法正确评估函数内部运行的成本。这意味着随着数据的增长,您的计划员将变得越来越偏离,认为您的功能得分有点低,而实际上它可能会很高。除非您的函数可以使用其中一种波动性修饰符,否则我将始终将其编写为标准查询语句。

如果优化是一个问题,您可以使用embedgo 包并使用漂亮整洁的 .sql 文件:-)

过滤器和 SQL 注入

所有过滤器都是可选的。

我也很欣赏这样一个事实,即您想要一个能够接受任何参数集并吐出正确输出的查询“来统治他们所有人”。然而,这可能会变成评估所有可能组合和场景的兔子洞。

有时最好有一点冗余,同时有更小、更好的意图定义的查询。

并且进行这样的查询是正常的 - (SELECT JSON_AGG(el.name) FROM Mentors.mentor_employment me INNER JOIN lists.employment_list el ON el.id = me.employment_id WHERE me.mentor_id = m.id) AS "competenceList" ,我不确定它是否经过优化,但我没有看到任何其他选项。

嵌套查询不一定是坏事,请记住,对我们来说清楚的 SQL 对规划者来说并不总是清楚的。在判断子查询之前,我建议您使用 运行fetch_mentor函数的内容EXPLAIN ANALYZE,这将告诉您规划器将如何处理它,并帮助您了解是否存在任何瓶颈。

如果您是新手EXPLAIN ANALYZE,您可以复制它的输出并将其粘贴到https://flame-explain.com/visualize/input等工具中

于 2021-12-16T09:20:01.353 回答