Как посчитать значения в выбранной строке с некоторым условием - Potgresql - PullRequest
0 голосов
/ 22 октября 2019

Я ищу некоторые встроенные функции Postresql, которые могут подсчитывать значения строк (не столбцов) с некоторыми условиями. Какой-то аналог

=countif(a:a;"Yes")

в Excel

Я видел много ответов на симулированные проблемы здесь на stackoverflow, но все решения предоставлены, когда вы фильтруете данные в столбцах таблицы, а не в строках. Но мне нужно разобрать данные в строках. Мне не нравится решение с кросс-таблицей, так как исходный выбор имеет более 60 столбцов и предпочитает не выполнять один и тот же запрос дважды (но я сделаю это, если это единственное решение).

В некоторых тестовых примерах, где следуетбыть последним столбцом "num_of_yes", который отображает количество ответов "Да" в строке. Тестовые данные

CREATE TABLE main_data (
    id serial PRIMARY KEY,
    name VARCHAR(255) default NULL,
    lastname VARCHAR(255) default NULL,
    username VARCHAR(255) default NULL,
    job VARCHAR(255) default NULL,
    age integer  default NULL,
    licenseid integer  default NULL,
    account integer  default NULL
);

INSERT INTO main_data VALUES(1,'Jhon', 'Brown', 'jbrown', 'some job', 35, 11112333, 3333455);
INSERT INTO main_data VALUES(2,'Bob', NULL, 'bob', 'another job', 64, 1000500, 5555252);
INSERT INTO main_data VALUES(3,'Mike', 'McDonald', 'mike', NULL, 8, NULL, NULL);

Выбор запроса:

select id, name,
case when lastname notnull then 'Yes'::text else 'No'::text end as "has_lastname",
case when username notnull then 'Yes'::text else 'No'::text end as "has_username",
case when job notnull then 'Yes'::text else 'No'::text end as "has_job",
case when age < 16 then 'Yes'::text else 'No'::text end as "is_child",
case when licenseid notnull then 'Yes'::text else 'No'::text end as "has_licenseid",
case when account notnull then 'Yes'::text else 'No'::text end as "has_account"
from main_data
order by id;

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

| id | name | has_lastname | has_username | has_job | is_child | has_licenseid | has_account |
|----|------|--------------|--------------|---------|----------|---------------|-------------|
|  1 | Jhon | Yes          | Yes          | Yes     | No       | Yes           | Yes         |
|  2 | Bob  | No           | Yes          | Yes     | No       | Yes           | Yes         |
|  3 | Mike | Yes          | Yes          | No      | Yes      | No            | No          |

Мне нужно добавить последний столбец сколичество ответов «Да».

Желаемый вывод должен быть таким:

| id | name | has_lastname | has_username | has_job | is_child | has_licenseid | has_account | num_of_yes |
|----|------|--------------|--------------|---------|----------|---------------|-------------|------------|
|  1 | Jhon | Yes          | Yes          | Yes     | No       | Yes           | Yes         |          5 |
|  2 | Bob  | No           | Yes          | Yes     | No       | Yes           | Yes         |          4 |
|  3 | Mike | Yes          | Yes          | No      | Yes      | No            | No          |          3 |

Я использую Postgresql 9.6.5

Ответы [ 2 ]

3 голосов
/ 22 октября 2019

Вы можете преобразовать строку в значение JSONB, а затем сосчитать значения, которые Yes:

select *, 
       (select count(*) 
        from jsonb_each_text(to_jsonb(t) - 'id' - 'name') as x(k,v)
        where v = 'Yes') as num_of_yes
from (
  select id, name,
         case when lastname is not null then 'Yes' else 'No' end as "has_lastname",
         case when username is not null then 'Yes' else 'No' end as "has_username",
         case when job is not null then 'Yes' else 'No' end as "has_job",
         case when age < 16 then 'Yes' else 'No' end as "is_child",
         case when licenseid is not null then 'Yes' else 'No' end as "has_licenseid",
         case when account is not null then 'Yes' else 'No' end as "has_account"
  from main_data
) t  
order by id;

Выражение to_jsonb(t) - 'id' - 'name' преобразует всю строку в значение JSON и удаляет id и name ключи от этого. Затем jsonb_each_text() выполняет итерацию по всем парам ключ / значение, а where v = 'Yes' затем подсчитывает подзапрос тех, которые являются Yes

Онлайн-пример: https://rextester.com/PLJA96007


Другой вариант - использовать функцию num_nonnulls():

select id, name,
       case when lastname is not null then 'Yes' else 'No' end as "has_lastname",
       case when username is not null then 'Yes' else 'No' end as "has_username",
       case when job is not null then 'Yes' else 'No' end as "has_job",
       case when age < 16 then 'Yes' else 'No' end as "is_child",
       case when licenseid is not null then 'Yes' else 'No' end as "has_licenseid",
       case when account is not null then 'Yes' else 'No' end as "has_account",
       num_nonnulls(lastname, username, job, licenseid, account, nullif(age < 16, false)) as num_of_yes
from main_data
order by id;

Скорее всего, это будет быстрее, чем решение JSONB.


Обратите внимание, что если вы хотите истинноеВ столбце boolean выражения case можно упростить до: например, lastname is not null as "has_lastname" или age < 16 as "is_child"

1 голос
/ 22 октября 2019

Вы можете рассмотреть возможность использования array_length и string_to_array

select *
        , array_length(string_to_array(replace(t1::text,t1.name,''), ',Yes'), 1) - 1  
from 
  (select id, name,
  case when lastname notnull then 'Yes'::text else 'No'::text end as "has_lastname",
  case when username notnull then 'Yes'::text else 'No'::text end as "has_username",
  case when job notnull then 'Yes'::text else 'No'::text end as "has_job",
  case when age < 16 then 'Yes'::text else 'No'::text end as "is_child",
  case when licenseid notnull then 'Yes'::text else 'No'::text end as "has_licenseid",
  case when account notnull then 'Yes'::text else 'No'::text end as "has_account"
  from main_data) t1
order by id;
...