Группировать по обнуляемому столбцу особым образом - PullRequest
0 голосов
/ 24 сентября 2018

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

Я начал с моегосмотреть как

-- This is what I wish I could do but its wrong
SELECT Id, Location, COUNT(*) as Count, COUNT(Location) as LocationCount
FROM #X
GROUP BY Id, Location
ORDER BY Id, Location 

Что не работает, поскольку существуют местоположения с одним идентификатором, которые имеют NULL-местоположение и ненулевое расположение, которое является действительным на основе бизнес-правил, но дублирует Id на основе группировки.

Это демонстрирует:

DROP TABLE IF EXISTS #X 

SELECT *
INTO #X
FROM (
    SELECT 1 as Id, 1 as Location
    UNION
    Select 1 as Id, NULL as Location
    UNION
    Select 2 as Id, NULL as Location
    UNION
    Select 3 as Id, 2 as Location
) X

-- This is what I wish I could do but its wrong
SELECT Id, Location, COUNT(*) as Count, COUNT(Location) as LocationCount
FROM #X
GROUP BY Id, Location
ORDER BY Id, Location

Результат:

enter image description here

Желаемый:

enter image description here


Поскольку идентификатор дублирован, его нельзя сделать индексированным представлением.

То, что я хочу, можно сделать, просто используя AVG, MAX или что-то в этом роде.игнорирует NULL:

SELECT Id, AVG(Location) as Location, COUNT(*) as Count, COUNT(Location) as LocationCount
FROM #X
GROUP BY Id
ORDER BY Id, Location

Но это не работает, потому что AVG недопустимо в индексированном представлении, а также потому, что его группировка только по Id, поэтому, если два ненулевых значения были назначены одному идентификатору,такие как 6 и 8, он будет возвращать среднее значение обоих (7) вместопозволяя это.

enter image description here


Поэтому я пришел к мысли, что могу создать два уникальных представления: одно для нулевых значений, а другое для ненулевых значений.затем создайте третий вид, который объединяет их на основе желаемой логики.Это работает, но это кажется обходным процессом для выполнения того, что я хочу, и требует больше накладных расходов и вычислений / обработки при использовании представления, но это лучше, чем отсутствие индексированных представлений:

;WITH IndexableViewNonNulls AS (
    SELECT Id, Location, COUNT(*) as Count
    FROM #X
    WHERE Location IS NOT NULL
    GROUP BY Id, Location
), IndexableViewNulls AS (
    SELECT Id, Location, COUNT(*) as Count
    FROM #X
    WHERE Location IS NULL
    GROUP BY Id, Location
), CTE AS (
    SELECT *, Count as LocationCount
    FROM IndexableViewNonNulls
    UNION ALL
    SELECT *, 0 as LocationCount
    FROM IndexableViewNulls
)

SELECT 
      Id
      ,AVG(Location) as Location
      ,SUM(Count) as Count
      ,SUM(LocationCount) as LocationCount
FROM CTE
GROUP BY Id

enter image description here

Вот полная скрипка: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=f3df7c4b5703e9270ea1efe9bef5c152

Редактировать: Вот еще одна скрипка с индексированным представлением на месте.Обратите внимание, что вставка NULL нарушает уникальный индекс, но я не хочу этого.Я хочу, чтобы он нарушал, если все два ненулевых объекта все время сохраняют вид одинаковым (Местоположение показывает NULL, если ВСЕ равно NULL, местоположение, если есть 1 или более местоположений)

https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=544b5983bf25cd9c2ff1f55d68bd34d8

Редактировать 2: Вот мое решение, использующее 2 индексированных представления и 1 нормальное представление для их объединения: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b8cf5ff638305a0d228c856b6391246d

Редактирование 3: Таблица #X не является обычной таблицей, а просто добавлениеИндекс в таблице не является допустимым решением.В реальном сценарии таблица #X представляет собой само представление, поэтому решение должно использовать индексированные представления

1 Ответ

0 голосов
/ 25 сентября 2018

Я думаю, что вы можете создать отфильтрованный индекс:

create unique index unq_index_x_id_location on x(id, location) where location is not null;

Если вы просто хотите, чтобы представление соответствовало этому ограничению, то я думаю, что это делает то, что вы хотите:

SELECT Id, MIN(Location) as location
FROM #X
GROUP BY Id
HAVING MIN(Location) = MAX(Location) OR MIN(LOCATION) IS NULL; 
...