source

테이블의 항목을 재정렬하는 방법

manysource 2023. 10. 22. 20:10

테이블의 항목을 재정렬하는 방법

자리가 있습니다.my_table NAME, SEQ_NO, LOCKED .

아이템을 제거하고 추가하고 다시 주문하고 싶습니다(수정SEQ_NO) 시퀀스가 항상 1에서 COUNT(*)로 진행되고 잠긴 항목은 해당 SEQ_NO를 유지하며 잠금 해제된 항목은 해당 번호를 얻을 수 없습니다.잠금해제된항목만새로운SEQ_NO로업데이트됩니다.

예:

이것.

NAME SEQ_잠김 없음푸1N막대 3 Yabc 4 Y바즈5NCde 7 N

결과:

NAME SEQ_잠김 없음푸1N바즈2N막대 3 Yabc 4 YCde 5 N

내가 어떻게 그럴 수 있을까?

당신의 목표는 때때로.1..COUNT(*)numbering과 '잠긴 행에 번호를 변경하지 않음'으로 인해 확인할 수 없는 충돌이 발생합니다.예를 들어,

NAME  SEQ_NO    LOCKED
Foo    1        N
Bar   13        Y
Abc   14        Y
Baz    5        N
Cde    7        N

이 시나리오에 필요한 출력은 다음과 같다고 가정하겠습니다.

NAME  SEQ_NO    LOCKED
Foo    1        N
Baz    2        N
Cde    3        N
Bar   13        Y
Abc   14        Y

예를 들어 잠금 해제된 데이터는 원래 시퀀스 번호 순서대로 유지되고 잠금된 데이터는 분명히 새로운 번호를 얻지 못하는 것으로 보입니다.

저는 원본 데이터에 중복되는 시퀀스 번호가 없다고 가정합니다.


빠른 요약

그것은 흥미롭고 까다로운 문제입니다.데이터 순서를 바꾸려면 잠금 해제된 행을 어디에 배치할지 아는 것이 관건입니다.예제 데이터에서:

NAME  OLD_SEQ   LOCKED   NEW_SEQ
Foo   1         N        1
Bar   3         Y        3
Abc   4         Y        4
Baz   5         N        2
Cde   7         N        5

잠금 해제된 행에 1..3부터 수열 번호를 부여할 수 있으므로, ord:old 수열 A {1:1, 2:5, 3:7}의 쌍으로 끝납니다.결과 집합 1..5의 슬롯 목록을 생성할 수 있습니다.해당 슬롯 목록에서 {1, 2, 5}을(를) 순서대로 정렬된 목록에서 잠금 해제된 행이 차지할 슬롯 목록으로 남겨두면서 잠금 해제된 행이 해당 슬롯을 제거합니다.그런 다음 순서대로 번호를 매기고 을 남깁니다: 새로운 B {1:1, 2:2, 3:5}.그런 다음 첫 번째 필드에 이 두 목록 A와 B를 결합하고 시퀀스를 투영하여 새로운 이전 슬롯 번호 C {1:1, 2:5, 5:7} 쌍을 남길 수 있습니다.잠긴 행은 각 경우에 새 = 오래된 new:old 값 집합을 생성하므로 D {3:3, 4:4}이(가) 생성됩니다.최종 결과는 C와 D의 결합이므로 결과 집합에는 다음과 같은 내용이 포함됩니다.

  • 새 시퀀스 번호 1의 이전 시퀀스 번호 1;
  • 올드 5 인 뉴 2;
  • (기존 3개의 새 3개);
  • (구형 4 신형 4) 및
  • 7살부터 5살까지.

이것은 잠금 행에 시퀀스 번호 13과 14가 있는 경우에도 적용됩니다. 잠금 해제된 행에는 새 시퀀스 번호 1, 2, 3이 할당되고 잠금 행은 변경되지 않습니다.질문에 대한 의견 중 하나가 '1잠김, 5잠김, 10잠김'에 대해 묻습니다. 그러면 '1잠김, 2잠김, 10잠김'이 발생합니다.

SQL에서 이를 수행하려면 상당한 양의 SQL이 필요합니다.OLAP 기능을 잘 제어하는 사람이 내 코드보다 더 빨리 도착할 수도 있습니다.그리고 SELECT 결과를 UPDATE 문으로 변환하는 것도 까다롭습니다(그리고 저는 완전히 해결하지 못했습니다).그러나 올바른 결과 순서로 제시된 데이터를 얻을 수 있는 것은 매우 중요하며, 이를 해결하기 위한 핵심은 목록 A와 B로 표시되는 순서 단계입니다.


