MySQL: Possible to apply the OR operator across multiple selected rows? - mysql

I have three MySQL tables: users, roles and positions.
The users table is pretty self-explanatory. The roles table is a list of job titles a person might hold, such as janitor, president, manager, etc. The roles table also has a long array of boolean permissions, such as access_basement or user_directory_access. If the role has has that bit value set to false (or "0") that role lacks that permission.
Where it gets tricky is that a user might have multiple roles, hence why they are connected by the positions table, which is simply a pairing of the userId and roleId fields. So if I perform a query like:
SELECT * FROM users
LEFT JOIN positions ON users.userId=positions.userId
LEFT JOIN roles ON roles.roleId=positions.roleId
WHERE users.userId=123
I might get results like:
+---------+-----------+-----------------+-----------------------+
| name | title | basement_access | user_directory_access |
+---------+-----------+-----------------+-----------------------+
| Bob | Janitor | true | false |
+---------+-----------+-----------------+-----------------------+
| Bob | President | false | true |
+---------+-----------+-----------------+-----------------------+
Since Bob has two roles, but has different access with each, I'd like to combine the results with a since MySQL query and the logical OR operation across all rows, resulting in a table like:
+---------+-----------------+-----------------------+
| name | basement_access | user_directory_access |
+---------+-----------------+-----------------------+
| Bob | true | true |
+---------+-----------------+-----------------------+
So the question is: is it possible to apply the OR operator across multiple selected MySQL rows?
Thanks!

One way to solve this is if you use values 0 and 1 as role permissions. And use some query like:
SELECT u.name, SUM(r.basement_access) AS basement_access, SUM(r.user_directory_access) AS user_directory_access
FROM users u
LEFT JOIN positions p ON u.userId=p.userId
LEFT JOIN roles r ON r.roleId=p.roleId
WHERE u.userId=123
GROUP BY u.userId;

Related

ER_NON_UNIQ_ERROR and how to design tables correctly

I have come across this problem and I've tried to solve it few days now.
Let's say I have following tables
properties
-----------------------------------------
| id | address | building_material |
-----------------------------------------
| 1 | Street 1 | 1 |
-----------------------------------------
| 2 | Street 2 | 2 |
-----------------------------------------
building_materials
-----------------------------
| id | building_material |
-----------------------------
| 1 | Wood |
-----------------------------
| 2 | Stone |
-----------------------------
Now. I would like to provide an API where you could send a request and ask for every property that has building material of wood. Like this:
myapi.com/properties?building_material=Wood
So I would like to query database like this (I want to return the string value of building_material not the numeric value):
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE building_material = "Wood"
But this will give me an error
Column 'building_material' in where clause is ambiguous
Also if I want to get property with id of 1.
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE id = 1
Column 'id' in where clause is ambiguous
I understand that the error means that I have same column name in two tables and I don't specify which id I want like p.id.
Problem is I don't know how many query parametes API user is going to send and I would like to avoid looping through them and changing id to p.id and building_material to bm.building_material. Also I don't want that user has to send request to the API like this
myapi.com/properties?bm.building_material=Wood
I've thought about changing the properties table building_material to fk_building_material and changing properties table id to property_id.
I just don't like the idea that on client side I would then have to refer property's building material as fk_building_material. Is this a valid method to solve this problem or what would be the correct way of designing these tables?
The query mentions two tables, so all the columns in both tables are "on the table" for use anywhere in the query.
In one table building_material is an "id" for linking to the other table; in the other table, it is a string. While this is possible, it is confusing to the reader. And to the parser. To resolve the confusion, you must qualify building_material with which one you want; that is done with a table alias (or table) in front (as you did in all other places).
There are two ids are all ambiguous. But this is the "convention" used by table designers. So, it is OK for an id in one table to be different than the id in the other table. (p.id refers to one thing in one table; bm.id refers to another in another table.)
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE bm.building_material = "Wood" -- Note "bm."

mysql error : "subquery returns more than one row" but i need more than one row from subquery

