如何简化包含计算的mssql查询

时间:2018-10-24 10:08:57

标签: sql sql-server

我有3个表,这些表包含我要获取计数然后进行计算的数据。我有一个有效的查询,但是重复。

SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as 'Num_To_Go',
(SELECT COUNT(*) from place_been where people.person_id=person_id) as 'Num_Visited',
((​SELECT​ COUNT(*)​ ​FROM​ place_been ​WHERE​ people.person_id=person_id)​ /​ (​SELECT COUNT(*) ​FROM​ place_to_go ​WHERE​ people.person_id=person_id))​ ​*​ 100 ​AS​ ​'Perc_Visited'
FROM people;

我要完成的工作是没有重复的子查询来计算百分比。为此,我所做的任何更改都将导致语法错误,并且令人沮丧。

我可能已经可以使用

SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as 'Num_To_Go',
(SELECT COUNT(*) from place_been where people.person_id=person_id) as 'Num_Visited',
(CONVERT(DECIMAL(3,0), 'Num_To_Go'))/(CONVERT(DECIMAL(3,0), 'Num_Visited')​) ​*​ 100 ​AS​ ​'Perc_Visited'
FROM people;

但这最终导致将数据类型varchar转换为数字时出错

非常感谢任何指针。

5 个答案:

答案 0 :(得分:2)

我会使用APPLY

SELECT person_id, Num_To_Go, Num_Visited, (Num_To_Go * 1.0 / Num_Visited) * 100 AS Perc_Visited
FROM people p OUTER APPLY
     ( SELECT COUNT(*) AS Num_To_Go
       FROM place_to_go pg
       WHERE P.person_id = pg.person_id
     ) pg OUTER APPLY
     ( SELECT COUNT(*) AS Num_Visited
       FROM place_been pb
       WHERE p.person_id = pb.person_id
     ) pb;

答案 1 :(得分:1)

您可以尝试使用子查询

select *, (CONVERT(DECIMAL(3,0), Num_To_Go))/(CONVERT(DECIMAL(3,0), Num_Visited)​) ​*​ 100.00 ​AS​ ​'Perc_Visited'
from
(
SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as 'Num_To_Go',
(SELECT COUNT(*) from place_been where people.person_id=person_id) as 'Num_Visited',
FROM people
)A

答案 2 :(得分:0)

这在黑暗中有点刺痛,也许是:

SELECT p.person_id,
       COUNT(DISTINCT p2g.{id_column}) AS NumToGo,
       COUNT(DISTINCT pb.{id_column}) AS NNumVisited,
       ((COUNT(DISTINCT pb.{id_column}) * 1.0) / COUNT(DISTINCT p2g.{id_column})) * 100 AS Perc_Visited --* 1.0 due to integer math. I.e. 99/100 = 0
FROM people p
     LEFT JOIN place_to_go p2g ON p.person_id = p2g.person_id
     LEFT JOIN place_been pb ON p.person_id = pb.person_id
GROUP BY person_id;

答案 3 :(得分:0)

这是我要解决的方法:Runnable Example

select ppl.person_id
, coalesce(ptg.cnt,0) as 'Num_To_Go'
, coalesce(pb.cnt,0) as 'Num_Visited'
, case 
    when coalesce(ptg.cnt,0) = 0 then 100 --avoid /0 error ; if there are no places to go let's say we've been to them all
    else 100.0 * coalesce(pb.cnt,0) / ptg.cnt
end 'Perc_Visited'
from people ppl
left outer join (select person_id, count(1) cnt from place_to_go group by person_id) ptg on ptg.person_id = ppl.person_id
left outer join (select person_id, count(1) cnt from place_been group by person_id) pb on pb.person_id = ppl.person_id
  • 我已经将查询移至FROM子句下的子查询中;这样您就可以对每个表(place_to_go,place_been)每人一次计数,然后在需要时随时重复使用这些结果。
  • 我使用person_id字段加入这些子查询。我使用了left outer join,因此即使某人在任何一个表中都没有记录,我们仍然会在结果中看到该人。
  • 我使用coalesce(cnt,0)来确保没有与我们看到的0(而不是null)相关的人相关的记录。
  • 我在逻辑中使用了case语句来计算百分比,因为涉及除法并且除数可能为0,从而导致被零除的错误。此案例声明可确保在这种情况下我们返回100%;并且仅在我们可以避免此异常的情况下使用计算。
  • 最后,我插入100.0 *而不是100 *来确保我们的解决方案可以返回非整数结果;也就是说,我们不会被截断为0个小数位。

但是,您的设计也存在问题。它假定您去过的每个地方都在去的地方表中列出。如果这个假设是正确的,那么最好为places_to_go创建一个表,并在其中添加一个字段来标记您是否去过。这样,您就可以在代码中强制执行该规则,提高性能并减少空间。

Runnable Example

create table places_to_go
(
    place_id bigint not null foreign key references places(place_id)
    , person_id bigint not null foreign key references people(person_id)
    , have_been bit not null default (0)
    --& indexes / primary key field for this table / whatever else as required
)

select ppl.person_id
, coalesce(ptg.cnt_to_go,0) as 'Num_To_Go'
, coalesce(ptg.cnt_have_been,0) as 'Num_Visited'
, case 
    when coalesce(ptg.cnt_to_go,0) = 0 then null --avoid /0 error ; 
    else 100.0 * ptg.cnt_have_been / ptg.cnt_to_go
end 'Perc_Visited'
from people ppl
left outer join 
(
    select person_id
    , count(1) cnt_to_go
    , count(case when have_been = 1 then 1 end) cnt_have_been
    from place_to_go
    group by person_id
) ptg 
on ptg.person_id = ppl.person_id

答案 4 :(得分:0)

将查询包装在派生表中。对结果进行最终计算:

select person_id, [Num_To_Go], [Num_Visited],
       [Num_To_Go] * 100.0 / [Num_Visited]​ ​AS​ ​[Perc_Visited]
from
(
    SELECT person_id,
           (SELECT COUNT(*) from place_to_go where people.person_id=person_id) as [Num_To_Go],
           (SELECT COUNT(*) from place_been where people.person_id=person_id) as [Num_Visited]
    FROM people
) dt

或者具有CTE(公用表表达式):

with cte as
(
    SELECT person_id,
           (SELECT COUNT(*) from place_to_go where people.person_id=person_id) as [Num_To_Go],
           (SELECT COUNT(*) from place_been where people.person_id=person_id) as [Num_Visited]
    FROM people
)
select person_id, [Num_To_Go], [Num_Visited],
       [Num_To_Go] * 100.0 [Num_Visited] ​AS​ [Perc_Visited]
from cte