SQL left join for users table - sql

I have the following example table (simplified from what I really have as there are a few more joins in there)
|permission|username|
| perm1 | u1 |
| perm2 | u1 |
| perm3 | u1 |
| perm1 | u2 |
| perm4 | u2 |
I have many users and many groups in this table
I am having trouble with user permissions where some users can do things in my application and others cannot. At the moment I am having to manually go through and figure out what the differences are.
I want to crate a query from the above table that gives me an answer like this
|permission| u1 | u2 |
|perm1 | 1 | 1 |
|perm2 | 1 | 0 |
|perm3 | 1 | 0 |
|perm4 | 0 | 1 |
I do not really want 1 and 0 under the users but I want something to I can easily visualise that user 1 has these permissions and user2 does not. This would be really useful for me to ensure that users have the same permissions.
I have done the SQL to get the top table so I can see the permissions.
I was thinking that some sort of left join on itself might the answer there but everything I try seems to end up as invalid SQL.
Any hints please? Im happy to experiment if I get a starting point :)
Thanks in advance

What you're looking for is called "pivoting". One way to do that is a group by, like:
select permission
, count(case when username = 'u1' then 1 end) as User1
, count(case when username = 'u2' then 1 end) as User2
, count(case when username = 'u3' then 1 end) as User3
, ...
from YourTable
group by
permission
It looks like Oracle supports the PIVOT keyword. You can use it to accomplish the same thing:
select *
from YourTable
pivot (
count(*) as HasPermission
for (username) in ('u1', 'u2', 'u3', ...)
);
Example for both approaches at SQL Fiddle.

Related

SQL to search with a subsets of records

Is there any way to select all subsets from table A where corresponding subsets exist in table B? Each subset in table A must have at least all the entries that corresponding subset in table B.
In this link it's called "Division with a Remainder" but my problem is more complex because I've got many to many relation.
Table A
UserName text | File text | AccessLevel int
Table B
AppName text | File text | AccessLevel int
Sample data
Table A
User1 | aaa.txt | 1
User1 | bbb.txt | 3
User1 | ccc.txt | 1
User2 | aaa.txt | 3
Table B
Appl1 | aaa.txt | 1
Appl1 | bbb.txt | 1
Appl2 | aaa.txt | 1
Appl3 | bbb.txt | 5
Appl4 | aaa.txt | 1
Appl4 | bbb.txt | 1
Appl4 | ccc.txt | 1
Appl4 | ddd.txt | 1
Expected results:
User1 | Appl1
User1 | Appl2
User2 | Appl2
User1 has "complete" access to applications Appl1 and Appl2 because he has necessary access to ALL files used by these applications. He doesn't have access to application Appl3 because access level is not high enough. He doesn't have access to application Appl4 because he doesn't have access to file ddd.txt.
Basically I need to compare subsets of records and return all cases where subset in table B is equal or greater than subset in table A. Is there any way to do it in SQL?
Any help appreciated.
One method is a self-join and then comparing the number of matching records to the number needed for the application.
Assuming no duplicates:
select a.username, b.appname
from a join
(select b.*, count(*) over (partition by b.appname) as cnt
from b
) b
on a.filetext = b.filetext and
a.accesslevel >= b.accesslevel
group by a.username, b.appname, b.cnt
having count(*) = b.cnt
SELECT distinct A1.USERNAME, B1.APPLICATION
FROM TABLEUSER A1
INNER JOIN TABLEAPPLI B1 on B1.filename=A1.filename and B1.accesslevel<=A1.accesslevel
where not exists
(
select *
from TABLEUSER A2 INNER JOIN TABLEAPPLI B2 on B2.filename<>A2.filename or B2.accesslevel>A2.accesslevel
where (A1.USERNAME, B1.APPLICATIONAME) = (A2.USERNAME, B2.APPLICATIONAME)
)

Postgres recursive CTE or crosstab function

