为事件记录设置关系数据库

时间:2017-12-11 18:41:14

标签: sql-server tsql

我的SQL有点生疏,因为除了已经设置的现有数据库的基本查询之外,我还没有使用它。

我正在尝试创建一个事件记录数据库,并希望采用“极端”方法进行规范化。我会有一个主表,主要由'smallint'字段组成,指向包含字符串的子表。

示例:

我有一个外部系统,我想通过SQL启用一些登录,用户填写一些关键参数,构建和插入/更新语句,并被推送到日志表,以便以后可以查看它们他们需要知道XYZ值在运行时或过去的某个时间。

我有一个主表,其中包括:

SELECT [log_id] - bigint (auto-increment) PK
      ,[date_time] - smalldatetime
      ,[cust_id] - smallint FK
      ,[recloc] - char(8)
      ,[alert_level] - smallint FK
      ,[header] - varchar(100)
      ,[body] - varchar(1000)
      ,[process_id] - smalint FK
      ,[routine_id] - smallint FK
      ,[workflow_id] - smallint FK
  FROM [EventLogs].[dbo].[eventLogs]

所有'smallint'字段都指向包含扩展数据的子表:

示例:

SELECT [routine_id] PK/FK
      ,[routine_name]
      ,[description]
  FROM [EventLogs].[dbo].[cpRoutine]


SELECT [process_id] PK/FK
      ,[process_name]
      ,[description]
  FROM [EventLogs].[dbo].[cpProcess]

我的目标是让外部系统执行一个到达所有这些表的更新/插入语句。我将所有'smallint'字段链接为FK目前。

如何制作触及所有这些表的更新/插入语句?如果子表已包含键值对,我不想触摸它。子表的想法是在那里存储重复数据,并在主日志记录表中为其分配一个键以保持大小。我是否需要检查子表中是否存在记录,保存索引号,然后为主表构建插入语句?尽量在这里尽可能高效。

示例:

我想从外部系统记录以下内容:

- date_time - GETDATE()
- customer_number - '0123456789'    
- recloc - 'ABC123'
- alert_level - 'info'
- header - 'this is a header'
- body - 'this is a body'
- process_name - 'the process'
- routine_name - 'the routine'
- workflow_name - 'the workflow'

我是否需要为主表(eventLogs)创建insert语句,但是先检查每个子表并添加缺失值,然后在主表中保存insert语句的id?

  1. 选择process_id,process_name来自cpProcess,其中process_name ='the process'
  2. 如果未返回任何值,请使用process_name
  3. 执行insert语句
  4. 现在再次查询该表以获取ID,以便我可以构建提供主日志表的“主插入语句”
  5. 对所有其他子表重复
  6. 最终插入语句类似于:
  7. SQL代码:

    INSERT INTO eventLogs (date_time, cust_id, recloc, alert_level, header, body, process_id, routine_id, workflow_id)
    VALUES('2017-12-31', '1', 'ABC123', '3', 'this is a header', 'this is a body', '13', '19', '12')
    

    看起来我在服务器检查子表中的值来做我的插入后来回做太多....

    这里的最终目标是创建一个友好的视图,其中包含分配给'smallint'键的所有数据。

1 个答案:

答案 0 :(得分:0)

你很亲密:

  1. 从cpProcess中选择process_id,其中process_name ='the process'
  2. 如果没有返回值,请使用process_name执行insert语句,通过IDENT_CURRENT,SCOPE_IDENTITY或IDENTITY获取ID(或使用从属“load”过程并从输出参数中获取ID)。
  3. 对每个子表重复此操作,直到获得最终插入[eventLogs]所需的值。
  4. 如果它是一个相对低速的过程,这可以正常工作。当您提高速度时可能会出现问题,但如果您只是在进行INSERT,那么它仍然不是很糟糕。我过去曾使用SQL Server Service Broker来解耦这些过程以提高性能,但这显然增加了复杂性。

    根据负载的不同,您可能还决定在事实/维度星形中构建聚合表,以便INSERT OLTP进程与SELECT OLAP进程隔离。

    您所看到的是构建规范化数据结构所涉及的复杂性。你接近“采取”极端“规范化的方法”经常被绕过,因为它“太难”了。这并不意味着你不应该这样做,但你应该权衡投资回报率。我已决定将所有内容转储到下面的日志表中,如下所示,在任何给定时间内只有不到一万条记录。您只需要查看要求并做出最佳选择。

    CREATE TABLE [log].[data]
      (
           [id]          INT IDENTITY(1, 1)
           , [timestamp] DATETIME DEFAULT sysdatetime()
           , [entry]     XML NOT NULL
      ); 
    

    我在设计的构建阶段经常使用的一个选项是在适配器后面构建占位符,如下所示。使用getter和setter方法总是及以后,当您需要更好的性能或数据存储时,您可以根据需要重构底层数据结构,将适配器修改为新的数据结构,并且您节省了一些时间。否则你最终可能会在项目早期追逐很多兔子。通常,当项目向前推进时,您会发现底层结构的设计会根据需求而发生变化,并且您需要花费大量时间进行更改。使用这种方法,您可以立即获得一个工作机制。

    如果您需要折叠此结构以提供更好的性能,那么与在设计期间不断更改结构(在我看来)相比,这将是微不足道的。

    哦,是的,您可以使用标准的关系表。我在应用程序和事件日志记录中使用了大量XML,因为它允许临时结构化数据。这个概念是一样的。您可以直接在表中使用顶级表,只使用[process_name]等列,而现在不使用子列。

    请记住,您不应该直接访问基础表!防止这种情况的一种方法是实际将它们放在专用模式中,例如[log_secure],并将该模式​​保护到除admin和accessor / mutator方法之外的所有模式。

    IF schema_id(N'log') IS NULL
        EXECUTE (N'CREATE SCHEMA log');
    
    go
    
    IF object_id(N'[log].[data]', N'U') IS NOT NULL
        DROP TABLE [log].[data];
    
    go
    
    CREATE TABLE [log].[data]
      (
           [id]          BIGINT IDENTITY(1, 1)
           , [timestamp] DATETIMEOFFSET NOT NULL -- DATETIME if timezone isn't needed
             CONSTRAINT [log__data__timestamp__df] DEFAULT sysdatetimeoffset()
           , [entry]     XML NOT NULL,
           CONSTRAINT [log__data__id__pk] PRIMARY KEY CLUSTERED ([id])
      );
    
    IF object_id(N'[log].[get_entry]', N'P') IS NOT NULL
        DROP PROCEDURE [log].[get_entry];
    
    go
    
    CREATE PROCEDURE [log].[get_entry] @id      BIGINT
                                       , @entry XML output
                                       , @begin DATETIMEOFFSET
                                       , @end   DATETIMEOFFSET
    AS
        BEGIN
            SELECT @entry
            FROM   [log].[data]
            WHERE  [id] = @id;
        END;
    
    go
    
    IF object_id(N'[log].[set_entry]', N'P') IS NOT NULL
        DROP PROCEDURE [log].[set_entry];
    
    go
    
    CREATE PROCEDURE [log].[set_entry] @entry       XML
                                       , @timestamp DATETIMEOFFSET = NULL
                                       , @id        BIGINT output
    AS
        BEGIN
            INSERT INTO [log].[entry]
                        ([timestamp]
                         , [entry])
            VALUES      ( COALESCE(@timestamp, sysdatetimeoffset()),@entry );
    
            SET @id = SCOPE_IDENTITY();
    
        END;
    
    go