Java开发人员在编写SQL时又犯了10个常见错误(您不会相信最后一个)

(抱歉,点击诱饵的标题。无法抗拒!)

我们正在执行任务。 教你SQL。 但最重要的是,我们想教您如何欣赏SQL。 你会爱上它!

正确或错误地执行SQ​​L都不应该是当传福音者宣传传福音的对象时经常会遇到的You're-Doing-It-Wrong™态度。 正确使用SQL应该与正确使用SQL有关。 当您发现可以轻松地用300行精益功能代码(例如,使用Java 8 )替换2000行缓慢,难以维护且难看的命令式(或面向对象)代码时,您会开始欣赏的东西更好,使用50行SQL。

我们很高兴看到我们的博客朋友在阅读我们的文章后开始欣赏SQL,尤其是window函数。 例如,取:

因此,在我们之前非常受欢迎的帖子之后:

……我们将带给您:

Java开发人员在编写SQL时再犯的10个常见错误

当然,这并不仅仅适用于Java开发人员,而是从Java(和SQL)开发人员的角度编写的。 因此,我们再次开始:

  1. 不使用窗口功能

  2. 毕竟我们一直在讲道,这一定是我们在本系列文章中排名第一的错误。 窗口函数可能是所有函数中最酷的SQL功能 。 它们是如此有用,它们应该成为任何人切换到更好的数据库(例如PostgreSQL)的第一原因:

    如果免费和/或开源对您很重要,那么您绝对没有比使用PostgreSQL更好的选择(如果您是Java开发人员,甚至可以使用免费的jOOQ开源版 )。

    而且,如果您有幸在拥有Oracle或SQL Server(或DB2,Sybase)许可证的环境中工作,那么您可以从自己喜欢的新工具中获得更多收益。

    在本节中,我们将不重复所有的窗口函数优点,我们已经在博客上经常介绍它们:

治愈:

