Redis sorted set by string values - language-agnostic

Suppose you have a hash 'users' whose entries map numeric IDs to JSON-encoded arrays, so, for instance, the integer 1 maps to the string {name: 'John', surname: 'Doe', occupation: 'plumber'}.
The numeric IDs of items in the hash are stored in various lists. Thus, if 'foobar' is one of these lists, to retrieve the actual data from it I would run a simple Lua script (implementing a server-side join operation). Or, as I've just learned, I could use something like
SORT foobar BY inexistent_key GET user:*
but that implies storing each user's data into a separate key which seems expensive (in my case I have many small collections so I want to take advantage of Redis compression of hashes).
The question is finally this: I need to keep these lists ordered alphabetically by, say, each user's surname, then by name. What is the best way to achieve this, without changing too much the underlying data model (if possible)?
So far the best I could think of was using the SORT command together with the BY and STORE clauses, that is, running
SORT foobar BY surname:* STORE foobar
whenever the list is changed, but that way I'd need many keys. If I could use a hash in the BY clause that would be the ideal solution, it seems to me.
If the fields I want to sort by were somehow limited (as in, just a couple hundred surnames) I could think of mapping strings to integers and use a Redis sorted set, but this doesn't seem to be the case.

You can sort by hash keys, without the complexity of Lua scripts, but you will have to duplicate the keys in your Json structure to Redis' hash keys.
The below example has the following structure:
users is a set with the user id's in query.
user:X is the Redis hash which contains the duplicated name / surname data.
userdata:X is your original users Json hash.
redis> hmset user:1 name First surname User
OK
redis> set userdata:1 "{occupation: 'Tester'}"
OK
redis> hmset user:2 name Last surname Violet
OK
redis> set userdata:2 "{occupation: 'Bookseller'}"
OK
redis> hmset user:3 name Middle surname Veredict
OK
redis> set userdata:3 "{occupation: 'Judge'}"
OK
redis> hmset user:4 name Ultimate surname Veredict
OK
redis> set userdata:4 "{occupation: 'Ultimate Judge'}"
OK
redis> sadd users 1
(integer) 1
redis> sadd users 2
(integer) 1
redis> sadd users 3
(integer) 1
redis> sadd users 4
(integer) 1
redis> sort users by user:*->surname get user:*->name get user:*->surname get userdata:* alpha
1) "First"
2) "User"
3) "{occupation: 'Tester'}"
4) "Middle"
5) "Veredict"
6) "{occupation: 'Judge'}"
7) "Ultimate"
8) "Veredict"
9) "{occupation: 'Ultimate Judge'}"
10) "Last"
11) "Violet"
12) "{occupation: 'Bookseller'}"
Edit
I have overlooked that multiple By only consider the last clause. So you can't sort by more than one key in one command.
Also the SORT command used for lexicographic ordering now requires alpha modifier.

In the end it seems to me that a way to approach my problem is to use the table.sort() function available in Lua. In addition to the small hash 'users' and the small list of IDs 'foobar' I introduced another small hash, say 'users:sort-strings', where I store the strings by which I'd like to sort the IDs in 'foobar' (in the fictitious example a combination of surname and name). To sort 'foobar' I would then run the following Lua snippet in Redis, passing as keys 'foobar', 'users:sort-strings' and 'foobar:tmp' (a temporary key).
local lst = redis.call('LRANGE', KEYS[1], 0, -1)
local sort_function = function (id0, id1)
local s0 = redis.call('HGET', KEYS[2], id0)
local s1 = redis.call('HGET', KEYS[2], id1)
return (s0 < s1)
end
table.sort(lst, sort_function)
for key, value in ipairs(lst) do
redis.call('RPUSH', KEYS[3], value)
end
redis.call('DEL', KEYS[1])
redis.call('RENAME', KEYS[3], KEYS[1])

Related

How to get the values for which the format and suffix are known but the exact values are not known and there can be multiple values from the database?

I have a use case as below:
I have thousands of records in the database and let's say I am having one column named myValue.
Now the myValue's actual value can be an alphanumeric string where the first two characters are alphabets, the next 6 characters are numbers and the last character is a fixed alphabet let say 'x', which may be or may not be present in the value. (For Example 'AB123456','AB123456x')
So I know the format of the value for myValue field but not know all the actual values as there are lots of records.
Now I want to retrieve all such values for which the value without last character x (For Example, 'AB123456') and the same value with last character x (For Example, 'AB123456x') exists.
So is there any way I can retrieve such data?
I am right now doing trial and error on existing data but have not found success and there are thousands of such rows, so any help on this would be appreciated a lot.
You can do so like this:
SELECT myvalue
FROM t
WHERE myvalue LIKE '________'
AND EXISTS (
SELECT 1
FROM t AS x
WHERE x.myvalue = CONCAT(t.myvalue, 'x')
)
A (most likely) faster alternate is:
SELECT TRIM(TRAILING 'x' FROM myvalue) AS myvalue2
FROM t
GROUP BY myvalue2
HAVING COUNT(DISTINCT myvalue) > 1

MySQL Search for Value (Integer or String) in a Database Array Field

