I have the following table in PostgreSQL database:
CREATE TABLE maclist (
username character varying,
mac macaddr NOT NULL,
CONSTRAINT maclist_user_mac_key UNIQUE (username, mac)
);
I need a way to check if the MAC address is assigned to the user or the user has no assigned MAC addresses at all. Basically I need a query that returns row if all conditions are true or no condition is true ie NOT a XOR b.
EDIT:
Example:
username | mac
john | 11:22:33:44:55:66
john | 11:22:33:44:55:67
doe | 11:22:33:44:55:68
If I query:
username = john, mac = 11:22:33:44:55:66 -> true, 1 whatever...
username = john, mac != 11:22:33:44:55:66 -> 0, null or nothing...
username = jane, mac = no matter what except john's or doe's -> true, 1 whatever...
username = jane, mac = john's or doe's -> 0, null or nothing...
I need true under two conditions:
There is a row for that (user, mac) combination. There are no rows
for that user AND there are no rows for that mac
So far I got this:
SELECT yes
FROM
(SELECT 1 AS yes) AS dummy
LEFT JOIN maclist ON (username = 'user'
OR mac = '11:22:33:44:55:66')
WHERE ((username = 'user'
AND mac = '11:22:33:44:55:66')
OR (username IS NULL
AND mac IS NULL);
It works, but it seems to me like a hack and I also have no idea about the performance of this query as the database grows.
My question is if there are any better ways to do this.
EDIT: I've revised the logic slightly, I think it matches what you want
The following code will return 1 under two conditions...
There is a row for that user and that mac
There are 0 rows for that user and there are 0 rows for that mac
Under all other conditions, the query returns 0.
There are rows for that user but none of them are for that mac
There are rows for that mac but none of them are for that user
SELECT
CASE WHEN COUNT(*) = 0 THEN 1
WHEN SUM(CASE WHEN mac = '11:22:33:44:55:66'
AND user = 'user' THEN 1
ELSE 0 END) = 1 THEN 1
ELSE 0
END
FROM
maclist
WHERE
username = 'user'
OR mac = '11:22:33:44:55:66'
You can write the query simply as:
prepare query_mac(text, macaddr) as
select exists (select 1 from maclist where username = $1 and mac = $2)
or (not exists (select 1 from maclist where username = $1)
and not exists (select 1 from maclist where mac = $2)
);
PostgreSQL will evaluate each of the three queries separately, and use the unique index on (username,mac) for the first two. If you need to, you can add an index on (mac) for the third.
Practically all databases won't use an index when an OR is involved, so avoid those in the joins.
Edited:
It seems you are really making two separate queries:
who is the mac assigned to (if any)
what mac is assigned to the user (if any)
so a union won't be as easy as a collection of subselects. Use separate queries to split up the info into their own simple query, and combine them into one:
select
(select username from maclist
where mac = '11.22:33:44:55:66') as mac_assigned_to,
(select mac from maclist
where username = 'user') as user_assigned_to
Both columns may have nulls if there is nothing currently assigned, otherwise they'll show what's assigned for each concern.
Related
I have following tables:
User - userId, userName, ...
Settings - settingId, userId, settingKey, settingValue
for example for userId = 123, I might have:
settingId: 1
userId: 123
settingKey: "allowClient"
settingValue: "0"
settingId: 2
userId: 123
settingKey: "allowAccess"
settingValue: "1"
Then for example how can I query for all users that have settingValue of "0" corresponding to settingKey of "allowClient" and settingValue of "1" corresponding to settingKey of "allowAccess"? Sometimes the settingKey and settingValue that I'm looking for might not even be there for a particular user, in which case, I would just want to ignore those users.
My "attempt":
select * from User u inner join Settings s on u.userid = s.userid
where s.settingKey = 'allowClient and s.settingValue = '0'
and s.settingKey = 'allowAccess' and s.settingValue = '1'
this doesn't work for obvious reason because it's putting AND on all the conditions. I'm not aware of any sql construct that can get around this and allow me to just say what I actually want.
Your first attempt doesn't work because the WHERE clause check each row one at a time. And no single row fulfils all of those conditions at once.
So, you could use an EXISTS() check on each of the two keys, for a very literal expression of your problem...
SELECT
user.*
FROM
user
WHERE
EXISTS (
SELECT *
FROM settings
WHERE userId = user.userId
AND settingKey = 'allowClient'
AND settingValue = '0'
)
AND
EXISTS (
SELECT *
FROM settings
WHERE userId = user.userId
AND settingKey = 'allowAccess'
AND settingValue = '1'
)
Depending on data characteristics, you may benefit from a single sub-query instead of two EXISTS() checks.
This is closer to what you were trying to do.
Filter to get two rows per user (using OR instead of AND)
Aggregate back down to a single row and check if both conditions were met
(But I'd go with two EXISTS() first, and let the optimiser do its work.)
WITH
matching_user
(
SELECT
userId
FROM
settings
WHERE
(settingKey = 'allowClient' AND settingValue = '0')
OR
(settingKey = 'allowAccess' AND settingValue = '1')
GROUP BY
userId
HAVING
COUNT(DISTINCT settingKey) = 2 -- DISTINCT only needed if one user has the same key set twice
)
SELECT
user.*
FROM
user
INNER JOIN
matching_user
ON user.userId = matching_user.userId
Finally, you could just join twice, which is functionally similar to the double-exists check, but shorter code, though not always as performant.
SELECT
user.*
FROM
user
INNER JOIN
settings AS s0
ON s0.userId = user.userId
AND s0.settingKey = 'allowClient'
AND s0.settingValue = '0'
INNER JOIN
settings AS s1
ON s1.userId = user.userId
AND s1.settingKey = 'allowAccess'
AND s1.settingValue = '1'
Using the two different aliases prevents ambiguity (which would cause an error).
It does assume that the joins will only ever find 0 or 1 rows, if they can find many, you get duplication. EXISTS() doesn't have that problem.
Original table has 1466303 records in it, I have inserted 1108441 of those records in to a separate table. What I would like to know is what data is left over? So I have made a query using multiple exists to find the data that was left:
SELECT SG_customer,
PHONE,
SG_Name,
SG_Secondary_Address,
SG_Primary_Address,
SG_City,
SG_State,
SG_Zip,
SG_Email
FROM FMJ_DB_VPI_EXPANDED_DATA X
WHERE NOT EXISTS (SELECT 1
FROM FMJScore
WHERE SGID = X.SG_Customer
AND Phone = X.Phone
AND Name = X.SG_Name
AND SecondAddress = X.SG_Secondary_Address
AND Address = X.SG_Primary_Address
AND City = X.SG_City
AND State = X.SG_State
AND Zip = X.SG_Zip
AND Email = X.SG_Email)
Running this returns back 144391 records, there should be a difference of 357862, I don't understand why its returning back so many records.
I assume you want null to be treated equal to null, I also assume that '' is not used as a value, if it is replace it with something that does not normally occur:
SELECT SG_customer,
PHONE,
SG_Name,
SG_Secondary_Address,
SG_Primary_Address,
SG_City,
SG_State,
SG_Zip,
SG_Email
FROM FMJ_DB_VPI_EXPANDED_DATA X
WHERE NOT EXISTS (SELECT 1
FROM FMJScore
WHERE coalesce(SGID,'') = coalesce(X.SG_Customer,'')
AND coalesce(Phone,'') = coalesce(X.Phone,'')
AND coalesce(Name,'') = coalesce(X.SG_Name,'')
AND coalesce(SecondAddress,'') = coalesce(X.SG_Secondary_Address,'')
AND coalesce(Address,'') = coalesce(X.SG_Primary_Address,'')
AND coalesce(City,'') = coalesce(X.SG_City,'')
AND coalesce(State,'') = coalesce(X.SG_State,'')
AND coalesce(Zip,'') = coalesce(X.SG_Zip,'')
AND coalesce(Email,'') = coalesce(X.SG_Email,''))
The optimizer might not be able to use indexes efficiently due to the function call
I'm having a problem with the result obtained on a select in my sqlite.
I already have a database fed, and I'm doing queries in my application in adobe air. In my table I have 6 columns:
id | name | email | CITY_ID | state_id | phone
When I do a select of the entire table, it returns me an array of objects.
result[30].id = 30;
result [30]. name = John;
result [30]. email = john#xxx.com;
result [30]. city_id = 1352;
result [30]. state_id = 352;
result [30]. phone = xxxxxxxxx;
All information came right, but the id value is incorrect ( correct is not 30 ) . It seems to me that i'm getting the numerical order and not getting the id column value.
Has anyone had this problem?
UPDATE
My query is:
_selectStat = new SQLStatement();
_selectStat.addEventListener( SQLEvent.RESULT, onDataLoaded );
_selectStat.addEventListener( SQLErrorEvent.ERROR, onSqlError );
_selectStat.sqlConnection = _connection;
var sql:String = 'SELECT * FROM "main"."agencia"';
_selectStat.text = sql;
_selectStat.execute();
I'm not familiar with Adobe development or sqlite, so I'm speculating here. If 'id' is a database property, then you may need to indicate that you want the column 'id', and not the property 'id'. There should be a way to do this with the adobe application syntax or the sqlite SQL syntax. In mssql, brackets [] are used for this, so it would be [id] to indicate the column 'id' and not the property. There should be something similar for your environment.
Having a mental block with going around this query.
I have the following tables:
review_list: has most of the data, but in this case the only important thing is review_id, the id of the record that I am currently interested in (int)
variant_list: model (varchar), enabled (bool)
variant_review: model (varchar), id (int)
variant_review is a many to many table linking the review_id in review_list to the model(s) in variant_list review and contains (eg):
..
test1,22
test2,22
test4,22
test1,23
test2,23... etc
variant_list is a list of all possible models and whether they are enabled and contains (eg):
test1,TRUE
test2,TRUE
test3,TRUE
test4,TRUE
what I am after in mysql is a query that when given a review_id (ie, 22) will return a resultset that will list each value in variant_review.model, and whether it is present for the given review_id such as:
test1,1
test2,1
test3,0
test4,1
or similar, which I can farm off to some webpage with a list of checkboxes for the types. This would show all the models available and whether each one was present in the table
Given a bit more information about the column names:
Select variant_list.model
, Case When variant_review.model Is Not Null Then 1 Else 0 End As HasReview
From variant_list
Left join variant_review
On variant_review.model = variant_list.model
And variant_review.review_id = 22
Just for completeness, if it is the case that you can have multiple rows in the variant_review table with the same model and review_id, then you need to do it differently:
Select variant_list.model
, Case
When Exists (
Select 1
From variant_review As VR
Where VR.model = variant_list.model
And VR.review_id = 22
) Then 1
Else 0
End
From variant_list
I have two tables.
USER user_id password
FRIEND_LIST user_id friend_id
If user 1 is friend of user 2 then in friend_list there will be 2 records:
1 2
2 1
Thats how I'm controlling friend list.
My question is how can I create an efficient query that validates if a user is a friend of a friend.
For example user 1 has in his friend list user 2. and user 3 has in his friend list user 2. So user 2 is a common friend of both 1 and 3.
Here is how friend_list table looks like:
1 2
2 1
3 2
2 3
No I want to know if user 1 has a friend that has as friend user 3.
The pseudocode is as follows:
validate(){
valid = false
list = get all friends from user 1 and store them in 'list'.
for each friend in list {
list2 = get all friends from friend
for each friend2 in list2 {
if friend2.user_id = 3 }
valid = true
break; //stop here because we have found that 3 is a friend of a friend of 1
}
}
}
return valid
}
This is how it would look like in a programming language. Now I want to validate the same but just with an SQL query.
I tried this but I dont know if this is a good way to validate that.
select *
from friend_list fl1
inner join friend_list fl2 on fl1.user_id = fl2.user_id
inner join friend_list fl3 on fl2.friend_id = fl3.user_id
where fl1.user_id = 1 and fl3.friend_id = 3
Thanks in advance.
Thank you very much for your support. This is the first time I use this forum
and helped me a lot.
I used the EXISTS code you posted like this.
SELECT EXISTS (
SELECT
*
FROM
friend_list AS users
INNER JOIN
friend_list AS friends
ON users.friend_id = friends.user_id
WHERE
users.user_id = 1
AND friends.friend_id = 3
) AS result
As you're looking to find out if there are Any instances of "a and b have a friend in common" you're better of using the EXISTS keyword.
The benefit of this over using COUNT or DISTINT is that the optimiser knows a couple of things:
1. The actual data doesn't matter
2. It can stop searching after the first hit
For example...
IF EXISTS (
SELECT
*
FROM
friend_list AS [user]
INNER JOIN
friend_list AS [friend]
ON [user].friend_id = [friend].user_id
WHERE
[user].user_id = #user_id
AND [friend].friend_id = #friend_of_friend_id
)
BEGIN
RETURN 1
-- Or whatever code you want to execute
END
ELSE
BEGIN
RETURN 0
-- Or whatever code you want to execute
END
Although this doesn't have "TOP 1" and uses "*", it neither actually returns multiple fields or rows. It literally just searches for the existance of a match and stops when it finds one.
The query you already wrote is along the right lines, I think you need one less join to the friend-list table:
select distinct fl1.user_id, fl2.friend_id
from friend_list fl1
inner join friend_list fl2 on fl1.friend_id = fl2.user_id
where fl1.user_id = 1 and fl2.friend_id = 3
So '1' (fl1.user_id) is friends with 'x' (fl1.friend_id and fl2.user_id) who is friends with '3' (fl2.friend_id).
Because you have two complementary entries in friend-list for each pair of friends, the query is nice and simple. It would be a bit tougher if each pair of friends only got one row in friend-list ....
(edit: realised too many joins were happening...)
(edit: added a distinct into the select after comment conversation)