TDQD — 테스트 기반 쿼리 설계

다른 복잡한 SQL 쿼리 작업과 마찬가지로 쿼리를 단계적으로 구축하는 것이 비결입니다.언급한 바와 같이, 잠금 행과 잠금 해제 행을 다르게 취급해야 합니다.이 경우 대상은 궁극적으로 UPDATE 문이지만 UPDATE를 위한 데이터 생성 방법을 알아야 하므로 SELECT를 먼저 수행합니다.

번호 변경 가능 행

-- Query 1
SELECT Name, Seq_No
  FROM My_Table
 WHERE Locked = 'N'
 ORDER BY Seq_No;

NAME  SEQ_NO
Foo   1
Baz   5
Cde   7

적절한 경우 ORDER BY 절과 함께 주문할 수 있지만 하위 쿼리는 일반적으로 ORDER BY 절을 허용하지 않으므로 번호를 생성해야 합니다.OLAP 기능을 사용하면 보다 콤팩트하게 작업할 수 있습니다.Oracle에서는 ROWNUM을 사용하여 행 번호를 생성할 수 있습니다.특정한 속도는 아니지만 어떤 DBMS에서도 통할 수 있는 요령이 있습니다.

잠긴 행의 간섭이 없다고 가정하여 행 번호를 바꿉니다.

-- Query 2
SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
  FROM My_Table m1
  JOIN My_Table m2
    ON m1.Seq_No >= m2.Seq_No
 WHERE m1.Locked = 'N' AND m2.Locked = 'N'
 GROUP BY m1.Name, m1.Seq_No
 ORDER BY New_Seq;

NAME  Old_Seq   New_Seq
Foo   1         1
Baz   5         2
Cde   7         3

이것은 비등가조인이며 이것이 특별히 빠른 작동을 하지 않게 해주는 이유입니다.

번호를 매길 수 없는 행

-- Query 3
SELECT Name, Seq_No
  FROM My_Table
 WHERE Locked = 'Y'
 ORDER BY Seq_No;

NAME  Seq_No
Bar   3
Abc   4

새순서번호

우리가 숫자들의 목록을 얻을 수 있다고 가정해보자, 1..N(표본 데이터에서 N = 5인 경우).잠금 항목(3, 4)이 (1, 2, 5)를 떠나는 목록에서 제거합니다.순위가 정해지면(1 = 1, 2 = 2, 3 = 5), 잠금 해제된 레코드 새 시퀀스로 순위에 합류할 수 있지만 다른 번호를 레코드의 최종 시퀀스 번호로 사용합니다.그것은 우리에게 몇 가지 해결해야 할 작은 문제들만 남습니다.우선 각각의 숫자 1을 생성하는 것입니다.N; 우리는 그 끔찍한 비균등 트릭 중 하나를 할 수 있지만, 더 좋은 방법이 있을 것입니다.

-- Query 4
SELECT COUNT(*) AS Ordinal
  FROM My_Table AS t1
  JOIN My_Table AS t2
    ON t1.Seq_No >= t2.Seq_No
 GROUP BY t1.Seq_No
 ORDER BY Ordinal;

Ordinal
1
2
3
4
5

그러면 잠금 시퀀스 번호를 이 목록에서 제거할 수 있습니다.

-- Query 5
SELECT Ordinal
  FROM (SELECT COUNT(*) AS ordinal
          FROM My_Table t1
          JOIN My_Table t2
            ON t1.Seq_No <= t2.Seq_No
         GROUP BY t1.Seq_No
       ) O
 WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
 ORDER BY Ordinal;

 Ordinal
 1
 2
 5

이제 우리는 그것들의 순위를 매길 필요가 있습니다. 그것은 또 다른 자기 결합을 의미하지만, 이번에는 그 표현에 대한 것입니다.'공통 테이블 표현식' 또는 'WITH 절'이라고도 하는 CTE를 사용하는 시간:

-- Query 6
WITH HoleyList AS
    (SELECT ordinal
       FROM (SELECT COUNT(*) ordinal
               FROM My_Table t1
               JOIN My_Table t2
                 ON t1.seq_no <= t2.seq_no
              GROUP BY t1.seq_no
            ) O
      WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
    )
SELECT H1.Ordinal, COUNT(*) AS New_Seq
  FROM HoleyList H1
  JOIN HoleyList H2
    ON H1.Ordinal >= H2.Ordinal
 GROUP BY H1.Ordinal
 ORDER BY New_Seq;

Ordinal  New_Seq
1        1
2        2
5        3

마무리중

