【问题标题】:Relational division SQL关系除法 SQL
【发布时间】:2015-12-29 07:29:10
【问题描述】:

我编写了一个过滤器,它生成一个查询来显示特殊员工。

我有表员工和很多 1:1、1:n 和 n:m 关系,例如对于这样的员工的技能和语言:

Employees
id name
1  John
2  Mike

Skills
id skill experience
1  PHP   3
2  SQL   1

Employee_Skills
eid sid
1   1
1   2

现在我想筛选至少有 2 年 PHP 使用经验和 1 年 SQL 使用经验的员工。

我的过滤器总是为每个表、关系和字段生成正确的工作查询。

但现在我的问题是,当我想用​​ a 多次过滤相关表中的同一字段时,它不起作用。

e.g.
John PHP 3
John SQL 1

PHP 和 SQL 是不同的行,所以 AND 不能工作。

我尝试使用 group_concat 和 find_in_set,但我遇到的问题是我无法过滤超过 2 年的 find_in_set 经验,并且 find_in_set 不知道 PHP 是 3 而 SQL 是 1。

我也试过了

WHERE emp.id IN (SELECT eid FROM Employee_Skills WHERE sid IN (SELECT id FROM Skills WHERE skill = 'PHP' AND experience > 1)) AND emp.id IN (SELECT eid FROM Employee_Skills WHERE sid IN (SELECT id FROM Skills WHERE skill = 'SQL' AND experience > 0))

这适用于这个例子,但它只适用于 n:m 并且它太复杂而无法知道关系类型。

我有最终的查询

ski.skill = 'PHP' AND ski.experience > 1 AND ski.skill = 'SQL' AND ski.experience > 0

我想操纵查询使其工作。

查询必须如何处理关系划分。

【问题讨论】:

  • 帮自己一个忙,不要这样命名您的联合表 (emp_ski)。使用清晰/有意义的内容,例如employee_skillsemployee_to_skills。你以后会很高兴的。
  • 字段experience应该在Emp_Ski表中
  • 数据库很大很复杂,这只是一个小例子来解释我的问题。

标签: php mysql sql


【解决方案1】:

您可以尝试下一种方法:

select * from Employees
where id in (
    select eid
    from Employee_Skills as a
    inner join
    Skills as ski
    on (a.sid = ski.id)
    where
    (ski.skill = 'PHP' AND a.experience > 2) OR
    (ski.skill = 'SQL' AND a.experience > 1)
    group by eid
    having count(*) = 2
)

因此,对于您将添加 OR 语句的每个过滤器,having 将过滤所有通过过滤器的员工,只需传递适当的数字

【讨论】:

  • 但是当我想用 PHP 和(SQL 或 JS)过滤员工时,我不能使用 HAVING。
  • 你仍然可以使用这种方法,比如(ski.skill = 'PHP' AND a.experience > 2) OR ( (ski.skill = 'SQL' AND a.experience > 1) OR (ski.skill = 'JS' AND a.experience > 1) ),但它当然不是通用的(它不适用于NOT),通用方法就像你用IN描述的那样
【解决方案2】:

您可以进行一种枢轴查询,将所有已知技能的经验放在列中。这可能是一个很长的查询,但您可以在 php 中动态构建它,因此它将所有技能作为列添加到最终查询中,如下所示:

SELECT     e.*, php_exp, sql_exp
FROM       Employee e
INNER JOIN (
        SELECT     es.eid,
                   SUM(CASE s.skill WHEN 'PHP' THEN s.experience END) php_exp,
                   SUM(CASE s.skill WHEN 'SQL' THEN s.experience END) sql_exp,
                   SUM(CASE s.skill WHEN 'JS'  THEN s.experience END)  js_exp
                   -- do the same for other skills here --
        FROM       Employee_Skills es
        INNER JOIN Skills s ON  es.sid = s.id
        GROUP BY   es.eid
        ) pivot ON pivot.eid = e.id
WHERE       php_exp > 2 AND sql_exp > 0;

WHERE 子句非常简洁直观:您可以像在其他情况下一样使用逻辑运算符。

如果技能集相当静态,您甚至可以为子查询创建一个视图。那么最终的SQL就相当简洁了。

这是fiddle

替代方案

使用相同的原理,但在HAVING 子句中使用SUM,可以避免收集所有技能的经验:

SELECT     e.*
FROM       Employee e
INNER JOIN (
        SELECT     es.eid
        FROM       Employee_Skills es
        INNER JOIN Skills s ON  es.sid = s.id
        GROUP BY   es.eid
        HAVING     SUM(CASE s.skill WHEN 'PHP' THEN s.experience END) > 2
        AND        SUM(CASE s.skill WHEN 'SQL' THEN s.experience END) > 0
        ) pivot ON pivot.eid = e.id;

这是fiddle

您还可以将CASE 构造替换为IF 函数,如下所示:

        HAVING     SUM(IF(s.skill='PHP', s.experience, 0)) > 2
        ... etc.

但归根结底是一样的。

【讨论】:

    【解决方案3】:

    直接的方法是重复加入技能:

    SELECT e.*
        FROM Employees AS e
    
        JOIN Employee_Skills AS j1 ON (e.id = j1.eid)
        JOIN Skills AS s1 ON (j1.sid = s1.id AND s1.skill = 'PHP' AND s1.experience > 3)
    
        JOIN Employee_Skills AS j2 ON (e.id = j2.eid)
        JOIN Skills AS s2 ON (j2.sid = s2.id AND s2.skill = 'SQL' AND s2.experience > 1)
    
        ...
    

    由于所有子句都是必需的,因此将其转换为直接 JOIN。

    您需要为每个子句添加两个 JOIN,但它们的连接速度非常快。

    一种更老套的方法是将技能压缩成与员工 1:1 关系的代码。如果经验从未超过,例如 30,那么您可以将第一个条件的经验乘以 1,第二个乘以 30,第三个乘以 30*30,第四个乘以 30*30*30……并且永远不会溢出。

    SELECT eid, SUM(CASE skill 
                          WHEN 'PHP' THEN 30*experience
                          WHEN 'SQL' THEN  1*experience) AS code
    FROM Employees_Skills JOIN Skills ON (Skills.id = Employees_Skills.sid)
    GROUP BY eid HAVING code > 0;
    

    实际上,既然你想要 3 年 PHP,你的代码可以 > 91。如果你有经验 2、3 和 5 的三个条件,你会要求超过 x = 2*30*30 + 3*30 + 5。这只会减少结果,因为 3*30*30 + 2*30 + 4 仍然通过过滤器,但对你没有用。但是由于您想要对代码进行限制,并且 "> x" 的成本与 "> 0" 相同,并提供更好的结果......(如果您需要比一系列 AND 更复杂的过滤,但 > 0 更安全)。

    上表你加入Employees,然后对结果进行真正的过滤,要求

    ((code / 30*30) % 30) > 7  // for instance :-)
    AND
    ((code / 30) % 30) > 3     // for PHP
    AND
    ((code /  1) % 30) > 1     // for SQL
    

    (*1和/1是多余的,插入只是为了澄清)

    此解决方案需要对技能进行全表扫描,实际上不可能进行自动优化。所以它比其他解决方案慢。另一方面,它的成本增长要慢得多,所以如果您有复杂的查询,或者需要 OR 运算符或条件表达式而不是 AND,实施“hackish”解决方案可能更方便。

    【讨论】:

      猜你喜欢
      • 2021-12-21
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 2023-03-09
      • 2011-08-08
      • 1970-01-01
      相关资源
      最近更新 更多