2012-07-13 11 views
6

Tengo una relación de muchos a muchos entre las facturas y las transacciones con tarjeta de crédito, y estoy tratando de mapear las sumas de las mismas. La mejor manera de pensar en el problema es imaginar TransactionInvoiceMap como un gráfico bipartito. Para cada subgrafo conectado, encuentre el total de todas las facturas y el total de todas las transacciones dentro de ese subgrafo. En mi consulta, deseo devolver los valores calculados para cada uno de estos subgrafos junto con los identificadores de transacciones a los que están asociados. Los totales para las transacciones relacionadas deben ser idénticos.¿Cómo creo eficientemente subconjuntos lógicos de datos en una tabla de asignación de muchos a muchos?

Más explícitamente, dadas las siguientes operaciones/facturas

Table: TransactionInvoiceMap 
TransactionID InvoiceID 
1    1 
2    2 
3    2 
3    3 

Table: Transactions 
TransactionID Amount 
1    $100 
2    $75 
3    $75 

Table: Invoices 
InvoiceID Amount 
1   $100 
2   $100 
3   $50 

mi salida deseada es

TransactionID TotalAsscTransactions TotalAsscInvoiced 
1    $100     $100 
2    $150     $150 
3    $150     $150 

Nota que las facturas 2 y 3 y las transacciones 2 y 3 son parte de un grupo lógico.

Aquí hay una solución (simplificada, nombres cambiados) que aparentemente funciona, pero es muy lenta. Me está costando entender cómo optimizar esto, pero creo que implicaría eliminar las subconsultas en TransactionInvoiceGrouping. Siéntase libre de sugerir algo radicalmente diferente.

with TransactionInvoiceGrouping as (
    select 
     -- Need an identifier for each logical group of transactions/invoices, use 
     -- one of the transaction ids for this. 
     m.TransactionID, 
     m.InvoiceID, 
     min(m.TransactionID) over (partition by m.InvoiceID) as GroupingID 
    from TransactionInvoiceMap m 
) 
select distinct 
    g.TransactionID, 
    istat.InvoiceSum as TotalAsscInvoiced, 
    tstat.TransactionSum as TotalAsscTransactions 
from TransactionInvoiceGrouping g 
    cross apply (
     select sum(ii.Amount) as InvoiceSum 
     from (select distinct InvoiceID, GroupingID from TransactionInvoiceGrouping) ig 
      inner join Invoices ii on ig.InvoiceID = ii.InvoiceID 
     where ig.GroupingID = g.GroupingID 
    ) as istat 
    cross apply (
     select sum(it.Amount) as TransactionSum 
     from (select distinct TransactionID, GroupingID from TransactionInvoiceGrouping) ig 
      left join Transactions it on ig.TransactionID = it.TransactionID 
     where ig.GroupingID = g.GroupingID 
     having sum(it.Amount) > 0 
    ) as tstat 
+0

Se agrupan porque todos "se tocan" entre sí. Si dibujara líneas para cada relación, habría un gráfico que cubriría los cuatro objetos. Pensé que había un nombre mathy para este tipo de agrupación, pero no puedo recordarlo. –

+0

@StuartBranhan - Entonces, ¿están agrupados porque tienen una factura común ?, ¿es así como funciona? ¿Y solo por esa razón está asumiendo $ 150 como el monto total de las transacciones, porque está agregando el monto de la transacción 2 y la transacción 3? – Lamak

+0

Sí, o si hay una transacción común. La metáfora gráfica funciona mejor para explicarlo. ¿Escribiste el comentario anterior al que respondí? Probablemente sea una buena idea guardar los comentarios para que tengan sentido para los lectores posteriores. –

Respuesta

2

he implementado la solución en un recursive CTE:

