MySQL and Bit Bashing - mysql

I'm using Bit bashing on SQL for user rights, like UNIX rights :
1 - 001 - Execute
2 - 010 - Write
4 - 100 - Read
So if I have right 6 ( 2+4 or 110) I can write AND read but I can't execute. On my case i have many more rights, so my users can have the value "128" for rights.
How can I do a query and ask all used with a right (like 2 for UNIX example)
SELECT * from user WHERE user_right ?? '2'
The users rights must be 2 (010), 3 (011), 6 (110) or 7 (111) ?!
Thank you all and sorry for my English :/

128 would mean there were 7 total user rights:
1 1 1 1 1 1 1 = 0177 = 0x7F = 127
MySQL already has an abstraction for this, known as the SET type, which essentially maps your named/visible value for the right to a binary value. So if you had a set column user_right:
('Execute', 'Write', 'Read')
They would actually be stored on the server backend as:
(1, 2, 4)
This allows for normal queries like:
SELECT * FROM user WHERE user_right LIKE '%Write%';
or using FIND_IN_SET:
SELECT * FROM user WHERE FIND_IN_SET('Write',user_right) > 0;
or direct bitset operations:
SELECT * from user WHERE user_right & 2;
to name a few. If you add an ORDER BY user_right to your select queries, it returns them in their numerical (not alphabetical) order, but otherwise you can quickly determine the values of a user with:
SELECT user_name, user_right FROM user;
and seeing that the returned value is:
'some user' | Read,Write
while still having the benefits of bitwise operations for very fast filtering, such as:
SELECT user_name, user_right FROM user WHERE user_right & ~1;
to return all users with read and write permissions that do not have execute rights.

Related

How to write a "shortest path" like command or at least a "find a path" command in MYSQL?

Imaging, there are social-media-data in a table like this:
u1 u2
0 3
1 2
1 4
2 3
3 1
3 4
It means:
User 0 follows User 3
User 1 follows User 2
User 1 follows User 4
...
Now I want to know: Is there a way/path from User 0 to User 2?
Yes. User 0 follows User 3. User 3 follows User 1. User 1 follows User 2.
But how can a solve this problem as a SQL-Command?
I want to know if there is such a path. Optional I want to know what the path is. And is it possible to get the shortest path somehow?
The query below works in MySQL 8.x since it requires a "Recursive Common Table Expression" (Recursive CTE):
with n (initial, path, current) as (
select u1, concat('', u1, '/', u2), u2 from my_table
where u1 = 0 -- initial node
union all
select n.initial, concat(n.path, '/', m.u2), m.u2
from my_table m
join n on n.current = m.u1
)
select * from n
where current = 2 -- target node
However, if you are using MySQL 5.x, then you're out of luck (to the best of my knowledge).

How to Find First Valid Row in SQL Based on Difference of Column Values

I am trying to find a reliable query which returns the first instance of an acceptable insert range.
Research:
some of the below links adress similar questions, but I could get none of them to work for me.
Find first available date, given a date range in SQL
Find closest date in SQL Server
MySQL difference between two rows of a SELECT Statement
How to find a gap in range in SQL
and more...
Objective Query Function:
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
Where InsertRange(1) is the value the query should return. In other words, this would be the first instance where the above condition is satisfied.
Table Structure:
Primary Key: StartRange
StartRange(i-1) < StartRange(i)
StartRange(i-1) + EndRange(i-1) < StartRange(i)
Example Dataset
Below is an example User table (3 columns), with a set range distribution. StartRanges are always ordered in a strictly ascending way, UserID are arbitrary strings, only the sequences of StartRange and EndRange matters:
StartRange EndRange UserID
312 6896 user0
7134 16268 user1
16877 22451 user2
23137 25142 user3
25955 28272 user4
28313 35172 user5
35593 38007 user6
38319 38495 user7
38565 45200 user8
46136 48007 user9
My current Query
I am trying to use this query at the moment:
SELECT t2.StartRange, t2.EndRange
FROM user AS t1, user AS t2
WHERE (t1.StartRange - t2.StartRange+1) > NewValue
ORDER BY t1.EndRange
LIMIT 1
Example Case
Given the table, if NewValue = 800, then the returned answer should be 23137. This means, the first available slot would be between user3 and user4 (with an actual slot size = 813):
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
InsertRange = (StartRange(6) - EndRange(5)) > NewValue
23137 = 25955 - 25142 > 800
More Comments
My query above seemed to be working for the special case where StartRanges where tightly packed (i.e. StartRange(i) = StartRange(i-1) + EndRange(i-1) + 1). This no longer works with a less tightly packed set of StartRanges
Keep in mind that SQL tables have no implicit row order. It seems fair to order your table by StartRange value, though.
We can start to solve this by writing a query to obtain each row paired with the row preceding it. In MySQL, it's hard to do this beautifully because it lacks the row numbering function.
This works (http://sqlfiddle.com/#!9/4437c0/7/0). It may have nasty performance because it generates O(n^2) intermediate rows. There's no row for user0; it can't be paired with any preceding row because there is none.
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
Then, you can use that as a subquery, and apply your conditions, which are
gap >= 800
first matching row (lowest StartRange value) ORDER BY SB
just one LIMIT 1
Here's the query (http://sqlfiddle.com/#!9/4437c0/11/0)
SELECT SB-EA Gap,
EA+1 Beginning_of_gap, SB-1 Ending_of_gap,
UserId UserID_after_gap
FROM (
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
) pairs
WHERE SB-EA >= 800
ORDER BY SB
LIMIT 1
Notice that you may actually want the smallest matching gap instead of the first matching gap. That's called best fit, rather than first fit. To get that you use ORDER BY SB-EA instead.
Edit: There is another way to use MySQL to join adjacent rows, that doesn't have the O(n^2) performance issue. It involves employing user variables to simulate a row_number() function. The query involved is a hairball (that's a technical term). It's described in the third alternative of the answer to this question. How do I pair rows together in MYSQL?

