Я создаю набор данных, который выглядит так

category    user     total

   1      jonesa       0

   2      jonesa       0

   3      jonesa       0

   1      smithb       0

   2      smithb       0

   3      smithb       5

   1      brownc       2

   2      brownc       3

   3      brownc       4

Если у конкретного пользователя 0 записей в всех категориях, можно ли удалить их строки из набора? Если у пользователя есть какие-то действия, подобные smithb, я хотел бы сохранить все его записи. Даже строки с нулями. Не знаю, как это сделать, я подумал, что оператор CASE может помочь, но я не уверен, это довольно сложно для меня. Вот мой запрос

SELECT DISTINCT c.category,
  u.user_name,
  CASE WHEN (
    SELECT COUNT(e.entry_id)
    FROM category c1 
    INNER JOIN entry e1
      ON c1.category_id = e1.category_id
      WHERE c1.category_id = c.category_id
      AND e.user_name = u.user_name
      AND e1.entered_date >= TO_DATE ('20140625','YYYYMMDD')
      AND e1.entered_date <= TO_DATE ('20140731', 'YYYYMMDD')) > 0 -- I know this won't work
    THEN 'Yes'
  ELSE NULL
  END AS TOTAL
FROM user u 
INNER JOIN role r
  ON u.id = r.user_id
    AND r.id IN (1,2),
    category c 
LEFT JOIN entry e
  ON c.category_id = e.category_id
WHERE c.category_id NOT IN (19,20)

Я понимаю, что оператор case не будет работать, но это была попытка того, как это могло быть возможно. Я действительно не уверен, возможно ли это или лучшее направление. Цените любое руководство.

sql
0
Jonnny 6 Авг 2014 в 18:52

3 ответа

Лучший ответ

Есть несколько способов сделать это, но чем менее подробен SQL, тем сложнее вам будет следовать логике. По этой причине я думаю, что использование нескольких общих табличных выражений позволит избежать необходимости использовать избыточные объединения, будучи наиболее удобочитаемыми.

-- assuming user_name and category_name are unique on [user] and [category] respectively.  

WITH valid_categories (category_id, category_name) AS 
(
    -- get set of valid categories
    SELECT c.category_id, c.category AS category_name
    FROM category c
    WHERE c.category_id NOT IN (19,20)
),
valid_users ([user_name]) AS 
(
    -- get set of users who belong to valid roles
    SELECT u.[user_name]
    FROM [user] u 
    WHERE EXISTS (
        SELECT *
        FROM [role] r
        WHERE u.id = r.[user_id] AND r.id IN (1,2)
    )

),
valid_entries (entry_id, [user_name], category_id, entry_count) AS
(
    -- provides a flag of 1 for easier aggregation
    SELECT e.[entry_id], e.[user_name], e.category_id, CAST( 1 AS INT) AS entry_count
    FROM [entry] e  
    WHERE e.entered_date BETWEEN TO_DATE('20140625','YYYYMMDD') AND TO_DATE('20140731', 'YYYYMMDD')
    -- determines if entry is within date range 
),
user_categories ([user_name], category_id, category_name) AS

(   SELECT u.[user_name], c.category_id, c.category_name
    FROM valid_users u
    -- get the cartesian product of users and categories
    CROSS JOIN valid_categories c
    -- get only users with a valid entry 
    WHERE EXISTS (
        SELECT *
        FROM valid_entries e
        WHERE e.[user_name] = u.[user_name]
    )
)

/*

You can use these for testing.

SELECT COUNT(*) AS valid_categories_count
FROM valid_categories

SELECT COUNT(*) AS valid_users_count
FROM valid_users

SELECT COUNT(*) AS valid_entries_count
FROM valid_entries

SELECT COUNT(*) AS users_with_entries_count
FROM valid_users u
WHERE EXISTS (
    SELECT *
    FROM user_categories uc
    WHERE uc.user_name = u.user_name
)

SELECT COUNT(*) AS users_without_entries_count
FROM valid_users u
WHERE NOT EXISTS (
    SELECT *
    FROM user_categories uc
    WHERE uc.user_name = u.user_name
)

SELECT uc.[user_name], uc.[category_name], e.[entry_count] 
FROM user_categories uc
INNER JOIN  valid_entries e ON (uc.[user_name] = e.[user_name] AND uc.[category_id] = e.[category_id])
*/

-- Finally, the results: 

SELECT uc.[user_name], uc.[category_name], SUM(NVL(e.[entry_count],0)) AS [entry_count]
FROM user_categories uc
LEFT OUTER JOIN  valid_entries e ON (uc.[user_name] = e.[user_name] AND uc.[category_id] = e.[category_id])
1
Jim V. 6 Авг 2014 в 18:02

Вот еще один способ:

WITH totals AS (
  SELECT
    c.category,
    u.user_name,
    COUNT(e.entry_id) AS total,
    SUM(COUNT(e.entry_id)) OVER (PARTITION BY u.user_name) AS user_total
  FROM
    user u
  INNER JOIN
    role r ON u.id = r.user_id
  CROSS JOIN
    category c
  LEFT JOIN
    entry e ON c.category_id = e.category_id
           AND u.user_name = e.user_name
           AND e1.entered_date >= TO_DATE ('20140625', 'YYYYMMDD')
           AND e1.entered_date <= TO_DATE ('20140731', 'YYYYMMDD')
  WHERE
    r.id IN (1, 2)
    AND c.category_id IN (19, 20)
  GROUP BY
    c.category,
    u.user_name
)
SELECT
  category,
  user_name,
  total
FROM
  totals
WHERE
  user_total > 0
;

Производная таблица totals вычисляет итоговые значения для каждого пользователя и категории, а также итоговые значения по всем категориям для каждого пользователя (с использованием SUM() OVER ...). Основной запрос возвращает только строки, в которых общее количество пользователей больше нуля.

1
Andriy M 7 Авг 2014 в 07:31

Попробуй это:

delete from t1
where user in (
  select user
  from t1
  group by user
  having count(distinct category) = sum(case when total=0 then 1 else 0 end) )

Подзапрос может подобрать всех пользователей, соответствующих вашему требованию удаления.

count(distinct category) узнайте, сколько категорий у пользователя.
sum(case when total=0 then 1 else 0 end) узнать, сколько строк с действиями у пользователя.

2
jan 6 Авг 2014 в 16:04