计算包含字母/数字的行数

时间:2012-05-10 16:07:55

标签: sql postgresql count aggregate-functions

我想要实现的目标是直截了当的,但是有点难以解释,而且我不知道在postgres中它是否真的可行。我处于相当基础的水平。 SELECT, FROM, WHERE, LEFT JOIN ON, HAVING,e.t.c基本的东西。

我正在尝试计算包含特定字母/数字的行数,并根据字母/数字显示该计数。

即有多少行包含" a / A" (不区分大小写)

我查询的表格是电影名称列表。我想做的就是分组和统计' a-z'和' 0-9'并输出总数。我可以顺序运行36个查询:

SELECT filmname FROM films WHERE filmname ilike '%a%'
SELECT filmname FROM films WHERE filmname ilike '%b%'
SELECT filmname FROM films WHERE filmname ilike '%c%'

然后在结果上运行pg_num_rows以查找我需要的数字,依此类推。

我知道这是多么密集和ilike更多,所以我宁愿避免这样。虽然数据(下面)在数据中有大小写,但我希望结果集不区分大小写。即#34;盯着山羊的男人"对于结果集,a / A,t / T和s / S不会计数两次。我可以将表复制到辅助工作表,其中数据全部是strtolower,如果它使查询更简单或更容易构建,则处理查询的数据集。

替代方案可能类似于

SELECT sum(length(regexp_replace(filmname, '[^X|^x]', '', 'g'))) FROM films;

对于每个字母组合但又有36个查询,36个数据集,如果我可以在单个查询中获取数据,我更愿意。

这是一组包含14集电影的短片(实际上包含275行)

District 9
Surrogates
The Invention Of Lying
Pandorum
UP
The Soloist
Cloudy With A Chance Of Meatballs
The Imaginarium of Doctor Parnassus
Cirque du Freak: The Vampires Assistant
Zombieland
9
The Men Who Stare At Goats
A Christmas Carol
Paranormal Activity

如果我在一列中手动布置每个字母和数字然后注册,如果该字母出现在电影标题中,在该列中给出一个x,然后计算它们以产生一个总数我会在下面有这样的东西。 x的每个垂直列都是该电影名称中字母的列表,无论该字母出现的次数或其大小。

以上简短结果的结果是:

A  x x  xxxx xxx  9 
B       x  x      2 
C x     xxx   xx  6
D x  x  xxxx      6
E  xx  xxxxx x    8
F   x   xxx       4 
G  xx    x   x    4
H   x  xxxx  xx   7
I x x  xxxxx  xx  9
J                 0
K         x       0
L   x  xx  x  xx  6
M    x  xxxx xxx  8
N   xx  xxxx x x  8
O  xxx xxx x xxx  10
P    xx  xx    x  5
Q         x       1
R xx x   xx  xxx  7
S xx   xxxx  xx   8
T xxx  xxxx  xxx  10
U  x xx xxx       6
V   x     x    x  3
W       x    x    2
X                 0 
Y   x   x      x  3
Z          x      1 
0                 0  
1                 0  
2                 0 
3                 0
4                 0
5                 0
6                 0
7                 0
8                 0
9 x         x     1

在上面的示例中,每列都是"电影名称"如您所见,第5列仅标记了" u"和" p"第11列仅标记" 9"。最后一列是每个字母的标记。

我想以某种方式构建一个查询给我结果行:A 9,B 2,C 6,D 6,E 8 e.t.c考虑从我的电影专栏中提取的每一行条目。如果那封信没有出现在任何一行我想要零。

我不知道这是否可行,或者是否在php中系统地进行36次查询是唯一的可能性。

在当前数据集中有275个条目,每月增长约8.33(一年100个)。我预测它到2019年将达到1000行左右,到那时我毫无疑问会使用一个完全不同的系统,因此我不必担心使用庞大的数据集进行搜索。

目前最长的冠军是" Percy Jackson&奥林匹克运动员:闪电小偷"在50个字符(是的,我知道的可怜的电影;-),最短的是1," 9"。

我正在运行Postgres的9.0.0版本。

如果我以多种方式多次说过同样的事情,我会道歉,我正在努力获取尽可能多的信息,以便你知道我想要实现的目标。

如果您需要任何澄清或更大的数据集进行测试,请直接询问,我会根据需要进行编辑。

建议非常欢迎。

编辑1

Erwin 感谢您的编辑/标签/建议。同意他们所有。

修复了失踪" 9" Erwin 建议的拼写错误。我手动转录错误。

kgrittn ,感谢您的建议,但我无法从9.0.0更新版本。我问过我的提供商他们是否会尝试更新。

响应

感谢您的回复 Erwin

对延迟回复表示抱歉,但我一直在尝试让您的查询工作并学习新关键字以了解您创建的查询。

我调整了查询​​以适应我的表格结构,但结果集并不像预期的那样(全部为零)所以我直接复制了你的行并得到了相同的结果。

虽然两种情况下的结果集都列出了具有相应字母/数字的所有36行,但是所有行都显示为计数(ct)为零。

我试图解构查询,看看它可能会失败。

结果

SELECT DISTINCT id, unnest(string_to_array(lower(film), NULL)) AS letter
FROM  films


是"没有找到行"。也许它应该从更广泛的查询中提取出来,我不确定。

当我删除了不需要的函数时,结果是14行所有" NULL"

如果我调整功能