I try to generate some user statistics from a table that includes logging information.
**TABLE users**
user_id | user_name
-------------------
1 | julia
2 | bob
3 | sebastian
**TABLE logs**
user_id | action | timepoint
------------------------------------
1 | create_quote | 2015-01-01
1 | send_quote | 2015-02-03
1 | create_quote | 2015-02-02
1 | start_job | 2015-01-15
2 | start_job | 2015-02-23
2 | send_quote | 2015-03-04
2 | start_job | 2014-12-02
My desired output is the following table
user_id | username | create_quote | send_quote | start_job
-----------------------------------------------------------
1 | julia |2 | 1 | 1
2 | bob |0 | 1 | 1
3 | sebastian |0 | 0 | 0
It includes all users (even if there was nothing logged), but only the actions between date '2015-01-01' and '2015-05-31'. Actions are counted/grouped by action type and user.
The SQL statement could look someting like
SELECT * FROM myfunction() WHERE to_char(timepoint, 'YY/MM') BETWEEN '15/01' AND '15/05';
Do you have any idea how to manage this? I've been trying around with CTEs and recursion as well as with the crosstab function but could not find any solution.
I think the crosstab function would be a lot more elegant, but in the case that you don't have the extension loaded or, like me, struggle with the syntax, this is a kind of clumsy, brute-force way you could do it:
CREATE OR REPLACE FUNCTION get_stats(
from_date date,
thru_date date)
RETURNS table (
user_id integer,
username text,
create_quote bigint,
send_quote bigint,
start_job bigint
) AS
$BODY$
select
l.user_id, u.username,
sum (case when action = 'create_quote' then 1 else 0 end) as create_quote,
sum (case when action = 'send_quote' then 1 else 0 end) as send_quote,
sum (case when action = 'start_job' then 1 else 0 end) as start_job
from
logs l
join users u on l.user_id = u.user_id
where
l.timepoint between from_date and thru_date
group by
l.user_id, u.username
$BODY$
LANGUAGE sql VOLATILE
COST 100
ROWS 1000;
And then your query would be:
select * from get_stats('2015-01-01', '2015-05-31')
Personally, I would skip the function and just create it as a query, but it's conceivable there are reasons where you would want the function wrapper.
-- EDIT --
Based on an attempted edit, I see you may be okay with a query. Also, you wanted users that have no entries.
With all of that in mind, I think this might work:
select
u.user_id, u.username,
sum (case when action = 'create_quote' then 1 else 0 end) as create_quote,
sum (case when action = 'send_quote' then 1 else 0 end) as send_quote,
sum (case when action = 'start_job' then 1 else 0 end) as start_job
from
users u
left join logs l on
l.user_id = u.user_id and
l.timepoint between '2015-01-01' and '2015-05-31'
group by
u.user_id, u.username

SELECT certain fields based on certain WHERE conditions

I am writing an advanced MySQL query that searches a database and retrieves user information. What I am wondering is can I select certain fields if WHERE condition 1 is met and select other fields if WHERE condition 2 is met?
Database: users
------------------------
| user_id | first_name |
------------------------
| 1 | John |
------------------------
| 2 | Chris |
------------------------
| 3 | Sam |
------------------------
| 4 | Megan |
------------------------
Database: friendship
--------------------------------------
| user_id_one | user_id_two | status |
--------------------------------------
| 2 | 4 | 0 |
--------------------------------------
| 4 | 1 | 1 |
--------------------------------------
Status 0 = Unconfirmed
Status 1 = Confirmed
OK, as you can see John & Megan are confirmed friends while Chris & Megan are friends but the relationship is unconfirmed.
The query I am trying to write is as follow: Megan(4) searches for new friends I want all of the users except for the ones she is a confirmed friend with to be returned. So, the results should return 2,3. But since a relationship with user_id 2 exists but is not confirmed, I want to also return the status since an entry in the friendship table does exist between the two. If a user exist but there is no connection in the relationship table it still returns that users information but returns status as a NULL or doesn't return status at all since it doesn't exist in that table.
I hope this makes since. Ask questions if you need to.
Why not use a left join or an if-not-exists?
SELECT users.*
FROM (users LEFT JOIN friendships
ON status=1 AND (user_id_one=user_id OR user_id_two=user_id) )
WHERE
status IS NULL
or
SELECT users.*
FROM users
WHERE
NOT EXISTS (SELECT *
FROM friendships
WHERE status=1
AND (user_id_one=user_id
OR user_id_two=user_id))
You can create to separate queries and then UNION the result tables. In each query, add a field that always has the same value.
So something like this should work:
(SELECT id, 'Not Friends' As Status FROM t1 WHERE condition1)
UNION
(SELECT id, 'Unconfirmed' As Status FROM t1 WHERE condition2)
Just make sure the same number and name of fields exists in both queries.

