2010-10-13 18 views
7

Tengo problemas con el uso de Arel para agregar 2 columnas en la misma consulta. Cuando ejecuto esto, todo el servidor se congela por un minuto, antes de que el servidor dev de los raíles se cuelgue. Sospecho que hay un bucle infinito :).Arel que causa un bucle infinito en la agregación

Tal vez he entendido mal el concepto de Arel, y le agradecería que alguien pudiera echarle un vistazo.

El resultado esperado de esta consulta es algo como esto: [{: user_id => 1,: sum_account_charges => 300,: sum_paid_debts => 1000}, ...]

a_account_charges = Table(:account_charges) 
a_paid_debts = Table(:paid_debts) 
a_participants = Table(:expense_accounts_users) 

account_charge_sum = a_account_charges 
    .where(a_account_charges[:expense_account_id].eq(id)) 
    .group(a_account_charges[:user_id]) 
    .project(a_account_charges[:user_id], a_account_charges[:cost].sum) 

paid_debts_sum = a_paid_debts 
.where(a_paid_debts[:expense_account_id].eq(id)) 
.group(a_paid_debts[:from_user_id]) 
.project(a_paid_debts[:from_user_id], a_paid_debts[:cost].sum) 

charges = a_participants 
.where(a_participants[:expense_account_id].eq(id)) 
.join(account_charge_sum) 
.on(a_participants[:user_id].eq(account_charge_sum[:user_id])) 
.join(paid_debts_sum) 
.on(a_participants[:user_id].eq(paid_debts_sum[:from_user_id])) 
+1

¿Has resuelto esto? –

+0

¿Qué tipo de SQL produjo esto para usted? – nessur

+0

¿Has publicado un error? – DNNX

Respuesta

4

I' Soy nuevo en arel, pero después de golpear esto durante varios días y realmente cavar, no creo que se pueda hacer. Aquí hay un resumen de lo que hice, si alguien tiene alguna idea adicional, sería bienvenido.

Primero, estos scripts crearán las tablas de prueba y las rellenarán con datos de prueba. He configurado 9 accounts_account_users, cada uno con un conjunto diferente de cargos/paid_debts, de la siguiente manera: 1 cargo/1 pago, 2 cargos/2 pagos, 2 cargos/1 pago, 2 cargos/0 pagos, 1 cargo/2 pagos, 0 cargos/2 pagos, 1 cargo/0 pagos, 0 cargos/1 pago, 0 cargos, 0 pagos.

CREATE TABLE IF NOT EXISTS `expense_accounts_users` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `expense_account_id` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ; 

INSERT INTO `expense_accounts_users` (`id`, `expense_account_id`) VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1); 

CREATE TABLE IF NOT EXISTS `account_charges` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `expense_account_id` int(11) DEFAULT NULL, 
    `user_id` int(11) DEFAULT NULL, 
    `cost` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ; 

INSERT INTO `account_charges` (`id`, `expense_account_id`, `user_id`, `cost`) VALUES (1, 1, 1, 1), (2, 1, 2, 1), (3, 1, 2, 2), (4, 1, 3, 1), (5, 1, 3, 2), (6, 1, 4, 1), (7, 1, 5, 1), (8, 1, 5, 2), (9, 1, 7, 1); 

CREATE TABLE IF NOT EXISTS `paid_debts` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `expense_account_id` int(11) DEFAULT NULL, 
    `user_id` int(11) DEFAULT NULL, 
    `cost` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ; 

INSERT INTO `paid_debts` (`id`, `expense_account_id`, `user_id`, `cost`) VALUES (1, 1, 1, 1), (2, 1, 2, 1), (3, 1, 2, 2), (4, 1, 3, 1), (5, 1, 4, 1), (6, 1, 4, 2), (7, 1, 6, 1), (8, 1, 6, 2), (9, 1, 8, 1); 

En última instancia, para obtener los datos que usted está después de una sola vez, esta es la instrucción SQL que tendría que utilizar:

SELECT user_charges.user_id, 
    user_charges.sum_cost, 
    COALESCE(SUM(paid_debts.cost), 0) AS 'sum_paid' 
FROM (
    SELECT expense_accounts_users.id AS 'user_id', 
    COALESCE(sum(account_charges.cost), 0) AS 'sum_cost' 
    FROM expense_accounts_users 
    LEFT OUTER JOIN account_charges on expense_accounts_users.id = account_charges.user_id 
    GROUP BY expense_accounts_users.id) 
