【问题标题】:SQL: performantly find the percent overlap between two foreign key child tablesSQL:高效地找到两个外键子表之间的重叠百分比
【发布时间】:2016-10-24 23:27:27
【问题描述】:

假设我有以下表格/字段:

CREATE TABLE tbl_projects (
  prjc_id int PRIMARY KEY
)

CREATE TABLE tbl_project_requirements (
  preq_prjc_id int -- Foreign key to tbl_projects
  preq_type_id int -- A standardized requirement category
)

给定一个特定项目,我想找到具有几乎类似需求类别的其他项目......或者假设他们的需求至少有 75% 重叠.

我可以做到以下几点:

DECLARE @prjc_id int = 1

CREATE TABLE #project_reqs (type_id int)
INSERT INTO #project_reqs
SELECT preq_req_type_id
FROM tbl_project_requirements
WHERE preq_prjc_id = @prjc_id

SELECT prjc_id
FROM tbl_projects
  CROSS APPLY (
    SELECT CASE 
        WHEN COUNT(*) = 0 THEN 0.0
        ELSE COALESCE(SUM(CASE WHEN type_id = prjc_type_id THEN 1.0 ELSE 0.0 END), 0.0)
           / CONVERT(float, COUNT(*))
      END AS similarity
    FROM #project_reqs 
      FULL OUTER JOIN (
        SELECT prjc_type_id
        FROM tbl_project_requirements
        WHERE preq_prjc_id = prjc_id
      ) reqs ON preq_type_id = type_id
  ) reqs
WHERE prjc_id != @prjc_id
  AND similarity >= 0.75

在上面,我将匹配需求类别除以每两个项目之间的总不同需求类别,以获得重叠百分比。

虽然这行得通,但我感觉到代码异味,并且认为这不会很好地扩展。是否有任何方法可以有效地计算两个项目之间子记录的重叠?也许某种部分哈希匹配或...?

更新

我想我找到了一个高效的解决方案:

DECLARE @prjc_id int = 1

CREATE TABLE #project_reqs (type_id int)
INSERT INTO #project_reqs
SELECT preq_req_type_id
FROM tbl_project_requirements
WHERE preq_prjc_id = @prjc_id

DECLARE @project_req_count float
SELECT @project_req_count = COUNT(*)
FROM #project_reqs

CREATE TABLE #projects (
  pj_prjc_id int,
  pj_func_count float,
  pj_func_common float
)

INSERT INTO #projects
SELECT preq_prjc_id,
  COUNT(*),
  COUNT(type_id)
FROM tbl_project_requirements
  LEFT OUTER JOIN #project_reqs
    ON preq_type_id = type_id
GROUP BY preq_prjc_id
HAVING COUNT(type_id) != 0

SELECT pj_prjc_id
FROM #projects
WHERE pj_func_common / (pj_func_count + @project_req_count - pj_func_common) >= 0.75

DROP TABLE #project_reqs
DROP TABLE #projects

