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
答案 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
子句中包含硬编码列表,但是当燃料类型数量增加时,这可能更容易管理并具有更好的性能。
如果您使用应用程序服务器,则可以先执行此查询:
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