I have two tables in my problem, one is the USER table with all details of user and a REQUEST table with friend requests from all users to all users. I want to list all the friends of one user who logs into my application.
The USER table attributes are,
id | uname | passwd | fname | e mail | dob | mobileno | habbits | tastes | profession | image
11 | nagaraj | ***** | naga | ng#gml | 3/94 | 998345 | singing | sports | teacher
12 | chiraag | ******* | chiru | ch#gml | 2/93 | 894617 | writing | music | student
Similarly, the REQUEST table attributes are,
rqstTO | rqstFM | fname | e mail | mobile | status
chirag | nagaraj | nagaraj | ng#gml | 99821 | accepted
The tables look like this. Now I want to display the details of the friends( from USER table), only those friends who have accepted the request from the user who logged in. Output may be multiple users. The conditions to be satisfied are
all details of friends (from user table)
rqstFM should be the user logged in and rqstTO must be the friends to whom he sent requests (since request table has request details from all to all users)
status=accepted (they must have accepted the request)
The query I have written is
SELECT *
FROM user
WHERE habits='singing' AND uname = (
SELECT rqstTO
FROM request
WHERE rqstFM ='"+username+"' AND status='Accepted'
);
(here +username+ is the uname of the person logged in obtained from HTML FORM tag). The error I keep getting is "subquery returns more than one row" but I do need multiple rows as logged in user may have sent requests to many ppl and among those all who accepted his request, their details need to be displayed from user table. Please, correct my query or provide an appropriate query for my problem
You need to use IN instead of =:
You can use as mentioned below:
SELECT * FROM user
WHERE habits='singing' AND uname IN
( SELECT rqstTO FROM request WHERE rqstFM ='"+username+"' AND status='Accepted');
However using subqueries are not recommended due to performance issues. You should rewrite your query by using JOIN
You can use 'JOIN' like below: (I haven`t tested it)
SELECT u.* FROM user u
JOIN request r ON u.uname = r.rqstTO
WHERE u.habits='singing' AND r.rqstFM ='"+username+"' AND r.status='Accepted';
You want something like this (not tested it however):
SELECT user.uname, request.fname
FROM user
INNER JOIN request ON request.fname = user.fname
WHERE request.status = 'Accepted';
However, I would probably have a bit of a rethink of those tables, maybe this is personal preference but you would likely be better off having a join table that simply contains an id and the username, and then join on two other tables. One containing the requests and one containing the member details.

export phpList subscribers via sql in mysql database

For some reason, I am unable to export a table of subscribers from my phpList (ver. 3.0.6) admin pages. I've searched on the web, and several others have had this problem but no workarounds have been posted. As a workaround, I would like to query the mySQL database directly to retrieve a similar table of subscribers. But I need help with the SQL command. Note that I don't want to export or backup the mySQL database, I want to query it in the same way that the "export subscribers" button is supposed to do in the phpList admin pages.
In brief, I have two tables to query. The first table, user contains an ID and email for every subscriber. For example:
id | email
1 | e1#gmail.com
2 | e2#gmail.com
The second table, user_attribute contains a userid, attributeid, and value. Note in the example below that userid 1 has values for all three possible attributes, while userid's 2 and 3 are either missing one or more of the three attributeid's, or have blank values for some.
userid | attributeid | value
1 | 1 | 1
1 | 2 | 4
1 | 3 | 6
2 | 1 | 3
2 | 3 |
3 | 1 | 4
I would like to execute a SQL statement that would produce a row of output for each id/email that would look like this (using id 3 as an example):
id | email | attribute1 | attribute2 | attribute3
3 | e3#gmail.com | 4 | "" | "" |
Can someone suggest SQL query language that could accomplish this task?
A related query I would like to run is to find all id/email that do not have a value for attribute3. In the example above, this would be id's 2 and 3. Note that id 3 does not even have a blank value for attributeid3, it is simply missing.
Any help would be appreciated.
John
I know this is a very old post, but I just had to do the same thing. Here's the query I used. Note that you'll need to modify the query based on the custom attributes you have setup. You can see I had name, city and state as shown in the AS clauses below. You'll need to map those to the attribute id. Also, the state has a table of state names that I linked to. I excluded blacklisted (unsubscribed), more than 2 bounces and unconfirmed users.
SELECT
users.email,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=1
) AS name,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=3
) AS city,
(SELECT st.name
FROM `phplist_user_user_attribute` attrs
LEFT JOIN `phplist_listattr_state` st
ON attrs.value = st.id
WHERE
attrs.userid = users.id and
attributeid=4
) AS state
FROM
`phplist_user_user` users
WHERE
users.blacklisted=0 and
users.bouncecount<3 and
users.confirmed=1
;
I hope someone finds this helpful.

Correct way of storing a list of related values in Database

Let's say I have the following scenario.
A database of LocalLibrary with two tables Books and Readers
| BookID| Title | Author |
-----------------------------
| 1 | "Title1" | "John" |
| 2 | "Title2" | "Adam" |
| 3 | "Title3" | "Adil" |
------------------------------
And the readers table looks like this.
| UserID| Name |
-----------------
| 1 | xy L
| 2 | yz |
| 3 | xz |
----------------
Now, lets say that user can create a list of books that they read (a bookshelf, that strictly contains books from above authors only i.e authors in our Db). So, what is the best way to represent that bookshelf in Database.
My initial thought was a comma separated list of BookIDin Readers table. But it clearly sounds awkward for a relational Db and I'll also have to split it every time I display the list of users' books. Also, when a user adds a new book to shelf, there is no way of checking if it already exists in their shelves except to split the comma-separated list and and compare the IDs of two. Deleting is also not easy.
So, in one line, the question is how does one appropriately models situations like these.
I have not done anything beyond simple SELECTs and INSERTs in MySQL. It would be much helpful if you could describe in simpler terms and provide links for further reading.
Please comment If u need some more explanation.
Absolutely forget the idea about a comma separated list of books to add to the Readers table. It will be unsearchable and very clumsy. You need a third table that join the Books table and the Readers table. Each record in this table represent a reader reading a book.
Table ReaderList
--------------------
UserID | BookID |
--------------------
You get a list of books read by a particular user with
select l.UserID, r.Name, l.BookID, b.Title, b.Author
from ReaderList l left join Books b on l.BookID = b.BookID
left join Readers r on l.UserID = r.UserID
where l.UserID = 1
As you can see this pattern requires the use of the keyword JOIN that bring togheter data from two or more table. You can read more about JOIN in this article
If you want, you could enhance this model adding another field to the ReaderList like the ReadingDate

Summary of MySQL detail records matching by IP address ranges - mySQL Jedi Knight required

So, I have to draw upon all the powers of the greatest mySQL minds that SO has to offer. I have to summarize detail records based on the IP address in each record. Here's the scenario:
In short, we have consortiums that want to know: "Which schools within my consortium watched which videos how many times"? In SQL terms, it amounts to COUNTing the detail records, grouped by which IP range it might fall into.
We have several university Consortiums - each with a handful of different schools that are members.
Each school within a consortium uses various IP ranges to access the videos that we serve to these schools.
The IP Ranges are specified with wild cards, so each school specifies something like '100.200.35.x, 100.201.x.x, 100.202.39.50, etc.', with the average number of ranges per school being 10 or 15.
The raw text log files to summarize are already in a database (one row for each log entry), and has the actual IP address that accessed the video file.
There are 100's of millions of detail records, so I fully expect this to be a long slow process that runs for a considerable period.
PHP scripts exist that can "explode" the wildcards into the individual IPs that are represented, but I fear this will be the final answer and could take weeks to run.
(For simplicity sake, I'm only going to refer to the video filename that was accessed and COUNT the log entries for it, but in fact all the details such as start/stop/duration,etc. are there and will ultimately be part of this solution.)
With Consortium records something like this: (All table designs except log details open to suggestion):
| id|consortium |
| 10|Ivy League |
| 20|California |
And School/IP records something like this:
| id|school |consortium_id|
| 101|Harvard |10 |
| 102|Yale |10 |
| 103|UCLA |20 |
| 104|Berkeley |20 |
| id|school_id|ip_range |
| 1| 101 |100.200.x.x |
| 2| 101 |100.201.65.x |
| 3| 101 |100.202.39.50 |
| 4| 101 |100.202.39.51 |
| 5| 101 |100.200.x.x |
| 6| 101 |100.201.65.x |
| 7| 101 |100.202.39.50 |
And detail records something like this:
|session |ip_address |filename |
|560554790925|100.202.390.500|history101.mp4 |
|406417611526|43.22.90.5 |newsreel.mp4 |
|650423700223|100.202.39.50 |history101.mp4 |
|650423700223|100.202.50.12 |science101.mp4 |
|513057324209|100.202.39.56 |history101.mp4 |
I like to think I'm pretty handy with mySQL, but this one is stretching it, and am hoping that there's a spectacular function or set of steps that someone might offer.
With your existing data structure, you could do string matching as follows (but it's not very efficient):
SELECT schools.school, detail.filename, COUNT(*)
FROM schools
JOIN ipranges ON schools.id = ipranges.school_id
JOIN detail ON detail.ip_address LIKE REPLACE(ipranges.ip_range, 'x', '%')
WHERE schools.consortium_id = ?
GROUP BY schools.school, detail.filename
A better way would be to store your IP ranges as network address and prefix length:
ALTER TABLE ipranges
ADD COLUMN network INT UNSIGNED,
ADD COLUMN prefix TINYINT;
UPDATE ipranges SET
network = INET_ATON(REPLACE(ip_range, 'x', 0)),
prefix = 32 - 8*(CHAR_LENGTH(ip_range) - CHAR_LENGTH(REPLACE(ip_range,'x',''));
ALTER TABLE ipranges
DROP COLUMN ip_range;
ALTER TABLE detail
ADD COLUMN ip_address_new INT UNSIGNED;
UPDATE detail SET
ip_address_new = INET_ATON(ip_address);
ALTER TABLE detail
DROP COLUMN ip_address,
CHANGE ip_address_new ip_address INT UNSIGNED;
Then it would merely be a case of performing some bit comparisons:
SELECT schools.school, detail.filename, COUNT(*)
FROM schools
JOIN ipranges ON schools.id = ipranges.school_id
JOIN detail ON detail.ip_address & ~((1 << 32 - ipranges.prefix) - 1)
= ipranges.network
WHERE schools.consortium_id = ?
GROUP BY schools.school, detail.filename
SELECT D.filename, S.school, COUNT(D.*)
FROM detail_records AS D
INNER JOIN ip_map AS I ON D.ip_address LIKE CONCAT(SUBSTRING(I.ip_range, 1, LOCATE('x', I.ip_range)-1), '%')
INNER JOIN school AS S ON S.id = I.school_id
INNER JOIN consortium AS C ON C.id = S.consortium_id
WHERE S.consortium_id = <consortium identifier>
GROUP BY D.filename, S.school