따라서 이제 해당 결과를 쿼리 2와 결합하여 잠금 해제된 행의 최종 숫자를 얻고 쿼리 3과 결합하여 필요한 출력을 얻어야 합니다.물론 Locked in output에 대해서도 정확한 값을 구해야 합니다.여전히 단계적으로 진행 중:

-- Query 7
WITH
Query2 AS
   (SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
      FROM My_Table m1
      JOIN My_Table m2 ON m1.Seq_No <= m2.Seq_No
     WHERE m1.Locked = 'N' AND m2.Locked = 'N'
     GROUP BY m1.Name, m1.Seq_No
   ),
HoleyList AS
   (SELECT ordinal
      FROM (SELECT COUNT(*) AS ordinal
              FROM My_Table t1
              JOIN My_Table t2
                ON t1.seq_no <= t2.seq_no
             GROUP BY t1.seq_no
           ) O
      WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
    ),
Reranking AS    
   (SELECT H1.Ordinal, COUNT(*) AS New_Seq
      FROM HoleyList H1
      JOIN HoleyList H2
        ON H1.Ordinal >= H2.Ordinal
     GROUP BY H1.Ordinal
   )
SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
  FROM Reranking r
  JOIN Query2    q
    ON r.New_Seq = q.New_Seq
 ORDER BY r.New_Seq;

Ordinal  New_Seq  Name  Old_Seq  Locked
1        1        Cde   7        N
2        2        Baz   5        N
5        3        Foo   1        N

이를 쿼리 3의 변형과 결합해야 합니다.

-- Query 3a
SELECT Seq_No Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
  FROM My_Table
 WHERE Locked = 'Y'
 ORDER BY New_Seq;

Ordinal  New_Seq  Name  Old_Seq  Locked
3        3        Bar   3        Y
4        4        Abc   4        Y

결과집합

다음과 같은 결과를 합하면 다음과 같습니다.

-- Query 8
WITH
Query2 AS
   (SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
      FROM My_Table m1
      JOIN My_Table m2 ON m1.Seq_No <= m2.Seq_No
     WHERE m1.Locked = 'N' AND m2.Locked = 'N'
     GROUP BY m1.Name, m1.Seq_No
   ),
HoleyList AS
   (SELECT ordinal
      FROM (SELECT COUNT(*) AS ordinal
              FROM My_Table t1
              JOIN My_Table t2
                ON t1.seq_no <= t2.seq_no
             GROUP BY t1.seq_no
           ) O
      WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
    ),
Reranking AS    
   (SELECT H1.Ordinal, COUNT(*) AS New_Seq
      FROM HoleyList H1
      JOIN HoleyList H2
        ON H1.Ordinal >= H2.Ordinal
     GROUP BY H1.Ordinal
   ),
Query7 AS
   (SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
      FROM Reranking r
      JOIN Query2    q
        ON r.New_Seq = q.New_Seq
   ),
Query3a AS
   (SELECT Seq_No Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
      FROM My_Table
     WHERE Locked = 'Y'
   )
SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
  FROM Query7
UNION
SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
  FROM Query3a
 ORDER BY New_Seq;

결과는 다음과 같습니다.

Ordinal  New_Seq  Name  Old_Seq  Locked
1        1        Cde   7        N
2        2        Baz   5        N
3        3        Bar   3        Y
4        4        Abc   4        Y
5        3        Foo   1        N

따라서 데이터를 올바르게 순서화하는 SELECT 문을 작성하는 것이 가능합니다(쉽지는 않지만).

UPDATE 작업으로 변환

이제 우리는 그 괴상함을 UPDATE 성명서에 넣을 방법을 찾아야 합니다.내 장치에 맡겨서 쿼리 8의 결과를 임시 테이블로 선택한 다음 소스 테이블에서 모든 레코드를 삭제하는 트랜잭션 측면에서 생각합니다.My_Table) 및 쿼리 8의 결과 중 적절한 프로젝트를 원래 테이블에 삽입한 다음 커밋합니다.

Oracle은 동적으로 생성된 '세션별' 임시 테이블을 지원하지 않고 글로벌 임시 테이블만 지원하는 것으로 보입니다.SQL Standard를 사용하지 않는 데에는 타당한 이유가 있습니다.그럼에도 불구하고, 다른 어떤 것이 효과가 있을지 확신할 수 없는 이곳에서 효과가 있을 것입니다.

이 작업과는 별개로:

CREATE GLOBAL TEMPORARY TABLE ReSequenceTable
(
    Name     CHAR(3) NOT NULL,
    Seq_No   INTEGER NOT NULL,
    Locked   CHAR(1) NOT NULL
)
ON COMMIT DELETE ROWS;