【问题讨论】:

    标签: sql sql-server hash hashset


    【解决方案1】:

    有更优雅的方法可以找到共同的需求。

    ;with proj as (
        select preq_prjc_id pr, count(preq_type_id) typeCnt
        from tbl_project_requirements
        group by preq_prjc_id
    )
    ,crossProj as (
        select p1.pr proj1,p2.pr proj2, p1.typeCnt
        from proj p1
        cross join proj p2 --make Cartesian product
        where p1.pr <> p2.pr
    )
    ,req as (
        select preq_type_id, cp.proj1, cp.proj2, cp.typeCnt
        from tbl_project_requirements pq
        inner join crossProj cp on pq.preq_prjc_id=cp.proj1
        intersect -- what is common
        select preq_type_id, cp.proj1, cp.proj2, cp.typeCnt
        from tbl_project_requirements pq
        inner join crossProj cp on pq.preq_prjc_id=cp.proj2
    )
    --calculate final result
    select proj1, proj2,
    count(preq_type_id) commonPreq, 
    --percent of common requirements relative to proj1
    count(preq_type_id) * 100.00 / typeCnt [percentage]
    from req
    group by proj1, proj2, typeCnt
    having count(preq_type_id) * 100.00 / typeCnt >75
    order by [percentage] desc
    

    更新

    ;with proj as (
        select preq_prjc_id pr, count(preq_type_id) typeCnt
        from tbl_project_requirements
        group by preq_prjc_id
    )
    ,crossProj as (
        select p1.pr proj1,p2.pr proj2, p1.typeCnt
        from proj p1
        cross join proj p2 --make Cartesian product
        where p1.pr <> p2.pr
    )
    ,req as (
        select preq_type_id, cp.proj1, cp.proj2
        from tbl_project_requirements pq
        inner join crossProj cp on pq.preq_prjc_id=cp.proj1
        intersect -- what is common
        select preq_type_id, cp.proj1, cp.proj2
        from tbl_project_requirements pq
        inner join crossProj cp on pq.preq_prjc_id=cp.proj2
    )
    --calculate final result
    select proj1, proj2,
    count(preq_type_id) commonPreq,
    --percent of common requirements relative to proj1
    count(preq_type_id) * 100.00 /(p1.typeCnt + p2.typeCnt - count(preq_type_id))  [percentage]
    from req
    inner join proj p1 on req.proj1=p1.pr
    inner join proj p2 on req.proj2=p2.pr
    group by proj1, proj2,p1.typeCnt, p2.typeCnt
    having count(preq_type_id) * 100.00 /(p1.typeCnt + p2.typeCnt - count(preq_type_id)) >75
    order by [percentage] desc
    

    【讨论】:

    • 我很难理解这个策略......但是你的评论“相对于 proj1 的常见要求的百分比”让我认为这不是总不同要求的百分比proj1 和 proj2 之间
    • 你所说的“proj1 和 proj2”是什么意思?例如,proj1 有 20 个需求,proj2 有 15 个需求,两者共有 12 个需求。我计算12 * 100.00 / 20
    • proj1 有 8 个要求 proj2 中没有,proj2 有 3 个要求 proj1 中没有,两者共有 12 个要求... 12 / (12+8+3)
    • 我认为你的策略有效,但是它得到了笛卡尔积的重叠分数......而我只想计算一个项目相对于所有其他项目的分数......我想我想出了高性能在您的帮助下解决。在问题中查看我的更新
    • 只需添加where 过滤器。 crossProj as (select p1.pr proj1,p2.pr proj2, p1.typeCnt from proj p1 cross join proj p2 where p1.pr = 1 and p1.pr &lt;&gt; p2.pr
    【解决方案2】:

    如果您只为一个项目执行此操作,那么您应该已经足够了解 75% 匹配所需的匹配数量(或者至少您可以轻松快速地计算出来):

    DECLARE @num_matches_required INT
    
    SELECT @num_matches_required = CEILING(COUNT(*) * 0.75)
    FROM
        tbl_Project_Requirements
    WHERE
        preq_prjc_id = @preq_prjc_id
    
    SELECT
        R2.preq_prjc_id    -- One reason not to use abbreviations... I have to think, "Is it proj? prj? prjc? prjct?"
    FROM
        tbl_Project_Requirements R1
    INNER JOIN tbl_Project_Requirements R2 ON
        R2.preq_type_id = R1.preq_type_id AND
        R2.preq_prjc_id <> @preq_prjc_id
    WHERE
        R1.preq_prjc_id = @preq_prjc_id
    GROUP BY
        R2.preq_prjc_id
    HAVING
        COUNT(*) >= @num_matches_required 
    

    【讨论】:

    • 此示例是否忽略了 R2 中的需求,但 R1 中没有?我想获得 R1 和 R2 中所有不同要求之间的重叠百分比(因此我的 FULL OUTER JOIN)
    • 啊,所以它是总不同需求的百分比,而不是相关项目的需求?那就对了,我觉得这个方法行不通。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 2017-09-27
    相关资源
    最近更新 更多