如何在插入SQL表时有效地规范化数据(Postgres)

时间:2014-05-17 06:21:42

标签: python sql postgresql pandas

我想将一个大型日志文件导入(Postgres-)SQL

某些字符串列非常重复,例如列'event_type'包含10个不同字符串值中的1个。

我对数据规范化有一个粗略的了解。

首先,假设:将event_type存储在一个单独的表中(可能具有外键关系)是有益的(对于存储大小,索引和查询速度)?

为了规范化,我必须在原始日志中检查event_type的不同值,并将它们插入到event_types表中。

有许多字段类型,例如event_types。

所以其次:有没有办法告诉数据库在插入数据时创建和维护这种表?

还有其他策略来实现这一目标吗?我正在和熊猫一起工作。

1 个答案:

答案 0 :(得分:4)

这是开始从迄今为止存储的数据(例如在日志文件中)构建数据库时的典型情况。有一个解决方案 - 像往常一样 - 但它不是一个非常快的解决方案。也许你可以写一个日志消息处理程序来处理消息进来;如果通量(消息/秒)不是太大,您将不会注意到开销,特别是如果您忘记将消息写入平面文本文件。

首先,关于规范化问题。是的,您应该始终规范化并使用所谓的第三范式(3NF)。这基本上意味着任何类型的真实数据(例如您的event_type)只存储一次。 (有些情况下你可以稍微放松一下并转到2NF - 通常只有当真实世界的数据需要很少的存储空间时,例如ISO国家代码,M / F(男/女)选择等 - 但在大多数其他情况下,3NF会更好。)

在您的具体情况下,假设您的event_type是char(20)类型。十个这样的事件及其相应的int代码很容易适合单个数据库页面,通常为4kB的磁盘空间。如果您有1,000条日志消息,其中event_type为char(20),那么您只需要20kB来存储该信息或五个数据库页面。如果您的日志消息中有其他此类项目,则存储减少会相应变大。其他项目(例如datetimestamp)可以以其本机格式(分别为4和8字节)存储,以实现更小的存储,更好的性能和增强的功能(例如比较日期或查看范围)。

其次,你不能告诉数据库创建这样的表,你必须自己做。但是一旦创建,存储过程就可以解析您的日志消息并将数据放在正确的表中。

对于日志消息,你可以这样做(假设你想在数据库中进行解析而不是在python中):

CREATE FUNCTION ingest_log_message(mess text) RETURNS int AS $$
DECLARE
  parts  text[];
  et_id  int;
  log_id int;
BEGIN
  parts := regexp_split_to_array(mess, ','); -- Whatever your delimiter is

  -- Assuming:
  --   parts[1] is a timestamp
  --   parts[2] is your event_type
  --   parts[3] is the actual message

  -- Get the event_type identifier. If event_type is new, INSERT it, else just get the id.
  -- Do likewise with other log message parts whose unique text is located in a separate table.
  SELECT id INTO et_id
  FROM event_type
  WHERE type_text = quote_literal(parts[2]);
  IF NOT FOUND THEN
    INSERT INTO event_type (type_text)
    VALUES (quote_literal(parts[2]))
    RETURNING id INTO et_id;
  END IF;

  -- Now insert the log message
  INSERT INTO log_message (dt, et, msg)
  VALUES (parts[1]::timestamp, et_id, quote_literal(parts[3]))
  RETURNING id INTO log_id;

  RETURN log_id;
END; $$ LANGUAGE plpgsql STRICT;

您需要的表格是:

CREATE TABLE event_type (
  id        serial PRIMARY KEY,
  type_text char(20)
);

CREATE TABLE log_message (
  id        serial PRIMARY KEY,
  dt        timestamp,
  et        integer REFERENCES event_type
  msg       text
);

然后,您可以将此函数作为简单的SELECT语句调用,该语句将返回新插入日志消息的id

SELECT * FROM ingest_log_message(the_message);

注意在函数体中使用quote_literal()函数。这有两个重要的功能:(1)字符串内的引号被正确转义(因此像“不是”这样的单词不会搞乱命令); (2)它防止恶意发生器对日志消息进行SQL注入。

以上所有内容显然都需要根据您的具体情况量身定制。