AS user_charges 
LEFT OUTER JOIN paid_debts ON user_charges.user_id = paid_debts.user_id 
GROUP BY user_charges.user_id 

Tienes que hacer una combinación externa izquierda entre los usuarios y cobra primero para que consigas una fila por cada usuario, luego debes DEJAR OUTER JOIN el resultado a deudas para evitar multiplicar tus resultados con las dos uniones dentro de la misma construcción.

(nótese el uso de COALESCE para convertir los valores NULL desde la izquierda y combinaciones externas a ceros - un elemento de conveniencia, tal vez)

El resultado de esta afirmación es la siguiente:

user_id sum_cost sum_paid 
1   1   1 
2   3   3 
3   3   1 
4   1   3 
5   3   0 
6   0   3 
7   1   0 
8   0   1 
9   0   0 

Después muchos intentos, encontré que este código arel fue el más cercano a lo que estamos buscando:

c = Arel::Table.new(:account_charges) 
d = Arel::Table.new(:paid_debts) 
p = Arel::Table.new(:expense_accounts_users) 
user_charges = p 
.where(p[:expense_account_id].eq(1)) 
.join(c, Arel::Nodes::OuterJoin) 
.on(p[:id].eq(c[:user_id])) 
.project(p[:id], c[:cost].sum.as('sum_cost')) 
.group(p[:id]) 
charges = user_charges 
.join(d, Arel::Nodes::OuterJoin) 
.on(p[:id].eq(d[:user_id])) 
.project(d[:cost].sum.as('sum_paid')) 

Esencialmente, soy jo Ingreso de usuarios a los cargos en el primer constructo con un LEFT OUTER JOIN, luego tratando de tomar el resultado de eso y IZQUIERDA EXTERIOR ÚNASE a deudas. Este código Arel produce la siguiente instrucción SQL:

SELECT `expense_accounts_users`.`id`, 
    SUM(`account_charges`.`cost`) AS sum_cost, 
    SUM(`paid_debts`.`cost`) AS sum_paid 
FROM `expense_accounts_users` 
LEFT OUTER JOIN `account_charges` ON `expense_accounts_users`.`id` = `account_charges`.`user_id` 
LEFT OUTER JOIN `paid_debts` ON `expense_accounts_users`.`id` = `paid_debts`.`user_id` 
WHERE `expense_accounts_users`.`expense_account_id` = 1 
GROUP BY `expense_accounts_users`.`id` 

Lo cual, cuando se ejecuta, produce esta salida:

id sum_cost sum_paid 
1 1   1 
2 6   6 
3 3   2 
4 2   3 
5 3   NULL 
6 NULL  3 
7 1   NULL 
8 NULL  1 
9 NULL  NULL 

muy cerca, pero no del todo. En primer lugar, la falta de COALESCE nos da valores NULL en lugar de ceros. No estoy seguro de cómo realizar la llamada a la función COALESCE desde dentro de arel.

Más importante, sin embargo, la combinación de la IZQUIERDA combinaciones externas en una declaración sin la subselección interna está dando como resultado los totales sum_paid conseguir multiplicado en los ejemplos 2, 3 y 4 - cada vez que hay más de un de cualquiera de cargo o pago y al menos un del otro.

Sobre la base de algunas lecturas en línea, tenía la esperanza de que el cambio de la Arel ligeramente resolvería el problema:

charges = user_charges 
.join(d, Arel::Nodes::OuterJoin) 
.on(user_charges[:id].eq(d[:user_id])) 
.project(d[:cost].sum.as('sum_paid')) 

Pero cualquier momento utilicé user_charges [] en la segunda construcción Arel, me dio un error método no definido para SelectManager # []. Esto puede ser un error, o puede ser correcto, realmente no puedo decirlo.

Simplemente no veo que arel tenga una forma de hacer uso del SQL del primer constructo como un objeto consultable en el segundo constructo con el alias de subconsulta necesario, como se necesita para que esto suceda en una declaración SQL .

+2

Puede agregar la función COALESCE creando una función con nombre: Arel :: Nodes :: NamedFunction.new (: COALESCE, [t [: su_columna], 0) generará la salida de SQL correcta. –

+0

¡Qué bueno! ¡Gracias por eso! – Yardboy

+0

donde t = arel_table dentro de su definición de clase. –