Как выбрать элемент только один раз в обычном SQL

#sql #sql-server #oracle

#sql #sql-server #Oracle

Вопрос:

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

 CREATE TABLE "DOWNLOAD_HISTORY" 
   (    "ID" NUMBER GENERATED BY DEFAULT AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 344 CACHE 20 NOORDER  NOCYCLE  NOKEEP  NOSCALE  NOT NULL ENABLE, 
    "IDUSER" NUMBER NOT NULL ENABLE, 
    "IDFORNITORE" NUMBER NOT NULL ENABLE, 
    "IDRFX" NUMBER NOT NULL ENABLE, 
    "DOCNUM" NUMBER NOT NULL ENABLE, 
    "DOWNLOAD_TIMESTAMP" TIMESTAMP (6) NOT NULL ENABLE
   ) ;
 

Этот DDL поступает из базы данных OracleDB12.
Далее приведены некоторые примеры данных

введите описание изображения здесь

Чего я хотел бы достичь: если уже есть строка с тем же кортежем (IDRFX, DOCNUM), не включая его в оператор SELECT . Итак, кортеж (384,1) должен появиться только один раз.

Что я сделал (до сих пор): с идеей, что этот запрос может быть выполнен даже из базы данных MSSQL, я попытался написать эти два запроса на обычном SQL.

 SELECT * 
FROM DOWNLOAD_HISTORY dh 
WHERE dh.IDRFX = 384 AND ID IN (SELECT ID FROM DOWNLOAD_HISTORY dh2 WHERE dh.DOCNUM <> dh2.DOCNUM AND dh.IDRFX <> dh2.IDRFX )



SELECT t1.*
FROM DOWNLOAD_HISTORY t1
WHERE NOT EXISTS (SELECT * from DOWNLOAD_HISTORY t2
                  where t2.DOCNUM = t1.DOCNUM 
                    and t2.IDRFX = t1.IDRFX)
 

Но ни один из них на самом деле не смог меня никуда привести. Если возможно, я хотел бы спросить, где я делаю ошибку и как я могу ее исправить.

Ответ №1:

Без аналитических функций….

     with data as(
select 1 id, 384 idrfx, 1 docnum from dual union all
select 2 id, 384 idrfx, 1 docnum from dual union all
select 3 id, 384 idrfx, 2 docnum from dual union all
select 4 id, 385 idrfx, 1 docnum from dual 
)
,uniqueTuple as  (
select min(id) id from data
group by idrfx,docnum 
)

select d.* 
from data d
join uniqueTuple u on u.id = d.id 
 

Ответ №2:

Вы можете использовать row_number аналитическую функцию следующим образом:

 SELECT * FROM
(SELECT t1.*, row_number() over (partition by t1.DOCNUM, t1.IDRFX order by 1) as rn
FROM DOWNLOAD_HISTORY t1)
WHERE RN = 1;
 

ИЛИ вы можете использовать NOT EXISTS запрос следующим образом:

 SELECT t1.*
FROM DOWNLOAD_HISTORY t1
WHERE NOT EXISTS (SELECT * from DOWNLOAD_HISTORY t2
                  where t2.DOCNUM = t1.DOCNUM 
                    and t2.IDRFX = t1.IDRFX
                    and t2.ID > t1.ID); -- add only this condition in your query
 

Комментарии:

1. Я, возможно, не думал о добавлении последнего ПРЕДЛОЖЕНИЯ AND в запрос NOT EXIST! Спасибо, это решило мою проблему и в обычном SQL!

2. Из любопытства: что лучше с точки зрения производительности?

3. NOT EXISTS хорошо для производительности, но в oracle многие изменения выполняются после Oracle 11g, и аналитическая функция также быстрее в Oracle.

Ответ №3:

Один из способов — использовать подзапрос с ROW_NUMBER()

 SELECT *
FROM
(
    SELECT *, 
        ROW_NUMBER() OVER (PARTITION BY t.DOCNUM,IDRFX ORDER BY t.ID) RN
    FROM DOWNLOAD_HISTORY t
) SQ
WHERE SQ.RN = 1;                    
 

Комментарии:

1. Является ли ROW_NUMBER() стандартной функцией SQL или характерной для MSSQL и ORACLE?

2. ROW_NUMBER() — это стандартная оконная функция. Он доступен практически во всех СУБД ( последних версиях )

Ответ №4:

Я думаю, ваш ответ не работает, потому что DOWNLOAD_TIMESTAMP уникален. Только столбцы IDUSER, IDFORNITORE, IDRFX и IDRFX, похоже, содержат одни и те же кортежи несколько раз.

Итак, либо вы делаете SELECT DISTINCT для столбцов, которые действительно должны быть distinct (поэтому нет идентификатора или DOWNLOAD_TIMESTAMP), либо вы настраиваете суперак-запрос в своем WHERE Not exist и меняете SELECT * на SELECT, а затем только для столбцов, в которых вы хотите иметь уникальные кортежи.

Так что это было бы самым простым решением для меня.

 SELECT DISTINCT
IDUSER, 
IDFORNITORE, 
IDRFX, 
IDRFX
FROM Database
 

Комментарии:

1. Закрыть! Единственная проблема заключается в том, что я должен вернуть download_timestamp , так что это решает проблему только на 75%: (

2. Но тогда ваши кортежи больше не будут уникальными. Как вы определяете метку download_timestamp для данного кортежа? Если вам нужно минимальное или максимальное значение download_timestamp для данного кортежа, вы можете просто ПРИСОЕДИНИТЬ таблицу к самой таблице и добавить download_timestamp к кортежу таким образом.

3. Это информация, которая определяется системой во время выполнения. Когда система выполняет этот запрос, ей также необходимо отобразить метку download_timestamp