Как вычесть строки из одной таблицы из другой только один раз - PullRequest
2 голосов
/ 24 марта 2012

Я работаю над университетским проектом, и у меня есть следующий вопрос: У меня есть 2 таблицы в БД Oracle ... Мне нужно выбрать те строки из таблицы table1, которые не включены в table2 ... Но главная проблема заключается в том, что мне нужно исключить те строки из таблицы table2, которые были выбраны один раз ... Например:

Table1                       Table2                      ResultTable
id  | Number | Letter        id  | Number | Letter       id  | Number | Letter
_____________________        _____________________       _____________________
1        4        S          1       6        G          2        2        P 
2        2        P          2       8        B          3        5        B
3        5        B          3       4        S          4        4        S
4        4        S          4       1        A          6        2        P
5        1        A          5       1        H
6        2        P          6       2        X

Итак, как вы видите, если в одной строке из Таблицы 1 есть «двойник» в Таблице2, они оба исключаются.

Ответы [ 4 ]

1 голос
/ 24 марта 2012

Вероятно, наиболее тщательный запрос таков:

SELECT table1.id,
       table1.digit,
       table1.letter
  FROM ( SELECT id,
                digit,
                letter,
                ROW_NUMBER() OVER (PARTITION BY digit, letter ORDER BY id) rn
           FROM table1
       ) table1
  LEFT
  JOIN ( SELECT id,
                digit,
                letter,
                ROW_NUMBER() OVER (PARTITION BY digit, letter ORDER BY id) rn
           FROM table2
       ) table2
    ON table2.digit = table1.digit
   AND table2.letter = table1.letter
   AND table2.rn = table1.rn
 WHERE table2.id IS NULL
 ORDER
    BY table1.id
;

, который дает каждой записи в table1 и table2 "номер строки" в своей группе "двойников".Например, это:

SELECT id,
       digit,
       letter,
       ROW_NUMBER() OVER (PARTITION BY digit, letter ORDER BY id) rn
  FROM table1
 ORDER
    BY table1.id
;

возвращает это:

        ID      DIGIT LETT         RN
---------- ---------- ---- ----------
         1          4 S             1
         2          2 P             1
         3          5 B             1
         4          4 S             2     -- second row with 4 S
         5          1 A             1
         6          2 P             2     -- second row with 2 P

Тем не менее, если вы знаете, что (digit, letter) не может появиться более одного раза в table2, вы можетезначительно упростить это, используя EXISTS вместо ROW_NUMBER():

SELECT id,
       digit,
       letter
  FROM table1 table1a
 WHERE EXISTS
        ( SELECT 1
            FROM table1
           WHERE digit = table1a.digit
             AND letter = table1a.letter
             AND id < table1a.id
        )
    OR NOT EXISTS
        ( SELECT 1
            FROM table2
           WHERE digit = table1a.digit
             AND letter = table1a.letter
        )
;
1 голос
/ 24 марта 2012

Разбейте его на части.

Возможно, у вас есть EOR - Эксклюзив ИЛИ.

Так что вы можете иметь
(condition1<br> OR<br> condition2)<br> AND NOT<br> (condition1 AND condition2).

0 голосов
/ 24 марта 2012

Используйте ключевое слово Oracle MINUS, которое делает именно то, что вы просите.Подробнее см. http://oreilly.com/catalog/mastorasql/chapter/ch07.html.

0 голосов
/ 24 марта 2012

Я не вижу, как сделать то, что вы хотите, с одним SQL SELECT.

Я думаю, вам понадобится временная таблица и несколько операторов.

Назовите его tmpResults, с id1 и id2, которые соответствуют id из Table1 и id из Table2 соответственно.

-- get matched rows - this is too many, we'll delete some later.

INSERT INTO tmpResults (id1, id2)
  SELECT Table1.id id1, Table2.id id2 
    FROM Table1 INNER JOIN Table2 
         ON Table1.Number = Table2.Number AND Table1.Letter = Table2.Letter;

-- Delete where Table1 has matched more than 1 row
DELETE tmpResults 
   WHERE rowid IN
     (SELECT tmpResults.RowId 
          FROM tmpResults 
           INNER JOIN 
             (SELECT id1, MAX(id2) id2m FROM tmpResults GROUP BY id1 HAVING count(*) > 1) m1
           ON tmpResults.id1 = m1.id1 AND tmpResults.id2 = m1.id2m );

-- Delete where Table2 has matched more than 1 row
DELETE tmpResults
  WHERE rowid IN
    (SELECT tmpResults.RowId
       FROM tmpResults
          INNER JOIN 
             (SELECT MAX(id1) id1m, id2 FROM tmpResults GROUP BY id2 HAVING count(*) >1) m2
          ON tmpResults.id1 = m2.id1m AND tmpResults.id2 = m2.id2 );

-- now tmpResults should have unique matches only, so we want Table1 where there is no match
SELECT Table1.* 
  FROM Table1 
    LEFT JOIN tmpResults
      ON table1.id = tmpResults.id1
    WHERE tmpResults.id2 IS NULL;
...