删除MySQL。 以一个不错的数据库。 并开始使用窗口功能。 您将永远不会回去,保证。

  • 不声明NOT NULL约束

  • 该列表已经是上一个列表的一部分,在该列表中,我们声称您应该向架构中添加尽可能多的元数据,因为您的数据库将能够利用该元数据进行优化。 举例来说,如果你的数据库知道 ,在一个外键值BOOK.AUTHOR_ID 也必须包含正好一次AUTHOR.ID ,那么整套的优化的,可以在复杂的查询来实现的。

    现在让我们再来看一下NOT NULL约束。 如果您使用的是Oracle,则NULL值将不属于索引。 例如,如果要表达IN约束,则无关紧要:

    SELECT * FROM table
    WHERE value IN (
      SELECT nullable_column FROM ...
    )

    但是,使用NOT IN约束会发生什么?

    SELECT * FROM table
    WHERE value NOT IN (
      SELECT nullable_column FROM ...
    )

    由于SQL处理NULL方式有点不直观 ,因此存在第二个查询意外地根本不返回任何结果的轻微风险,即子查询的结果是否至少有一个NULL值。 对于所有正确使用SQL的数据库来说都是如此。

    但是,由于nullable_column上的索引不包含任何NULL值,因此Oracle必须在表中查找完整的内容,从而导致FULL TABLE SCAN 。 现在是出乎意料的! 对此的详细信息可以在本文中看到。

    治愈:

    仔细检查所有可为空但已建立索引的列,并检查是否真的不能向这些列添加NOT NULL约束。

    工具:

    如果您使用的是Oracle,请使用以下查询来检测所有可为空但尚未索引的列:

    SELECT
      i.table_name,
      i.index_name,
      LISTAGG(
        LPAD(i.column_position,  2) || ': ' || 
        RPAD(i.column_name    , 30) || ' '  ||
        DECODE(t.nullable, 'Y', '(NULL)', '(NOT NULL)'), 
        ', '
      ) WITHIN GROUP (ORDER BY i.column_position) 
        AS "NULLABLE columns in indexes"
    FROM user_ind_columns i
    JOIN user_tab_cols t
    ON (t.table_name, t.column_name) = 
      ((i.table_name, i.column_name))
    WHERE EXISTS (
      SELECT 1
      FROM user_tab_cols t
      WHERE (t.table_name, t.column_name, t.nullable) = 
           ((i.table_name, i.column_name, 'Y'       ))
    )
    GROUP BY i.table_name, i.index_name
    ORDER BY i.index_name ASC;

    输出示例:

    TABLE_NAME | INDEX_NAME   | NULLABLE columns in indexes
    -----------+--------------+----------------------------
    PERSON     | I_PERSON_DOB | 1: DATE_OF_BIRTH (NULL)

    然后修复它

    (这里对Maven的偶然批评是无关紧要的!)

    如果您对更多详细信息感到好奇,请参阅以下文章:

  • 使用PL / SQL包状态

  • 现在,如果您不使用Oracle,那将是一件无聊的事情,但是如果您(并且您是Java开发人员)使用PL / SQL程序包状态,则要格外小心。 您是否真的在做您想做的事?

    是的,PL / SQL具有package-state ,例如

    CREATE OR REPLACE PACKAGE pkg IS
      -- Package state here!
      n NUMBER := 1;
    
      FUNCTION next_n RETURN NUMBER;
    END pkg;
    
    CREATE OR REPLACE PACKAGE BODY pkg IS
      FUNCTION next_n RETURN NUMBER
      IS
      BEGIN
        n := n + 1;
        RETURN n;
      END next_n;
    END pkg;

    太好了,因此您创建了一个内存中计数器,每次调用pkg.next_n都会生成一个新数字。 但是谁拥有那个柜台呢? 是的,会议。 每个会话都有自己的初始化“包实例”。

    但是,不,这可能不是您可能想到的会议。

    我们的Java开发人员通过连接池连接到数据库。 当我们从这样的池中获得JDBC连接时,我们将从先前的“会话”(例如先前的HTTP请求(不是HTTP会话!))回收该连接。 但这不一样。 数据库会话(可能)将超过HTTP请求的寿命,并且将由下一个请求继承(可能来自完全不同的用户)。 现在,假设您在该包裹中有一个信用卡号……?

    不是治愈:

    不。 不要只是跳到使用SERIALLY_REUSABLE

    CREATE OR REPLACE PACKAGE pkg IS
      PRAGMA SERIALLY_REUSABLE;
      n NUMBER := 1;
    
      FUNCTION next_n RETURN NUMBER;
    END pkg;

    因为:

    • 现在,您甚至不能从SQL使用该程序包(请参阅ORA-06534 )。
    • 将此PRAGMA与其他软件包的常规软件包状态混合在一起只会使事情复杂得多。

    所以,不要。

    不是治愈:

    我知道。 PL / SQL可能是野兽。 它通常看起来像是一种古怪的语言。 但是面对它。 用PL / SQL编写时,许多事情的运行速度要快得多,因此,请不要放弃。 删除PL / SQL也不是解决方案。

    治愈:

    不惜一切代价,尝试避免PL / SQL中的程序包状态。 将包状态视为Java中的static变量。 尽管它们有时可能有时对高速缓存(当然还有常量)很有用,但您实际上可能并未实际访问所需的状态。 考虑一下负载均衡器,突然将您转移到另一个JVM。 考虑一下类加载器,由于某种原因,它可能两次加载了相同的类。

    而是通过过程和函数将状态作为参数传递。 这样可以避免副作用,并使您的代码更清晰,更可预测。

    或者,很明显,将状态持久化到某个表。

  • 一直运行相同的查询

  • 主数据很无聊。 您可能编写了一些实用程序来获取主数据的最新版本(例如语言,语言环境,翻译,租户,系统设置),并且一旦可用就可以每次查询。

    不惜一切代价,不要这样做。 您不必在应用程序中缓存很多东西,因为在缓存方面,现代数据库已变得非常快:

    • 表/列内容
    • 索引内容
    • 查询/物化视图结果
    • 程序结果(如果是确定性的)
    • 游标
    • 执行计划

    因此,对于您的一般查询,至少从性能角度来看,实际上不需要ORM二级缓存(当然,ORM缓存主要满足其他目的)。

    但是,当您查询主数据(即永不更改的数据)时,网络延迟,流量和许多其他因素将损害您的数据库体验。

    治愈:

    请花10分钟时间, 下载Guava并使用其出色且易于设置的缓存 ,该缓存附带各种内置的失效策略。 选择基于时间的失效(即轮询),选择Oracle AQ或Streams ,或者为基于事件的失效选择PostgreSQL的NOTIFY ,或者只要无所谓就永久保留高速缓存。 但是,不要一直发出相同的主数据查询。

    ……这显然带给我们:

  • 不知道N + 1问题

  • 你有一个选择。 在软件产品开始时,您必须选择以下各项:

    因此,显然,您选择了ORM,因为否则您将不会遭受“ N + 1”的困扰。 “ N + 1”是什么意思?

    这个Stack Overflow问题的公认答案很好地说明了这一点 。 本质上,您正在运行:

    SELECT * FROM book
    
    -- And then, for each book:
    SELECT * FROM author WHERE id = ?
    SELECT * FROM author WHERE id = ?
    SELECT * FROM author WHERE id = ?

    当然,您可以去调整数百条注释,以正确地预取或渴望获取每本书的相关作者信息,以产生类似于以下内容的内容:

    SELECT * 
    FROM   book
    JOIN   author 
      ON   book.author_id = author.id

    但这将是很多工作,并且您将冒着急于获取过多您不想要的内容的风险,从而导致另一个性能问题。

    也许,您可以升级到JPA 2.1并使用新的@NamedEntityGraph来表达漂亮的注释树,如下所示:

    @NamedEntityGraph(
        name = "post",
        attributeNodes = {
            @NamedAttributeNode("title"),
            @NamedAttributeNode(
                value = "comments", 
                subgraph = "comments"
            )
        },
        subgraphs = {
            @NamedSubgraph(
                name = "comments",
                attributeNodes = {
                    @NamedAttributeNode("content")
                }
            )
        }
    )

    该示例摘自Hanty Bai的这篇博客文章 。 然后,Hanty继续说明您可以通过以下语句使用上述美感:

    em.createQuery("select p from Post p where p.id=:id",
                   Post.class)
      .setHint("javax.persistence.fetchgraph", 
               postGraph)
      .setParameter("id", this.id)
      .getResultList()
      .get(0);

    让我们大家在适当的时候赞赏上述JEE标准的应用,然后考虑……

    治愈:

    您只需要听本文开头的明智话,就可以用几行SQL替换成千上万行乏味的Java / Annotatiomania™代码。 因为这也可能有助于您避免我们尚未涉及的另一个问题,即选择过多的列,如您在这些帖子中所见:

    由于您已经在使用ORM,因此这可能仅意味着求助于本机SQL,或者您可能设法用JPQL表达查询。 当然,我们同意Alessio Harri的观点,认为您应该将jOOQ与JPA一起使用:

    外卖:

    尽管以上内容肯定会帮助您解决您最喜欢的ORM可能遇到的一些现实问题,但是您也可以更进一步,以这种方式进行思考。 经过多年的痛苦和对象关系阻抗不匹配的困扰,JPA 2.1专家组现在正尝试通过向JPQL查询添加更多基于声明的基于注释的获取图提示来摆脱这种注释疯狂。一个可以调试,更不用说维护了。

    替代方法是简单直接的SQL。 对于Java 8,我们将通过Streams API添加功能转换。 很难被击败

    但是,显然,您在该主题上的观点和经验可能与我们的观点和经验不同,因此让我们开始关于...的更客观的讨论。

  • 不使用公用表表达式

  • 尽管公用表表达式显然可以提高可读性,但它们也可以提高性能。 考虑一下我最近在客户的PL / SQL程序包中遇到的以下查询(不是实际查询):

    SELECT round (
      (SELECT amount FROM payments WHERE id = :p_id)
        * 
      (
        SELECT e.bid
        FROM   currencies c, exchange_rates e
        WHERE  c.id     = 
          (SELECT cur_id FROM payments WHERE id = :p_id)
        AND    e.cur_id = 
          (SELECT cur_id FROM payments WHERE id = :p_id)
        AND    e.org_id = 
          (SELECT org_id FROM payments WHERE id = :p_id)
      ) / (
        SELECT c.factor
        FROM   currencies c, exchange_rates e
        WHERE  c.id     = 
          (SELECT cur_id FROM payments WHERE id = :p_id)
        AND    e.cur_id = 
          (SELECT cur_id FROM payments WHERE id = :p_id)
        AND    e.org_id = 
          (SELECT org_id FROM payments WHERE id = :p_id)
      ), 0
    ) 
    INTO amount 
    FROM dual;

    那么,这是做什么的呢? 这实际上将付款金额从一种货币转换为另一种货币。 我们不要过多地研究业务逻辑,而直接解决技术问题。 上面的查询产生以下执行计划(在Oracle上):

    ------------------------------------------------------
    | Operation                         | Name           |
    ------------------------------------------------------
    | SELECT STATEMENT                  |                |
    |  TABLE ACCESS BY INDEX ROWID      | PAYMENTS       |
    |   INDEX UNIQUE SCAN               | PAYM_PK        |
    |   NESTED LOOPS                    |                |
    |    INDEX UNIQUE SCAN              | CURR_PK        |
    |     TABLE ACCESS BY INDEX ROWID   | PAYMENTS       |
    |      INDEX UNIQUE SCAN            | PAYM_PK        |
    |    TABLE ACCESS BY INDEX ROWID    | EXCHANGE_RATES |
    |     INDEX UNIQUE SCAN             | EXCH_PK        |
    |      TABLE ACCESS BY INDEX ROWID  | PAYMENTS       |
    |       INDEX UNIQUE SCAN           | PAYM_PK        |
    |      TABLE ACCESS BY INDEX ROWID  | PAYMENTS       |
    |       INDEX UNIQUE SCAN           | PAYM_PK        |
    |     NESTED LOOPS                  |                |
    |      TABLE ACCESS BY INDEX ROWID  | CURRENCIES     |
    |       INDEX UNIQUE SCAN           | CURR_PK        |
    |        TABLE ACCESS BY INDEX ROWID| PAYMENTS       |
    |         INDEX UNIQUE SCAN         | PAYM_PK        |
    |      INDEX UNIQUE SCAN            | EXCH_PK        |
    |       TABLE ACCESS BY INDEX ROWID | PAYMENTS       |
    |        INDEX UNIQUE SCAN          | PAYM_PK        |
    |       TABLE ACCESS BY INDEX ROWID | PAYMENTS       |
    |        INDEX UNIQUE SCAN          | PAYM_PK        |
    |  FAST DUAL                        |                |
    ------------------------------------------------------

    在这种情况下,实际的执行时间可以忽略不计,但是如您所见,在查询中一次又一次地访问相同的对象。 这违反了常见错误4:始终运行相同的查询

    如果我们使用了通用的表表达式,那么整个内容将更容易阅读,维护和执行,对于Oracle而言。 从原始源代码中,观察以下内容:

    -- We're always accessing a single payment:
      FROM payments WHERE id = :p_id
    
    -- Joining currencies and exchange_rates twice:
      FROM currencies c, exchange_rates e

    因此,让我们先考虑付款:

    -- "payment" contains only a single payment
    -- But it contains all the columns that we'll need
    -- afterwards
    WITH payment AS (
        SELECT cur_id, org_id, amount
        FROM   payments
        WHERE  id = :p_id
    )
    SELECT round(p.amount * e.bid / c.factor, 0)
    
    -- Then, we simply don't need to repeat the
    -- currencies / exchange_rates joins twice
    FROM   payment p
    JOIN   currencies c     ON  p.cur_id = c.id
    JOIN   exchange_rates e ON  e.cur_id = p.cur_id
                            AND e.org_id = p.org_id

    请注意,我们也按照上一个列表中的建议将表列表替换为ANSI JOIN。

    您不会相信这是相同的查询,对吗? 那执行计划呢? 这里是!

    ---------------------------------------------------
    | Operation                      | Name           |
    ---------------------------------------------------
    | SELECT STATEMENT               |                |
    |  NESTED LOOPS                  |                |
    |   NESTED LOOPS                 |                |
    |    NESTED LOOPS                |                |
    |     FAST DUAL                  |                |
    |     TABLE ACCESS BY INDEX ROWID| PAYMENTS       |
    |      INDEX UNIQUE SCAN         | PAYM_PK        |
    |    TABLE ACCESS BY INDEX ROWID | EXCHANGE_RATES |
    |     INDEX UNIQUE SCAN          | EXCH_PK        |
    |   TABLE ACCESS BY INDEX ROWID  | CURRENCIES     |
    |    INDEX UNIQUE SCAN           | CURR_PK        |
    ---------------------------------------------------

    毫无疑问,这要好得多。

    治愈:

    如果您足够幸运,并且使用的是支持窗口功能的数据库之一,那么您获得通用表表达式支持的可能性就非常高(100%)。 这是您从MySQL迁移到PostgreSQL的另一个原因,或者赞赏您可以在很棒的商业数据库上工作的事实。

    公用表表达式就像SQL中的局部变量。 在每个大型语句中,一旦您觉得自己已经写过东西,就应该考虑使用它们。

    外卖:

    一些数据库(例如PostgreSQLSQL Server )也支持DML语句的常用表表达式。 换句话说,您可以编写:

    WITH ...
    UPDATE ...

    这使DML更加强大。

  • 不将行值表达式用于UPDATE

  • 我们在先前的清单中广告过使用行值表达式 。 它们非常易读和直观,并且通常还会促进使用某些索引,例如在PostgreSQL中。

    但是很少有人知道,在大多数数据库中,它们也可以在UPDATE语句中使用。 检验以下查询,我再次在客户的PL / SQL包中找到了该查询(当然,再次进行了简化):

    UPDATE u
    SET n = (SELECT n + 1    FROM t WHERE u.n = t.n),
        s = (SELECT 'x' || s FROM t WHERE u.n = t.n),
        x = 3;

    因此,此查询将子查询作为数据源来更新两列,并且第三列“定期”更新。 效果如何? 适度地:

    -----------------------------
    | Operation          | Name |
    -----------------------------
    | UPDATE STATEMENT   |      |
    |  UPDATE            | U    |
    |   TABLE ACCESS FULL| U    |
    |   TABLE ACCESS FULL| T    |
    |   TABLE ACCESS FULL| T    |
    -----------------------------

    在构造此查询时,让我们忽略全表扫描。 实际查询可以利用索引。 但是T被两次访问,即在两个子查询中。 在这种情况下,Oracle似乎无法应用标量子查询缓存

    急救:行值表达式。 让我们简单地将UPDATE改为:

    UPDATE u
    SET (n, s) = ((
          SELECT n + 1, 'x' || s FROM t WHERE u.n = t.n
        )),
        x = 3;

    让我们忽略这种行值表达式赋值右侧的有趣的,特定于Oracle的双括号语法,但是让我们欣赏这样的事实,我们可以轻松地一次性为元组(n, s)赋一个新值! 注意,我们也可以这样写,并同时分配x

    UPDATE u
    SET (n, s, x) = ((
          SELECT n + 1, 'x' || s, 3 
          FROM t WHERE u.n = t.n
        ));

    如您所料,执行计划也得到了改进,并且T仅被访问一次:

    -----------------------------
    | Operation          | Name |
    -----------------------------
    | UPDATE STATEMENT   |      |
    |  UPDATE            | U    |
    |   TABLE ACCESS FULL| U    |
    |   TABLE ACCESS FULL| T    |
    -----------------------------

    治愈:

    使用行值表达式。 只要有可能。 它们使您的SQL代码具有令人难以置信的更具表现力,并且也有可能使它更快。

    请注意,以上内容受jOOQ的UPDATE语句支持。 这是我们希望让您知道这个便宜的内置广告的时刻:

    jooq-在Java中编写SQL的最佳方法

  • 可以使用PostgreSQL时使用MySQL

  • 在某些人看来,这似乎有点时髦。 但是,让我们考虑一下事实:

    • MySQL声称是“最受欢迎的开源数据库”。
    • PostgreSQL声称是“最先进的开源数据库”。

    让我们考虑一下历史。 MySQL一直非常易于安装,维护,并且拥有一个强大而活跃的社区。 这导致MySQL仍然是该星球上几乎所有Web托管商的RDBMS选择。 这些托管者还托管PHP,同样易于安装和维护。

    但!

    我们Java开发人员倾向于对PHP有看法,对吗? 此图片在这里总结:

    PHP Hammer

    PHP Hammer

    好吧,它有效,但是如何起作用?

    关于MySQL也可以这样说。 MySQL总是以某种方式工作 ,但是尽管像Oracle这样的商业数据库在查询优化和功能范围方面都取得了巨大进步,但MySQL在过去十年中几乎没有动静。

    许多人选择MySQL的主要原因是它的价格(0.00美元)。 但是通常,同一个人发现MySQL速度很慢,并很快得出结论,SQL本身就很慢-无需评估选项。 这也是为什么所有NoSQL商店都将自己与MySQL而不是Oracle相提并论的原因,而Oracle一直是赢得交易处理性能委员会(TPC)基准的数据库。 一些例子:

    尽管上一篇文章直截了当地添加了“(和其他RDBMS)”,但并没有涉及任何细节,但是那些“其他RDBMS”做错了什么。 它实际上仅将MongoDB与MySQL进行了比较。

    治愈:

    我们说:停止抱怨SQL,实际上,您实际上是在抱怨MySQL。 至少有四个非常受欢迎的数据库非常好,比MySQL好几百万倍。 这些是:

    (当然,只是在跟最后一个开玩笑)

    外卖:

    不要因为积极的NoSQL营销而堕落。 10gen的 是一个 极其 资金雄厚的公司,即使MongoDB的继续令人失望,在技术上。

    同样 是真实的 Datastax

    两家公司都在解决很少有人遇到的问题。 他们将小众产品作为商品出售给我们,使我们认为真正的商品数据库(RDBMS)不再满足我们的需求。 他们资金雄厚,拥有庞大的营销团队,可以直言不讳。

    同时, PostgreSQL变得更好,而作为此博客/帖子的读者,您将押注获胜的团队!

    ……再次引用Mark Madsen

    免责声明:

    这篇文章强烈反对MySQL。 我们并不是要认真谈论能够完全实现其目的的数据库,因为这不是一个黑白世界。 哎呀,在某些情况下您可以对SQLite感到满意。 MySQL,便宜且易于使用,易于安装商品数据库。 我们只是想让你知道的事实,即你明确选择便宜的,不那么好的数据库,而不是廉价的,真棒之一。

  • 忘记UNDO / REDO日志

  • 我们声称MERGE语句或批量/批处理更新是好的。 没错,但是尽管如此,在事务上下文中更新大型数据集时,您应该保持警惕。 如果您的交易“花费的时间太长” ,即一次要更新一千万条记录,则会遇到两个问题:

    • 如果另一个进程也正在向同一表写入数据,则可能增加争用条件的风险。 这可能会导致他们或您的交易发生回滚,可能使您再次推出巨大的更新
    • 您会在系统上造成大量并发,因为每个其他事务/会话(要查看要更新的数据)都必须先临时回滚所有更新,直到它们到达磁盘状态为止您进行大量更新之前就已经存在。 那就是酸的价格。

    解决此问题的一种方法是允许其他会话读取未提交的数据。

    解决此问题的另一种方法是频繁提交自己的工作,例如在1000次插入/更新之后。

    无论如何,由于CAP定理 ,您将不得不做出让步。 如果在500万次(已提交)记录后数百万次更新出错,则频繁提交将产生数据库不一致的风险。 然后,回滚意味着将所有数据库更改还原为备份。

    治愈:

    此问题没有确定的解决方法。 但是请注意,在这种情况下,只需要在实际的计划维护窗口之外简单地更新活动表和在线表的1000万条记录就可以了。 实际上,最简单的解决方法是在N次插入/更新后提交您的工作。

    外卖:

    到这个时候,NoSQL的狂热者将再次声称(由于上述公司的过度营销),NoSQL已通过删除架构和类型安全性解决了这一问题。 “不要更新,只需添加另一个属性!” - 他们说。

    但这不是真的

    首先,我可以将列添加到数据库中而没有任何问题。 ALTER TABLE ADD语句在实时数据库上立即执行。 用数据填充列也不会打扰任何人,因为还没有人读取该列(请记住,不要SELECT * !)。 因此,在RDBMS中添加列与向MongoDB文档中添加JSON属性一样便宜。

    但是,如何更改列呢? 删除它们? 合并它们?

    非规范化将您带到任何地方都是不对的。 非规范化始终是开发人员的短期胜利。 对于运营团队而言,这几乎不是长期的胜利。 为了加快ALTER TABLE语句的执行,在数据库中拥有冗余数据就像在地毯下扫了灰尘。

    不要相信营销人员。 当您使用它时,请三思而后行,而忘记我们自己是SQL工具供应商! 这又是“正确的”消息:

    jooq-在Java2中编写SQL的最佳方法

  • 没有正确使用BOOLEAN类型

  • 这本身并不是真正的错误。 这又是几乎没人知道的事情。 当SQL:1999标准引入新的BOOLEAN数据类型时,他们确实做到了。 因为以前,我们在SQL中已经有类似布尔值的东西。 我们在SQL-92中具有<search condition> ,其本质上是在WHEREONHAVING子句以及CASE表达式中使用的谓词。

    但是,SQL:1999只是将新的<boolean value expression>定义为常规的<value expression> ,并重新定义了<search condition> ,如下所示:

    <search condition> ::=
        <boolean value expression>

    做完了! 现在,对于我们大多数Java / Scala等开发人员而言,这似乎不是一项创新。 哎呀,这是boolean 。 显然,它可以互换用作谓词和变量。

    但是,从那些在设计语言时从COBOL汲取灵感的大量使用SQL的人的思维定势,这是向前迈出了一大步。

    现在,这是什么意思? 这意味着您还可以将任何谓词用作列! 例如:

    SELECT a, b, c
    FROM (
      SELECT EXISTS (SELECT ...) a,
             MY_COL IN (1, 2, 3) b,
             3 BETWEEN 4 AND 5   c
      FROM   MY_TABLE
    ) t
    WHERE a AND b AND NOT(c)

    同意,这只是一个虚拟查询,但是您知道这有多强大吗?

    再次幸运的是,PostgreSQL完全支持此功能(与Oracle不同,Oracle在SQL中仍然没有任何BOOLEAN数据类型)。

    治愈:

    BOOLEAN使用BOOLEAN类型感觉非常正确,那就去做吧! 您可以将布尔值表达式转换为谓词,然后将谓词转换为布尔值表达式。 他们是一样的。 这使SQL变得如此强大。

    结论

    在过去的几年中, SQL通过诸如SQL:1999SQL:2003SQL:2008以及现在的SQL:2011等出色的标准稳步发展。 它是唯一幸存的主流声明性语言,现在XQuery对于主流来说已经被认为已经死了。 正如PL / SQL和T-SQL(以及其他过程方言)所示,它可以轻松地与过程语言混合。 如jOOQ所示,它可以轻松地与面向对象或功能语言混合。

    Data Geekery ,我们相信SQL是查询数据的最佳方法。 您不同意以上任何一项吗? 很好,您不必这样做。 有时,甚至我们都同意众所周知的温斯顿·丘吉尔(Winston Churchill)的话:

    除所有其他形式外,SQL是最糟糕的数据库查询形式。

    但是正如Yakov Fain最近所说的那样:

    您可以从SQL运行,但不能隐藏

    因此,让我们最好重新开始工作并学习这头野兽! 谢谢阅读。

    翻译自: https://www.javacodegeeks.com/2014/05/yet-another-10-common-mistakes-java-developers-make-when-writing-sql-you-wont-believe-the-last-one.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值