MySQL match area code only when given the full number

I have a database that lists a few area codes, area code + office codes and some whole numbers and a action. I want it to return a result by the digits given but I am not sure how to accomplish it. I have some MySQL knowledge but its not very deep.
Here is a example:
match | action
_____________________
234 | goto 1
333743 | goto 2
8005551212| goto 3
234843 | goto 4
I need to query the database with a full 10 digit number -
query 8005551212 gives "goto 3"
query 2345551212 gives "goto 1"
query 3337431212 gives "goto 2"
query 2348431212 gives "goto 4"
This would be similar to the LIKE selection, but I need to match against the database value instead of the query value. Matching the full number is easy,
SELECT * FROM database WHERE `match` = 8005551212;
First the number to query will always be 10 digits, so I am not sure how to format the SELECT statement to differentiate the match of 234XXXXXXX and 234843XXXX, as I can only have one match return. Basically if it does not match the 10 digits, then it checks 6 digits, then it will check the 3 digits.
I hope this makes sense, I do not have any other way to format the number and it has to be accomplished with just a single SQL query and return over a ODCB connection in Asterisk.
Try this
SELECT match, action FROM mytable WHERE '8005551212' like concat(match,'%')
The issue is that you will get two rows in one case .. given your data..
SELECT action
FROM mytable
WHERE '8005551212' like concat(match,'%')
order by length(match) desc limit 1
That should get the row that had the most digits matched..
try this:
SELECT * FROM (
SELECT 3 AS score,r.* FROM mytable r WHERE match LIKE CONCAT(SUBSTRING('1234567890',1,3),'%')
UNION ALL
SELECT 6 AS score,r.* FROM mytable r WHERE match LIKE CONCAT(SUBSTRING('1234567890',1,6),'%')
UNION ALL
SELECT 10 AS score,r.* FROM mytable r WHERE match LIKE CONCAT(SUBSTRING('1234567890',1,10),'%')
) AS tmp
ORDER BY score DESC
LIMIT 1;
What ended up working -
SELECT `function`,`destination`
FROM reroute
WHERE `group` = '${ARG2}'
AND `name` = 0
AND '${ARG1}' LIKE concat(`match`,'%')
ORDER BY length(`match`) DESC LIMIT 1

Updating multiple columns depending on 1 CASE-condition

I have a program that needs to synchronize it's frequently changing values (in temporary memory) with a database. The critical key (not primary!) in that table is the column id. My program changes the id but keeps the old id in memory, too.
Now, I would like to update several specified columns for multiple records/rows in one single statement. Furthermore, it should be reasonably fast for 5 up to 10 of such statements in 1 second with 4 GB RAM and ~ 50 MBit/s connection that is not only used for these sql-calls.
My sql-specifications
Server: 127.0.0.1 via
TCP/IP
Software: MySQL
Software version: 5.5.27 - MySQL Community Server (GPL)
Protocol version: 10
Server charset: UTF-8 Unicode (utf8)
I tried to use brackets...
UPDATE someTable
SET (id, name) = CASE id
WHEN 1 THEN (111, "Dr. Frankenstein")
WHEN 2 THEN (222, "the Monster")
WHEN 3 THEN (333, "Mr. X")
ELSE (id, name) END
WHERE id IN (1, 2, 3)
...which simply results in the following error:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(id, name) = CASE id WHEN 1 THEN (111, "Dr. Frankenstein") WHEN 2 THEN (222,' at line 2
Know I wonder: Is there a way to do it in just one statement with the current syntax? Would it be feasable that way or should I just split it into multiple statement which is ugly in terms of the program that makes the sql-calls.
Answers and suggestions are welcome!
A case statement only returns one value:
UPDATE someTable
SET id = (CASE id WHEN 1 THEN 111 WHEN 2 THEN 222 WHEN 3 THEN 333 ELSE id END),
name = (CASE id WHEN 1 THEN 'Dr. Frankenstein'
WHEN 2 THEN 'the Monster'
WHEN 3 THEN 'Mr. X'
ELSE name
END)
WHERE id IN (1, 2, 3);
For performance, be sure you an an index on id. This will help with finding the records to update. Do note that changing the id value requires updating the index, which can be a bit longer than a normal update. However, expecting 5-10 transactions a second is reasonable.
Hope this works:
UPDATE someTable
SET id = CASE
id
WHEN 1 THEN 111
WHEN 2 THEN 222
WHEN 3 THEN 333
ELSE id END
,
name = CASE
id
WHEN 1 THEN "Dr. Frankenstein"
WHEN 2 THEN "the Monster"
WHEN 3 THEN "Mr. X"
ELSE name END
WHERE id IN (1, 2, 3)

selecting BIT field in sub query gives '49' instead of 0 or 1

select *, (select logactivity from users where regtoken= '12345') as log from server
the log activity field in 'users' is a BIT field which under "SELECT * FROM USERS" comes up 0 or 1. But when I use the subquery above, I get 49 instead of 1, 48 instead of 0. Why?
48 and 49 are integer values of character (ascii) representations of characters0 and 1 .
Perhaps the issue is not in your query, but in your code somewhere which deals with this case.
php example : http://codepad.org/5i65IWTG