检查相等数量的负数作为正数

时间:2013-07-25 13:54:07

标签: tsql

我有一个包含两列的表:intGroupID,decAmount

我希望有一个基本上可以返回intGroupID的查询,如果对于每个正(+)decAmount,则存在相等且相反的负( - )decAmount。

因此,(id = 1,amount = 1.0),(1,2.0),(1,-1.0),(1,-2.0)的表将返回intGroupID为1,因为每个正数都有存在一个负数来匹配。

到目前为止我所知道的是必须有相同数量的decAmounts(因此我强制执行count(*)%2 = 0)并且所有行的总和必须= 0.0。但是,通过该逻辑得到的一些案例是:

ID |量

  • 1 | 1.0
  • 1 | -1.0
  • 1 | 2.0
  • 1 | -2.0
  • 1 | 3.0
  • 1 | 2.0
  • 1 | -4.0
  • 1 | -1.0

它的总和为0.0且行数为偶数,但正数与负数之间没有1对1的关系。我需要一个查询,基本上可以告诉我每个正数量是否有负数,而不重复使用任何行。

我尝试计算数字的不同绝对值并强制它小于所有行的数量,但它并没有捕获所有内容。

到目前为止我的代码:

    DECLARE @tblTest TABLE(
    intGroupID INT
    ,decAmount DECIMAL(19,2)
);

INSERT INTO @tblTest (intGroupID ,decAmount)
VALUES (1,-1.0),(1,1.0),(1,2.0),(1,-2.0),(1,3.0),(1,2.0),(1,-4.0),(1,-1.0);

DECLARE @intABSCount INT = 0
    ,@intFullCount INT = 0;

SELECT @intFullCount = COUNT(*) FROM @tblTest;

SELECT @intABSCount = COUNT(*) FROM (
SELECT DISTINCT ABS(decAmount) AS absCount FROM @tblTest GROUP BY ABS(decAmount)
) AS absCount

SELECT t1.intGroupID
FROM @tblTest AS t1

    /* Make Sure Even Number Of Rows */
    INNER JOIN
    (SELECT COUNT(*) AS intCount FROM @tblTest 
    )
    AS t2 ON t2.intCount % 2 = 0

    /* Make Sure Sum = 0.0 */
    INNER JOIN
    (SELECT SUM(decAmount) AS decSum FROM @tblTest)
    AS t3 ON decSum = 0.0

/* Make Sure Count of Absolute Values < Count of Values */
WHERE 
    @intABSCount < @intFullCount
GROUP BY t1.intGroupID

我认为可能有更好的方法来检查这个表,可能是通过查找对并从表中删除它们,看看一旦没有更多的正/负匹配,表中是否还有任何东西,但我宁愿不必使用递归/游标。

6 个答案:

答案 0 :(得分:1)

Create TABLE #tblTest (
    intA INT
    ,decA DECIMAL(19,2)
);

INSERT INTO #tblTest (intA,decA)
VALUES (1,-1.0),(1,1.0),(1,2.0),(1,-2.0),(1,3.0),(1,2.0),(1,-4.0),(1,-1.0), (5,-5.0),(5,5.0) ;


SELECT * FROM #tblTest;

SELECT 
    intA
    , MIN(Result) as IsBalanced
FROM
(
    SELECT intA, X,Result =
          CASE
             WHEN count(*)%2 = 0 THEN 1
             ELSE 0
          END
    FROM
    (
       ---- Start thinking here --- inside-out
       SELECT 
          intA 
          , x = 
             CASE
                WHEN decA < 0 THEN
                    -1 * decA
                ELSE
                    decA
             END 
       FROM #tblTest
    ) t1
    Group by intA, X
)t2
GROUP BY intA

答案 1 :(得分:1)

未经测试但我认为你可以得到这个想法

这将返回不符合的id 不容易测试/调试

select pos.*, neg.* 
  from 
     (  select id, amount, count(*) as ccount
          from tbl 
         where amount > 0 
         group by id, amount ) pos
  full outer join 
     (  select id, amount, count(*) as ccount
          from tbl 
         where amount < 0 
         group by id, amount ) neg
    on pos.id = neg.id 
   and pos.amount = -neg.amount 
   and pos.ccount = neg.ccount
 where pos.id is null 
    or neg.id is null 

I think this will return a list of id that do conform 

select distinct(id) from tbl 
except
select distinct(isnull(pos.id, neg.id)) 
  from 
     (  select id, amount, count(*) as ccount
          from tbl 
         where amount > 0 
         group by id, amount ) pos
  full outer join 
     (  select id, amount, count(*) as ccount
          from tbl 
         where amount < 0 
         group by id, amount ) neg
    on pos.id = neg.id 
   and pos.amount = -neg.amount 
   and pos.ccount = neg.ccount
 where pos.id is null 
    or neg.id is null

答案 2 :(得分:0)

