2

我想使用一个函数或宏来创建一个 SQL 语句,方法是使用一个填充了像键值一样的参数的列表。

例如: (("id" 3) ("version" 3) ("name" "foo") ("age" 10))

我想返回一个字符串

"insert into table ('id','version','name','age') values(3,3,'foo',10);"

这是我的代码:

(defvar *param* '(("id" 3) ("version" 3) ("name" "foo") ("age" 10)))

(defun dis (elt)
  (if (stringp elt)
      (concatenate 'string "'" elt "'")
      (write-to-string elt)))

(defun tostring (lst)
  (if (eql 1 (length lst))
      (dis (car lst))
      (concatenate 'string (dis (car lst)) "," (tostring (cdr lst)))))

(defun fillinsert (lst)
  (let ((keys (mapcar #'car lst))
        (values (mapcar #'cadr lst)))
    (concatenate 'string
                 "insert into table ("
                 (tostring keys)
                 ") values("
                 (tostring values)
                 ");")))

我不知道如何只创建一次访问列表的字符串。谢谢

4

3 回答 3

3

不使用FORMAT我写的代码如下。主要思想是使用通常的输出函数来创建所需的内容。然后WITHOUT-OUTPUT-TO-STRING表单将这个输出作为一个字符串返回——不管输出有多长,因此字符串是多长的。这样您就不必一直创建字符串和连接字符串,这既丑陋又通常效率低下。

(defun create-sql-sentence (kvl)
  (flet ((format-list (list key)
           (write-string "(")
           (loop for start = t then nil
                 for kv in list
                 for item = (funcall key kv)
                 unless start do (write-string ",")
                 do (typecase item
                      (string
                       (write-string "'")
                       (write-string item)
                       (write-string "'"))
                      (t
                       (write item))))
           (write-string ")")))                       
    (with-output-to-string (*standard-output*)
      (write-string "insert into table ")
      (format-list kvl #'first)
      (write-string " values")
      (format-list kvl #'second)
      (write-string ";"))))

以上不使用递归。它也不重复使用concatenated。它也不会反复调用length

于 2013-07-23T13:00:19.643 回答
1

这是一个格式解决方案:

CL-USER>
(setf *entry* '(("id" 3) ("version" 3) ("name" "foo") ("age" 10)))
(("id" 3) ("version" 3) ("name" "foo") ("age" 10))
CL-USER>
(format nil "insert into table (~{~S~^,~}) values (~{~S~^,~});"
        (mapcar #'first *entry*)
        (mapcar #'second *entry*))
"insert into table (\"id\",\"version\",\"name\",\"age\") values (3,3,\"foo\",10);"
CL-USER>

格式字符串使用 list ( ~{) 指令以指定格式打印列表中的项目。它使用波浪符 ( ~^) 指令禁用列表最右侧最后一个逗号的打印。~Sformat 指令确保每个对象都以 Lisp 阅读器可以读回的格式打印。这意味着字符串得到周围的引号,而数字没有。

我假设您的 SQL 引擎将双引号解释为与单引号相同。如果需要单引号,则需要更多的逻辑才能使事情正常工作。一种方法是扩展格式指令并定义您自己的~用单引号括起来的字符串。但我没有这样做,因为 IME 大多数 SQL 引擎将双引号解释为与单引号相同。

于 2013-07-24T05:38:50.223 回答
0

如果你想用它loop来构建你的字符串,并且只遍历列表一次,你可以使用循环的解构特性。fillinsert然后可以像这样定义您的函数:

(defun fillinsert (lst)
  (loop for (key value) in lst
        collect key into keys
        collect value into values
        finally (return (concatenate 'string
                                     "insert into table ("
                                     (tostring keys)
                                     ") values("
                                     (tostring values)
                                      ");")))

或(用于构建format字符串):

(defun fillinsert (lst)
  (loop for (key value) in lst
        collect key into keys
        collect value into values
        finally (return (format nil
                                "insert into table (~a) values (~a);"
                                (tostring keys)
                                (tostring values)))))
于 2013-07-23T22:23:16.120 回答