27

I need to count different values on a column, such as:

Hours
1
1
2
null
null
null

The result must be: 3. My query is:

select count(distinct hour) from hours;

but it returns: 2. I tested also:

select count(*) from hours group by hour

but it returns three rows:

(1) 3
(2) 2
(3) 1

How can I count null values as 1 value and use distinct to avoid count repeated values?

I'm learning advanced SQL, they want me these requirements for all the solutions:

Try to minimize the number of subqueries you need to solve the query. Furthermore, you are not allowed to use the following constructions:

  • SELECT in the FROM or SELECT. You are allowed to have subqueries (SELECT in the WHERE or HAVING)
  • Combinations of aggregation functions such as COUNT (COUNT. ..)), SUM (COUNT. ..)) and the like.
  • UNION if you can avoid it.
  • Non-standard functions (such as NVL)
  • CASE
das-g
  • 9,718
  • 4
  • 38
  • 80
David
  • 503
  • 3
  • 7
  • 18

11 Answers11

40
select  count(distinct col1) + count(distinct case when col1 is null then 1 end)
from    YourTable
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
Andomar
  • 232,371
  • 49
  • 380
  • 404
20

if hour is a number, then if it can only be an integer:

select count(distinct coalesce(hour, 0.1)) cnt from test;

otherwise if it can be any floating point, change NULL to a char string.

eg

select count(distinct coalesce(to_char(hour), 'a')) cnt from test;
9
select 
   count(0) 
from
  (
      select distinct hour from hours
  )

SqlFiddle

Egor Skriptunoff
  • 23,359
  • 2
  • 34
  • 64
  • 1
    +1 for a nice solution -- I'd prefer a count(*) myself though. – David Aldridge Sep 09 '13 at 18:32
  • @DavidAldridge count(0) is very efficient than count(*) – Ram Oct 30 '15 at 02:24
  • 1
    @DRAM On what database? Not on Oracle -- you've fallen for a myth widely disproved elsewhere. – David Aldridge Oct 31 '15 at 09:48
  • 1
    @DavidAldridge - Hmm, that's interesting. Please give a disproving link. – Egor Skriptunoff Oct 31 '15 at 09:54
  • @DavidAldridge .. you are right.. it is a myth. Thanks for the insight . ref: http://stackoverflow.com/questions/1221559/count-vs-count1 – Ram Oct 31 '15 at 10:05
  • @DRAM - That SO question is about MS SQL Server. The Oracle performance depends on Oracle implementation. `count(*)` requires more Oracle server RAM to calculate. Am I wrong? – Egor Skriptunoff Oct 31 '15 at 10:47
  • @EgorSkriptunoff I think the source of the myth is that `count(*)` seems like `select *`, and the assumption is that `count(*)` requires that all columns be read". Of course it does not, because the semantics are entirly different. If fact `count(*)` doesn't have to access the table at all, and can use an index that is guaranteed to contain all the rows, or use a materialised view. It would also be pretty stupid of the Oracle developers to have missed the chance to optimise `count(*)`. Here is a discussion: https://asktom.oracle.com/pls/asktom/f?p=100:11:0::NO::P11_QUESTION_ID:1156159920245 – David Aldridge Oct 31 '15 at 13:35
  • @DavidAldridge - Thanks for the link. – Egor Skriptunoff Oct 31 '15 at 15:46
3
SELECT
      ( SELECT COUNT(DISTINCT hour)
        FROM hours
      )
    + CASE WHEN EXISTS
           ( SELECT *
             FROM hours
             WHERE hour IS NULL
           )
        THEN 1 
        ELSE 0
      END
   AS result
FROM dual ;
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
2

maybe

    select count(distinct hour||' ') from hours;

will do?

Andres
  • 21
  • 1
  • 2
    Welcome to Stack Overflow! Could you please edit your answer to add an explanation? For example, what would cause your answer to be the correct one? – Chris Forrence Sep 09 '13 at 15:19
