使用Ruby转换任何查询以计算查询

时间:2017-01-28 04:38:25

标签: sql ruby postgresql

在我的应用程序中,我使用PG执行用户在应用程序中定义的查询。

require 'pg'
database = PG.connect(*credentials)
query = 'select id, created_at from users where id % 2 = 0'
database.connection.exec(query) 

部分应用程序需要在运行实际查询之前获取计数,因此我使用正则表达式将查询转换为计数查询。 (假设不允许LIMIT和ORDER BY)

query = 'select id, created_at from users where id % 2 = 0'
query.gsub!(%r{(?<=SELECT)[^\/]+(?=FROM)}, ' count(*) ')
count = database.exec(query).first['count'].to_i

但如果查询包含CTE和/或子查询......

query = 'with new_table as (select id from users where id % 2 = 0)   
select created_at, name from users where id in (select * from new_table)'

上面的正则表达式不起作用,我还没能找到另一种基于正则表达式的解决方案。

使用SQL,Ruby或REGEX,我如何将只读数据库用户可以执行的任何查询转换为计数查询而不将查询包装在自己的CTE中,或只是运行查询并计算结果?

更简单地说,给定一个查询,如何在不实际运行完整查询的情况下获取该查询的行数?

Looker,PeriscopeData或Mode的任何工程师都应该把这个放在包里: - )

2 个答案:

答案 0 :(得分:0)

使用正则表达式修改SQL查询是不可取的,因为您不尝试使用正则表达式修改XML:您需要能够理解语法的内容。您正在寻找的是SQL查询生成器。

SQL查询生成器有点像没有ORM的ORM。您使用它来使用方法调用而不是字符串来编写SQL查询,但您不必告诉它所有表和列都像ORM,也不必为所有表创建类。它只是进行SQL查询。

您的查询作为对象保存,只有在与数据库通信时才会变为SQL。如果要修改查询,可以使用方法调用并重新生成SQL。因此,您可以添加where子句和分组和限制,并添加更多行以选择和连接表,是,计数。

它们通常也会为您解决SQL不兼容问题,因此相同的代码可以在MySQL或SQLite或Postgresql或Oracle上运行。

一个好的非Ruby版本在Javascript中是Knex.js。我很难找到适用于Ruby的纯SQL查询生成器。我发现的那些(ActiveRecord,Sequel,squeel和ARel)都是ORM,需要你设置类和模式以及所有这些。我唯一能找到的不是ORM的Ruby是qdsl

答案 1 :(得分:0)

简单方法:只需围绕创建一个新查询,然后将其转换为subquery

require 'pg'
database = PG.connect(*credentials)
query = 'select id, created_at from users where id % 2 = 0'

# Create a `count` query, based on the existing one. 
# The original query must NOT end with ';'
count_query = 'SELECT count(*) AS count FROM (' + query + ') AS q0' 
database.connection.exec(count_query) 

# Follow on
database.connection.exec(query)

这可能不是获得计数的最高效方式,但我认为这是最简单的(可能)不那么容易出错。它假定原始查询格式正确,并且它不会调用具有副作用的函数[应该为很少的用例保留的做法]。

假设users表类似于:

CREATE TABLE users AS
SELECT * FROM 
(VALUES 
    (1::integer, 'name1'::text, now()::timestamp),
    (2,          'name2',       now() - interval '1 hour'),
    (3,          'name3',       now() - interval '2 hours'),
    (4,          'name4',       now() - interval '2 hours')
) x(id, name, created_at) ;

此解决方案适用于WITH条款,因为以下SQL查询是合法的:

SELECT count(*) FROM 
(
    with new_table as (select id from users where id % 2 = 0)   
    select created_at, name from users where id in (select * from new_table)
) AS q0  ;

...并按预期返回2