从人类可读的描述构建SQL查询是否有经验法则?

4
每当有查询描述出现在我们面前时,我们会尝试应用启发式和头脑风暴来构建查询。
是否有一种系统的逐步或数学方法可以从给定的人类可读描述中构造SQL查询?
例如,如何确定SQL查询是否需要连接而不是子查询,是否需要分组,是否需要IN子句等等...
例如,曾经学过数字电子学的人都知道像Karnaugh Map或Quin McClausky方法这样的方法。这些都是一些简化数字逻辑的系统方法。
如果有像这样的方法可以手动分析SQL查询以避免每次都进行头脑风暴吗?

1
那个描述会采用什么格式?人类可读的文本吗? - usr
2
有些东西可以用不同的方式来完成。因此,子查询或连接之间的选择可以完成相同的任务,但取决于数据库或数据的不同,可能会有不同的性能。 - Juan Carlos Oropeza
1
我刚刚在我的答案中编辑了一个链接,链接到一个允许以“人类可读”的形式直接表达查询的系统。根据我的回答,最好考虑查询的“含义”,而不是对其进行“描述”。@Nick.McDermaid 尽管自然语言不清晰,但我们最终必须将精确的正式表达映射到原始的非正式表达中,因此某些模糊性必须保留,即使是我们的正式表达的含义也是如此。(尽管我们可以确定正式陈述之间的联系。)但是,在谓词逻辑中直接使用受限制的自然语言进行推理有助于解决问题。 - philipxy
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - philipxy
1
@Nick.McDermaid,你似乎将“数据模型”用于“数据库模式”。谓词决定了模型/模式表中的内容以及它们所陈述的内容。你评论中的谓词、表模式、表值、查询表达式和查询值不同,但查询命题(表格告诉你什么)是相同的(假设“期初余额”值为OpeningBalance)。因此,更新表格和解释查询需要有每个基本表格谓词的清晰自然语言版本(我不是指WHERE SQL“谓词”即条件)。 - philipxy
显示剩余10条评论
2个回答

3
是否有一种系统化的、逐步的或数学方法,可以从给定的人类可读描述中构建一个SQL查询?
是的,确实有。
事实上,自然语言表达式、逻辑表达式、关系代数(和微积分)表达式和SQL表达式(最后两者的混合体)在相当直接的方式上对应。 (下面的内容是针对没有重复行和没有null值的情况。)
每个表格(基础表格或查询结果)都有一个相关联的谓词——一个自然语言填充-(命名)空白语句模板,由列名参数化。
[liker] likes [liked]

表格存储每一行,使用行的列值填充(命名的)空白,生成一个真命题。这是一个带有该谓词及其行命题的表格:

liker  | liked
--------------
Bob    | Dex    /* Bob likes Dex */
Bob    | Alice  /* Bob likes Alice */
Alice  | Carol  /* Alice likes Carol */

将谓词用表中一行的值填充,得到的每个命题都是真的。而将谓词用不在表中的一行的值填充,得到的每个命题都是假的。下面是表格内容:

/*
    Alice likes Carol
AND NOT Alice likes Alice
AND NOT Alice likes Bob
AND NOT Alice likes Dex
AND NOT Alice likes Ed
...
AND Bob likes Alice
AND Bob likes Dex
AND NOT Bob likes Bob
AND NOT Bob likes Carol
AND NOT Bob likes Ed
...
AND NOT Carol likes Alice
...
AND NOT Dex likes Alice
...
AND NOT Ed likes Alice
...
*/   

数据库管理员为每个基本表提供谓词。表声明的SQL语法非常类似于给定谓词的自然语言版本的传统逻辑简写。下面是一个声明基本表以保存我们的值的示例:

/* (person, liked) rows where [liker] likes [liked] */
/* (person, liked) rows where Likes(liker, liked) */
CREATE TABLE Likes (
    liker ...,
    liked ...
);

假设R的谓词是r,而S的谓词是s

一个SQL查询(子)表达式将参数表中的值转换为一个新的表值,其中包含使新谓词成立的行。根据(子)表达式的关系/表运算符,可以用参数表谓词来表达新表谓词。查询是一个SQL表达式,其谓词是我们想要的行表的谓词。