How should joins used in mysql?

If i have two tables like
user table-"u"
userid | name
1 | lenova
2 | acer
3 | hp
pass table-"p"
userid | password
1 | len123
2 | acer123
3 | hp123
as for as i learnt from tutorials I can join these 2 tables using many joins available in
mysql as said here
If i have a table like
role table-"r"
roleid | rname
1 | admin
2 | user
3 | dataanalyst
token table-"t"
tokenid| tname
1 | xxxx
2 | yyyy
3 | zzzz
tole_token_association table-"a"
roleid | tokenid
1 | 1
1 | 2
3 | 1
3 | 3
3 | 1
I have to make a join such that I have to display a table which corresponds
like this "rolename" has all these tokens.How to make this? I am confused. Is it possible to make a join? I am liking mysql a lot. I wish to play with queries such that not playing. I want to get well versed. Any Suggestions Please?
It's easiest to see when the column names that need to be joined are named identically:
SELECT r.rname,
t.tname
FROM ROLE r
JOIN ROLE_TOKEN_ASSOCIATION rta ON rta.roleid = r.roleid
JOIN TOKEN t ON t.tokenid = rta.tokenid
This will return only the roles with tokens associated. If you have a role that doesn't have a token associated, you need to use an OUTER join, like this:
SELECT r.rname,
t.tname
FROM ROLE r
LEFT JOIN ROLE_TOKEN_ASSOCIATION rta ON rta.roleid = r.roleid
JOIN TOKEN t ON t.tokenid = rta.tokenid
This link might help -- it's a visual representation of JOINs.

One table, need multiple values from different rows/tuples

I have tables like:
'profile_values'
userID | fid | value
-------+---------+-------
1 | 3 | joe#gmail.com
1 | 45 | 203-234-2345
3 | 3 | jane#gmail.com
1 | 45 | 123-456-7890
And:
'users'
userID | name
-------+-------
1 | joe
2 | jane
3 | jake
I want to join them and have one row with two of the values like:
'profile_values'
userID | name | email | phone
-------+-------+----------------+--------------
1 | joe | joe#gmail.com | 203-234-2345
2 | jane | jane#gmail.com | 123-456-7890
I have solved it but it feels clumsy and I want to know if there is a better way to do it. Meaning solutions that are either more readable or faster(optimized) or simply best-practice.
Current solution: multiple tables selected, many conditional statements:
SELECT u.userID AS memberid,
u.name AS first_name,
pv1.value AS fname,
pv2.value as lname
FROM users AS u,
profile_values AS pv1,
profile_values AS pv2,
WHERE u.userID = pv1.userID
AND pv1.fid = 3
AND u.userID = pv2.userID
AND pv2.fid = 45;
Thanks for the help!
It's a typical pivot query:
SELECT u.userid,
u.name,
MAX(CASE WHEN pv.fid = 3 THEN pv.value ELSE NULL END) AS email,
MAX(CASE WHEN pv.fid = 45 THEN pv.value ELSE NULL END) AS phone,
FROM USERS u
JOIN PROFILE_VALUES pv ON pv.userid = u.userid
GROUP BY u.userid, u.name
Add "LEFT" before the "JOIN" if you want to see users who don't have any entries in the PROFILE_VALUES table.
I didn't say this, which I should have (#OMG Ponies) but I wanted to use this within a MySQL view. And for views to be editable you're not allowed to use aggregate functions and other constraints. So what I had to do whas use the same SQL query as I had described in the initial question.
Hope this helps someone, adding tag for creating views.