Найти уникальную запись из таблицы

Рассмотрим следующую таблицу:

Имя таблицы: IN_OUT_RECORD

Запрос:

select DISTINCT  * 
from IN_OUT_RECORD AD 
where (AD_USER_ID = 'ST1301220007') 
AND (AD.AD_DATE BETWEEN '2024-06-01 00:00:00' AND ('2024-06-12 23:59:00'));
AD_ID AD_USER_ID AD_STATUS AD_DATE 11277 СТ1301220007 В 2024-06-01 09:10:40 11278 СТ1301220007 ВНЕ 2024-06-01 09:10:55 11279 СТ1301220007 В 2024-06-01 09:10:56 11280 СТ1301220007 ВНЕ 2024-06-01 18:21:23 11281 СТ1301220007 В 2024-06-05 09:20:21 11282 СТ1301220007 ВНЕ 2024-06-05 19:05:22 11283 СТ1301220007 В 2024-06-07 09:05:25 11284 СТ1301220007 ВНЕ 2024-06-07 18:05:25 11285 СТ1301220007 В 2024-06-07 18:06:25 11286 СТ1301220007 ВНЕ 2024-06-08 09:10:56 11287 СТ1301220007 В 2024-06-08 09:11:50

отсюда я хочу получить только один IN и OUT за день.

например:

AD_ID AD_USER_ID AD_STATUS AD_DATE 11277 СТ1301220007 В 2024-06-01 09:10:40 11280 СТ1301220007 ВНЕ 2024-06-01 18:21:23 11281 СТ1301220007 В 2024-06-05 09:20:21 11282 СТ1301220007 ВНЕ 2024-06-05 19:05:22 11283 СТ1301220007 В 2024-06-07 09:05:25 11284 СТ1301220007 ВНЕ 2024-06-07 18:05:25 11287 СТ1301220007 В 2024-06-08 09:11:50

Пожалуйста, помогите мне получить правильный запрос.

Т.е. выберите строки с IN там, где не существует строка с меньшим временем данных и IN для того же дня, затем выберите строки с OUT, где не существует строка с большим временем данных и OUT для того же дня, и, наконец, ОБЪЕДИНИТЕ их. Для OUT дополнительно проверьте, существует ли IN с меньшей датой и временем для того же дня. Задача не сложная.

Akina 14.06.2024 07:50
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
2
1
73
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Поскольку вам кажется, что вы хотите сохранить AD_ID, это немного сложнее, чем простые GROUP BY и UNION.

Вам понадобятся индикаторы, которые AD_DATE для AD_STATUS «ВХОД» являются первыми, а какие AD_DATE для AD_STATUS «ВЫХОД» являются последними за день.

Мы можем сделать эти индикаторы такими, используя ROW_NUMBER() OVER (PARTITION BY .. ORDER BY ..). Для разделения по дням используйте функцию DATE(), чтобы игнорировать время.

После этого вы можете выбрать соответствующие записи IN и OUT за день, а также UNION их и, возможно, отсортировать их по USER_AD, DATE(AD_DATE) и AD_STATUS.

Я буду использовать некоторые cte, чтобы показать эти шаги:

WITH status_per_day AS (
  SELECT 
    records.*,
    ROW_NUMBER() OVER (
      PARTITION BY AD_USER_ID, AD_STATUS, DATE(AD_DATE) 
      ORDER BY AD_DATE ASC) AS first_time,  
    ROW_NUMBER() OVER (
      PARTITION BY AD_USER_ID, AD_STATUS, DATE(AD_DATE) 
      ORDER BY AD_DATE DESC) AS last_time
  FROM records
), 

unioned_statuses AS (
  SELECT *
  FROM status_per_day
  WHERE AD_STATUS = 'IN' 
  AND first_time = 1
  UNION 
  SELECT *
  FROM status_per_day
  WHERE AD_STATUS = 'OUT' 
  AND last_time = 1
)
  
SELECT AD_ID, AD_USER_ID, AD_STATUS, AD_DATE
FROM unioned_statuses
ORDER BY AD_USER_ID, DATE(AD_DATE), AD_STATUS

db<>поиграйте здесь

Видите ли вы лишний идентификатор строки = 11286 в выводе вашей скрипки?

Akina 14.06.2024 08:59

Учитывая описание проблемы, на выходе должно быть 11286. Я предполагаю, что ОП забыл поместить это в желаемый вывод.

VvdL 14.06.2024 09:03

