繁体   English   中英

联接表但只允许使用一次记录

[英]Join tables but allow use of records once only

CREATE TABLE #A (UpperLimit NUMERIC(4))
CREATE TABLE #B (Id NUMERIC(4), Amount NUMERIC(4))

INSERT INTO #A VALUES 
    (1000), (2000), (3000)
INSERT INTO #B VALUES 
    (1, 3100), 
    (2, 1900), 
    (3, 1800), 
    (4, 1700), 
    (5, 900), 
    (6, 800)

鉴于这两个表,我想将表 A 连接到 B ON B.Amount < A.UpperLimit但表 B 中的每条记录只能使用一次,因此所需的 output 将是:

期望的输出

我可以通过将表 B 的记录放入临时表 cursor 超过表 A 获取最高记录 < UpperLimit 并从临时表或其他一些编程解决方案中删除该记录来轻松地做到这一点,但我想避免这种情况,我是很确定这可以通过“正常”(递归 CTE?分区?)查询来完成。

可能有点冗长,但希望足够清晰。

with a as
    (select -- order and number rows in table A in some way
        row_number() over (order by UpperLimit) as RnA,
        *
        from #a),
b as
    (select -- order and number rows in table B in the same way
        row_number() over (order by Amount) as RnB,
        *
        from #b),
m as
    (select -- get and number all possible pairs of values from both tables considering the restriction
        row_number() over (order by a.UpperLimit desc, b.Amount desc) as RnM,
        *
        from a
            join b on
                b.Amount < a.UpperLimit),
r as
    (select -- use recursion to get all possible combinations of the value pairs with metrics of interest for comparison
        convert(varchar(max), RnA) as ListA,
        convert(varchar(max), RnB) as ListB,
        RnA,
        RnB,
        1 as CountB,
        convert(int, Amount) as SumB
        from m
        where RnM = 1
    union all
    select
        r.ListA + ' ' + convert(varchar(max), m.RnA),
        r.ListB + ' ' + convert(varchar(max), m.RnB),
        m.RnA,
        m.RnB,
        r.CountB + 1,
        r.SumB + convert(int, m.Amount)
        from m
            join r on
                m.RnA < r.RnA and
                m.RnB < r.RnB),
e as
    (select top(1) -- select combinations of interest using metrics
        ListA,
        ListB
        from r 
        order by CountB desc, SumB desc),
ea as
    (select -- turn id list into table for table A
        ea.Rn,
        ea.Value
        from e
            cross apply(select row_number() over (order by (select null)) as Rn, Value from string_split(e.ListA, ' ')) as ea),
eb as
    (select -- turn id list into table for table B
        eb.Rn,
        eb.Value
        from e
            cross apply(select row_number() over (order by (select null)) as Rn, Value from string_split(e.ListB, ' ')) as eb)
select -- get output table with actual values from the original tables
    a.UpperLimit,
    b.Amount,
    b.Id
    from ea
        join eb on
            ea.Rn = eb.Rn
        join a on
            ea.Value = a.RnA
        join b on
            eb.Value = b.RnB;

您可以使用以下递归 CTE 实现所需的 output

WITH 
DATA AS
(
  SELECT * FROM #A A1 INNER JOIN #B B1 ON A1.UpperLimit >= B1.Amount
),
MA AS
(
  SELECT MIN(UpperLimit) AS MinLimit, MAX(UpperLimit) AS MaxLimit FROM #A
),
RESULT AS 
(
  -- Get the first record corresponding with maximum upper limit  
  SELECT * 
  FROM DATA D1
  WHERE NOT EXISTS 
        (SELECT 1 
        FROM DATA D2 
        WHERE D2.UpperLimit = D1.UpperLimit AND D2.Amount > D1.Amount)
        AND D1.UpperLimit = (SELECT MaxLimit FROM MA)
  
  -- Recursive get remain record corresponding with other upper limit 
  UNION ALL      
  SELECT D1.* 
  FROM RESULT R1 INNER JOIN DATA D1 
       ON (R1.UpperLimit > D1.UpperLimit AND R1.Id != D1.Id) 
  WHERE D1.UpperLimit >= (SELECT MinLimit FROM MA)
       AND NOT EXISTS 
        (SELECT 1 
        FROM DATA D2 
        WHERE D2.UpperLimit = D1.UpperLimit AND D2.Amount > D1.Amount AND D2.Id != R1.Id)   
)

SELECT DISTINCT * FROM RESULT ORDER BY UpperLimit DESC;

演示: https://dbfiddle.uk/Y-m0K6Mk

为此,您可以使用带有TOP 1APPLY 外表中的每一行仅从APPLY获取一行。

SELECT
  *
FROM #A a
OUTER APPLY (
    SELECT TOP (1) *
    FROM #B b
    WHERE b.Amount < a.UpperLimit
) b;

要模拟内部联接(而不是左联接),请使用CROSS APPLY

此查询返回非常接近期望的结果。

在此处输入图像描述

WITH CTE AS (SELECT B.*,
                    ROW_NUMBER() OVER (PARTITION BY B.Value ORDER BY B.Value DESC) AS RowNum
             FROM #B B),
     cc as (SELECT A.Limit, CTE.*
            FROM #A A
                     LEFT JOIN CTE ON CTE.Value < A.Limit AND CTE.RowNum = 1),
     cc2 as (select *, MAX(Value) OVER ( PARTITION BY cc.Limit) as l1 from cc)
select Limit, ID, Value
from cc2
where Value = l1

此查询使用 3 个公用表表达式。 首先使用 ROW_NUMBER() function 和 PARTITION BY 子句对表 B 进行排序,第二个使用给定条件将表 A 与表 B 进行联接,第三个过滤表 A 上的限制中的记录并仅使用限制一次。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM