2015-09-06 1 views
2

У меня есть таблица с именами, типами и значениями.Использование предложения HAVING для идентификации групп с определенной комбинацией записей

DECLARE @t_Table TABLE 
(
    Name VARCHAR(10), 
    [Type] VARCHAR(10), 
    Value INT 
) 

INSERT INTO @t_Table 
VALUES('Jill', 'Yellow', 100) 
INSERT INTO @t_Table 
VALUES('Jill', 'Blue', 200) 
INSERT INTO @t_Table 
VALUES('Jill', 'Green', 300) 
INSERT INTO @t_Table 
VALUES('Jill', 'Green', 400) 
INSERT INTO @t_Table 
VALUES('Jill', 'Green', 500) 

INSERT INTO @t_Table 
VALUES('Bob', 'Yellow', 100) 
INSERT INTO @t_Table 
VALUES('Bob', 'Blue', 200) 
INSERT INTO @t_Table 
VALUES('Bob', 'Green', 300) 
INSERT INTO @t_Table 
VALUES('Bob', 'Orange', 400) 
INSERT INTO @t_Table 
VALUES('Bob', 'Orange', 400) 
INSERT INTO @t_Table 
VALUES('Bob', 'Purple', 500) 

INSERT INTO @t_Table 
VALUES('Steve', 'Yellow', 100) 
INSERT INTO @t_Table 
VALUES('Steve', 'Blue', 200) 
INSERT INTO @t_Table 
VALUES('Steve', 'Green', 300) 
INSERT INTO @t_Table 
VALUES('Steve', 'Orange', 400) 
INSERT INTO @t_Table 
VALUES('Steve', 'Orange', 400) 

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

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

SELECT Name, 
    TotalValue = SUM(Value) 
FROM @t_Table 
GROUP BY Name 
HAVING SUM(CASE WHEN [Type] = 'Yellow' THEN 1 
     WHEN [Type] = 'Blue' THEN 2 
     WHEN [Type] = 'Green' THEN 0 
     ELSE 4 END) = 3 

Что правильно возвращает этот результат:

Name  TotalValue 
---------- ----------- 
Jill  1500 

Как я могу построить следующее?

SELECT Name, 
    TotalValue = SUM(Value) 
FROM @t_Table 
GROUP BY Name 
/*HAVING exactly one record with [Type] = 'Yellow' 
    and exactly one record with [Type] = 'Blue' 
    and exactly one record with [Type] = 'Green' 
    and zero or more records with [Type] = 'Orange' 
    and no records of any other type 
*/ 

Если ожидаемый результат, учитывая данные выше, будет

Name  TotalValue 
---------- ----------- 
Steve  1400 

Я знаю следующее решения (ниже), но мне нужен тот, который имеет один агрегат в предложении HAVING. Я также открыт для другой структуры запросов, которая решает мою проблему, если она такая же простая или простая, как и предлагаемая мной структура, и работает аналогично или лучше.

SELECT 
    Name, 
    TotalValue = SUM(Value) 
FROM 
    @t_Table 
GROUP BY 
    Name 
HAVING 
    SUM(CASE WHEN [Type] = 'Yellow' THEN 1 ELSE NULL END) = 1 
    AND SUM(CASE WHEN [Type] = 'Blue' THEN 1 ELSE NULL END) = 1 
    AND SUM(CASE WHEN [Type] = 'Green' THEN 1 ELSE NULL END) = 1 
    AND SUM(CASE WHEN [Type] IN ('Yellow','Blue','Green','Orange') THEN 0 ELSE 1 END) = 0 
+0

Просто синтаксический сахар 'IIF' но сделать его короче' ВЫБРАТЬ Имя, TotalValue = SUM (Value) ОТ @t_Table GROUP BY имя HAVING SUM (IIF ([Тип] = 'желтый', 1, NULL)) = 1 И СУММ (IIF ([Тип] = 'Синий', 1, NULL)) = 1 И СУММ (IIF ([Тип] = 'Зеленый', 1, NULL)) = 1 И СУММ (IIF ([Type] IN («Желтый», «Синий», «Зеленый», «Оранжевый»), 0,1)) = 0' – lad2025

+0

Следует отметить, что IIF применяется только к SQL Server 2014 и более поздним. – rwking

+1

@rwking Следует отметить, что ** IIF применяется к SQL Server 2012 +. ** – lad2025

ответ

1

Как об использовании концепции, но с десятичной веса на каждом типе:

SqlFiddleDemo

SELECT Name, 
    TotalValue = SUM(Value) 
FROM @t_Table 
GROUP BY Name 
HAVING SUM(
    CASE [Type] 
    WHEN 'Yellow' THEN 1 
    WHEN 'Blue' THEN 10 
    WHEN 'Green' THEN 100 
    WHEN 'Orange' THEN 0 
    ELSE 0 
    END) = 111 

