views:

664

answers:

4

I ran across the following in a stored procedure:

  for
    select 1
    from   scan_queue
    where  ( date_time_locked is null       )
      and  ( scan_seqno >= :varMinScanSeqno )
      and  ( scan_seqno <= :varMaxScanSeqno )
    group by loan_id, collateral_id, insurance_id
    into   varNotUsed
  do
    varItemsToScan = varItemsToScan + 1;

My first thought was that this is probably an inefficient way of counting the number of groups, but my second thought was, "hey, how would you write that in a single query, anyway?" And I didn't have a good answer. (So this is more of an academic question.) I am not looking for a solution that concatenates the IDs, like so:

select count(distinct loan_id || collateral_id || insurance_id)
from   scan_queue
where  ( date_time_locked is null       )
  and  ( scan_seqno >= :varMinScanSeqno )
  and  ( scan_seqno <= :varMaxScanSeqno )

What is the best way to query this information?

EDIT: Since I apparently did not make this clear enough, I am using Firebird v1.5.

A: 

What's wrong with plain old count(distinct ..) ?

select count(*) from ( 
select loan_id, collateral_id, insurance_id
from   scan_queue
where  ( date_time_locked is null       )
  and  ( scan_seqno >= :varMinScanSeqno )
  and  ( scan_seqno <= :varMaxScanSeqno )
group by loan_id, collateral_id, insurance_id
)
Aviad Ben Dov
Is that actually valid SQL in your DBMS?
Nelson
It isn't in Oracle or Access. maybe SQL Server? But anyway, something feels wrong about it. Wish I had what I need to test it out. If it would work, it seems to me the group by clause should be removed.
sql_mommy
You're correct, my bad - somehow I remembered this would work. Let me fix that..
Aviad Ben Dov
Fixed! Try it now.
Aviad Ben Dov
There is no reason to have both the group by and distinct of the same columns.
Shannon Severance
Damn :/ You're correct, must be the hour. :)
Aviad Ben Dov
But it works nevertheless... :)
Aviad Ben Dov
if you remove the group by clause, it would work in other DBMSs, but not the one Asmodon is using (Firebird 1.5). It's the same concept as the other answers here, but given the limitations of Firebird 1.5, I think the original example in the question is the simplest solution (don't know about efficient, though, without testing.).
sql_mommy
This works in Firebird 2.0 and above, but not in 1.5.
Milan Babuškov
A: 

don't know if it's more or less efficient, but a subquery would do it.

select count(grouping) 
from
( select count(*) as grouping
    from   scan_queue
    where  ( date_time_locked is null       )
      and  ( scan_seqno >= :varMinScanSeqno )
      and  ( scan_seqno <= :varMaxScanSeqno )
    group by loan_id, collateral_id, insurance_id) a
sql_mommy
Thanks for the reply, but that did not work for me. Which DBMS are you using?
Nelson
I'm using Oracle and I tested this in access, too. Maybe I misunderstood the question? If my sub query returns 5 rows, then my outquery returns "5". Is that what you were looking for?
sql_mommy
Elizabeth, you understand the requirement perfectly, but this query is unfortunately not valid syntax in Firebird.
Nelson
ah! firebird. I haven't used it yet. I'm very surprised, though, because it seems so standard. So you can't do subqueries? Or you can't have a subquery that does an aggregate?
sql_mommy
Firebird supports subqueries but not derived tables (at least in v1.5), which is what your solution uses. : )
Nelson
what if you make the subquery include the fields being grouped by? It looks like Firebird is only somewhat Ansi-92 compliant - the bigger DBMS try to be Ansi-99. Thus the issue. I'm still surprised, though, because this would have worked in SQLServer 2000, which was only Ansi-92...
sql_mommy
I suspect the standards are actually driven by the features of the bigger DBMS. I'm not sure I follow your suggestion about making the "subquery include the fields being grouped by". Do you have an example? This may just not be possible with my DBMS.
Nelson
thank you for the explanation. I learned something today! I've always just referred to them all collectively as subqueries. To answer your question, then, I don't think you can do this in firebird. You could make a view of the groups and then count that, but I can't think of a way, other than what your example showed, that would make this possible in a single select. If someone figures out how, I'd be fascinated to see the solution.
sql_mommy
Ignore my comment about grouped by - it won't work b/c it's still a derived table. I posted that before I saw your prior comment. I just meant "select count(*), group1, group2, group3 from" instead of just "select count(*) from". It doesn't change the fact that it is still a derived table.
sql_mommy
No problem. Thank you very much for taking the time to look into this. I learned a bit about other DBMS! : )
Nelson
@Elizabeth, I don't think a view would help, since the inside query needs the parameters for the range on scan_sqno to look at. Views don't take parameters.
Shannon Severance
@Shannon, I assume Elizabeth meant that when selecting from the view, it would filter by scan_seqno in the predicate.
Nelson
This works in Firebird 2.0 and above, but not in 1.5.
Milan Babuškov
A: 

this works on SQL Server:

SELECT
    COUNT(*)
    FROM (SELECT
              GroupColumn
              FROM YourTable
              WHERE ...
              GROUP BY GroupColumn
         ) dt
KM
I am using Firebird, not SQL Server.
Nelson
perhaps you can use a CTE or a simple view fo replace the derived table
KM
Thanks for the response. Your idea of a view would probably work, but I don't know that I would want to create more metadata to support the query. I unfortunately do not have CTE support in v1.5, but apparently it is supported in v2.1. I am beginning to suspect that FB v1.5 simply does not have an elegant way to query this information.
Nelson
you do seem constrained by the lack of features in FB v1.5, perhaps it is time to upgrade to a newer version
KM
We are scheduled to update to FB 2.1 this year. It has been pushed back repeatedly because of complications the upgrade causes, however. You know how it goes. ; )
Nelson
This works in Firebird 2.0 and above, but not in 1.5.
Milan Babuškov
@Milan Babuškov, that is why I said "this works on SQL Server". the fact that a simple standard SQL construct is not supported in Firebird version 1.5 shows the problem lies in the version the OP is using.
KM
+1  A: 

Since you are using Firebird 1.5, there does not seem to be a clean solution.

Firebird 2.0 supports the 'sub-queries in the FROM clause' notation that is a basic requirement for answering such questions - see all the other answers.

So, if you need a clean solution, upgrade to Firebird 2.1. Otherwise, what you have seems to be about as good as you can get.

(Some other DBMS support temporary tables; if Firebird 1.5 does, you could select the initial data into a temporary table, then count the rows in the temporary table, then drop the temporary table again.)

Jonathan Leffler
We eventually did end up upgrading to FB 2.1. In the meantime, I used the code posted in the question.
Nelson