;with TranGroup as (
    select TransactionID 
     , InvoiceID as NextInvoice 
     , TransactionID as RelatedTransaction 
     , cast(TransactionID as varchar(8000)) as TransactionChain 
    from TransactionInvoiceMap 
    union all 
    select g.TransactionID 
     , m1.InvoiceID 
     , m.TransactionID 
     , g.TransactionChain + ',' + cast(m.TransactionID as varchar(11)) 
    from TranGroup g 
     join TransactionInvoiceMap m on g.NextInvoice = m.InvoiceID 
     join TransactionInvoiceMap m1 on m.TransactionID = m1.TransactionID 
    where ',' + g.TransactionChain + ',' not like '%,' + cast(m.TransactionID as varchar(11)) + ',%' 
) 
, RelatedTrans as (
    select distinct TransactionID, RelatedTransaction 
    from TranGroup 
) 
, RelatedInv as (
    select distinct TransactionID, NextInvoice as RelatedInvoice 
    from TranGroup 
) 
select TransactionID 
    , (
     select sum(Amount) 
     from Transactions 
     where TransactionID in (
      select RelatedTransaction 
      from RelatedTrans 
      where TransactionID = t.TransactionID 
     ) 
    ) as TotalAsscTransactions 
    , (
     select sum(Amount) 
     from Invoices 
     where InvoiceID in (
      select RelatedInvoice 
      from RelatedInv 
      where TransactionID = t.TransactionID 
     ) 
    ) as TotalAsscInvoiced 
from Transactions t 

es probable que haya un cierto margen de optimización (incluyendo el nombramiento de mi parte objeto!), Pero creo que tengo al menos una solución correcta que reunirá todas las posibles relaciones Transaction-Invoice para incluir en los cálculos.

No pude obtener las soluciones existentes en esta página para dar la salida deseada del OP, y se pusieron feas a medida que añadía más datos de prueba. No estoy seguro de si la solución "lenta" publicada del OP es correcta como se indica. Es muy posible que esté malinterpretando la pregunta.

Otros detalles:

he visto a menudo que las consultas recursivas pueden ser lentos cuando se trabaja con grandes conjuntos de datos. Tal vez ese puede ser el tema de otra pregunta SO. Si ese es el caso, las cosas para probar en el lado de SQL pueden ser limitar el rango (agregar where cláusulas), indexar tablas base, seleccionar primero el CTE en una tabla temporal, indexar esa tabla temporal, pensar en una mejor condición de detención para el CTE ... pero perfil primero, por supuesto.

+0

Extraño, obtuve resultados incorrectos con la consulta que publiqué también. Debo haberlo simplificado incorrectamente. Hay una lección para probar primero en variables de tabla. ;) Te dejará saber cómo va con tu código. –

+0

Después de jugar con él, su solución parece mucho más correcta de lo que tenemos actualmente. Todavía es muy lento, pero creo que algunos ajustes y cambios de interfaz de usuario en la interfaz podrían hacer que funcione. Actualizaré esta respuesta con una solución final más adelante. Los CTE recursivos son realmente difíciles de entender. > _> –

+0

Me alegro de que hayas empezado. He agregado algunas posibilidades para mejorar esto. No puedo esperar para ver tu respuesta final, y agradezco la bonificación + aceptar. –

0

Si he entendido la pregunta correcta, creo que está tratando de encontrar el mínimo de ID de transacción para cada factura y yo hemos utilizado la función de clasificación para hacer lo mismo.

WITH TransactionInvoiceGrouping AS (
SELECT 
    -- Need an identifier for each logical group of transactions/invoices, use 
    -- one of the transaction ids for this. 
    m.TransactionID, 
    m.InvoiceID,  
    ROW_NUMBER() OVER (PARTITION BY m.InvoiceID ORDER BY m.TransactionID) AS recno 
FROM TransactionInvoiceMap m 
) 
SELECT 
g.TransactionID, 
istat.InvoiceSum AS TotalAsscInvoiced, 
tstat.TransactionSum AS TotalAsscTransactions 
FROM TransactionInvoiceGrouping g 
CROSS APPLY(
    SELECT SUM(ii.Amount) AS InvoiceSum 
    FROM TransactionInvoiceGrouping ig 
     inner JOIN Invoices ii ON ig.InvoiceID = ii.InvoiceID 
    WHERE ig.TransactionID = g.TransactionID 
    AND ig.recno = 1 
) AS istat 
CROSS APPLY(
    SELECT sum(it.Amount) AS TransactionSum 
    FROM TransactionInvoiceGrouping ig 
     LEFT JOIN transactions it ON ig.TransactionID = it.TransactionID 
    WHERE ig.TransactionID = g.TransactionID 
    AND ig.recno = 1 
    HAVING SUM(it.Amount) > 0 
) AS tstat 

WHERE g.recno = 1 
Cuestiones relacionadas