views:

121

answers:

7

I got a little problem

i need a sql query that gives all rows back that only contains 0 in it.

the column is defined as varchar2(6) the values in the column looks like this:

Row      Value
1        0
2        00
3        00
4        100
5        bc00
6        000000
7        00000

my first solution would be like this:

Oracle:
substr('000000' || COLUMN_NAME, -6) = '000000'

SQL Server:
right('000000' + COLUMN_NAME, 6) = '000000'

is there an other way? (it needs to work on both systems)

the output would be the row 1,2,3,6,7

A: 

try this, which should be able to use and index on YourTable.COLUMN_NAME if it exists:

--SQL Server syntax, but should be similar in Oracle
--you could make this a temp of permanent table 
CREATE TABLE Zeros (Zero varchar(6))
INSERT INTO Zeros VALUES ('0')
INSERT INTO Zeros VALUES ('00')
INSERT INTO Zeros VALUES ('000')
INSERT INTO Zeros VALUES ('0000')
INSERT INTO Zeros VALUES ('00000')
INSERT INTO Zeros VALUES ('000000')

SELECT
    y.*
    FROM YourTable       y
        INNER JOIN Zeros z On y.COLUMN_NAME=z.Zero

EDIT
or even just this:

SELECT
    *
    FROM YourTable
    WHERE COLUMN_NAME IN ('0','00','000','0000','00000','000000')

building off of Dave Costa's answer:

Oracle:

SELECT
    *
    FROM YourTable
    WHERE YourColumn IN
        (SELECT LPAD('0',level,'0') FROM dual CONNECT BY LEVEL <= 6)

SQL Server 2005 and up:

;WITH Zeros AS
(SELECT 
     CONVERT(varchar(6),'0') AS Zero
 UNION ALL
 SELECT '0'+CONVERT(varchar(5),Zero)
     FROM Zeros
     WHERE LEN(CONVERT(varchar(6),Zero))<6
)
select Zero from Zeros
SELECT
    y.*
    FROM YourTable       y
    WHERE y.COLUMN_NAME IN (SELECT Zero FROM Zeros)
KM
A: 

Hi Auro,

This would also work in Oracle (but not in SQL Server):

REPLACE(column_name, '0') IS NULL

This will work in Oracle (and perhaps also in SQL Server, you will have to check):

LTRIM(column_name, '0') IS NULL

Alternatively, since it is a VARCHAR(6) column, you could also just check:

column_name IN ('0', '00', '000', '0000', '00000', '000000')

This is not pretty but it is probably the most efficient if there is an index on the column.

Vincent Malgrat
In SQL Server, LTRIM only takes 1 param, and REPLACE requires 3 parmas.
KM
A: 

Building off KM's answer, you can do the same thing in Oracle without needing to create an actual table.

SELECT y.*
  FROM YourTable  y
  WHERE YourColumn IN
    (SELECT LPAD('0',level,'0') FROM dual CONNECT BY LEVEL <= 6)

or

SELECT y.*
  FROM YourTable  y
    INNER JOIN
     (SELECT LPAD('0',level,'0') zeros FROM dual CONNECT BY LEVEL <= 6) z
            ON y.YourColumn = z.zeros

I think this is the most flexible answer because if the maximum length of the column changes, you just need to change 6 to the new length.

Dave Costa
Whoops, didn't notice the constraint that you want it to work in both Oracle and SQL Server. Not sure if SQL Server has a similar construct that would allow you to generate the join table in this fashion.
Dave Costa
too bad there is no dual table in sql but if it need to be one i can creat one ^^
Auro
@Dave Costa, SQL Server can use a CTE to recursively build tables
KM
A: 

How about using regular expression (supported by oracle, I think also MSSQL)

zapp0
can u explain it a little more?
Auro
http://www.regular-expressions.info/oracle.htmlI haven't tried this, but it's suposed to wirk in ms sql also http://msdn.microsoft.com/en-us/magazine/cc163473.aspx
zapp0
using a regular expression will cause it to not use an index
KM
regexp_like really doesn't but regexp_instr can if you use function based index.That said, who says optimizer is going to use index for "WHERE COLUMN_NAME IN ('0','00','000','0000','00000','000000')" at all. Depending on statistics, FTS might be faster.
zapp0
A: 

