【问题标题】:Oracle Gotchas for An Experienced Newb经验丰富的新手的 Oracle 陷阱
【发布时间】:2010-11-15 14:15:07
【问题描述】:

对于刚接触该平台但对关系数据库(MySQL、MS SQL Server、Postgres 等)不熟悉的人来说,Oracle 的一些陷阱是什么?

我正在寻找的东西的两个例子

  1. 许多关系数据库产品都会为您创建一个 auto_increment 键。 oracle没有,必须手动创建序列,然后创建触发器

  2. 通过 SQL Developer 界面插入数据时,您必须手动提交数据

PHP 相关问题的加分点,因为这是这个假设的经验丰富的新手将使用的平台我会

【问题讨论】:

  • 您不需要触发器来更新序列 - 您使用 sequence.nextval
  • 很好的信息,可能值得一个实际的答案(而不是评论),但它确实导致 SQL 的可移植性降低,这导致一些人(对或错)改为使用触发器。跨度>
  • 另外,第 2 点是可以在 SQL Developer 中打开的首选项(工具->首选项->数据库->工作表参数->SQL 工作表中的自动提交)。
  • 甜心,谢谢史蒂夫的提示。

标签: php mysql sql-server oracle postgresql


【解决方案1】:

注意:我在这里只解释陷阱,即。 e. Oracle 的行为与其他系统不同的情况。 Oracle 比其他 RDBMS 有很多好处,但它们不是帖子的主题。

  • 没有FROM,你就不能SELECT

    SELECT  1
    

    会失败,你需要:

    SELECT  1
    FROM    dual
    
  • 空字符串和NULL是一回事。

    SELECT  *
    FROM    dual
    WHERE   '' = ''
    

    什么都不返回。

  • 既没有TOP,也没有LIMIT。您将结果限制在 WHERE 子句中:

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    正是这样,使用子查询,因为ROWNUMORDER BY 之前被评估。

  • 您不能将相关的子查询嵌套超过一层。这个会失败:

    SELECT  (
            SELECT  *
            FROM    (
                    SELECT  dummy
                    FROM    dual di
                    WHERE   di.dummy = do.dummy
                    ORDER BY
                            dummy
                    )
            WHERE   rownum = 1
            )
    FROM    dual do
    

    这是个问题。

  • NULL 值未编入索引。此查询将不使用索引进行排序:

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    ,除非col 被标记为NOT NULL

    请注意,未编入索引的是NULL ,而不是。您可以在可为空的列上创建索引,非NULL 值将进入索引。

    但是,当查询条件假设NULL 值可能满足时,不会使用索引。

    在上面的示例中,您希望返回所有值(包括NULLs)。然后索引不知道非NULL 值,因此无法检索它们。

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    但是这个查询会使用索引:

    SELECT  *
    FROM    (
            SELECT  *
            FROM    mytable
            WHERE   col IS NOT NULL
            ORDER BY
                    col
            )
    WHERE   rownum < 10
    

    ,因为非NULL 值永远无法满足条件。

  • 默认情况下,NULLs 排在最后,而不是排在最前面(类似于 PostgreSQL,但与 MySQLSQL Server 不同)

    这个查询:

    SELECT  *
    FROM    (
            SELECT  1 AS id
            FROM    dual
            UNION ALL
            SELECT  NULL AS id
            FROM    dual
            ) q
    ORDER BY
            id
    

    会回来

    id
    ---
    1
    NULL
    

    要在SQL ServerMySQL 中进行排序,请使用:

    SELECT  *
    FROM    (
            SELECT  1 AS id
            FROM    dual
            UNION ALL
            SELECT  NULL AS id
            FROM    dual
            ) q
    ORDER BY
            id NULLS FIRST
    

    请注意,它会破坏rownum 的顺序,除非后者未在子查询之外使用(如上所述)

  • "MYTABLE""mytable"(双引号很重要)是不同的对象。

    SELECT  *
    FROM    mytable -- wihout quotes
    

    将选择前者,而不是后者。如果前者不存在,则查询失败。

    CREATE TABLE mytable
    

    创建"MYTABLE",而不是"mytable"

  • Oracle 中,所有隐式锁(由DML 操作产生)都是行级的,并且永远不会升级。也就是说,没有任何不受事务影响的行可以被隐式锁定。

    作家从不阻止读者(反之亦然)。

    要锁定整个表,您应该发出明确的LOCK TABLE 语句。

    行锁存储在数据页上。

  • Oracle 中,没有“CLUSTERED 索引”,有“索引组织表”。默认情况下,表是堆组织的(不像SQL ServerMySQLInnoDB)。

    Oracle 世界中,“集群存储”意味着组织多个表,以便共享一个公共键(来自多个表)的行也共享一个数据页。

    单个数据页托管来自多个表的多行,这使得在此键上的连接超级快。