I have two fields in my MySQL database that are arrays. The datatype is shown as LONGTEXT with a Comment of (DC2Type:array).
For example, the integer values stored in this field would look like this:
a:4:{i:0;i:9;i:1;i:10;i:2;i:11;i:3;i:12;}
And the String values would look like this:
a:2:{i:0;s:6:"Value1";i:1;s:6:"Value2";}
I need these fields this way so I can store columns that are filterable. E.g. the first one may be age groups so ages 9,10,11,12 are represented.
My query must then get all records that say are relevant for age 10 or in some cases say I want to find those that are 10 and 11.
I've tried the IN and FIND_IN_SET syntaxes but neither is returning any results.
Using IN
SELECT *
FROM table_name
WHERE MyField IN (10)
Using FIND_IN_SET
SELECT *
FROM table_name
WHERE FIND_IN_SET(MyField,'Value1') > 0;
I know arrays are probably not the best field to store values in but I didn't want to have separate fields for each AgeGroup e.g. Age1, Age2, etc. or each category e.g Value1, Value2, etc.
Any thoughts on how I can find a value or values from a database array field, please?
Thanks!
You can use a pattern match.
Integer:
WHERE MyField LIKE '%i:10;%'
String:
WHERE MyField LIKE '%s:6:"Value1";%'
6 has to be replaced with the length of the string you're searching for.
If you want to search for multiple numbers or strings, you can use a regular expression with alternation:
WHERE MyField RLIKE 'i:(10|11);'
WHERE MyField RLIKE 's:(6:"Value1"|10:"LongValue2");'
Note that none of these methods can make use of an index on the table. It's generally a bad idea to store arrays in database columns, you should store them as separate rows in a many-to-many table.

random column name generated by azure sql

I'm noticing when I make any select statement with json data outputs a column name with random title e.g.
select 'john' as firstname, 'smith' lastname for json path
if I run this in sql management studio (text results) I'll get
JSON_F52E2B61-18A1-11d1-B105-00805F49916B
-------------------------------------------- [{"firstname":"john","lastname":"smith"}]
(1 row(s) affected)
How to change the column name of the generated json data. I've tried using the root option but couldn't override the column title.
This is the same as using XML.
you cannot set the column name
for my opinion since you will always get single row and single column (which means that this is only one value)
the column name have no meaning. but maybe you have different scenario that i am not aware of.
anyway, if you want to workaround it you can use this query
select (select 'john' as firstname, 'smith' lastname for json path) as MyColumn

MySQL: How to replace all characters in a field except the last 4 chars? (to protect credit card #'s)

I've got a mySQL database table called "booking", which contains a column of credit card numbers. Obviously this is not good or secure, and I really only need the last 4 digits in that field. I need a mySQL call that will fix this mistake.
Is there a way have one SQL statement that could go through the entire database table and replace all the Visa, Mastercard, Discover card #'s (which are different lengths), and overwrite all but the last 4 chars with XXX's. Or would I need to write something that loops through each record to do the replace?
The table is called "booking", and the field is called "creditcardnumber"
Try like this
SELECT
CONCAT(
REPEAT('X', CHAR_LENGTH(card_number) - 4),
SUBSTRING(card_number, -4)
) AS masked_card
FROM
table_name ;
Check on this reference: * SQLFIDDLE.
Don't mind the table schema, it's just the sample old data. I changed the first record to a credit card number (duh - it's not starting with 5, 4, or 3 ;) but LAPD does the job.
Query:
select right(hasha,4),
lpad(right(hasha,4),length(hasha),'x')
from actors
where id = 1
;
results:
RIGHT(HASHA,4) LPAD(RIGHT(HASHA,4),LENGTH(HASHA),'X')
3456 xxxxxxxxxxxx3456

mysql in list only validates first id in list. maybe a blob issue [duplicate]

This question already has answers here:
MySQL query finding values in a comma separated string
(11 answers)
Closed 5 years ago.
I work with a system that used comma separated values as a one-2-many relation. It is stored in as a blob.
I try to use the MySQL IN (LIST), but only ends up with the rows where the uid is first in the list or only one in the list.
mytable
uid categories
1 24,25,26
2 25
3 22,23,25
4 25,27
run sql:
SELECT * FROM mytable WHERE 25 IN (categories)
Result:
uid categories
2 25
4 25,27
Missing (should have been selected but are not)
uid categories
1 24,25,26
3 22,23,25
Any idea why a string has to start with 25 and not just contain 25? I guess it is a string issue rather than an IN (LIST) issue
UPDATE - based on answers below:
When using th IN (LIST) on a blob, the blob is converted to an int and commas and "digits" are lost.
In stead use FIND_IN_SET(needle, haystack) that will work also on a blob containing comma separated values.
I think you are looking for FIND_IN_SET
SELECT * FROM mytable WHERE FIND_IN_SET(25,category)
This should give you your asnwer:
SELECT * FROM mytable WHERE CONCAT(',', categories, ',') LIKE '%,25,%'
But it would make more sense to create a connecting table, with each comma separated value as a new row.
What's happening is that MySQL is converting the blob to an integer (not a list of integers) for comparison. So '24,25,26' becomes the value 24. You can confirm this by running the query
SELECT CAST(categories AS signed) FROM mytable
This is not how IN (nor the blob data type) is meant to be used. Is there a reason you're not using another table for this?
Yes, it's astring issue. Your 'list' is just a string to mysql, meaning nothing.
You would have to use RegEx.
But you might think about normalizing your tables instead.