当我们给一个表和(可能隐含的)别名A加入时,该运算符会对一个值和谓词进行操作,类似于表的操作,但列名从C,...改为A.C,...。然后

  • R , S & R CROSS JOIN S are rows where r AND s

  • R WHERE condition is rows where r AND condition

  • R INNER JOIN S ON condition is rows where r AND s AND condition

  • R LEFT JOIN S ON condition is rows where (for S-only columns S1,...)

         r AND s AND condition
     OR
             r
         AND NOT FOR SOME values for S1,... [s AND condition]
         AND S1 IS NULL AND ...
    
  • SELECT DISTINCT A.C AS D,... FROM R (maybe with implicit A. and/or implicit AS D) is rows where

    • FOR SOME values for A.*,... [A.C = D AND ... AND r] (This can be less compact but looks more like the SQL.)
    • if there are no dropped columns, r with A.C,... replaced by D,...
    • if there are dropped columns, FOR SOME values for the dropped columns [ r with A.C,... replaced by D,... ]
  • (X,...) IN (R) means

    • r with columns C,... replaced by X,...
    • (X,...) IN R

例子:对于(人,喜欢)行的自然语言,其中[person]是Bob,Bob喜欢某个喜欢[liked]但不喜欢Ed的人:

/* (person, liked) rows where
FOR SOME value for x,
        [person] likes [x]
    and [x] likes [liked]
    and [person] = 'Bob'
    and not [x] likes 'Ed'
*/

使用简写谓词进行重写:

/* (person, liked) rows where
FOR SOME value for x,
        Likes(person, x)
    AND Likes(x, liked)
    AND person = 'Bob'
    AND NOT Likes(x, 'Ed')
*/

仅使用基本表和别名表的简写谓词进行重写:

/* (person, liked) rows where
FOR SOME values for l1.*, l2.*,
        person = l1.liker AND liked = l2.liked
    AND Likes(l1.liker, l1.liked)
    AND Likes(l2.liker, l2.liked)
    AND l1.liked = l2.liker
    AND person = 'Bob'
    AND NOT (l1.liked, 'Ed') IN Likes
*/

在SQL中重写:

SELECT DISTINCT l1.liker AS person, l2.liked AS liked
    /* (l1.liker, l1.liked, l2.liker, l2.liked) rows where
        Likes(l1.liker, l1.liked)
    AND Likes(l2.liker, l2.liked)
    AND l1.liked = l2.liker
    AND l1.liker = 'Bob'
    AND NOT (l1.liked, 'Ed') IN Likes
    */
FROM Likes l1
INNER JOIN Likes l2
ON l1.liked = l2.liker
WHERE l1.liker = 'Bob'
AND NOT (l1.liked, 'Ed') IN (SELECT * FROM Likes)

同样地,
  • R UNION CORRESPONDING S 是行,其中 r OR s

  • R UNION S 是行,其中 r OR s 的列由 R 的列替换

  • VALUES (X,...), ... 的列为 C,... 是行,其中 C = X AND ... OR ...

示例:

/* (person) rows where
    FOR SOME value for liked, Likes(person, liked)
OR  person = 'Bob'
*/
    SELECT liker AS person
    FROM Likes
UNION
    VALUES ('Bob')

所以,如果我们用给定的基本表自然语言模板来表达我们想要的行,这些行使得真或假(返回或不返回),那么我们可以将其翻译成SQL查询,这些查询是逻辑简写和运算符以及/或表名和运算符的嵌套。然后DBMS可以完全转换为表格来计算使我们的谓词成立的行。
参见如何从另一个SQL表中获取匹配数据以用于两个不同的列:内部连接和/或联合?(英文),了解如何将其应用于SQL。(另一个自连接。)
参见面向银行场景的关系代数,了解更多关于自然语言措辞的内容。(在关系代数的背景下。)
参见关系代数中的空值,了解关系查询的另一种表现形式。(英文)

虽然我不能完全同意你的观点,但这是非常有趣的看法。 - Alex Yu

1
这是我在非分组查询中的做法:
我将期望每行表格输出零个或一个结果行的表格放入FROM子句中。通常,您需要像“具有某些属性的所有客户”这样的内容。然后,客户表格就放在FROM子句中。
使用联接添加列和筛选行。联接不应重复行。联接应查找零个或一个行,从而保持直观性,因为您可以说“联接添加列并筛选出一些行”。
如果联接可以替换子查询,则应避免使用子查询。联接看起来更好,更通用,而且通常更有效(由于常见的查询优化器弱点)。
如何使用WHERE和投影很容易。

对于分组问题,我会首先建立非分组的问题。然后,添加分组相对容易,因为分组列是您想要每个唯一组合行的列。我认为难点是通过 from 和 join 将表合并。其余部分则更加直观。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接