如何使用SQL参数列表的循环在Common Lisp中创建SQL字符串

时间:2013-07-23 11:12:58

标签: common-lisp

我想使用函数或宏来创建一个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)
                 ");")))

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

3 个答案:

答案 0 :(得分: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

答案 1 :(得分: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>

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

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

答案 2 :(得分:0)

如果您想使用loop来构建字符串,并且只遍历列表一次,则可以使用循环的destructuring功能。您的函数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)))))