关系分区SQL

时间:2015-12-28 21:50:01

标签: php mysql sql

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

我有桌子员工和很多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,但我遇到的问题是我无法使用find_in_set过滤2年以上的经验,而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

我想操纵查询以使其正常工作。

查询如何处理关系划分。

3 个答案:

答案 0 :(得分: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将过滤所有过滤器的员工,只需传递适当的号码

答案 1 :(得分:0)

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

>>> t = ((1,2,3),(1,3,1),(2,2,3),(0,2,2))
>>> sorted(t, key=sum)[0]
(0, 2, 2)

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; 子句非常简洁直观:在其他情况下使用逻辑运算符。

如果这组技能是静态的,你甚至可以为子查询创建一个视图。然后最终的SQL非常简洁。

这是fiddle

<强>替代

使用相同的原则,但使用WHERE条款中的SUM,您可以避免收集所有技能的经验:

HAVING

这是fiddle

你也可以用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; 函数替换CASE构造,如下所示:

IF

但它归结为同样的。

答案 2 :(得分:0)

直截了当的方法是重复加入技能:

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,但它们是非常快速的连接。

一种更为骇人的方式是将技能压缩为与员工以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,你可以拥有代码&gt; 91.如果你有三个经历2,3和5的条件,你会要求超过x = 2 * 30 * 30 + 3 * 30 + 5.这只会减少结果,因为3 * 30 * 30 + 2 * 30 + 4仍然通过过滤器,但对你没用。但是因为你想要对代码进行限制,并且&#34;&gt; X&#34;费用与&#34;&gt;相同0&#34;并且给出了更好的结果......(如果你需要比一系列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,那么实现&#34; hackish&#34;可能会更方便。溶液