Another SQL version would be:

...
where len(COLUMN_NAME) > 0
 and len(replace(COLUMN_NAME, '0', '')) = 0

i.e., where there are more than 1 characters in the column, and all of them are 0. Toss in TRIM if there can be leading, trailing, or embedded spaces.

Philip Kelley
doing it this way, there is no change of index usage
KM
+3  A: 

This is the simplest one:

select * from tbl where replace(col,'0','') = ''

If you will not make computed column for that expression, you can opt for function-based index(note: Oracle and Postgres already supports this; Sql Server as of version 2008, not yet) to make that performant:

create index ix_tbl on tbl(replace(col,'0',''))

[EDIT]

I just keep the answer below for posterity, I tried to explain how to make the query use index from computed column.

Use this:

select * from tbl
where ISNUMERIC(col) = 1 and cast(col as int) = 0

For ISNUMERIC needs on Oracle, use this: http://www.oracle.com/technology/oramag/oracle/04-jul/o44asktom.html

[EDIT]

@Charles, re: computed column on Oracle:

For RDBMSes that supports computed column but it doesn't have persisted option, yes it will make function call for every row. If it supports persisted column, it won't make function call, you have real column on the table which is precomputed from that function. Now, if the data could make the function raise an exception, there are two scenarios.

First, if you didn't specify persist, it will allow you to save the computed column (ALTER TABLE tbl ADD numeric_equivalent AS cast(col as int)) even if the result from the data will raise an exception, but you cannot unconditionally select that column, this will raise exception:

select * from tbl

this won't raise exception:

select * from tbl where is_col_numeric = 1

this will:

select * from tbl where numeric_equivalent = 0 and is_col_numeric = 1

this won't (Sql Server supports short-circuiting):

select * from tbl where is_col_numeric = 1 and numeric_equivalent = 0 

For reference, the is_col_numeric above was created using this:

ALTER TABLE tbl ADD 
is_col_numeric  AS isnumeric(col)       

And this is is_col_numeric's index:

create index ix_is_col_numeric on tbl(is_col_numeric)

Now for the second scenario, you put computed column with PERSISTED option on table that already has existing data(e.g. 'ABXY','X1','ETC') that raises exception when function/expression(e.g. cast) is applied to it, your RDBMS will not allow you to make a computed column. If your table has no data, it will allow you to put PERSISTED option, but afterwards when you attempt to insert data(e.g. insert into tbl(col) values('ABXY')) that raises an exception, your RDBMS will not allow you to save your data. Thereby only numeric text can be saved in your table, your PERSISTED computed column degenerate into a constraint check, albeit a full detoured one.

For reference, here's the persisted computed column sample:

ALTER TABLE tbl ADD
numeric_equivalent  AS cast(col as int) persisted

Now, some of us might be tempted to not put PERSISTED option on computed column. This would be kind of self-defeating endeavor in terms of performance purposes, because you might not be able to create index on them later. When later you want to create index on the unpersisted computed column, and the table already has data 'ABXY', the database won't allow you to create an index. Index creation need to obtain the value from column, and if that column raises an exception, it won't allow you to create index on it.

If we attempt to cheat a bit i.e. we immediately create an index on that unpersisted computed column upon table creation, the database will allow you to do that. But when we insert 'ABXY' to table later, it will not be saved, the database is automatically constructing index(es) after we insert data to the table. The index constructor receives exception instead of data, so it cannot make an index entry for the data we tried inserting, subsequently inserting data will not happen.

So how can we attain index nirvana on computed column? First of all, we make sure that the computed column is PERSISTED, doing this will ensure that errors kicks-in immediately; if we don't put PERSISTED option, anything that could raise exception will be deferred to index construction, just making things fail later. Bugs are easier to find when they happen sooner. After making the column persisted, put an index on it

So if we have existing data '00','01', '2', this will allow us to make persisted computed column. Now after that, if we insert 'ABXY', it will not be inserted, the database cannot persist anything from computed column that raised an exception. So we will just roll our own cast that doesn't raise exception.

To wit(just translate this to Oracle equivalent):

