如何在某些条件下计算选定行中的值 - Postgresql

How to count the values in a selected row with some condition - Potgresql

我正在寻找一些内置的 Postresql 功能,它可以在某些条件下计算行值(而不是列)。

的一些类似物
=countif(a:a;"Yes")

在Excel

我在 Whosebug 上看到了很多类似问题的答案,但是当您在 table 列而不是行中过滤数据时提供的所有解决方案。 但我需要按行解析数据。 我不喜欢交叉表的解决方案,因为原始 select 有超过 60 列,并且不喜欢执行相同的查询两次(但如果它是唯一的解决方案,我会这样做)。

一些测试示例,其中应该是最后一列 "num_of_yes",它显示了连续 "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查询:

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;

我的 select 查询有以下输出:

| 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          |

我需要在最后一列添加 'Yes' 个答案。

所需的输出应该是这样的:

| 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

您可以将行转换为 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 值并从中删除 idname 键。然后 jsonb_each_text() 遍历所有 key/value 对,然后 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"

可以考虑使用array_lengthstring_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;