COALESCE(y.ct, 0) to COALESCE(y.ct, 4)<br />

然后我的数据集以4&#39; s响应所有字母,而不是前面所解释的零。

简要介绍了COALESCE&#34; 4&#34;作为替代值,我猜测y.ct为NULL并被第二个值替换(这是为了覆盖序列中字母不匹配的行,即如果没有电影包含&#39; q&#39;然后&#39; q&#39;列将具有零值而不是NULL?)

我试过的数据库是SQL_ASCII,我想知道这是不是一个问题,但我在一个运行版本8.4.0与UTF-8上的结果相同。

如果我犯了一个明显的错误,但我无法返回我需要的数据集,请道歉。

有什么想法吗?

再次感谢您的详细回复和解释。

4 个答案:

答案 0 :(得分:6)

此查询应该完成这项工作:

测试用例:

CREATE TEMP TABLE films (id serial, film text);
INSERT INTO films (film) VALUES
 ('District 9')
,('Surrogates')
,('The Invention Of Lying')
,('Pandorum')
,('UP')
,('The Soloist')
,('Cloudy With A Chance Of Meatballs')
,('The Imaginarium of Doctor Parnassus')
,('Cirque du Freak: The Vampires Assistant')
,('Zombieland')
,('9')
,('The Men Who Stare At Goats')
,('A Christmas Carol')
,('Paranormal Activity');

查询:

SELECT l.letter, COALESCE(y.ct, 0) AS ct
FROM  (
    SELECT chr(generate_series(97, 122)) AS letter  -- a-z in UTF8!
    UNION ALL
    SELECT generate_series(0, 9)::text              -- 0-9
    ) l
LEFT JOIN (
    SELECT letter, count(id) AS ct
    FROM  (
        SELECT DISTINCT  -- count film once per letter
               id, unnest(string_to_array(lower(film), NULL)) AS letter
        FROM   films
        ) x
    GROUP  BY 1
    ) y  USING (letter)
ORDER  BY 1;
  

更改string_to_array(),以便NULL分隔符将字符串拆分为   人物(Pavel Stehule)

     

以前这会返回一个空值。

  • 您可以使用regexp_split_to_table(lower(film), ''),而不是unnest(string_to_array(lower(film), NULL))(适用于9.1之前的版本!),但它通常会慢一些,并且使用长字符串会降低性能。

  • 我使用generate_series()生成[a-z0-9]个别行。并且LEFT JOIN到查询,因此每个字母都在结果中表示。

  • 使用DISTINCT计算每部电影一次。

  • 永远不要担心1000行。这就是现代硬件上现代PostgreSQL的花生。

答案 1 :(得分:0)

一个相当简单的解决方案,只需要一次表扫描,如下所示。

SELECT 
    'a', SUM( (title ILIKE '%a%')::integer),
    'b', SUM( (title ILIKE '%b%')::integer),
    'c', SUM( (title ILIKE '%c%')::integer)
FROM film

我将其他33个字符作为打字练习留给你:)

BTG 1000行对于postgresql数据库来说很小。当数据库大于服务器中的内存时,它开始变大。

编辑:有一个更好的主意

SELECT chars.c, COUNT(title)
FROM (VALUES ('a'), ('b'), ('c')) as chars(c)
    LEFT JOIN film ON title ILIKE ('%' || chars.c || '%')
GROUP BY chars.c
ORDER BY chars.c 

您还可以将(VALUES('a'),('b'),('c'))替换为chars(c)part,其中包含对包含您感兴趣的字符列表的表的引用。

答案 2 :(得分:0)

这将为您提供单行结果,每个匹配的字母和数字都有一列。

SELECT
  SUM(CASE WHEN POSITION('a' IN filmname) > 0 THEN 1 ELSE 0 END) AS "A",
  SUM(CASE WHEN POSITION('b' IN filmname) > 0 THEN 1 ELSE 0 END) AS "B",
  SUM(CASE WHEN POSITION('c' IN filmname) > 0 THEN 1 ELSE 0 END) AS "C",
  ...
  SUM(CASE WHEN POSITION('z' IN filmname) > 0 THEN 1 ELSE 0 END) AS "Z",
  SUM(CASE WHEN POSITION('0' IN filmname) > 0 THEN 1 ELSE 0 END) AS "0",
  SUM(CASE WHEN POSITION('1' IN filmname) > 0 THEN 1 ELSE 0 END) AS "1",
  ...
  SUM(CASE WHEN POSITION('9' IN filmname) > 0 THEN 1 ELSE 0 END) AS "9"
FROM films;

答案 3 :(得分:0)

类似于Erwins的方法,但从长远来看可能更舒服:

创建一个包含您感兴趣的每个角色的表格:

CREATE TABLE char (name char (1), id serial);
INSERT INTO char (name) VALUES ('a');
INSERT INTO char (name) VALUES ('b');
INSERT INTO char (name) VALUES ('c');

然后对其值进行分组很容易:

SELECT char.name, COUNT(*) 
  FROM char, film 
  WHERE film.name ILIKE '%' || char.name || '%' 
  GROUP BY char.name 
  ORDER BY char.name;

不要担心ILIKE。

我对使用关键字'char'作为表格标题并不是100%满意,但到目前为止还没有遇到过错误的经历。另一方面,它是自然名称。也许如果你把它翻译成另一种语言 - 比如德语中的'zeichen',你可以避免含糊不清。