SQL Composite密钥分组问题

时间:2012-02-27 02:08:59

标签: sql group-by composite-key unique-constraint

我有一个非常令人沮丧的SQL问题,我不能为我的生活解决一个派生查询返回一个复合键,但也在该表中的另一个字段上执行MIN()聚合函数。如果我在其中一个复合键上执行MIN()它会很容易,但由于我需要返回两个键并执行MIN()函数以及外部查询我无法解决如何执行此操作。整个查询如下所示:

SELECT
    p.name as productname
   ,tmp.packageid
   ,tmp.price
   ,ppk2.packageoptionid
   ,ppk2.selcomproductid
FROM ( 
        SELECT ppk.productid, ppk.packageid, MIN(ppk.price) as price
        FROM  product_package ppk
                 INNER JOIN package pk ON ppk.packageid = pk.id
                 INNER JOIN [plan] pl ON pk.planid = pl.id
        WHERE pk.networkid = 1
        GROUP BY ppk.productid, ppk.packageid
) tmp
INNER JOIN product_package ppk2 ON ( 
        ppk2.productid = tmp.productid 
    AND ppk2.packageid = tmp.packageid
)    
INNER JOIN product p ON (p.id = ppk2.productid)  
WHERE p.isenabled = 1;

当前结果:

--------------------------------------
productid   |   packageid   |   price
1               500             0
1               501             19.95
1               502             29.95
2               501             0
3               500             15    
3               504             39.95 

期望的结果:

--------------------------------------
productid   |   packageid   |   price
1               500             0
2               501             0
3               500             15  

派生查询“tmp”是我的问题所在,因为在加入外部表之前,我需要为每个具有最低价格的产品/包组合返回唯一的行。

非常感谢任何帮助!

3 个答案:

答案 0 :(得分:2)

每当我需要子查询以及最小的东西时,我就会使用这个技巧。我们的想法是将值和键与最高有效位中的值组合在一起并取其最小值。然后在外部选择中分开。

组合值的最佳方式取决于您使用的RDBMS。你没有提到你正在使用哪一个,所以我只提供伪代码:

select ..., (tmp.c >> 32) price
from
(select productid, min((price << 32) | packageid) c
  from product_package
  where networkid=1
  group by productid) tmp
inner join product_package ppk on ppk.productid=tmp.productid
  and ppk.packageid=(tmp.c & 0xFFFFFFFF)
inner join product p on p.id=ppk.productid
where p.isenabled=1

<< 32表示将值向左移位32位,|为按位“或”。所以这假设packageid被定义为32位整数(或数字(4))。 & 0xFFFFFFFF是按位“和”,32位的十六进制值用于屏蔽并返回packageid。

根据您的RDBMS,您可能需要找到这些内容的特定语法,或者如果它们不受支持,您可以使用简单数学 - << 32相当于乘以4294967296和& 0xFFFFFFFF除以4294967296。如果您使用的是MSSQL,可以使用convert(binary,price)+convert(binary,packageid)将它们和substring(..)合并分开。

答案 1 :(得分:1)

简单(阅读:昂贵)方式:构建两个视图:一个只获得每个ppk.price的最小productid WHERE pk.networkid = 1,并按productid分组。称之为Product_MinPrice_VIEW或其他。

构建第二个视图Product_VIEW,用SELECT INNER JOIN替换INNER JOIN的所有子Product_MinPrice_VIEW工作,通过SELECTS替换HAVINGS你刚才做的。

我发誓,与子GROUP-BY,{{1}}和{{1}}争吵是单调乏味且容易出错的。我有时候无法忍受。希望这将使您足够开发一个可以在以后优化并使其更加正确的解决方案。

最终答案

I have an extremely similar problem使用我正在处理的应用程序,同时(当我点击这个网站以获得更好的答案时),我刚刚推卸责任,并编写了一些应用程序级代码来处理任何重复项,让程序的逻辑在遇到时找到真正的最小值。不漂亮,但是我又没有一整天都想弄明白了!

对不起,我的回答无法帮到你。祝你好运!

答案 2 :(得分:1)

好吧,我不知道你实际拥有的数据。我只有你的查询返回的数据。您没有回答我的评论,要求提供您的表格和您正在使用的DBMS的数据样本。

但是,假设您的表的当前数据是您的查询中的数据,以下查询将为您提供您指定的“所需结果”:

select t1.* from t t1
left join t t2
on t1.productid = t2.productid and t1.details > t2.details
where t2.details is null

在表格中,查询转为:

+-----------+-----------+---------+
| PRODUCTID | PACKAGEID | DETAILS |
+-----------+-----------+---------+
|         1 |       500 |       0 |
|         1 |       501 |      20 |
|         1 |       502 |      30 |
|         2 |       501 |       0 |
|         3 |       500 |      15 |
|         3 |       504 |      40 |
+-----------+-----------+---------+

进入这个:

+-----------+-----------+---------+
| PRODUCTID | PACKAGEID | DETAILS |
+-----------+-----------+---------+
|         1 |       500 |       0 |
|         2 |       501 |       0 |
|         3 |       500 |      15 |
+-----------+-----------+---------+

让我知道是否清楚。