简体   繁体   中英

One to one left join

How to make a left join with only one-to-one row in each category. Here are category id's and product prices. Note that I do not want emerge repetitions in 5th category that would occur if I used LEFT JOIN.

外适用

(1) The most suitable join is when both category and price in each tables match. This is the case of the 2nd category (note that the rows are in different order in table A and B)
(2) If only the category match, then I want to show field of whatever row (like I did in the 1st category with info that it is one of many rows).
(3) If neither category nor price match, I want to get NULL.

I used the following query but it is too slow for me.

with 
A as (select A.id, A.price 
,ROW_NUMBER() over(partition BY id) as Row_id_A
,ROW_NUMBER() OVER(PARTITION BY id, price order by price asc) AS [Row_id_price_A]
from TableA as A)
,
B as (select B.id, B.price, B.field
,ROW_NUMBER() over(partition BY id) as Row_id_B
,ROW_NUMBER() OVER(PARTITION BY id, price order by price asc) AS [Row_id_price_B]
from TableB as B)

select A.id, A.price, A.Row_A, 
,ResultField=case 
    when A.Row_id_A=C.Row_id_B then C.field 
    when [Row_id_price_A]=[Row_id_price_B] then D.field
    else N'One of many: '+C.field
    end 
from A

outer apply (select top 1 * from B
where 
A.id=B.id
and Row_A=Row_B
) as C

outer apply (select top 1 * from B
where 
A.id=B.id
and Row_A=Row_B
and [Row_id_price_A]=[Row_id_price_B]
) as D

Update. I add sample data:

CREATE TABLE dbo.TableA(
   id    INTEGER NOT NULL 
  ,price INTEGER NOT NULL
);
INSERT INTO TableA(id,price) VALUES (1,50);
INSERT INTO TableA(id,price) VALUES (2,20);
INSERT INTO TableA(id,price) VALUES (2,30);
INSERT INTO TableA(id,price) VALUES (2,50);
INSERT INTO TableA(id,price) VALUES (4,15);
INSERT INTO TableA(id,price) VALUES (4,5);
INSERT INTO TableA(id,price) VALUES (5,100);
INSERT INTO TableA(id,price) VALUES (5,100);

CREATE TABLE dbo.TableB(
   id    INTEGER NOT NULL 
  ,price INTEGER NOT NULL
  ,field VARCHAR(2) NOT NULL
);
INSERT INTO TableB(id,price,field) VALUES (1,1,'A1');
INSERT INTO TableB(id,price,field) VALUES (2,30,'A2');
INSERT INTO TableB(id,price,field) VALUES (2,50,'A3');
INSERT INTO TableB(id,price,field) VALUES (2,20,'A4');
INSERT INTO TableB(id,price,field) VALUES (5,5,'A5');
INSERT INTO TableB(id,price,field) VALUES (5,100,'A6');
INSERT INTO TableB(id,price,field) VALUES (5,100,'A7');
INSERT INTO TableB(id,price,field) VALUES (6,1,'A8');

Sounds like using two left joins would work just fine:

select
 ...
 coalesce (B1.Field, B2.Field) as Field,
 ...

left join TableB B1 on B1.id = TableA.id and B1.price = TableA.price
left join TableB B2 on B2.id = TableA.id

This would usually be tricky, because it could cause you trouble with row duplication, but it shouldn't hurt in your case.

If you need the One of many text as well, just add it to the coalesce, eg coalesce(B1.Field, 'One of many: ' + B2.Field) - make sure you have the proper types, though.

EDIT:

Oh, you do care about duplication. In that case, a subquery might be a better choice:

select
 ...
 coalesce(B1.Field, (select top 1 Field from TableB where id = TableA.id)) as Field
 ...

You can have two left join with Table2 (one for exact match and one for one to many match) and use ROW_NUMBER() for managing association of rows for exact match

Something like this. SQL Fiddle

Sample Data

CREATE TABLE Table1
(
    ID INT NOT NULL,
    Price INT NOT NULL
);

CREATE TABLE Table2
(
    ID INT NOT NULL,
    Price INT NOT NULL,
    Field VARCHAR(20) NOT NULL
);

INSERT INTO Table1 VALUES(1,50),(2,20),(2,30),(2,50),(4,15),(4,5),(5,100),(5,100);
INSERT INTO Table2 VALUES
    (1,1,'A1'),(2,30,'A2'),(2,50,'A3'),(2,20,'A4'),
    (5,5,'A5'),(5,100,'A6'),(5,100,'A7'),(6,1,'A8');

Query

;WITH CT1 AS
(
SELECT *,rn = ROW_NUMBER()OVER(PARTITION BY ID,Price ORDER BY Price)
FROM Table1
), CT2 AS
(
SELECT *,rn = ROW_NUMBER()OVER(PARTITION BY ID,Price ORDER BY Field),
cc = ROW_NUMBER()OVER(PARTITION BY ID ORDER BY Price ASC)
FROM Table2
)
SELECT T1.*,ISNULL(T2.Field,'One of Many: ' + T3.Field) as Field
FROM CT1 T1 
LEFT JOIN CT2 T2 
    ON T1.ID = T2.ID
    AND (T1.Price = T2.Price AND T1.rn = T2.rn)
LEFT JOIN CT2 T3
    ON T1.ID = T3.ID
        AND T3.cc = 1
    ORDER BY T1.Id,T1.Price

Output

| ID | Price | rn |           Field |
|----|-------|----|-----------------|
|  1 |    50 |  1 | One of Many: A1 |
|  2 |    20 |  1 |              A4 |
|  2 |    30 |  1 |              A2 |
|  2 |    50 |  1 |              A3 |
|  4 |     5 |  1 |          (null) |
|  4 |    15 |  1 |          (null) |
|  5 |   100 |  1 |              A6 |
|  5 |   100 |  2 |              A7 |

Simply use this...

BEGIN TRAN
    SELECT A.id, A.price, B.field
    INTO #X
    FROM TableA A
    LEFT JOIN TableB B ON A.id = B.id AND A.price = B.price
    GROUP BY A.id, A.price, B.field

    SELECT X.id, X.price, IIF(X.price = B.price, B.field, 'One Of Many ' + B.field)
    FROM #X X
    LEFT JOIN TableB B ON X.id= B.id
    WHERE X.price = IIF(X.field IS NULL, X.price, B.price)
    GROUP BY X.id, X.price, B.price, B.field
ROLLBACK

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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