그러면:

-- Query 8a
BEGIN;   -- May be unnecessary and/or unsupported in Oracle
INSERT INTO ReSequenceTable(Name, Seq_No, Locked)
    WITH
    Query2 AS
       (SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
          FROM My_Table m1
          JOIN My_Table m2 ON m1.Seq_No <= m2.Seq_No
         WHERE m1.Locked = 'N' AND m2.Locked = 'N'
         GROUP BY m1.Name, m1.Seq_No
       ),
    HoleyList AS
       (SELECT ordinal
          FROM (SELECT COUNT(*) AS ordinal
                  FROM My_Table t1
                  JOIN My_Table t2
                    ON t1.seq_no <= t2.seq_no
                 GROUP BY t1.seq_no
               ) O
          WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
        ),
    Reranking AS    
       (SELECT H1.Ordinal, COUNT(*) AS New_Seq
          FROM HoleyList H1
          JOIN HoleyList H2
            ON H1.Ordinal >= H2.Ordinal
         GROUP BY H1.Ordinal
       ),
    Query7 AS
       (SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
          FROM Reranking r
          JOIN Query2    q
            ON r.New_Seq = q.New_Seq
       ),
    Query3a AS
       (SELECT Seq_No Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
          FROM My_Table
         WHERE Locked = 'Y'
       )
    SELECT Name, Ordinal, Locked
      FROM Query7
    UNION
    SELECT Name, Ordinal, Locked
      FROM Query3a;

DELETE FROM My_Table;
INSERT INTO My_Table(Name, Seq_No, Locked) FROM ReSequenceTable;
COMMIT;

아마도 적절한 업데이트를 통해 이를 수행할 수 있을 것입니다. 생각을 좀 해봐야 합니다.


요약

쉽지는 않지만 할 수 있습니다.

(적어도 저에게는) 핵심 단계쿼리 6에서 나온 결과 세트로, 업데이트된 결과 세트에서 잠금 해제된 행의 새로운 위치를 계산했습니다.그것은 즉각적으로 명백한 것은 아니지만, 답을 만들어 내는 데 매우 중요합니다.

나머지는 핵심 단계를 둘러싼 지원 코드일 뿐입니다.

이전에 언급했듯이, 일부 쿼리를 개선할 수 있는 많은 방법이 있을 것입니다.예를 들어, 시퀀스 생성1..N식탁에서 보면 아주 간단할지도 모릅니다.SELECT ROWNUM FROM My_Table, 이 값은 쿼리를 압축합니다(유익한 highly – 장황합니다).OLAP 기능이 있습니다. 그 중 하나 이상은 순위 결정 작업에 도움이 될 수 있습니다(아마도 더 간결하게; 더 나은 성능을 발휘하는 것처럼).

따라서 이것은 세련된 최종 답변이 아니라 올바른 일반적인 방향으로 강하게 밀어붙이는 것입니다.


PoC 테스트

Informix에 대한 코드 테스트가 완료되었습니다.Informix에서는 아직 CTE를 지원하지 않기 때문에 약간 다른 표기법을 사용해야 했습니다.매우 편리하고 매우 간단한 세션별 동적 임시 테이블을 제공합니다.INTO TEMP <temp-table-name>그렇지 않으면 ORDER BY 절이 나타날 수 있습니다.따라서 쿼리 8a를 다음과 같이 시뮬레이션했습니다.

+ BEGIN;
+ SELECT O.Ordinal
  FROM (SELECT COUNT(*) AS ordinal
          FROM My_Table AS t1
          JOIN My_Table AS t2
            ON t1.Seq_No <= t2.Seq_No
         GROUP BY t1.Seq_No
       ) AS O
 WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
 INTO TEMP HoleyList;
+ SELECT * FROM HoleyList ORDER BY Ordinal;
1
2
5
+ SELECT H1.Ordinal, COUNT(*) AS New_Seq
  FROM HoleyList AS H1
  JOIN HoleyList AS H2
    ON H1.Ordinal >= H2.Ordinal
 GROUP BY H1.Ordinal
 INTO TEMP ReRanking;
+ SELECT * FROM ReRanking ORDER BY Ordinal;
1|1
2|2
5|3
+ SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
  FROM My_Table m1
  JOIN My_Table m2
    ON m1.Seq_No >= m2.Seq_No
 WHERE m1.Locked = 'N' AND m2.Locked = 'N'
 GROUP BY m1.Name, m1.Seq_No
  INTO TEMP Query2;