2
select count(distinct nvl(hour,0)) from hours;
Giampaolo
  • 133
  • 2
  • 8
1

I'd say your requirements are pretty bizarre, given that you're almost certain to get a more efficient query simply using NVL(), COALESCE() or CASE. However, I managed to get the right result (and cope with the presence or absence of NULL values) using only subqueries. I've not managed to do this without using a subquery in the FROM clause yet.

SQL Fiddle

Query 1:

SELECT nnh.not_null_hours + nh.null_hours
FROM (
  SELECT COUNT(DISTINCT t.hour) not_null_hours
  FROM example_table t
) nnh
CROSS JOIN (
  SELECT 1 null_hours
  FROM dual
  WHERE EXISTS (
    SELECT 1
    FROM example_table t
    WHERE t.hour IS NULL
  )
  UNION ALL
  SELECT 0 null_hours
  FROM dual
  WHERE NOT EXISTS (
    SELECT 1
    FROM example_table t
    WHERE t.hour IS NULL
  )
) nh

Results:

| NNH.NOT_NULL_HOURS+NH.NULL_HOURS |
------------------------------------
|                                3 |

This is going to a lot of effort to cope with the requirements. A much simpler option is to use NVL, and then one of two simple choices... either:

  1. Use TO_CHAR to convert the non-NULL values to the datatype VARCHAR2 and NVL to convert NULL values to the VARCHAR2 'NULL' or
  2. Just use NVL with a magic number that you know will never be present in the result set (i.e. because of constraints on the table).

Query 1:

SELECT 
  COUNT(DISTINCT NVL(TO_CHAR(hour), 'NULL')) using_to_char_null
, COUNT(DISTINCT NVL(hour, -1)) using_magic_number
FROM example_table

Results:

| USING_TO_CHAR_NULL | USING_MAGIC_NUMBER |
-------------------------------------------
|                  3 |                  3 |
Ben
  • 1,902
  • 17
  • 17
  • Thanks. I think it's impossible to do without break any of this requirements, must be wrong. – David Feb 23 '13 at 15:15
  • I could do something with outer join? – David Feb 23 '13 at 17:41
  • I can't think of any useful join conditions that you can use without violating the "no subquery in FROM clause" rule, but I've added another answer with my best effort for Oracle. Not sure if I can take it any further with pure ANSI SQL alone. – Ben Feb 24 '13 at 18:17
1

Answer by Andres is the one that meets the requirements perfectly and without using any function at all apart from COUNT:

select count(distinct hour||' ') from hours;

i was looking for same thing for another purpose ( I could use anything at all ) but it did not seem correct or efficient to me until I saw this one, thank you Andres, such a simple solution yet a powerful one.

user3666197
  • 1
  • 6
  • 50
  • 92
arana
  • 129
  • 11
0

The closest I could get fitting the criteria specified is this: (SQL Fiddle)

Query 1:

SELECT COUNT(*)
FROM example_table t1
WHERE t1.ROWID IN (
  SELECT MAX(t2.ROWID)
  FROM example_table t2
  GROUP BY t2.hour
)

Results:

| COUNT(*) |
------------
|        3 |

Not sure if the ROWID pseudocolumn is allowed, given the other restrictions, but it works and gracefully handles NULL values. I don't think ROWID exists outside of Oracle, so likely this is going against the spirit of the question, but it fits the criteria listed at least.

Ben
  • 1,902
  • 17
  • 17
0

Probably the easiest way is to use DUMP:

SELECT COUNT(DISTINCT DUMP(hour)) AS distinct_count
FROM hours;

Output: 3

DBFiddle Demo

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
-1

Ah.. homework. Isn't it as simple as this?

SELECT COUNT(hour) 
  FROM hours

NULLS don't get counted.

Got it! My bad for not reading the requirements properly.

SELECT COUNT(DISTINCT COALESCE(hour,-1)) 
  FROM hours
Robert Co
  • 1,715
  • 8
  • 14