views:

122

answers:

4

Hi guys.

I came across with a problem in which I need some advice/help.

Background: I'm programming a map in an accounting software which requires special formatting and a tree-like hierarchy between the account codes, i.e.:

  • Account 1 is the father of accounts 11, 12 and 13;
  • On the other hand, account 11 is the father of accounts 111, 112 and 113;
  • There can also be an account relationship like this one: account 12 is the father of account 12111, because there is no 121 nor 1211, which means that the hierarchy is not a "one digit plus hierarchy" if I make my self clear;
  • And this logic continues till the end of the map.

Database table background: There are 3 tables involved in all process.

  1. A general ledger table (named ContasPoc) which I can relate with table number 2 (general ledger entries accounts) by each account code, in other words, the account code in table 1 is equal to the account code in table 2 (this field is named CodigoConta in both tables).
  2. The general ledger entries accounts (named LancamentosContas) which I can relate with table number 1 (general ledger) as I said. This table is also related with table number 3 (general ledger entries) by an Id.
  3. Table number 3 is as I said general ledger entries (named Lancamentos) and the field name that relates this table with number 2 is IdLancamento. I need this table for filtering purposes.

A real example: I want all the accounts from 24 till 26 that have values/movements but I also want their parents. Moreover, their parents will have to contain the sums of their children accounts just like the example output bellow:

Account Code | Debit Sum | Credit Sum | Balance  /*Balance = Debit Sum - Credit Sum*/
24           | 12,000    | 184,000    | -172,000 /*Sum of all children (242+243)   */
242          | 12,000    | 48,000     | -36,000  /*Sum of all children (2423+2424) */
2423         | 12,000    | 0,000      |  12,000  /*Sum of all children (24231)     */
24231        | 0,000     | 0,000      |  12,000  /*Account with values/movements   */
2424         | 0,000     | 48,000     | -48,000  /*Sum of all children (24241)     */
24241        | 0,000     | 48,000     | -48,000  /*Account with values/movements   */
243          | 0,000     | 136,000    | -136,000 /*Sum of all children (2433)      */
2433         | 0,000     | 136,000    | -136,000 /*Sum of all children (24331)     */
24331        | 0,000     | 136,000    | -136,000 /*Sum of all children (243313)    */
243313       | 0,000     | 136,000    | -136,000 /*Account with values/movements   */

As you can see there are lots of accounts but only the ones that have the following comment /*Account with values/movements*/ have values, the others are calculated by me, which brings me to the real problem:

Can I do it? Of course, but at the moment I'm using recursive methods and several queries to do it and this approach takes a awful lot of time so I definitely need a new one because most of the customers use old computers.

I've tried to get this kind of structure/data using a query with INNER JOINS. Didn't work because I don't get all the data I need, only the lines with movements.

I also and tried with LEFT JOINS. Didn't work so well as well because I get all the data I need plus a lot of which I don't need. But using a LEFT JOIN has one more problem, I only get this extra data (also the one I need) if I do not include any fields from table number 3 in the WHERE clause. This obviously has an answer but because I'm not such an expert in SQL Server I'm not seeing what is the reason for that.

Here are the query I built (the other one has only 1 difference, a LEFT JOIN instead of the first INNER JOIN):

SELECT IdConta, Min(IdContaPai) AS IdContaPai, Min(ContasPoc.CodigoConta) AS CodigoConta
FROM ContasPoc INNER JOIN (LancamentosContas INNER JOIN Lancamentos ON 
Lancamentos.IdLancamento = LancamentosContas.IdLancamento) ON ContasPoc.CodigoConta = 
LancamentosContas.CodigoConta
WHERE Lancamentos.IdEmpresa=17 AND ContasPoc.IdEmpresa=17 AND Lancamentos.IdLancamento = 
LancamentosContas.IdLancamento AND ContasPoc.CodigoConta>='24' AND ContasPoc.CodigoConta<='26'
GROUP BY IdConta 
ORDER BY Min(ContasPoc.CodigoConta)

After this long explanation (sorry about that) my 3 questions are:

  1. Is it possible to get such results (as I demonstrated above) only with database queries, in other words, without using recursive methods?
  2. If not (most probable) are stored procedures the way to go and if so how can I do it (never used them before)?
  3. Is there another method I'm not seeing?