+ SELECT * FROM Query2 ORDER BY New_Seq;
Foo|1|1
Baz|5|2
Cde|7|3
+ SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
  FROM Reranking r
  JOIN Query2    q
    ON r.New_Seq = q.New_Seq
  INTO TEMP Query7;
+ SELECT * FROM Query7 ORDER BY Ordinal;
1|1|Foo|1|N
2|2|Baz|5|N
5|3|Cde|7|N
+ SELECT Seq_NO Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
  FROM My_Table
 WHERE Locked = 'Y'
  INTO TEMP Query3a;
+ SELECT * FROM Query3a ORDER BY Ordinal;
3|3|Bar|3|Y
4|4|Abc|4|Y
+ SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
  FROM Query7
UNION
SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
  FROM Query3a
  INTO TEMP Query8;
+ SELECT * FROM Query8 ORDER BY Ordinal;
1|1|Foo|1|N
2|2|Baz|5|N
3|3|Bar|3|Y
4|4|Abc|4|Y
5|3|Cde|7|N
+ ROLLBACK;
merge into my_table
using (
   select rowid as rid,
          row_number() over (order by seq_no) as rn
   from my_table
   where locked = 'N'
) t on (t.rid = my_table.rowid) 
when matched then update
   set seq_no = t.rn;

이렇게 하면 모든 데이터 사례를 얻을 수는 없지만 예제 데이터에서는 작동합니다.

update my_table mt
set seq_no = 
(with renumber as (select /*+ MATERIALIZE */ rownum rn, name, seq_no, locked
from
(
select * from my_table
where locked = 'N'
order by seq_no
)
)
select rn from renumber rn where rn.seq_no = mt.seq_no
)
where locked = 'N'
;

아래의 완벽하게 계산된 예:

create table my_table as
select 'Foo' name, 1 seq_no, 'N' locked from dual union
select 'Bar' name, 3 seq_no, 'Y' locked from dual union
select 'Baz' name, 5 seq_no, 'N' locked from dual 
order by seq_no
;

select * from my_table
order by seq_no
;


update my_table mt
set seq_no = 
(with renumber as (select /*+ MATERIALIZE */ rownum rn, name, seq_no, locked
from
(
select * from my_table
where locked = 'N'
order by seq_no
)
)
select rn from renumber rn where rn.seq_no = mt.seq_no
)
where locked = 'N'
;

select * from my_table
order by seq_no
;
WITH
  yourTable_unlocked_resequenced
AS
(
  SELECT
    yourTable.*,
    CASE WHEN
      yourTable.locked ='N'
    THEN
      ROW_NUMBER() OVER (PARTITION BY locked ORDER BY seq_no)
    END AS unlocked_seq_no
  FROM
    yourTable
)
,
  master_list
AS
(
  SELECT
    ROW_NUMBER() OVER (ORDER BY seq_no)       AS seq_no
  FROM
    yourTable
)
,
  master_list_unlocked_resequenced
AS
(
  SELECT
    master_list.seq_no,
    ROW_NUMBER() OVER (PARTITION BY yourTable.locked ORDER BY master_list.seq_no) AS unlocked_seq_no
  FROM
     master_list
  LEFT JOIN
     yourTable
       ON  yourTable.seq_no = master_list.seq_no
       AND yourTable.locked = 'Y'
  WHERE
    yourTable.locked IS NULL
)
SELECT
  original.*,
  modified.*,
  COALESCE(modified.seq_no, original_seq_no) AS final_seq_no
FROM
  yourTable_unlocked_resequenced      AS original
LEFT JOIN
  master_list_unlocked_resequenced    AS modified
    ON original.unlocked_seq_no = modified.unlocked_seq_no

그것을 압축하는 것은 가능할지 모르지만, 저는 그것이 효과가 있다고 생각합니다.

현재 값을 사용하는 중...

Seq_No           1 3 4 5 7
Locked           N Y Y N N
Unlocked_Seq_No  1     2 3

Seq_No           1 2 3 4 5
Unlocked_Seq_No  1 2     3

Original_Seq_No  1 3 4 5 7
Modified_Seq_No  1 3 4 2 5

데이터를 약간 변경하면 다음과 같은 값을 얻을 수 있습니다.8(잠겼기 때문에 시퀀스 밖에 앉아 있는 것입니다.

Seq_No           1 3 5 7 8
Locked           N Y N N Y
Unlocked_Seq_No  1   2 3  

Seq_No           1 2 3 4 5
Unlocked_Seq_No  1 2   3 4

Original_Seq_No  1 3 5 7 8
Modified_Seq_No  1 3 2 4 8

언급URL : https://stackoverflow.com/questions/10431305/how-to-reorder-items-in-a-table