【讨论】:

  • 你需要解释一下。 AFAIK select '' IS NULL from DUAL,会给你一个错误。
  • @uswaretech:不,它会返回一行。试试看吧。
  • 昨天我在 sqlserver 上的反面。我们有基于 oracle 的脚本正在插入 '',而 sqlserver 不会将其变为 null...我花了一段时间才回溯到这个。
  • @uswaretech:Oracle 已经非常老了。在开发的时候,没有标准,他们决定空字符串和NULL's 是一样的。 ANSI SQL 要求它们不同,这就是为什么 Oracle 使用 VARCHAR2 而不是 VARCHAR,并保留 VARCHAR 以使其将来与 ANSI 兼容。
  • 我不同意你的例子,因为“NULL 值没有被索引。这个查询不会使用索引:”。一方面,即使该列不为空,它仍然不会使用索引 - 遍历整个表时,表扫描速度更快。但是,即使您有一个“NULL”列,您仍然可以对其进行索引,并且将使用该索引 - 只要您限制一个不为 null 的值。 (续)
【解决方案2】:

SELECT 1 不起作用,请改为从 dual 中选择 1。

如果您使用分层数据,连接方式非常好。

【讨论】:

    【解决方案3】:

    SQL Server 相比,我遇到的对模式对象和数据区分大小写的 Oracle 数据库似乎更多。

    【讨论】:

    • PostgreSQLMySQL 也是。最好说SQL Server 比其他RDBMSes 更不区分大小写:)
    • Oracle 区分大小写在哪里?我一直在假设它不像表名和列那样进行操作。有错吗?
    • @Alan: "MYTABLE""mytable"(引号很重要)是不同的对象。 SELECT * FROM mytable(不带引号)将选择前者,而不是后者。
    • 知道了,谢谢 Quassnoi,我想你在未来拯救了我不止一个挠头/敲打的时刻。
    • 大多数人错过这一点的原因是,如果您不将其括在引号中,Oracle 会将所有内容都转换为大写。所以,只要你不引用你的数据库对象名称,你就永远不会看到问题。坦率地说,首先没有太多理由这样做,除非你沉迷于人们在使用暴露你的数据库对象的 GUI 时看到大写的表和列名,但是你被困在双引号周围你所有的源代码 - 一个巨大的痛苦,IMO。
    【解决方案4】:

    不要忘记在行集中可能完全用空值填充的任何列周围使用 nvl(column)。否则,该列将从行集中丢失。

    没错,完全没有!

    例子:

    SELECT nvl(employeeName,'Archie'), nvl(employeeSpouse,'Edith') FROM Employee
    

    这将保证您在行集中获得两列,即使两者中的所有值都为空。您只会看到一堆 'Archie' 和 'Edith' 值。如果你不使用 nvl(),你可能只得到一列,或者都不回来。问题的关键在于,您的代码可以在您的开发环境中正常运行,甚至可以通过 QA,但是当它进入生产环境时,表中的 values 可能会改变 结构 的结果!

    因此,简而言之,无论何时选择可空列,请务必使用 nvl()。

    【讨论】:

    • @Chris:你能举例说明你的意思吗?
    • @Chris:刚刚创建的表:CREATE TABLE employee (employeeName VARCHAR2(20), employeeSpouse VARCHAR2(20)),填写:INSERT INTO employee VALUES (NULL, NULL) 并检查:SELECT * FROM employee。它返回给我一行,有两个NULL,这是应该的。
    • 抱歉,这是 Oracle 的 ODBC 驱动程序的问题,而不是 db 本身的问题。
    【解决方案5】:

    没有像 MySQL 那样的组连接。如果你想要一个组连接聚合函数,你必须自己编写。这是我的实现:

    drop type T_GROUP_CONCAT;
    
    create or replace type GROUP_CONCAT_PARAM as object
    (
      val varchar2(255),
      separator varchar2(10),
      numToConcat NUMBER,
      MAP MEMBER FUNCTION GROUP_CONCAT_PARAM_ToInt  return VARCHAR2
    );
    
    --map function needed for disctinct in select clauses
    CREATE OR REPLACE TYPE BODY GROUP_CONCAT_PARAM IS
        MAP MEMBER FUNCTION GROUP_CONCAT_PARAM_ToInt return VARCHAR2 is 
          begin 
            return val; 
          end; 
    
    end;
    
    
    /
    
    CREATE OR REPLACE TYPE T_GROUP_CONCAT 
    AS OBJECT (
    
    runningConcat VARCHAR2(5000),
    runningCount NUMBER,
    
    STATIC FUNCTION ODCIAggregateInitialize
      ( actx IN OUT T_GROUP_CONCAT
      ) RETURN NUMBER,
    
    MEMBER FUNCTION ODCIAggregateIterate
      ( self  IN OUT T_GROUP_CONCAT,
        val   IN       GROUP_CONCAT_PARAM
      ) RETURN NUMBER,
    
    MEMBER FUNCTION ODCIAggregateTerminate
      ( self             IN   T_GROUP_CONCAT,
        returnValue  OUT VARCHAR2,
        flags           IN   NUMBER
      ) RETURN NUMBER,
    
    MEMBER FUNCTION ODCIAggregateMerge
      (self  IN OUT T_GROUP_CONCAT,
       ctx2 IN      T_GROUP_CONCAT
      ) RETURN NUMBER
    
    );
    /
    
    CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT AS
    
    STATIC FUNCTION ODCIAggregateInitialize
      ( actx IN OUT T_GROUP_CONCAT
      ) RETURN NUMBER IS 
      BEGIN
        IF actx IS NULL THEN
          actx := T_GROUP_CONCAT ('', 0);
        ELSE
          actx.runningConcat := '';
          actx.runningCount := 0;
        END IF;
        RETURN ODCIConst.Success;
      END;
    
    MEMBER FUNCTION ODCIAggregateIterate
      ( self  IN OUT T_GROUP_CONCAT,
        val   IN     GROUP_CONCAT_PARAM
      ) RETURN NUMBER IS
      BEGIN
        if self.runningCount = 0 then
            self.runningConcat := val.val;
        elsif self.runningCount < val.numToConcat then
            self.runningConcat := self.runningConcat || val.separator || val.val;
        end if;
        self.runningCount := self.runningCount + 1;
        RETURN ODCIConst.Success;
      END;
    
    MEMBER FUNCTION ODCIAggregateTerminate
      ( self        IN  T_GROUP_CONCAT,
        ReturnValue OUT VARCHAR2,
        flags       IN  NUMBER
      ) RETURN NUMBER IS
      BEGIN
        returnValue := self.runningConcat;
        RETURN ODCIConst.Success;
      END;
    
    MEMBER FUNCTION ODCIAggregateMerge
      (self IN OUT T_GROUP_CONCAT,
       ctx2 IN     T_GROUP_CONCAT
      ) RETURN NUMBER IS
      BEGIN
        self.runningConcat := self.runningConcat || ',' || ctx2.runningConcat;
        self.runningCount := self.runningCount + ctx2.runningCount;
        RETURN ODCIConst.Success;
      END;
    
    END;
    /
    
    CREATE OR REPLACE FUNCTION GROUP_CONCAT
    ( x GROUP_CONCAT_PARAM
    ) RETURN VARCHAR2
    --PARALLEL_ENABLE
    AGGREGATE USING T_GROUP_CONCAT;
    /
    

    使用它:

    select GROUP_CONCAT(GROUP_CONCAT_PARAM(tbl.someColumn, '|', 2)) from someTable tbl
    

    【讨论】:

    【解决方案6】:

    【讨论】:

      【解决方案7】:

      一条评论:您不必为了使用序列而创建触发器,除非您坚持要复制 Sybase/SQL Server IDENTITY 列的行为。我发现直接在实际插入语句中使用序列更有用,例如

      INSERT
        INTO MyTable
           ( KeyCol
           , Name
           , Value
           )
      SELECT Seq_MyTable.NextVal
           , 'some name'
           , 123
        FROM dual;
      

      您无需担心触发器执行的开销,并且您可以灵活地处理向表中插入行,而不必担心分配序列值(例如将数据从架构移动到另一个架构时) .您还可以从序列中预先选择值,以插入数据范围以及 IDENTITY 功能难以实现或无法实现的其他技术。

      【讨论】:

        【解决方案8】:

        临时表

        您可以像普通表一样创建和索引它们,但每个会话/事务只能看到自己的数据。这与 MS SQL 不同。

        全局变量

        它们是通过引用传递的。这意味着如果将全局变量作为参数传递给过程,并在过程中修改全局变量,则参数值也会发生变化。不过,这不是一种非常流行的方法。

        触发器

        在最近的版本之前,无法确定触发类似触发器的方式。如果您真的关心“每行更新之前”是哪一个,您可以将其全部放在一个触发器中。

        【讨论】:

          猜你喜欢
          • 2012-06-21
          • 1970-01-01
          • 1970-01-01
          • 2012-06-18
          • 2023-03-10
          • 2012-03-29
          • 1970-01-01
          • 1970-01-01
          • 2011-09-09
          相关资源
          最近更新 更多