create function cast_as_int(@n varchar(20)) returns int with schemabinding
begin

    begin try
       return cast(@n as int);
    end try
    begin catch 
       return null;
    end catch

end;

Please do note that catching exception in UDF will not work yet in Sql Server, but Microsoft have plans to support that

This is now our non-exception-raising persisted computed column:

ALTER TABLE tbl ADD
numeric_equivalent  AS cast_as_int(a) persisted

Drop the existing index, then recreate it:

create index ix_num_equiv on tbl(numeric_equivalent)    

Now this query will become index-abiding-citizen, performant, and won't raise exception even the order of conditions is reversed:

select * from tbl where numeric_equivalent = 0 and is_col_numeric = 1

To make it more performant, since the numeric_equivalent column doesn't raise any more exceptions, we have no more use for is_col_numeric, so just use this:

select * from tbl where numeric_equivalent = 0 
Michael Buen
that looks simple :D and it wokrs on both system very nice thenk you!
Auro
simple or not, this will not use an index and force a table scan. Its a trade off: simple line of code saves 30 seconds of typing, but results in never using an index an causes a lifetime of slow queries, which will waste resources from other users and processes.
KM
Yeah, fully aware of that. that's why i'm loving Postgres more, because up to now, Sql Server still cannot put index on expressions. In Postgres, you can put index on basically any expressions
Michael Buen
@Michael Buen, SQL Server can index a materialized view (where you could do the string manipulation and index that column) or a you could create a persisted computed column in the table with an index. However, I prefer to store the data in a consistent manner in the first place and not have to jump through hoops to select it. Anyway, I always try to implement a solution that will use an index, simple code is nice, but fast execution is what matters.
KM
hmm.. :-) made me think that Postgres already has "materialized views" (though an invisible one, and cannot be applied to aggregation, only on whole table) because of its support for creating index on functions/expressions used in query. Anyway, I'll use the same idea(using indexed view(aka materialized view)) if I'm using Sql Server, index usage is important in queries. The only problem I see in indexed view, is it must be explicitly created(sometimes coming up with a good name is bother) in order to gain performance, and existing queries must be written towards that created view.
Michael Buen
Sql Server should have index creation on expression at some point in the future. I think it's the only enterprise-level database that glaringly lacks this feature. Postgres: http://www.postgresql.org/docs/7.3/static/indexes-functional.html Oracle: http://download.oracle.com/docs/cd/B28359_01/server.111/b28310/indexes003.htm#i1006674
Michael Buen
Oracle has the ability to create function based indexes
Gary
@Gary: yeah Oracle can, I included the link in my comment
Michael Buen
sql server has computed columns, which you can make persisted (stored not calculated each time) and you can index it.
KM
@KM: interesting, computed column can be persisted now. last i used Sql Server is version 2000, and it's more like a view within the table, in the sense that the column is virtual and is always evaluated. didn't know that it has PERSISTED option now, nice feature :-) Auro can now optimize the query on both Oracle and Sql Server(i.e. for 2005 and up)
Michael Buen
For Oracle, doesn't this mean you're making a function call for every row, and all the non-numeric rows will raise an exception in that function? Lots of overhead!
Charles
@Charles: see my answer above, wanting to make it more detailed, cannot put it in comment :-)
Michael Buen
Michael, thanks for adding the (very long!) additional information! There is so much that I don't know about databases (I'd say I know about 1% or less)!
Charles
+2  A: 

Do you like:

SELECT * FROM MY_TABLE 
WHERE REPLACE (MY_COLUMN, '0', NULL) IS NULL
AND MY_COLUMN IS NOT NULL;
The chicken in the kitchen
+1 Both elegant and cheap.
Charles
sadly, that doesn't work, but i will not downvote it. just change that to: `select * from tbl where replace(col, '0','') = ''`
Michael Buen
Michael, what makes you say it doesn't work? I tested it and it did work for me.
Charles
@Charles: I tested tcintk's answer on Sql Server,Postgres,MySQL, his query also includes bc00. Maybe tcintk's answer works on Oracle. I have read somewhere that Oracle has peculiar intepretation that NULL and empty string `''` are the same. Maybe tcintk is using Oracle
Michael Buen
Yep, it works on Oracle (sorry, I should have specified that).
Charles