SQLite/Postgres/Heroku: Problem translating query - ruby-on-rails

Heroku throws an error on my Postgres-Query stating:
ActiveRecord::StatementInvalid
(PGError: ERROR: syntax error at or
near "date" 2011-05-03T13:58:22+00:00
app[web.1]: LINE 1: ...2011-05-31')
GROUP BY EXTRACT(YEAR FROM TIMESTAMP
date)||EXT...
The SQLite query in development works as expected. Here is the code:
def self.calculate(year, month, user_id, partner_id)
case ActiveRecord::Base.connection.adapter_name
when 'SQLite'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('entries.date, ' +
'sum(case when joint = "f" then amount_calc else 0 end) as sum_private, ' +
'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' +
'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +
'sum(case when compensation = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
'sum(case when compensation = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group("strftime('%Y-%m', date)")
when 'PostgreSQL'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('entries.date, ' +
'sum(case when joint = "f" then amount_calc else 0 end) as sum_private, ' +
'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' +
'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +
'sum(case when compensation = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
'sum(case when compensation = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group("EXTRACT(YEAR FROM TIMESTAMP date)||EXTRACT(MONTH FROM TIMESTAMP date)")
else
raise 'Query not implemented for this DB adapter'
end
end
I would really appreciate any hints. And as I am already asking a question here, I am uncertain about the case when joint = "t" in the sums in both queries too, is there a better way to do this?
UPDATE
Thanks to both peufeu and a horse with no name the code now looks like:
when 'PostgreSQL'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('min(entries.date) as date, ' +
'sum(case when joint = false then amount_calc else 0 end) as sum_private, ' +
'sum(case when joint = true and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' +
'sum(case when joint = true and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +
'sum(case when compensation = true and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
'sum(case when compensation = true and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group('EXTRACT(YEAR FROM "date"), EXTRACT(MONTH FROM "date")')
...and works like expected.
Another of my statement runs into troubles now and I edit it here as it seems related to the answer of peufeu. Model/Controller:
def self.all_entries_month(year, month, user_id, partner_id)
mydate = Date.new(year, month, 1)
where(':user_id = entries.user_id OR (:partner_id = entries.user_id AND entries.joint = :true)', {
:user_id => user_id,
:partner_id => partner_id,
:true => true
}).
where(':first_day <= entries.date AND entries.date <= :last_day', {
:first_day => mydate,
:last_day => mydate.at_end_of_month
})
end
# group by tag and build sum of groups named group_sum
def self.group_by_tag
group('tag').
select('entries.*, sum(amount_calc) as group_sum')
end
controller:
#income = Entry.all_entries_month(#year, #month, current_user.id, current_partner.id).income
#cost = Entry.all_entries_month(#year, #month, current_user.id, current_partner.id).cost
# group cost by categories
#group_income = #income.group_by_tag.order('group_sum desc')
#group_cost = #cost.group_by_tag.order('group_sum')
The error is:
ActionView::Template::Error (PGError: ERROR: column "entries.id" must appear in the GROUP BY clause or be used in an aggregate function
2011-05-03T18:35:20+00:00 app[web.1]: : SELECT entries.*, sum(amount_calc) as group_sum FROM "entries" WHERE (1 = entries.user_id OR (2 = entries.user_id AND entries.joint = 't')) AND ('2011-04-01' <= entries.date AND entries.date <= '2011-04-30') AND (amount_calc <= 0 AND compensation = 'f') GROUP BY tag ORDER BY group_sum):
2011-05-03T18:35:20+00:00 app[web.1]: 6: </thead>
2011-05-03T18:35:20+00:00 app[web.1]: 7: <tbody>
2011-05-03T18:35:20+00:00 app[web.1]: 8: <% if categories %>
2011-05-03T18:35:20+00:00 app[web.1]: 9: <% categories.each do |category| %>
2011-05-03T18:35:20+00:00 app[web.1]: 10: <tr>
2011-05-03T18:35:20+00:00 app[web.1]: 11: <td class="align-left"><%= category.tag %></td>
2011-05-03T18:35:20+00:00 app[web.1]: 12: <td class="align-right"><%= my_number_to_percentage (category.group_sum.to_f / total_cost) * 100 %></td>
UPDATE 2: I found the solution
# group by tag and build sum of groups named group_sum
def self.group_by_tag
group('tag').
select('tag, sum(amount_calc) as group_sum')
end

Problem is there :
EXTRACT(YEAR FROM TIMESTAMP date)||EXTRACT(MONTH FROM TIMESTAMP date)
Solution :
EXTRACT(YEAR FROM "date"), EXTRACT(MONTH FROM "date")
The word "TIMESTAMP" is a bit out of place there ;) ... also :
Since DATE is a reserved SQL keyword, it is a very bad idea to use it as a column name. Here, I quoted it using " so postgres doesn''t get confused.
And you don't need to waste CPU time building a string concatenating your two EXTRACTs, just remember GROUP BY can use several parameters.
Then of course the query will fail because you SELECT "date" but your dont' GROUP BY date. postgres has no way to know which date you want from all the rows which have the same Year-Month. MySQL will return a value at random from the rows, postgres likes correctness so it will throw an error. You could SELECT min(date) for instance, which would be correct.
I am uncertain about the case when joint = "t"
That depends on how you want to get your results, nothing wrong there.
Maybe using several queries would scan a smaller portion of the table (I don't know about your dataset) or maybe not.

I don't know ruby/heroku, but the expression
joint = "t"
refers to a column t in PostgreSQL because object names are quoted with double quotes. So unless Ruby/Heroku is replacing those double quotes with single quotes that will be an invalid condition.
If t should be string literal (the character t) then you need to use single quotes: joint = 't'
If joint is of type boolean then you should use joint = true in PostgreSQL

Related

Dynamic Sql Error , Invalid syntax near '>'

I have the below dynamic sql statement and I'm getting the "Incorrect syntax near '>'. error message. I can not figure out what the problem is, not sure if I'm missing a tick mark or not. I've tried to add extra tick marks and nothing seems to work.
Declare #filters varchar(max)
SET #filters = 'Where PaymentAmount > 0'
BEGIN
SET #filters = #filters + ' AND CONVERT(DATE, AccountingDate) >= '''+ cast #BeginDate as nvarchar) + ''''
SET #filters = #filters + ' AND CONVERT(DATE, AccountingDate) <= '''+ cast(#EndDate as nvarchar) + ''''
END
SET #SQLString = 'Select
,[ReturnDate]
,[PolicyNumber]
From dbo.Bil_ReturnsRepository' + #filters
EXEC(#SQLString)
You need another space before you concat #filters to #SQLString.
SET #SQLString = 'Select
,[ReturnDate]
,[PolicyNumber]
From dbo.Bil_ReturnsRepository ' + #filters
Otherwise the generated sql would be
...
From dbo.Bil_ReturnsRepositoryWhere PaymentAmount > 0
...

How to get user name who updated the data from UI in MVC 5 and Entity Framework

We have a website in ASP.NET MVC 5 with Entity Framework.
When a logged in user made changes in UI (i.e. update the data) we save/update/delete the data in SQL Server as per operation performed by the user.
We also have a trigger for audit trailing.
With the trigger, table format for storing data is as below:
[AuditID]
[Type] -- Contains operation performed (Insert (I), Update (U), Delete (D))
[TableName]
[PK] -- Primary Key
[FieldName]
[OldValue]
[NewValue]
[UpdateDate]
[UserName]
We are storing SYSTEM_USER in [UserName] column.
If we have any solution to store the [UserName] who actually made changes from UI instead of system_user?
Do we have any approach to pass [UserName] from application (UI) to the trigger?
Please share your thoughts.
I have one solution - to add UpdatedBy column name in all the tables so that trigger can easily get the value of UpdatedBy column from magic tables or from main tables.
Please suggest best approach.
Below is the trigger used.
CREATE TRIGGER [ids].[tr_AuditEmploee]
ON Employee
FOR INSERT, UPDATE, DELETE
AS
DECLARE #bit INT,
#field INT,
#maxfield INT,
#char INT,
#fieldname VARCHAR(128),
#TableName VARCHAR(128),
#PKCols VARCHAR(1000),
#sql VARCHAR(2000),
#UpdateDate VARCHAR(21),
#UserName VARCHAR(128),
#Type CHAR(1),
#PKSelect VARCHAR(1000)
--You will need to change #TableName to match the table to be audited
SELECT #TableName = 'Employee'
-- date and user
SELECT
#UserName = SYSTEM_USER,
#UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112) + ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)
-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT #Type = 'U'
ELSE
SELECT #Type = 'I'
ELSE
SELECT #Type = 'D'
-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
-- Get primary key columns for full outer join
SELECT
#PKCols = COALESCE(#PKCols + ' and', ' on')
+ ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE
pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT
#PKSelect = COALESCE(#PKSelect+'+','')
+ '''' + COLUMN_NAME
+ '=''+convert(varchar(100),
coalesce(i.' + COLUMN_NAME +', d.' + COLUMN_NAME + '))+'''''
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE
pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF #PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, #TableName)
RETURN
END
SELECT
#field = 0,
#maxfield = MAX(ORDINAL_POSITION)
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = #TableName
WHILE #field < #maxfield
BEGIN
SELECT #field = MIN(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND ORDINAL_POSITION > #field
SELECT #bit = (#field - 1 )% 8 + 1
SELECT #bit = POWER(2,#bit - 1)
SELECT #char = ((#field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),#char, 1) & #bit > 0 OR #Type IN ('I','D')
BEGIN
SELECT #fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND ORDINAL_POSITION = #field
SELECT #sql = '
insert [audit].[AuditEmployee] ( Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
select ''' + #Type + ''','''
+ #TableName + ''',' + #PKSelect
+ ',''' + #fieldname + ''''
+ ',convert(varchar(1000),d.' + #fieldname + ')'
+ ',convert(varchar(1000),i.' + #fieldname + ')'
+ ',''' + #UpdateDate + ''''
+ ',''' + #UserName + ''''
+ ' from #ins i full outer join #del d'
+ #PKCols
+ ' where i.' + #fieldname + ' <> d.' + #fieldname
+ ' or (i.' + #fieldname + ' is null and d.' + #fieldname + ' is not null)'
+ ' or (i.' + #fieldname + ' is not null and d.' + #fieldname + ' is null)'
EXEC (#sql)
END
END

How can I get the table description (fields and types) from Firebird with dbExpress

I have written a tool for displaying database structures using the GetTableNames and GetFieldNames methods of TSQLConnection. How can I get the types of each field name similar to the following list (which is part of the DDL required to build the table)?
TABLE: ARTICLES
ID INTEGER NOT NULL
PRINTED SMALLINT DEFAULT 0
ACADEMIC SMALLINT
RELEVANCE SMALLINT
SOURCE VARCHAR(64) CHARACTER SET WIN1251 COLLATE WIN1251
NAME VARCHAR(128) CHARACTER SET WIN1251 COLLATE WIN1251
FILENAME VARCHAR(128) CHARACTER SET WIN1251 COLLATE WIN1251
NOTES VARCHAR(2048) CHARACTER SET WIN1251 COLLATE WIN1251
This is incomplete (because I've never used Firebird array data types) and not much tested but perhaps it will give you a good starting point:
SELECT
RF.RDB$FIELD_NAME FIELD_NAME,
CASE F.RDB$FIELD_TYPE
WHEN 7 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'SMALLINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 8 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'INTEGER'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 9 THEN 'QUAD'
WHEN 10 THEN 'FLOAT'
WHEN 12 THEN 'DATE'
WHEN 13 THEN 'TIME'
WHEN 14 THEN 'CHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ') '
WHEN 16 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'BIGINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 27 THEN 'DOUBLE'
WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN 'VARCHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')'
WHEN 40 THEN 'CSTRING' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')'
WHEN 45 THEN 'BLOB_ID'
WHEN 261 THEN 'BLOB SUB_TYPE ' || F.RDB$FIELD_SUB_TYPE
ELSE 'RDB$FIELD_TYPE: ' || F.RDB$FIELD_TYPE || '?'
END FIELD_TYPE,
IIF(COALESCE(RF.RDB$NULL_FLAG, 0) = 0, NULL, 'NOT NULL') FIELD_NULL,
CH.RDB$CHARACTER_SET_NAME FIELD_CHARSET,
DCO.RDB$COLLATION_NAME FIELD_COLLATION,
COALESCE(RF.RDB$DEFAULT_SOURCE, F.RDB$DEFAULT_SOURCE) FIELD_DEFAULT,
F.RDB$VALIDATION_SOURCE FIELD_CHECK,
RF.RDB$DESCRIPTION FIELD_DESCRIPTION
FROM RDB$RELATION_FIELDS RF
JOIN RDB$FIELDS F ON (F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE)
LEFT OUTER JOIN RDB$CHARACTER_SETS CH ON (CH.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID)
LEFT OUTER JOIN RDB$COLLATIONS DCO ON ((DCO.RDB$COLLATION_ID = F.RDB$COLLATION_ID) AND (DCO.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID))
WHERE (RF.RDB$RELATION_NAME = :TABLE_NAME) AND (COALESCE(RF.RDB$SYSTEM_FLAG, 0) = 0)
ORDER BY RF.RDB$FIELD_POSITION;
Use direct access to RDB$ tables. For example:
SELECT * FROM rdb$relations
will give you a list of all tables in a database.
SELECT
*
FROM
rdb$relation_fields rf JOIN rdb$fields f
ON f.rdb$field_name = rf.rdb$field_source
WHERE
rf.rdb$relation_name = :RN
will result in a list of all fields of given table
with information of field type. Param RN is a name of the table.
Using information from RDB$tables one can easily construct DDL
statement. The query below gives you a hint how to do it:
SELECT
TRIM(rf.rdb$field_name) || ' ' ||
IIF(rdb$field_source LIKE 'RDB$%',
DECODE(f.rdb$field_type,
8, 'INTEGER',
12, 'DATE',
37, 'VARCHAR',
14, 'CHAR',
7, 'SMALLINT'),
TRIM(rdb$field_source)) ||
IIF((rdb$field_source LIKE 'RDB$%') AND (f.rdb$field_type IN (37, 14)),
'(' || f.rdb$field_length || ')',
'') ||
IIF((f.rdb$null_flag = 1) OR (rf.rdb$null_flag = 1),
' NOT NULL', '')
FROM
rdb$relation_fields rf JOIN rdb$fields f
ON f.rdb$field_name = rf.rdb$field_source
WHERE
rf.rdb$relation_name = '<put_your_table_name_here>'
Using the link which TLama provided, I found my own solution, which is somewhat similar to the above solutions, but simpler.
SELECT R.RDB$FIELD_NAME AS field_name,
CASE F.RDB$FIELD_TYPE
WHEN 7 THEN 'SMALLINT'
WHEN 8 THEN 'INTEGER'
WHEN 9 THEN 'QUAD'
WHEN 10 THEN 'FLOAT'
WHEN 11 THEN 'D_FLOAT'
WHEN 12 THEN 'DATE'
WHEN 13 THEN 'TIME'
WHEN 14 THEN 'CHAR'
WHEN 16 THEN 'INT64'
WHEN 27 THEN 'DOUBLE'
WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN 'VARCHAR'
WHEN 40 THEN 'CSTRING'
WHEN 261 THEN 'BLOB'
ELSE 'UNKNOWN'
END AS field_type,
F.RDB$FIELD_LENGTH AS field_length,
CSET.RDB$CHARACTER_SET_NAME AS field_charset
FROM RDB$RELATION_FIELDS R
LEFT JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME
LEFT JOIN RDB$CHARACTER_SETS CSET ON F.RDB$CHARACTER_SET_ID = CSET.RDB$CHARACTER_SET_ID
WHERE R.RDB$RELATION_NAME= :p1
ORDER BY R.RDB$FIELD_POSITION
p1 is the table name which is passed as a parameter to the query.
In context, I have a treeview which has as its nodes the table names of a given database; for each node, the child nodes are the fields along with their definitions.
sqlcon.GetTableNames (dbTables); // sqlcon is the TSQLConnection
tv.items.Clear;
for i:= 1 to dbTables.count do
begin
node:= tv.items.Add (nil, dbTables[i - 1]);
with qFields do // the above query
begin
params[0].asstring:= dbTables[i - 1];
open;
while not eof do
begin
tv.items.addchild (node, trim (fieldbyname ('field_name').asstring) + ', ' +
trim (fieldbyname ('field_type').asstring) + ', ' +
fieldbyname ('field_length').asstring + ', ' +
fieldbyname ('field_charset').asstring);
next
end;
close
end
end;
Here is a screenshot of the program in action. I realise that the format is not the same as the DDL which I quoted, but it's obvious what each field means (at least to me, and this is a program for my private use).
I made a litle change to the first option to support computed by fields, add field_position and made a view to make more easy.
CREATE VIEW TABLES (
TABLE_NAME,
FIELD_NAME,
FIELD_POSITION,
FIELD_TYPE,
FIELD_NULL,
FIELD_CHARSET,
FIELD_COLLATION,
FIELD_DEFAULT,
FIELD_CHECK,
FIELD_DESCRIPTION
)
AS
SELECT
RF.RDB$RELATION_NAME,
RF.RDB$FIELD_NAME FIELD_NAME,
RF.RDB$FIELD_POSITION FIELD_POSITION,
CASE F.RDB$FIELD_TYPE
WHEN 7 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'SMALLINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 8 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'INTEGER'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 9 THEN 'QUAD'
WHEN 10 THEN 'FLOAT'
WHEN 12 THEN 'DATE'
WHEN 13 THEN 'TIME'
WHEN 14 THEN 'CHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ') '
WHEN 16 THEN
CASE F.RDB$FIELD_SUB_TYPE
WHEN 0 THEN 'BIGINT'
WHEN 1 THEN 'NUMERIC(' || F.RDB$FIELD_PRECISION || ', ' || (-F.RDB$FIELD_SCALE) || ')'
WHEN 2 THEN 'DECIMAL'
END
WHEN 27 THEN 'DOUBLE'
WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN
IIF (COALESCE(f.RDB$COMPUTED_SOURCE,'')<>'',
'COMPUTED BY ' || CAST(f.RDB$COMPUTED_SOURCE AS VARCHAR(250)),
'VARCHAR(' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')')
WHEN 40 THEN 'CSTRING' || (TRUNC(F.RDB$FIELD_LENGTH / CH.RDB$BYTES_PER_CHARACTER)) || ')'
WHEN 45 THEN 'BLOB_ID'
WHEN 261 THEN 'BLOB SUB_TYPE ' || F.RDB$FIELD_SUB_TYPE
ELSE 'RDB$FIELD_TYPE: ' || F.RDB$FIELD_TYPE || '?'
END FIELD_TYPE,
IIF(COALESCE(RF.RDB$NULL_FLAG, 0) = 0, NULL, 'NOT NULL') FIELD_NULL,
CH.RDB$CHARACTER_SET_NAME FIELD_CHARSET,
DCO.RDB$COLLATION_NAME FIELD_COLLATION,
COALESCE(RF.RDB$DEFAULT_SOURCE, F.RDB$DEFAULT_SOURCE) FIELD_DEFAULT,
F.RDB$VALIDATION_SOURCE FIELD_CHECK,
RF.RDB$DESCRIPTION FIELD_DESCRIPTION
FROM RDB$RELATION_FIELDS RF
JOIN RDB$FIELDS F ON (F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE)
LEFT OUTER JOIN RDB$CHARACTER_SETS CH ON (CH.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID)
LEFT OUTER JOIN RDB$COLLATIONS DCO ON ((DCO.RDB$COLLATION_ID = F.RDB$COLLATION_ID) AND (DCO.RDB$CHARACTER_SET_ID = F.RDB$CHARACTER_SET_ID))
WHERE (COALESCE(RF.RDB$SYSTEM_FLAG, 0) = 0)
ORDER BY RF.RDB$FIELD_POSITION
;

find in a "anded" array

I have 2 tables : entradas (id, blah...) and georelaciones (:entrada_id, lugar_id, blah...). georelaciones is just a link table for a polymorphic relation.
I want to retrieve all the entradas which are in relation with each lugar_id (pass in an array).
I have build this ugly scope ("d" will be an array of lugar_id):
scope :con_pais, lambda {|d|
# select e.id from entradas as e inner join georelaciones as g1 on (g1.lugar_id = 55 and g1.entrada_id = e.id) inner join georelaciones as g2 on (g2.lugar_id = 66 and g2.entrada_id = e.id)
cadena = ""
i = 0
d.each{ |lugar_id| i += 1 ; cadena << " inner join georelaciones as g" + i.to_s + " on (g"+ i.to_s + ".lugar_id = " + lugar_id.to_s + " and g" + i.to_s + ".entrada_id = e.id)"}
entradas = Entrada.find_by_sql("select distinct e.id from entradas as e " + cadena)
a = []
entradas.each{ |e| a << e.id }
Entrada.where("id in (?)", a)
}
I know that's not good because my table entradas has some million records and I do not take advantage of lazy loading due to the use of find_by_sql.
How can I query the database and return an ActiveRecord::Relation directly without using find_by_sql?
Entrada.find_by_sql("select distinct e.id from entradas as e " + cadena)
=>
Entrada.select("distinct e.id as e #{cadena}")
Assuming that I got your relations correct, you should be able to do something like this:
class Georelacion < ActiveRecord::Base
belongs_to :entrada
scope :con_pais, lambda { |d|
includes(:entrada).where("georelaciones.lugar_id IN (?)", d)
}
...
end
And then in the controller:
#lugar_ids = [1,2,3,4]
#georelaciones = Georelacion.con_pais(#lugar_ids).group_by(&:lugar_id)
That should give you a hash where each key is a lugar_id and the value for each key is an array of georelaciones with the associated entrada already loaded from the database.

ActiveRecord/Postgres: PGError must appear in the GROUP BY clause or be used in an aggregate function

In my model I have a couple of queries that can be used (and re-used) one after another. One of those should aggregate amounts. This works fine on SQLite and throws an error on Postgres:
ActiveRecord::StatementInvalid (PGError: ERROR: column "entries.date" must appear in the GROUP BY clause or be used in an aggregate function
: SELECT sum(case when joint = false then amount else amount / 2 end) as total, sum(case when joint = false then amount else 0 end) as sum_personal, sum(case when joint = true and user_id = 1 then amount / 2 else 0 end) as sum_user_joint, sum(case when joint = true and user_id = 2 then amount / 2 else 0 end) as sum_partner_joint FROM "entries" WHERE (1 = entries.user_id OR (2 = entries.user_id AND entries.joint = 't')) AND ('2011-04-01' <= entries.date AND entries.date <= '2011-04-30') AND (amount_calc > 0 AND compensation = 'f') ORDER BY date asc)
Relevant part of Model.rb
# all entries of one month
def self.all_entries_month(year, month, user_id, partner_id)
mydate = Date.new(year, month, 1)
where(':user_id = entries.user_id OR (:partner_id = entries.user_id AND entries.joint = :true)', {
:user_id => user_id,
:partner_id => partner_id,
:true => true
}).
where(':first_day <= entries.date AND entries.date <= :last_day', {
:first_day => mydate,
:last_day => mydate.at_end_of_month
})
end
def self.income
where('amount_calc > 0 AND compensation = ?', false)
end
def self.cost
where('amount_calc <= 0 AND compensation = ?', false)
end
def self.order_by_date
order('date asc')
end
# group by tag and build sum of groups named group_sum
def self.group_by_tag(order)
group('tag').
select('tag, ' +
'sum(case when joint = "f" then amount else amount / 2 end) as tag_sum'
).
order('tag_sum ' + order)
end
def self.multiple_sums(user_id, partner_id)
case ActiveRecord::Base.connection.adapter_name
when 'SQLite'
select('sum(case when joint = "f" then amount else amount / 2 end) as total, ' +
'sum(case when joint = "f" then amount else 0 end) as sum_personal, ' +
'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount / 2 else 0 end) as sum_user_joint, ' +
'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount / 2 else 0 end) as sum_partner_joint '
)
when 'PostgreSQL'
select('sum(case when joint = false then amount else amount / 2 end) as total, ' +
'sum(case when joint = false then amount else 0 end) as sum_personal, ' +
'sum(case when joint = true and user_id = ' + user_id.to_s + ' then amount / 2 else 0 end) as sum_user_joint, ' +
'sum(case when joint = true and user_id = ' + partner_id.to_s + ' then amount / 2 else 0 end) as sum_partner_joint '
)
else
raise 'Query not implemented for this DB adapter'
end
end
Controller
# get all entries of given month
#cost = Entry.all_entries_month(#year, #month, current_user.id, current_partner.id).cost
# group cost by categories
#group_cost = #cost.group_by_tag('asc')
# still need to sort by date
#cost = #cost.order_by_date
#calc_cost = #cost.multiple_sums(current_user.id, current_partner.id)[0]
How can I change my query multiple_sums without breaking the other queries? Or do I need to implement multiple_sums from ground without using the existing ones?
Remove order by clause, which is useless as far as I can see anyway because you're grouping into single row.
And please - reformat your queries so that they will be visible and readable.

Resources