Can someone help me on this one?

I hope I got all the relevant data in, but if not please tell me what I forgot and I will gladly answer back. Thanks in advance! Miguel

+2  A: 
WITH    q (AccountCode, Balance) AS (
        SELECT  24231, 12000
        UNION ALL
        SELECT  243313, -136000
        UNION ALL
        SELECT  24241, -48000
        UNION ALL
        SELECT  24, NULL
        UNION ALL
        SELECT  242, NULL
        UNION ALL
        SELECT  2423, NULL
        )
SELECT  qp.AccountCode, SUM(qc.Balance)
FROM    q qp
JOIN    q qc
ON      SUBSTRING(CAST(qc.AccountCode AS VARCHAR), 1, LEN(qp.AccountCode)) = qp.AccountCode
GROUP BY
        qp.AccountCode
ORDER BY
        AccountCode
Quassnoi
+1 substring() much better than my int arithmatic construct. This approach does rely on parents having NULL balances.
Andomar
The actual balances seem to be taken out of the table joined with a `LEFT JOIN`, so the parents will naturally have `NULL`s in `balance`
Quassnoi
Yes indeed. This way I don't get the NULL values, problem is that I'm also not getting the sums like in Andomar's approach. I thank you both for your answer. As I said in Andomar's answer, I need to do some more testing but your answers are great material to start with! Thank you both.
Miguel
A: 

You never "need" stored procedures. Think of it this way, a stored procedure is just code with DB queries inside it which runs entirely on the server. You could always do that code in your client language and have the DB queries return result sets which you operate on on the client side.

tster
That's a good point of course (and maybe that's why I never used them before :)) but the reason I brought stored procedures to the question is that I have the idea, from what I read about them, that using them is a lot faster when lots of queries are required, than making all these procedures and queries in my client language. Am I right or am I wrong?
Miguel
Yes you are right. It makes it so that you don't have to send the intermediate results back and forth from client to server, plus it removes the IO and marshalling involved in turning intermediate relations into objects or arrays in another language.
tster
Thanks for your answer tster! I will definitely have to take a look at all this stored procedures stuff.
Miguel
+2  A: 

Unless I'm mistaken, this becomes easy if you think of Account Code as a string. You could query like:

select *,
    (select sum(vw2.DebitSum)
     from SomeView vw2
     where vw.code < vw2.code and 
           vw2.code < cast(cast(vw.code as int) + 1 as varchar)
    ) as DebitSum
from SomeView vw
where '24' <= vw.code and vw.code < '27'

The subquery sums the debits for all children.

Andomar
Wow thanks! I think it might be it, though I need to do some more testing with all the solutions. I will post back my findings and accept an answer then. Thanks all for the comments and more are welcome!
Miguel
A: 

i don't understand why you don't just have an account table with an id for the primary key, and for every child account have a parent account field. then you just reference back to the account table and get your hierarchy in a looping cte.

DForck42
I do have those and as I said in my question I use them to create recursive methods that have, of course, loops of the results of some queries. The problem is that if I use that logic it takes a lot of time because the account table can have something like 2000 records and unfortunately my client's PCs are not very modern so I need a better solution.
Miguel
unless your looping logic is wrong or the indexes aren't set up right, 2000 records shouldn't take long at all. i would follow up with that before creating this complex system that you've posted.
DForck42
My looping/recursive logic is one thing that I also though of improving, but one thing I cannot dispose is my complex logic (unless I'm understanding you comment the wrong way, and if so please explain me...maybe it will bring some light to my currently dark problem, since none of the answers seems to work right for the testing I made) because these maps are standard. I will post a reply to my question to explain the way I'm generating my map right now because 600 chars may not be enough. Maybe people will see some failure or a better way since I may be obsessed with my present solution.
Miguel
OK.I've seen it may not be a good idea to answer my own question so I'll try to explain the current process step-by-step:1.I make a query for the accounts with values;2.I recursively fill a new table(ordered by code)with all accounts related to the last query(from last parent till child);3.I update the table with values using the same recursive logic as before also considering the logic I explained in my question.With 668 records(accounts table)+70(movements table)it took 10 sec.May seem fast but my PC has a E2200 + 3Gb RAM.Hope it's enough for you to understand the process.Tnks once more
Miguel
DForck42