Не знаю, зачем нужен UNION, который можно выбрать прямо из первого CTE с OR условиями.

Charlieface 14.06.2024 13:47

Не соответствует фактическому ответу. все еще показываю данные 11286 ST1301220007 OUT 2024-06-08 09:10:56

Art Bindu 14.06.2024 18:44

Это могло случиться, если кто-то забыл перфокарту при входе.

Art Bindu 14.06.2024 18:53

Это будет немного более эффективно, чем другой ответ, если вы сохраните одни и те же предложения ORDER BY в двух оконных функциях. Вы можете взломать LEAD, чтобы узнать, находитесь ли вы в последней строке.

WITH status_per_day AS (
  SELECT 
    records.*,
    ROW_NUMBER() OVER (
      PARTITION BY AD_USER_ID, DATE(AD_DATE), AD_STATUS 
      ORDER BY AD_DATE) AS rn,  
    LEAD(1) OVER (
      PARTITION BY AD_USER_ID, DATE(AD_DATE), AD_STATUS
      ORDER BY AD_DATE) AS is_not_last,
    MIN(CASE WHEN AD_STATUS = 'IN' THEN AD_DATE END) OVER (
      PARTITION BY AD_USER_ID, DATE(AD_DATE)) AS min_in_date
  FROM records
)  
SELECT AD_ID, AD_USER_ID, AD_STATUS, AD_DATE
FROM status_per_day
WHERE (
  AD_STATUS = 'IN' AND rn = 1
  OR
  AD_STATUS = 'OUT' AND is_not_last IS NULL AND min_in_date < AD_DATE
)
ORDER BY
  AD_USER_ID,
  DATE(AD_DATE),
  AD_STATUS;

db<>рабочий пример

Не соответствует фактическому ответу. все еще показываю данные 11286 ST1301220007 OUT 2024-06-08 09:10:56

Art Bindu 14.06.2024 18:43

Это могло случиться, если кто-то забыл перфокарту при входе.

Art Bindu 14.06.2024 18:53

Тогда я не понимаю логики, вам нужно будет внести ясность.

Charlieface 14.06.2024 19:32

Вы находитесь в офисе/колледже и используете перфокарту несколько раз для входа и выхода. По какой-то причине иногда время входа или выхода может не быть заблокировано из-за системной проблемы или другой проблемы. В этом случае ваш код не будет работать. Проверьте фактический выход и ваш выход

Art Bindu 15.06.2024 01:09

ОК, думаю, я понимаю. Вам просто нужно выполнить функцию условного окна, чтобы получить самый ранний IN и проверить, что он меньше текущего OUT.

Charlieface 16.06.2024 01:03

Да, @Charlieface, я добавил свой ответ в соответствии с этим

Art Bindu 16.06.2024 19:40

Это причина головной боли.

Есть запись, где сначала out, а потом in время.

Если вы запустите этот раздел:

WITH t1 AS (
select *,
    CASE WHEN(ad_status = 'IN') THEN
        MIN(ad_date) OVER (PARTITION BY cast(ad_date as date), ad_user_id, ad_status = 'IN')
    END AS MIN_IN,
    CASE WHEN(ad_status = 'OUT') THEN
        MAX(ad_date) OVER (PARTITION BY cast(ad_date as date), ad_user_id, ad_status = 'OUT')
    END AS MAX_OUT
from IN_OUT_RECORD
),
base_table as (
    SELECT * FROM t1
    WHERE (ad_status = 'IN' AND ad_date = MIN_IN)
    OR (ad_status = 'OUT' AND ad_date = MAX_OUT)
)
select * from base_table order by cast(ad_date as date);

Вы получаете вывод вроде:

Итак, та же проблема возникает и здесь.

По этой причине
    сначала вам нужно разделить эту таблицу на две части
    а затем вам нужно сравнить время entry и exit.

Итак, ваш следующий код:

base_table2 as (
select bin.ad_user_id,
    bin.ad_date,
    bin.ad_status as in_status, 
    bout.ad_status as out_status,
   --  bin.MIN_IN,
   --  bout.MAX_OUT,
    CASE 
        WHEN(LEAST(bin.MIN_IN, bout.MAX_OUT) = bin.MIN_IN) THEN bin.MIN_IN
        WHEN(GREATEST(bin.MIN_IN, bout.MAX_OUT) = bin.MIN_IN) THEN bin.MIN_IN
    END as entry_time,
    CASE 
        WHEN(GREATEST(bin.MIN_IN, bout.MAX_OUT) = bout.MAX_OUT) THEN bout.MAX_OUT 
        WHEN(LEAST(bin.MIN_IN, bout.MAX_OUT) = bout.MAX_OUT) THEN NULL
    END as exit_time
from       (select * from base_table where ad_status = 'IN')  bin
left join  (select * from base_table where ad_status = 'OUT') bout
on bin.ad_user_id = bout.ad_user_id
AND cast(bin.ad_date as date) = cast(bout.ad_date as date)
)
select * from base_table2

