“如果一对多表具有值”作为SELECT

时间:2016-03-25 22:38:56

标签: sql sql-server sql-server-2008

我有一张带汽车的桌子和另一张带燃料类型的桌子。第三个表跟踪哪些汽车可以使用哪种燃料类型。我需要为所有汽车选择所有数据,包括他们可以使用的燃料类型:

Car table有Car_ID,Car_Name等

燃料表有Fuel_ID,Fuel_Name

Car_Fuel表有Car_ID,Fuel_ID(一辆车可以有多个Fuel选项)

我想要回归:

SELECT
    *
    , Can_Use_Gas
    , Can_Use_Diesel
    , Can_Use_Electric
FROM Car

Can_Use列是一个BIT值,表示汽车在Car_Fuel表中是否有匹配的Fuel条目。

我可以使用多个SELECT语句执行此操作,但这看起来非常混乱(并且可能非常低效?)。我希望有更好的方法:

SELECT
    c.*
    , (SELECT COUNT(*) FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 1) AS Can_Use_Gas
    , (SELECT COUNT(*) FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 2) AS Can_Use_Diesel
    , (SELECT COUNT(*) FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 3) AS Can_Use_Electric
FROM Car c

4 个答案:

答案 0 :(得分:4)

据推测,Car_fuel中没有重复项,因此您不需要聚合。因此你可以这样做:

SELECT c.*,
       ISNULL((SELECT TOP 1 1 FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 1), 0) AS Can_Use_Gas
       ISNULL((SELECT TOP 1 1 FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 2), 0) AS Can_Use_Diesel
       ISNULL((SELECT TOP 1 1 FROM Car_Fuel f WHERE f.Car_ID = c.Car_ID AND f.Fuel_ID = 3), 0) AS Can_Use_Electric
FROM Car c;

这是ISNULL()性能优于COALESCE()的一种情况,因为COALESCE()会对第一个参数进行两次计算。

答案 1 :(得分:3)

虽然不是一个完美的解决方案,但您可以使用pivot clause

select *
from   ( select     car_name, fuel_name
         from       Car
         inner join Car_Fuel on Car.car_id = Car_Fuel.car_id
         inner join Fuel     on Car_Fuel.fuel_id = Fuel.fuel_id
    ) as data
    pivot (
        count(fuel_name)
        for fuel_name in (Gas, Diesel, Electric)
    ) as pivot_table;

请参阅this fiddle,其输出如下表格:

| car_name | Gas | Diesel | Electric |
|----------|-----|--------|----------|
|   Jaguar |   0 |      1 |        0 |
| Mercedes |   0 |      1 |        1 |
|    Volvo |   1 |      0 |        1 |

SQL语句仍然在for部分的pivot子句中包含硬编码列表,但是当燃料类型数量增加时,这可能更容易管理并具有更好的性能。

动态生成SQL

如果您使用应用程序服务器,则可以先执行此查询:

SELECT stuff(   (   SELECT ',' + fuel_name
                    FROM Fuel FOR XML PATH('')
                ), 1, 1, '') columns

这会将列列表作为逗号分隔值返回,例如:

Gas,Diesel,Electric

您将获取该结果并将其注入FOR子句中的第一个查询。

答案 2 :(得分:2)

我怀疑使用计数会效率低下,因为会有大量子查询运行以计算所有计数。

以下是使用自联接的替代方法。它不像你的例子那么短,但可能更容易维护和阅读,并且应该更有效率。

select car.car_id, car.car_name,
-- Select fuel variables 
CASE WHEN lpg.fuel_id IS NULL THEN 0 ELSE 1 END AS LPG,
CASE WHEN unleaded.fuel_id IS NULL THEN 0 ELSE 1 END AS Unleaded,
CASE WHEN electric.fuel_id IS NULL THEN 0 ELSE 1 END AS Electric,
CASE WHEN diesel.fuel_id IS NULL THEN 0 ELSE 1 END AS Diesel
FROM car
-- Self Join fuel records
LEFT join car_fuel as lpg on car.car_id = lpg.car_id and lpg.fuel_id = 1
LEFT join car_fuel as unleaded on car.car_id = unleaded.car_id and unleaded.fuel_id = 2
LEFT join car_fuel as electric on car.car_id = electric.car_id and electric.fuel_id = 3
LEFT join car_fuel as diesel on car.car_id = diesel.car_id and diesel.fuel_id = 4

如果汽车没有使用该燃料类型,则自联接将返回NULL。如果连接找到该汽车/燃料的记录,则CASE返回1,如果没有,则返回0

我希望这有帮助。

答案 3 :(得分:2)

您可以使用条件聚合。

对Car_Fuel表进行外部联接,然后执行GROUP BY Car_ID折叠行。

对于来自Car_Fuel的每一行,如果Fuel_ID与您要检查的那一行匹配,则返回1,否则返回0.并使用MAX()聚合来过滤行,找出是否有任何返回1。

例如:

SELECT c.Car_ID
     , c.Car_Name
     , MAX(CASE WHEN f.Fuel_ID=1 THEN 1 ELSE 0 END) AS Can_Use_Gas
     , MAX(CASE WHEN f.Fuel_ID=2 THEN 1 ELSE 0 END) AS Can_Use_Diesel
     , MAX(CASE WHEN f.Fuel_ID=3 THEN 1 ELSE 0 END) AS Can_Use_Electric
 FROM Car c
 LEFT
 JOIN Car_Fuel f
   ON f.Car_ID = c.Car_ID
GROUP
   BY c.Car_ID
    , c.Car_Name

使用SQL Server,您需要重复SELECT子句中GROUP BY列表中的每个非聚合表达式。如果您将Car表中的更多列添加到SELECT列表,则必须将这些列复制到GROUP BY。

如果这太痛苦了,你可以在内联视图中进行聚合,然后进行JOIN。要确保不返回NULL,可以在外部查询中将NULL值替换为0:

例如:

SELECT c.Car_ID
     , c.Car_Name
     , ISNULL(u.Can_Use_Gas,0)      AS Can_Use_Gas
     , ISNULL(u.Can_Use_Diesel,0)   AS Can_Use_Diesel
     , ISNULL(u.Can_Use_Electric,0) AS Can_Use_Electric
  FROM Car c
  LEFT
  JOIN ( SELECT f.Car_ID
              , MAX(CASE WHEN f.Fuel_ID=1 THEN 1 ELSE 0 END) AS Can_Use_Gas
              , MAX(CASE WHEN f.Fuel_ID=2 THEN 1 ELSE 0 END) AS Can_Use_Diesel
              , MAX(CASE WHEN f.Fuel_ID=3 THEN 1 ELSE 0 END) AS Can_Use_Electric
           FROM Car_Fuel f
          GROUP BY f.Car_ID
       ) u
    ON u.Car_ID = c.Car_ID