Это означает, что именно 1-желтый, 1-синий, 1-зеленый.

Более сложные условия могут быть выполнены с использованием BETWEEN или < <= > =. Одна нота будет работать так долго, что вы будете искать максимум 9 в одной группе.

Если вы боитесь переполнения из-за 10 системы на основе, рассмотреть возможность использования для системы на основе примера 1000, как:

SELECT Name, 
    TotalValue = SUM(Value) 
FROM @t_Table 
GROUP BY Name 
HAVING SUM(
    CASE [Type] 
    WHEN 'Yellow' THEN 1.0 
    WHEN 'Blue' THEN 1000.0 
    WHEN 'Green' THEN 1000000.0 
    WHEN 'Orange' THEN 0 
    ELSE 0 
    END) = 1 * 1000000.0 + 1 * 1000.0 + 1.0 -- For clearance use calculated version 
+0

Это пришло мне в голову как решение, но если, например, 11 «Желтые», 1 «Зеленый» и какое-то произвольное количество «Апельсинов» находятся в группе, результат будет неправильно выбран. Возможно, это аналогичный, но альтернативный подход, который использует битовые строки или бит-маскирование? Я не уверен, как его построить, хотя ... – Joe

+0

Так измените вес только как '1, 100, 10000, 1000000'. Я сомневаюсь, что вам нужно будет найти 101 желтый цвет или вы получите переполнение INT. 100. – lad2025

+0

@Joe См. Мою обновленную версию, с системой на основе 1000 вы почти уверены, если все еще боятся изменения на 10_000 на основе и используйте десятичный вместо int. – lad2025

0

Я думаю, что этот вопрос является более эффективным

with cte as 
(
select name, [type] tp, nb = count(*) 
from @t_table 
group by name, [type] 
) 

Select t1.name, sum(t1.Value) 
from @ t_table t1 inner join cte t2 on t1.name = t2.name 
where nb = (case t2.tp when 'Yellow' then 1 
         when 'Blue' then 1 
         ... 
      end) 
     AND Exist (select * from cte where name = t2.name and t2.tp = 'Yellow') 
     AND Exist (select * from cte where name = t2.name and t2.tp = 'Blue') 
     ... 
group by t1.name 
1

Представьте в HAVING предложении вы можете сравнить два многочлена.
Тогда представьте, что вы определить (из имеющихся у вас данных) этот полином:

count_YELLOW * х^3 + count_BLUE * х^2 + count_GREEN * х^1 + count_ORANGE

/* HAVING ровно одна запись с [Type] = 'Yellow' и ровно одна запись с [Type] = 'Blue' и ровно одна запись с [Type] = 'Green' и ноль или более записей с [Type] = 'Orange ' и никаких записей другого типа */

Теперь ... чтобы выразить то, что вы хотите в HAVING предложении вы бы сказали:

HAVING 
count_YELLOW * x^3 + 
count_BLUE * x^2 + 
count_GREEN * x^1 + 
count_ORANGE >= 
1 * x^3 + 
1 * x^2 + 
1 * x^1 + 
0 

AND 

count_YELLOW * x^3 + 
count_BLUE * x^2 + 
count_GREEN * x^1 + 
count_ORANGE < 
1 * x^4 

Теперь ... просто выбрать x = sum (all counts) + 1, или x = max (all counts) + 1, и вы можете превратить это в цифры.

Я думаю, что это сработает. Я могу попробовать это завтра в T-SQL.
Однако вы столкнетесь с большими числами. Это неизбежно:
, так как вы хотите без пробелов кодировать вектор из 4 чисел в один номер.

+0

Я думаю, что это можно сделать еще проще.Определить х, как вы говоря: 'ЗАЯВЛЯЮ @l_x BIGINT ВЫБОР @l_x = MAX ([число]) + 1 ОТ (SELECT [число] = COUNT (*) FROM @t_Table GROUP по имени, [Type]) T' Тогда, имеющее положение, как это должно быть достаточно, я думаю: \t 'HAVING SUM (CASE WHEN [Тип] = 'Yellow' THEN POWER (@l_x, 3) \t КОГДА [Тип] = 'Синий' ТОГДА POWER (@l_x, 2) \t ПРИ [Тип] = 'Зеленый' ТОГДА МОЩНОСТИ (@l_x, 1) \t ПРИ [Тип] = 'Оранжевый' ТОГДА 0 \t ИНАЧЕ МОЩНОСТИ (@l_x, 4) КОНЕЦ) = POWER (@l_x, 3) + POWER (@l_x, 2) + POWER (@l_x, 1) ' – Joe

+0

Вот [SQLFiddleDemo] (http://sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1/4011/0) выше решения. – Joe

+0

@Joe Хорошо, хорошо, рад, что вы поняли основную идею (с полиномом, то есть с представлением числа base-x). Как именно вы реализуете предложение HAVING, вы бы это знали лучше всего. –