Что дает вам результат:

Затем сравните эту таблицу с базовой таблицей и запросом:

select c1.*
from IN_OUT_RECORD c1
INNER JOIN base_table2 c2
on c1.ad_user_id = c1.ad_user_id
and ( 
   (c1.ad_status = 'IN' and c1.ad_date = c2.entry_time)
   OR
   (c1.ad_status = 'OUT' and c1.ad_date = c2.exit_time)
)
order by c1.ad_date;

Что дает итоговую таблицу в соответствии с вашими результатами:

Это код: db<>fiddle

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

  • entry time существует, но нет exit time
  • нет entry time но exit time существует

Здесь это не подойдет.
Если вы хотите применить эту логику, обновите создание base_table2 соответствующим образом.

Это хороший вопрос, уделите немного времени на его решение.
Я надеюсь, что это разрешит запрос в соответствии с описанием.

Ответ принят как подходящий

другое решение может быть

  1. сначала найдите минимальные и максимальные значения на дату:
SELECT 
    r1.AD_USER_ID, 
    DATE(r1.AD_DATE) d,
    MIN(CASE WHEN r1.AD_STATUS='IN' THEN AD_DATE END) as min_date,
    MAX(CASE WHEN r1.AD_STATUS='OUT' THEN AD_DATE END) as max_date
  FROM records r1
  WHERE r1.AD_USER_ID = 'ST1301220007' 
  GROUP BY r1.AD_USER_ID, DATE(r1.AD_DATE)
AD_USER_ID д мин_дата максимальная_дата СТ1301220007 2024-06-01 2024-06-01 09:10:40 2024-06-01 18:21:23 СТ1301220007 05.06.2024 2024-06-05 09:20:21 2024-06-05 19:05:22 СТ1301220007 07.06.2024 2024-06-07 09:05:25 2024-06-07 18:05:25 СТ1301220007 08.06.2024 2024-06-08 09:11:50 2024-06-08 09:10:56
  1. Затем выполните UNION ALL, чтобы найти нужные записи:
WITH cte AS (
  SELECT 
    r1.AD_USER_ID, 
    DATE(r1.AD_DATE) d,
    MIN(CASE WHEN r1.AD_STATUS='IN' THEN AD_DATE END) as min_date,
    MAX(CASE WHEN r1.AD_STATUS='OUT' THEN AD_DATE END) as max_date
  FROM records r1
  WHERE r1.AD_USER_ID = 'ST1301220007' 
  GROUP BY r1.AD_USER_ID, DATE(r1.AD_DATE)
)
SELECT r1.AD_ID, r1.AD_USER_ID, r1.AD_STATUS, r1.AD_DATE
FROM records r1
INNER JOIN cte ON cte.AD_USER_ID = r1.AD_USER_ID 
              and cte.min_date = r1.AD_DATE
              and r1.AD_STATUS = 'IN'
UNION ALL
SELECT r1.AD_ID, r1.AD_USER_ID, r1.AD_STATUS, r1.AD_DATE
FROM records r1
INNER JOIN cte ON cte.AD_USER_ID = r1.AD_USER_ID 
              and cte.max_date = r1.AD_DATE and cte.max_date > cte.min_date
              and r1.AD_STATUS = 'OUT'
ORDER BY AD_ID

выход:

AD_ID AD_USER_ID AD_STATUS AD_DATE 11277 СТ1301220007 В 2024-06-01 09:10:40 11280 СТ1301220007 ВНЕ 2024-06-01 18:21:23 11281 СТ1301220007 В 2024-06-05 09:20:21 11282 СТ1301220007 ВНЕ 2024-06-05 19:05:22 11283 СТ1301220007 В 2024-06-07 09:05:25 11284 СТ1301220007 ВНЕ 2024-06-07 18:05:25 11287 СТ1301220007 В 2024-06-08 09:11:50

DBFIDDLE: https://dbfiddle.uk/q2o-QFZO

Другие вопросы по теме