2016-07-05 2 views
2

Я ищу SQL-запрос (или даже лучший запрос LINQ), чтобы удалить людей, отменивших их отпуск, т.е. удалить все записи с тем же именем и тем же START и END и DAYS_TAKEN значения отличаются только знаком.Удалить все записи с противоположным знаком

Как получить от этого

NAME |DAYS_TAKEN |START  |END  |UNIQUE_LEAVE_ID  
--------|-----------|-----------|-----------|----------- 
Alice | 2  | 1 June | 3 June | 1 --remove because cancelled 
Alice | -2  | 1 June | 3 June | 2 --cancelled 
Alice | 3  | 5 June | 8 June | 3 --keep 
Bob  | 10  | 4 June | 14 June | 4 --keep 
Charles | 12  | 2 June | 14 June | 5 --remove because cancelled 
Charles | -12  | 2 June | 14 June | 6 --cancelled 
David | 5   | 3 June | 8 June | 7 --keep 

К этому?

NAME |DAYS_TAKEN |START  |END  |UNIQUE_LEAVE_ID  
--------|-----------|-----------|-----------|----------- 
Alice | 3  | 5 June | 8 June | 3 --keep 
Bob  | 10  | 4 June | 14 June | 4 --keep 
David | 5   | 3 June | 8 June | 7 --keep 

То, что я пытался

Query1 найти все отмененные записи (не уверен, если это правильно)

SELECT L1.UNIQUE_LEAVE_ID 
FROM LEAVE L1 
INNER JOIN LEAVE L2 ON L2.DAYS_TAKEN > 0 AND ABS(L1.DAYS_TAKEN) = L2.DAYS_TAKEN AND L1.NAME= L2.NAME AND L1.START = L2.START AND L1.END = L2.END 
WHERE L1.DAYS_TAKEN < 0 

Затем я использую Query1 дважды внутренний выбор, как так

SELECT L.* FROM LEAVE L WHERE 
L.UNIQUE_LEAVE_ID NOT IN (Query1) 
AND L.UNIQUE_LEAVE_ID NOT IN (Query1) 

Есть ли способ использовать внутренний запрос только один раз?

