11

I have a table with an amount field of type Numeric. It contains different amount values. For example

5.00
7.13
8.86
6.00
1.00

... etc.

I've to fetch only those records that are nonzero after the decimal point. ie, fetch only the records corresponding to the amounts

7.13
8.86

How can I do it?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
harry
  • 1,410
  • 3
  • 12
  • 31

4 Answers4

18

numeric is exact!

Unlike claimed by another answer, numeric is not a floating-point type, but an arbitrary precision type as defined by the SQL standard. Storage is exact. I quote the manual:

The type numeric can store numbers with a very large number of digits and perform calculations exactly. It is especially recommended for storing monetary amounts and other quantities where exactness is required.

Answer

The natural candidate for your question is the function trunc(). It truncates toward zero - basically keeping the integer part while discarding the rest. Fastest in a quick test, but the difference is insubstantial among the top contenders.

SELECT * FROM t WHERE amount <> trunc(amount);

floor() truncates to the next lower integer, which makes a difference with negative numbers:

SELECT * FROM t WHERE amount <> floor(amount);

If your numbers fit into integer / bigint you can also just cast:

SELECT * FROM t WHERE amount <> amount::bigint;

This rounds to full numbers, unlike the above.

Test

Tested with PostgreSQL 9.1.7. Temporary table with 10k numeric numbers with two fractional digits, around 1% have .00.

CREATE TEMP TABLE t(amount) AS
SELECT round((random() * generate_series (1,10000))::numeric, 2);

Correct result in my case: 9890 rows. Best time from 10 runs with EXPLAIN ANALYZE.

Erwin 1

SELECT count(*) FROM t WHERE amount <> trunc(amount)          -- 43.129 ms

mvp 2 / qqx

SELECT count(*) FROM t WHERE amount != round(amount)          -- 43.406 ms

Erwin 3

SELECT count(*) FROM t WHERE amount <> amount::int            -- 43.668 ms

mvp 1

SELECT count(*) FROM t WHERE round(amount,2) != round(amount) -- 44.144 ms

Erwin 4

SELECT count(*) FROM t WHERE amount <> amount::bigint         -- 44.149 ms

Erwin 2

SELECT count(*) FROM t WHERE amount <> floor(amount)          -- 44.918 ms

Nandakumar V

SELECT count(*) FROM t WHERE amount - floor(amount) > .00     -- 46.640 ms

Mostly still true in Postgres 12 (except everything's > 10x faster now). Test with 100k rows instead of 10k:

db<>fiddle here

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • `random()` **cannot** generate value of `1./3*3`, which CAN be stored in `NUMERIC` field, and it is NOT equal to `1` – mvp Mar 08 '13 at 03:03
  • 1
    @mvp: A `numeric` field **cannot** hold the *exact* value `1.0 / 3` to begin with. No existing type can. You are discussing a case that is interesting in itself, but not applicable to the question - as I understand it. – Erwin Brandstetter Mar 08 '13 at 15:56
  • Over 10^9 iterations of this code: `EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT j, TRUNC((RANDOM() * 5)) AS x FROM GENERATE_SERIES(1, 1000000000) AS j;`, and also for `FLOOR((RANDOM...`, the FLOOR() function was 137ms faster (181996.462 ms (03:01.996) vs. 182132.736 ms (03:02.133) - well within any possible error margins. PostgreSQL 14.2, compiled by Visual C++ build 1914, 64-bit - home laptop with nothing else running. Just an FYI! – Vérace Dec 27 '22 at 15:07
3

This will work:

SELECT *
FROM t
WHERE round(amount,2) != round(amount)

And no, you cannot directly compare floating numbers - below code DOES NOT work (SQLFiddle as proof):

SELECT *
FROM t
WHERE amount != round(amount)

If amount = 1./3 * 3, it looks like it is 1, but it is NOT - comparison will fail.

mvp
  • 111,019
  • 13
  • 122
  • 148
  • `numeric` is *not* a floating point number. Thus your "proof" does not apply. – Erwin Brandstetter Mar 08 '13 at 00:10
  • `NUMERIC` is the type used in question. My example also uses `NUMERIC` field, which was calculated as `1./3*3` - this IS possible to happen in real code. However, your example using `random()` CANNOT generate that corner case - it cannot generate NUMERIC value that is equal to `1./3*3` – mvp Mar 08 '13 at 03:01
3

Will this help

SELECT * FROM table WHERE amount - floor(amount) > .00

Nandakumar V
  • 4,317
  • 4
  • 27
  • 47
2
SELECT *
FROM t
WHERE amount != round(amount);
qqx
  • 18,947
  • 4
  • 64
  • 68