男孩,我找到了比以前的答案更简单的方法。我希望所有疯狂的编辑都能为后人保存。

  • 这是通过将id的所有数字按其绝对值(1,-1分组1)分组来实现的。
  • 组的总和确定是否有相同数量的对。如果它是0那么它是相等的,总和的任何其他值意味着存在不平衡。
  • COUNT聚合检测均匀度只是检测偶数个零所必需的。我假设0可能存在,它们应该发生偶数次。如果这不是问题,请删除它,因为0将始终通过第一次测试。
  • 我用一堆不同的方法重写了查询,以获得最佳的执行计划。下面的最终结果只有一个大堆排序,由于缺少索引,这是不可避免的。

<强>查询

WITH tt AS (
    SELECT intGroupID, 
        CASE WHEN SUM(decAmount) > 0 OR COUNT(*) % 2 = 1 THEN 1 ELSE 0 END unequal
    FROM @tblTest 
    GROUP BY intGroupID, ABS(decAmount)
)
SELECT tt.intGroupID, 
    CASE WHEN SUM(unequal) != 0 THEN 'not equal' ELSE 'equals' END [pair]
FROM tt
GROUP BY intGroupID;

经过测试的值

(1,-1.0),(1,1.0),(1,2),(1,-2), -- should work
(2,-1.0),(2,1.0),(2,2),(2,2), -- fail, two positive twos
(3,1.0),(3,1.0),(3,-1.0), -- fail two 1's , one -1
(4,1),(4,2),(4,-.5),(4,-2.5), -- fail: adds up the same sum, but different values
(5,1),(5,-1),(5,0),(5,0), -- work, test zeros
(6,1),(6,-1),(6,0), -- fail, test zeros
(7,1),(7,-1),(7,-1),(7,1),(7,1) -- fail, 3 x 1

<强>结果

A   pairs
_   _____
1   equal
2   not equal
3   not equal
4   not equal
5   equal
6   not equal
7   not equal

答案 3 :(得分:0)

这有帮助吗?

-- Expected result - group 1 and 3 
declare @matches table (groupid int, value decimal(5,2))
insert into @matches select 1, 1.0
insert into @matches select 1, -1.0
insert into @matches select 2, 2.0
insert into @matches select 2, -2.0
insert into @matches select 2, -2.0
insert into @matches select 3, 3.0
insert into @matches select 3, 3.5
insert into @matches select 3, -3.0
insert into @matches select 3, -3.5
insert into @matches select 4, 4.0
insert into @matches select 4, 4.0
insert into @matches select 4, -4.0


-- Get groups where we have matching positive/negatives, with the same number of each
select  mat.groupid, min(case when pos.PositiveCount = neg.NegativeCount then 1 else 0 end) as 'Match'
from    @matches mat
LEFT JOIN (select groupid, SUM(1) as 'PositiveCount', Value
                from @matches where value > 0 group by groupid, value) pos 
                on pos.groupid = mat.groupid and pos.value = ABS(mat.value)

LEFT JOIN (select groupid, SUM(1) as 'NegativeCount', Value
                from @matches where value < 0 group by groupid, value) neg 
                on neg.groupid = mat.groupid and neg.value = case when mat.value < 0 then mat.value else mat.value * -1 end

group by mat.groupid
-- If at least one pair within a group don't match, reject
having min(case when pos.PositiveCount = neg.NegativeCount then 1 else 0 end) = 1

答案 4 :(得分:0)

以下内容应返回“失衡”群组:

;with pos as (
    select intGroupID, ABS(decAmount) m
    from TableName
    where decAmount > 0
), neg as (
    select intGroupID, ABS(decAmount) m
    from TableName
    where decAmount < 0
)
select distinct IsNull(p.intGroupID, n.intGroupID) as intGroupID
from pos p
    full join neg n on n.id = p.id and abs(n.m - p.m) < 1e-8
where p.m is NULL or n.m is NULL

要获取不成对的元素,select声明可以更改为以下内容:

select IsNull(p.intGroupID, n.intGroupID) as intGroupID, IsNull(p.m, -n.m) as decAmount
from pos p
    full join neg n on n.id = p.id and abs(n.m - p.m) < 1e-8
where p.m is NULL or n.m is NULL

答案 5 :(得分:0)

您可以通过以下方式比较您的值:

declare @t table(id int, amount decimal(4,1))
insert @t values(1,1.0),(1,-1.0),(1,2.0),(1,-2.0),(1,3.0),(1,2.0),(1,-4.0),(1,-1.0),(2,-1.0),(2,1.0)

;with a as
(
select count(*) cnt, id, amount
from @t
group by id, amount
)
select id from @t
except
select b.id from a
full join a b
on a.cnt = b.cnt and a.amount = -b.amount
where a.id is null

由于某种原因,我不能写评论,但Daniels评论不正确,我的解决方案确实接受(6,1),(6,-1),(6,0)这是正确的。在问题中没有指定0,因为它是0值,它可以通过eather方式处理。我的回答不接受(3,1.0),(3,1.0),(3,-1.0)

To Blam:不,我不会错过

or b.id is null

我的解决方案与您的解决方案类似,但不完全相同