(Это база данных Oracle, вызывается из .NET/C#)

ответ

2

Вы можете использовать запрос вроде следующего:

SELECT NAME, START, END 
FROM LEAVE 
GROUP BY NAME, START, END 
HAVING SUM(DAYS_TAKEN) = 0 

для того, чтобы получить NAME, START, END группы, которые были отменены (в предположении, DAYS_TAKEN записи отмены отменяет дни первоначальной записи).

Выход:

NAME |START  |END   
--------|-----------|---------- 
Alice | 1 June | 3 June 
Charles | 2 June | 14 June 

Используя приведенный выше запрос в качестве производной таблицы, которую вы можете получить записи не относящиеся к 'отмененных' группы:

SELECT L1.NAME, L1.DAYS_TAKEN, L1.START, L1.END, L1.UNIQUE_LEAVE_ID 
FROM LEAVE L1 
LEFT JOIN (
    SELECT NAME, START, END 
    FROM LEAVE 
    GROUP BY NAME, START, END 
    HAVING SUM(DAYS_TAKEN) = 0 
) L2 ON L1.NAME = L2.NAME AND L1.START = L2.START AND L1.END = L2.END 
WHERE L2.NAME IS NULL 

Выход:

NAME |DAYS_TAKEN |START  |END  |UNIQUE_LEAVE_ID  
--------|-----------|-----------|-----------|----------- 
Alice | 3   | 5 June | 8 June | 3 
Bob  | 10  | 4 June | 14 June | 4 
David | 5   | 3 June | 8 June | 7 
1

Вы можете использовать not exists:

select l.* 
from leave l 
where not exists (select 1 
        from leave l2 
        where l2.name = l.name and l2.start = l.start and 
         l2.end = l.name and l2.days_taken = - l.days_taken 
       ); 

Этот запрос может воспользоваться индексом на leave(name, start, end, days_taken).

1

Вот вариант с SUM() OVER:

SELECT x.* 
    FROM (SELECT l.*, SUM (days_taken) OVER (PARTITION BY name, "START", "END", ABS (days_taken) ORDER BY NULL) s 
      FROM leave l) x 
WHERE s <> 0 

И если у вас есть Oracle 12, это даст вам отмененный:

SELECT l.* 
    FROM leave l, 
     LATERAL (SELECT days_taken 
        FROM leave l2 
       WHERE l2.name = l.name 
        AND l2."START" = l."START" 
        AND l2."END" = l."END" 
        AND l2.days_taken = -l.days_taken) x 

и это то, что должно остаться:

SELECT l.* 
    FROM leave l 
     OUTER APPLY (SELECT days_taken 
         FROM leave l2 
        WHERE l2.name = l.name 
         AND l2."START" = l."START" 
         AND l2."END" = l."END" 
         AND l2.days_taken = -l.days_taken) x 
WHERE x.days_taken IS NULL 

И что-то о именах столбцов. Использование зарезервированного слова в Oracle SQL не рекомендуется, но если вы это сделаете, используйте «», как здесь.

0

Я использовал ответ Giorgos, чтобы придумать решение Linq.Это решение также рассматривает людей, которые отменяют/применяют свой отпуск несколько раз. См. Алису и Эдгара ниже.

Выборочные данные

int id = 0; 
    List<Leave> allLeave = new List<Leave>() 
    { 
    new Leave() { UniqueLeaveID=id++, Name="Alice", Start=new DateTime(2016,6,1), End=new DateTime(2016,6,3), Taken=-2 }, 
    new Leave() { UniqueLeaveID=id++,Name="Alice", Start=new DateTime(2016,6,1), End=new DateTime(2016,6,3), Taken=2 }, 
    new Leave() { UniqueLeaveID=id++, Name="Alice", Start=new DateTime(2016,6,1), End=new DateTime(2016,6,3), Taken=2 }, 
    new Leave() { UniqueLeaveID=id++,Name="Alice", Start=new DateTime(2016,6,3), End=new DateTime(2016,6,5), Taken=3 }, 

    new Leave() { UniqueLeaveID=id++,Name="Bob", Start=new DateTime(2016,6,4), End=new DateTime(2016,6,14), Taken=10 }, 

    new Leave() { UniqueLeaveID=id++,Name="Charles", Start=new DateTime(2016,6,2), End=new DateTime(2016,6,14), Taken=12 }, 
    new Leave() { UniqueLeaveID=id++,Name="Charles", Start=new DateTime(2016,6,2), End=new DateTime(2016,6,14), Taken=-12 }, 

    new Leave() { UniqueLeaveID=id++,Name="David", Start=new DateTime(2016,6,3), End=new DateTime(2016,6,8), Taken=5 }, 

      new Leave() { UniqueLeaveID=id++,Name="Edgar", Start=new DateTime(2016,6,3), End=new DateTime(2016,6,8), Taken=5 }, 
    new Leave() { UniqueLeaveID=id++,Name="Edgar", Start=new DateTime(2016,6,3), End=new DateTime(2016,6,8), Taken=5 }, 
    new Leave() { UniqueLeaveID=id++,Name="Edgar", Start=new DateTime(2016,6,3), End=new DateTime(2016,6,8), Taken=5 }, 
    new Leave() { UniqueLeaveID=id++,Name="Edgar", Start=new DateTime(2016,6,3), End=new DateTime(2016,6,8), Taken=5 } 

    }; 

Linq запросов (берегитесь Oracle версии 11 против 12)

var filteredLeave = allLeave 
.GroupBy(a => new { a.Name, a.Start, a.End }) 
.Select(a => new { Group = a.OrderByDescending(b=>b.Taken), Count = a.Count() }) 
.Where(a => a.Count % 2 != 0) 
.Select(a => a.Group.First()); 

"OrderByDescending" обеспечивает только положительные дни, взятые возвращаются.

Oracle SQL

SELECT 
* 
FROM 
(
    SELECT 
    L1.NAME, L1.START, L1.END, MAX(TAKEN) AS TAKEN, COUNT(*) AS CNT 
    FROM LEAVE L1 
    GROUP BY L1.NAME, L1.START, L1.END 
) L2 
WHERE MOD(L2.CNT,2)<>0 -- replace MOD with % for Microsoft SQL 

условие "WHERE MOD (L2.CNT, 2) <> 0" (или в Linq "a.Count% 2! = 0") возвращает только людей, которые применяются один раз или нечетное число раз (например, apply - cancel - apply). Но люди, которые применяют - отменяют - применяют - отменить, отфильтровываются.