SQL Server - агрегировать, если только одно отдельное значение + нули без предупреждений ANSI - PullRequest
1 голос
/ 08 октября 2019

Предположим, у меня есть такие данные

first_name    last_name     city
John          Bon Jovi      null
John          Lennon        null
John          Deer          null

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

{ "first_name": "John", "city": null }

По сути, запрос должен проверить,в каждом столбце есть только одно отдельное значение, и если оно есть, поместите это значение в json. Все непустые столбцы относительно легко получить с помощью запроса, подобного следующему:

select
    case when count(distinct first_name) = 1 then max(first_name) end as first_name,
    case when count(distinct last_name) = 1 then max(last_name) end as last_name,
    case when count(distinct city) = 1 then max(city) end as city
from ...
for json path, without_array_wrapper

или

select
    case when max(first_name) = min(first_name) then max(first_name) end as first_name,
    case when max(last_name) = min(last_name) then max(last_name) end as last_name,
    case when max(city) = min(city) then max(city) end as city
from ...
for json path, without_array_wrapper

Результатом вышеупомянутых запросов является json, подобный этому {"first_name":"John"}. Но тогда возникают проблемы с нулями. Проблема (1) - приведенные выше запросы не учитывают нулевые значения, поэтому, если у меня есть такие данные

first_name    last_name     city
----------------------------------
John          Lennon        null
John          Lennon        null
John          null          null

, тогда фамилия также включается в результирующий json

{ "first_name": "John", "last_name": "Lennon" }

Хорошо, это понятно (причина ...Null value is eliminated by an aggregate...), и я могу решить его с помощью запроса, подобного следующему:

select
    case when count(distinct first_name) = 1 and count(first_name) = count(*) then max(first_name) end as first_name,
    case when count(distinct last_name) = 1 and count(last_name) = count(*) then max(last_name) end as last_name,
    case when count(distinct city) = 1 and count(city) = count(*) then max(city) end as city
from ...
for json path, without_array_wrapper

Но есть и другие проблемы с нулями, которые я не могу решить аккуратно дляв настоящее время. Задача (2) - я хочу, чтобы в моем json также было "city":null. Конечно, я могу сделать что-то вроде этого

...
case when count(city) = 0 then 'null' end as city
...

и затем заменить строку null реальными нулями, но это не очень аккуратно. Еще одна неприятная вещь ( 3 ) - я бы очень хотел избавиться от предупреждений

Предупреждение: нулевое значение устраняется с помощью агрегата или другой операции SET.

без выключения ANSI_WARNINGS. Пока я могу думать только об использовании некоторых заполнителей с isnull, который не выглядит как чистое решение

...
case when count(distinct isnull(city, 'null')) = 1 then max(city) end as city
...

Итак, любые идеи о том, как элегантно решать проблемы ( 2 ) и ( 3 )? см. примеры в db<>fiddle.

1 Ответ

0 голосов
/ 09 октября 2019

Хорошо, пока никто не отправил никаких ответов, я подумал об одном способе сделать это. Это не идеально, но, похоже, работает. Так что идея состоит в том, чтобы использовать @var = @var + 1 трюк внутри select. Но это должно быть немного сложнее:

declare
    @first_name varchar(4), @first_name_state tinyint = 0,
    @last_name varchar(4), @last_name_state tinyint = 0,
    @city varchar(4), @city_state tinyint = 0,
    @country varchar(10), @country_state tinyint = 0,
    @result nvarchar(max) = '{}';

select
    @first_name_state =
        case
            when @first_name_state = 0 then 1
            when @first_name_state = 1 and @first_name = t.first_name then 1
            when @first_name_state = 1 and @first_name is null and t.first_name is null then 1
            else 2
        end,
    @first_name = t.first_name,
    @last_name_state =
        case
            when @last_name_state = 0 then 1
            when @last_name_state = 1 and @last_name = t.last_name then 1
            when @last_name_state = 1 and @last_name is null and t.last_name is null then 1
            else 2
        end,
    @last_name = t.last_name,
    @city_state =
        case
            when @city_state = 0 then 1
            when @city_state = 1 and @city = t.city then 1
            when @city_state = 1 and @city is null and t.city is null then 1
            else 2
        end,
    @city = t.city,
    @country_state =
        case
            when @country_state = 0 then 1
            when @country_state = 1 and @country = t.country then 1
            when @country_state = 1 and @country is null and t.country is null then 1
            else 2
        end,
    @country = t.country
from Table1 as t;

if @first_name_state = 1
    set @result = json_modify(json_modify(@result,'$.first_name','null'),'strict $.first_name',@first_name);

if @last_name_state = 1
    set @result = json_modify(json_modify(@result,'$.last_name','null'),'strict $.last_name',@last_name);

if @city_state = 1
    set @result = json_modify(json_modify(@result,'$.city','null'),'strict $.city',@city);    

if @country_state = 1
    set @result = json_modify(json_modify(@result,'$.country','null'),'strict $.country',@country);    

select @result;
----------------------------------
{"first_name":"John","city":null}

см. db<>fiddle with examples.

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

Не используйте переменную в операторе SELECT для объединения значений (то есть для вычисления агрегата). ценности). Могут возникнуть непредвиденные результаты запроса. Потому что все выражения в списке SELECT (включая назначения) не обязательно выполняются ровно один раз для каждой выходной строки.

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

...