How do I perform a LIKE query on a PostgreSQL primary id column? - sql

If I have a number (such as 88) and I want to perform a LIKE query in Rails on a primary ID column to return all records that contain that number at the end of the ID (IE: 88, 288, etc.), how would I do that? Here's the code to generate the result, which works fine in SQLLite:
#item = Item.where("id like ?", "88").all
In PostgreSQL, I'm running into this error:
PG::Error: ERROR: operator does not exist: integer ~~ unknown
How do I do this? I've tried converting the number to a string, but that doesn't seem to work either.

Based on Erwin's Answer:
This is a very old question, but in case someone needs it, there is one very simple answer, using ::text cast:
Item.where("(id::text LIKE ?)", "%#{numeric_variable}").all
This way, you find the number anywhere in the string.
Use % wildcard to the left only if you want the number to be at the end of the string.
Use % wildcard to the right also, if you want the number to be anywhere in the string.

Simple case
LIKE is for string/text types. Since your primary key is an integer, you should use a mathematical operation instead.
Use modulo to get the remainder of the id value, when divided by 100.
Item.where("id % 100 = 88")
This will return Item records whose id column ends with 88
1288
1488
1238872388
862388
etc...
Match against arbitrary set of final two digits
If you are going to do this dynamically (e.g. match against an arbitrary set of two digits, but you know it will always be two digits), you could do something like:
Item.where(["id % 100 = ?", last_two_digits)
Match against any set or number of final digits
If you wanted to match an arbitrary number of digits, so long as they were always the final digits (as opposed to digits appearing elsewhere in the id field), you could add a custom method on your model. Something like:
class Item < ActiveRecord
...
def find_by_final_digits(num_digits, digit_pattern)
# Where 'num_digits' is the number of final digits to match
# and `digit_pattern` is the set of final digits you're looking fo
Item.where(["id % ? = ?", 10**num_digits, digit_pattern])
end
...
end
Using this method, you could find id values ending in 88, with:
Item.find_by_final_digits(2, 88)
Match against a range of final digits, of any length
Let's say you wanted to find all id values that end with digits between 09 and 12, for whatever reason. Maybe they represent some special range of codes you're looking up. To do this you could do another custom method to use Postgres' BETWEEN to find on a range.
def find_by_final_digit_range(num_digits, start_of_range, end_of_range)
Item.where(["id % ? BETWEEN ? AND ?", 10**num_digits, start_of_range, end_of_range)
end
...and could be called using:
Item.find_by_final_digit_range(2, 9, 12)
...of course, this is all just a little crazy, and probably overkill.

The LIKE operator is for string types only.
Use the modulo operator % for what you are trying to do:
#item = Item.where("(id % 100) = ?", "88").all
I doubt it "works" in SQLite, even though it coerces the numeric types to strings. Without leading % the pattern just won't work.
-> sqlfiddle demo
Cast to text and use LIKE as you intended for arbitrary length:
#item = Item.where("(id::text LIKE ('%'::text || ?)", "'12345'").all
Or, mathematically:
#item = Item.where("(id % 10^(length(?)) = ?", "'12345'", "12345").all

LIKE operator does not work with number types and id is the number type so you can use it with concat
SELECT * FROM TABLE_NAME WHERE concat("id") LIKE '%ID%'

Related

PGSQL Postgres Update based on numeric range on a text column

I have a table with IDs, some have letters, most do not. And associated points with those IDs. The IDs are stored as text.
I would like to add points to a given range of IDS that ARE integers and give them all the same points. 535.
I have found a way using a subquery to SELECT the data I need, but it appears that updating it is another matter. I would love to be able to only get data that is CAST-able without a subquery. however since it errors out when it touches something that isn't a number, that doesn't seem to be possible.
select * from (select idstring, amount from members where idstring ~ '^[0-9]+$') x
WHERE
CAST(x.idstring AS BIGINT) >= 10137377001
and
CAST(x.idstring AS BIGINT) <= 10137377100
What am I doing ineficiently in the above, and how an I update the records that I want to?
In a perfect world my statement would be as simple as:
UPDATE members SET amount = 535
WHERE idstring >= 10137377001
AND idstring <= 10137377100
But since Idstring both contains entries that contain letters and is stored as text, it complicates things significantly. TRY_CAST would be perfect here, however there is no easy equivalent in postgres.
An example of the ids in the table might be
A52556B
36663256
6363632900B
3000525
ETC.
You can use TO_NUMBER together with your regular expression predicate, like so:
UPDATE members
SET amount = 535
WHERE idstring ~ '^[0-9]+$'
AND to_number(idstring, '999999999999') BETWEEN 10137377001 AND 10137377100
Working example on dbfiddle
You can encase the typecast and integer comparison within a CASE statement.
UPDATE members
SET amount = COALESCE(amount, 0) + 535
WHERE CASE WHEN idstring ~ '^[0-9]+$'
THEN idstring::BIGINT BETWEEN 10137377001 AND 10137377100
ELSE FALSE END;
Here I've assumed you might want to add 535 rather than setting it explicitly to 535 based on what you said above, but if my assumption is incorrect then SET amount = 535 is just fine.

SQL query problem in WHERE clause, this returns all that start with

I've written the following SQL query to return all sites having "id" equal to 2.
SELECT * FROM `sites` WHERE id = '2'
And it works well. The problem is that even if I add some characters after "2" like this :
SELECT * FROM `sites` WHERE id = '2etyupp-7852-trG78'
It returns the same results as above.
How to avoid this ? that's to say return none on the second query ?
Thanks
The reason is that you are mixing types:
where id = '2'
------^ number
-----------^ string
What is a SQL engine supposed to do? Well, the standard approach is to convert the string to a number. So this is run as:
where id = 2
What happens when the string is not a number? In most databases, you get a type conversion error. However, MySQL does implicit conversion, converting the leading digits to a number. Hence, your second string just comes 2.
From this, I hope you learn not to mix data types. Compare numbers to numbers. Compare strings to strings.

Comparing two fields with leading zeros

I have tables A and B that share several fields and have the same datatype/length and I'm trying to get additional information to B and for that I need to do a match on case_number.
The problem is case_number in table A has a length of 10 and anything less than 10 is preceded with zeros (i.e 84534 --> 0000084534) table B does not (84534 = 84534) So when I attempt to match on case_number I get no results. Both fields are varchar2 and this is Oracle and I'm unable to modify table A.
I tried to use LPAD and that does not seem to help. I need a function to work in select statement.
The simplest solution seems to be to left-pad the string from the second table with zeros:
...
where a.case_number = lpad(b.case_number, 10, '0')
...
Alternatively, you could leave b.case_number unchanged and left-trim '0' from a.case_number, but this will only work if you can guarantee that b.case_number never has leading zeros (and, in particular, that b.case_number can't be zero).
...
where ltrim(a.case_number, '0') = b.case_number
...
One method is to convert to a number:
to_number(x) = to_number(y)

selecting rows depending on the first digit of an integer in a column

Using SQL in PostgreSQL I need to select all the rows from my table called "crop" when the first digit of the integer numbers in column "field_id" is 7.
select *
from crop
where (left (field_id,1) = 7)
First, you know that the column is a number, so I would be inclined to explicitly convert it, no matter what you do:
where left(crop::text, 1) = '7'
where crop::text like '7%'
The conversion to text is simply to be explicit about what is happening and it makes it easier for Postgres to parse the query.
More importantly, if the value has a fixed number of digits, then I would suggest using a numeric range; something like this:
where crop >= 700000 and crop < 800000
This makes it easier for Postgres to use an index on the column.
Try with cast, like this:
select *
from crop
where cast(substring(cast(field_id as varchar(5)),1,1) as int) = 7
where 5 in varchar(5) you should put number how long is your integer.

select using wildcard to find ending in two character then numeric

I am querying to find things ending in "ST" followed by a number 1 - 999.
SELECT NUMBER WHERE NUMBER LIKE '%ST -- works correctly to return everything ending in "ST"
SELECT NUMBER WHERE NUMBER LIKE '%[1-999] -- works correctly to return everything ending in 1 - 999
SELECT NUMBER WHERE NUMBER LIKE '%ST[1-999] -- doesn't work - returns nothing
Also tried:
SELECT NUMBER WHERE NUMBER LIKE '%ST%[1-999] -- works, but also returns things like "GRASTNT3" that have extra things between the "ST" and the number
Can anyone help this struggling beginner?
Thanks!
The problem is that [1-999] doesn't mean what you think it does.
SQL Server interprets that as a set of values (1-9, 9, 9) which basically means that if there's more than 1 digit after the ST, the entry won't be returned.
So far as I can tell, your best bet is:
SELECT NUMBER WHERE
NUMBER LIKE '%ST[1-9][0-9][0-9]' OR
NUMBER LIKE '%ST[1-9][0-9]' OR
NUMBER LIKE '%ST[1-9]'
(assuming that your numbers don't have leading zeros - if they do, replace the ones with more zeros)
You need to do
SELECT NUMBER WHERE
NUMBER LIKE '%ST[1-9][0-9][0-9]'
OR NUMBER LIKE '%ST[1-9][0-9]'
OR NUMBER LIKE '%ST[1-9]';
The group in the the [] is a Char/NChar not an Int.
Better still normalise and type your data, so you have an ST bit and an int column for the number.
If you find you need to define different filters on variable string data, consider Full Text Searching or another Lucene related technology depending on your RDBMS.