加入多对多关系

时间:2012-05-07 16:12:18

标签: sql postgresql

我有三个表:application,permissions和applications_permissions

|------------|   |------------------------|   |-----------|
|applications|   |applications_permissions|   |permissions|
|------------|   |------------------------|   |-----------|
| id         | <-| application_id         |   | id        |
| price      |   | permission_id          |-> | name      |
|------------|   |------------------------|   |-----------|

对于应用程序,有两类:免费和商业(价格='0'和价格!='0')

现在我想知道每个权限,总应用程序中有多少百分比引用它;这两个类别

免:

id, percentage
1 , 20.0230
2 ,  0.0000
3 ,  0.0312
...

商业:

id, percentage
1 , 18.0460
2 ,  0.0000
3 ,  0.0402
...

我已经计算出以下查询,但它不包含没有应用程序的权限ID:/

SELECT (SELECT name FROM permissions WHERE id = applications_permissions.permission_id) AS "name",
        100::float * COUNT(*)/(SELECT COUNT(name) FROM applications WHERE price = \'0\') AS "percent"
  FROM applications, applications_permissions
  WHERE applications.id = applications_permissions.application_id 
    AND applications.price = \'0\'
  GROUP BY applications_permissions.permission_id
  ORDER BY percent DESC')

我该怎么做? 我现在已经尝试了几个小时(那个查询,misc JOINs)但它让我望而却步:/

4 个答案:

答案 0 :(得分:4)

简化。初稿是最优的。
要在一个查询中计算所有内容:

SELECT p.id
     ,(100 * sum((a.price > 0)::int)) / cc.ct AS commercial
     ,(100 * sum((a.price = 0)::int)) / cf.ct AS free
FROM  (SELECT count(*)::float AS ct FROM applications WHERE price > 0) AS cc
     ,(SELECT count(*)::float AS ct FROM applications WHERE price = 0) AS cf
      ,permissions p
LEFT   JOIN applications_permissions ap ON ap.permission_id = p.id
LEFT   JOIN applications a ON a.id = ap.application_id
GROUP  BY 1, cc.ct, cf.ct
ORDER  BY 2 DESC, 3 DESC, 1;

假设您的价格实际上是一个数字列 - 所以0代替'0'

这包括permissions根本没有附加applicationsLEFT JOIN)。

如果applications可能permissions没有附加到任何ct,那么这些列表的累计不会达到100%。

我执行总计数(float)一次并将其转换为子查询中的/ ct。其余的计算可以使用整数运算来完成,只有最后的GROUP BY将数字转换为浮点数。这是最快和最精确的。


与CTE相同

如果您对更多新内容持开放态度:请尝试使用CTEs (Common Table Expressions - WITH queries) - 从PostgreSQL 8.4开始提供。 它更干净,可能稍微快一些,因为我在一个CTE中都做了两个并且有更便宜的WITH c AS ( SELECT sum((a.price > 0)::int) AS cc ,sum((a.price = 0)::int) AS cf FROM applications ), p AS ( SELECT id ,sum((a.price > 0)::int) AS pc ,sum((a.price = 0)::int) AS pf FROM permissions p LEFT JOIN applications_permissions ap ON ap.permission_id = p.id LEFT JOIN applications a ON a.id = ap.application_id GROUP BY 1 ) SELECT p.id ,(100 * pc) / cc::float AS commercial ,(100 * pf) / cf::float AS free FROM c, p ORDER BY 2 DESC, 3 DESC, 1; - 这两个都可以用子查询来完成:

{{1}}

答案 1 :(得分:3)

使用LEFT OUTER JOIN

SELECT * FROM permissions LEFT OUTER JOIN
applications_permissions as rel on permissions.id = rel.permission_id LEFT OUTER JOIN
applications on rel.application_id = applications.id

答案 2 :(得分:1)

这有用吗?

对于free案例:

SELECT p.id, (100::float * COUNT(p.id)/(SELECT COUNT(*) from Applications)) Percent
FROM Applications a, Permissions p, Applications_Permissions a_p
WHERE a.id = a_p.application_id AND p.id = a_p.permission_id AND a.price = 0
GROUP BY p.id
ORDER BY Percent DESC

答案 3 :(得分:1)

以下是一个查询中的结果:

SELECT p.id
, p.name
, (CASE WHEN total.free=0 THEN NULL ELSE 100::float * sub.free::float / total.free::float END) AS percent_free
, (CASE WHEN total.comm=0 THEN NULL ELSE 100::float * sub.comm::float / total.comm::float END) AS percent_comm
FROM permissions AS p
LEFT JOIN (
  SELECT permission_id
  , SUM(CASE WHEN a.price<=0 THEN 1 ELSE 0 END) AS free
  , SUM(CASE WHEN a.price>0  THEN 1 ELSE 0 END) AS comm
  FROM applications_permissions AS pa
  JOIN applications AS a ON (pa.application_id=a.id)
  GROUP BY permission_id
) AS sub ON (p.id=sub.permission_id)
, (
  SELECT
    SUM(CASE WHEN price<=0 THEN 1 ELSE 0 END) AS free
  , SUM(CASE WHEN price>0  THEN 1 ELSE 0 END) AS comm
  FROM applications
) AS total

或仅对免费应用程序(通过更改where子句分别为商业应用程序)的结果:

SELECT p.id
, p.name
, (CASE WHEN total.nbr=0 THEN NULL ELSE 100::float * sub.nbr::float / total.nbr::float END) AS percent
FROM permissions AS p
LEFT JOIN (
  SELECT permission_id, COUNT(*) AS nbr
  FROM applications_permissions AS pa
  JOIN applications AS a ON (pa.application_id=a.id)
  WHERE (a.price<=0)
  GROUP BY permission_id
) AS sub ON (p.id=sub.permission_id)
, (
  SELECT COUNT(*) AS nbr
  FROM applications
  WHERE (price<=0)
) AS total