Struggling with a SQL Query - mysql

I've got a table called transaction
Transaction:
User_Id: int
Transaction_Type: int
Amount: int
How would I query all users that do not have transaction type 1.
SELECT * FROM Transaction WHERE Transaction_Type <> 1 Group By User_Id
seems to return all users & their transaction types that are not = to 1. I need all users that are missing / have no record of type 1.

This can be done with an IN() subquery
SELECT DISTINCT User_Id FROM Transaction
WHERE User_Id NOT IN (SELECT DISTINCT User_Id FROM Transaction WHERE Transaction_Type = 1)
Or with a NOT EXISTS
SELECT
DISTINCT User_Id
FROM Transaction t
WHERE
NOT EXISTS (SELECT User_Id FROM Transaction tn WHERE Transaction_Type = 1)
AND t.User_Id = tn.User_Id

There's probably a more elegant way of doing this, but this should work:
SELECT * FROM Transaction WHERE User_Id NOT IN (SELECT User_Id FROM Transaction WHERE Transaction_Type = 1)

SELECT *
FROM ( SELECT DISTINCT User_Id FROM Transaction ) AS User
LEFT JOIN Transaction ON Transaction.User_Id = User.id
AND Transaction.Transaction_Id != 1
WHERE Transaction.Transaction_Id IS NULL
GROUP BY User.id

SELECT DISTINCT User_Id
FROM Transaction
WHERE Transaction_Id <> 1
GROUP BY User_Id
This gets each User_Id which has at least one transaction which is not of type 1. If you also want the ones with no transactions, add OR Transaction_Id IS NULL to the WHERE statement.

For example
SELECT DISTINCT User_Id
FROM [Transaction]
EXCEPT
SELECT User_Id
FROM [Transaction]
WHERE Transaction_Id = 1
That's if you don't have a separate table for users. If you do, then use an outer join.
I would suggest not using keywords (like Transaction) as table/column names

I'd do this with a JOIN. Assuming a Users table:
SELECT u.*
FROM Users u
LEFT JOIN Transaction t
ON t.User_ID = u.User_ID
AND t.Transaction_Type = 1
WHERE t.User_ID IS NULL
This method avoids the subquery and DISTINCT.

Related

Subquery using UNION ALL in MySQL

I guess I don't understand the order in which subqueries work.
When I run this SQL statement I get 1 (out of 3 that exist in the table) random 'friend id' :
SELECT t1.sender_id AS Connections FROM
(SELECT DISTINCT sender_id
FROM connection
WHERE receiver_id = 'my_id'
AND status = 'Approved') t1
UNION ALL
SELECT t2.receiver_id FROM
(SELECT DISTINCT receiver_id
FROM connection
WHERE sender_id = 'my_id'
AND status = 'Approved') t2
ORDER BY RAND() LIMIT 1;
One random id is returned which is what I want.
BUT when I wrap the previous SQL statement within another SQL statement to get the friend's name and id (from the id in sub-query) the results come back randomly as either empty or 1 friend or 2 friends or all 3 friends :
SELECT id, name FROM profile
WHERE id = (
SELECT t1.sender_id AS Connections FROM
(SELECT DISTINCT sender_id
FROM connection
WHERE receiver_id = 'my_id'
AND status = 'Approved') t1
UNION ALL
SELECT t2.receiver_id FROM
(SELECT DISTINCT receiver_id
FROM connection
WHERE sender_id = 'my_id'
AND status = 'Approved') t2
ORDER BY RAND() LIMIT 1);
I want it to emit the same behaviour as the first code snippet.
The problem is that the subquery is being re-executed for every row being tested in profile. Each time it returns a different random ID; if that ID happens to match the current row of profile, the row is returned.
Instead of using WHERE id =, use a JOIN. This will just run the subquery once.
SELECT p.id, p.name
FROM profile AS p
JOIN (
SELECT t1.sender_id AS Connections FROM
(SELECT DISTINCT sender_id
FROM connection
WHERE receiver_id = 'my_id'
AND status = 'Approved') t1
UNION ALL
SELECT t2.receiver_id FROM
(SELECT DISTINCT receiver_id
FROM connection
WHERE sender_id = 'my_id'
AND status = 'Approved') t2
ORDER BY RAND() LIMIT 1) AS r
ON p.id = r.Connections
Are you really asking for all the sender_ids and one receiver_id?
It feels like you have an extra layer of SELECTs.
When in doubt, add extra parentheses:
( SELECT ... )
UNION ALL
( SELECT ... ORDER BY ...) -- this ORDER BY applies only to the select
ORDER BY ... -- This applies to the result of the UNION, not the second select

How to join a table with specific conditions

I would like to select data from a table like this (the table name is conversations_users) :
I would like to be able to retrieve a conversation ID that includes only two users. As instance, if I search a conversation specific to users 1 and 3 the conversation number 6 should be the unique result, because the conversation 5 also includes user 2.
I have tried to perform a request like
SELECT * FROM conversations_users AS table1 JOIN
conversations_users AS table2 ON
table1.conversation_ID = table2.conversationID
WHERE table1.userID = 3 AND
table2.userID = 1
But it returns both conversations 5 and 6. How can I fix that ?
Thank you in advance,
Pierre
Add the ON clause:
SELECT * FROM conversations_users AS table1 JOIN
conversations_users AS table2
ON table1.conversation_ID = table2.conversation_ID
WHERE table1.userID = 3 AND
table2.userID = 1
Update:
To get only coversations, where only 1 and 3 are involved, you can use having clause:
SELECT table1.conversation_ID FROM conversations_users AS table1 JOIN
conversations_users AS table2
ON table1.conversation_ID = table2.conversation_ID
WHERE table1.userID = 3 AND
table2.userID = 1
Group by table1.conversation_ID
having Count(*) = 2
The query you need looks like:
SELECT conversation_ID, GROUP_CONCAT(DISTINCT userID ORDER BY userID) as users
FROM conversations_users
GROUP BY conversation_ID
HAVING users = '1,3'
The GROUP BY clause groups the rows having the same conversation_ID and from each group it generates a new record that contains the conversation_ID and the distinct values of userID, in ascending order, concatenated with comma (,).
The HAVING clause keeps only those records that have '1,3' in the column users computed by the GROUP BY clause.
The query produces the output you need but it is not efficient because it reads the entire table. It could be more efficient by picking first the conversations of users 1 and 3 and then applying the above only to them.
It looks like this:
SELECT conversation_ID, GROUP_CONCAT(DISTINCT userID ORDER BY userID) as users
FROM (
SELECT *
FROM conversations_users
WHERE userID in (1, 3)
) conversations
GROUP BY conversation_ID
HAVING users = '1,3'
In order to work faster than the previous query, the conversations_users must have an index on the userID column.
If you want to restrict to those conversations which involve exactly n number of users. I think below generic query should work. Replacing 'n' as per requirement.
select *
from conversations_users
where conversation_id IN (select conversation_id
from conversations_users
group by conversation_id
having count(userid) = 2)
Thanks,
Amitabh
The inner select grabs all conversationIDs with other users than 1 or 3
the outer select (with distinct) collects all conversations wich are NOT in this subset
SELECT DISTINCT conversationID
FROM conversations_users t1
WHERE conversationID NOT IN ( SELECT conversationID
FROM conversations_users
WHERE userID NOT in (1, 3)
)
You can use join with where condition in this case.
SELECT #, userid ,conversation_ID FROM user AS table1 JOIN
conversations_users AS table2
ON user_ID = conversation_ID
WHERE table1.userID = 3 AND
table2.userID = 1
Group by conversation_ID
You can apply suitable condition by where clause instead of group by

SQL query discard records with multiple entries

I'm trying to find a query to resolve my issue:
i have something like this on my database:
i need a query that selects all users that have role 5 but if for some reason they have any other role it should skip them
SELECT user_id FROM user_table WHERE roleid = 5 AND (roleid != 3 OR roleid != 1)
That still returns user 64
I think you are looking for a not exists clause:
select *
from t
where roleid = 5 and
not exists (select 1
from t t2
where t2.user_id = t.user_id and t2.roleid <> 5
);
Alternatively, if you are just looking for such users, then you can use group by and having:
select user_id
from t
group by user_id
having min(roleid) = max(roleid) and min(roleid) = 5;
(If roleid could be NULL, the logic would need to take this into account.)

Subquery - records are not in ordered form

I have three tables:
user: id, name
keyword: id, name
userkeyword: id, user_id, keyword_id
I want to execute query in following way:
Display those users whose keyword/s are matched with the login user's
keywords. In the order of maximum number of keyword matched user
should display first
e.g : If userA having 4 matched keywords, userB having 8, userC having 1, userD having 6 then the result should be in the order of,
userB
userD
userA
userC
For that I have done with this query (assume login user's id is 1):
select *
from user
where id IN (
select user_id
from userkeywords
where keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by user_id
order by count(keyword_id) desc)
AND id != 1
Here the result is getting perfect but the order is not correct. I have merged two queries in following manner"
select *
from user
where id IN (?)
AND id!=1
+
select user_id
from userkeywords
where keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by user_id
order by count(keyword_id) desc
Second query returns user_id in correct order but when I merged both queries, order was changed (wrong).
Hope I have mentioned my query properly with enough detail.
A subquery returns an unordered set, so the order by in a subquery only matters for its limit clause, if there is any. Any database other than MySQL would give an error message for a purely decorative sort order.
There's no way to sort on a column that only exists in the where clause. You'd have to rewrite the query. One option is to replace your in conditions with joins:
select uk2.name
from userkeywords uk1
join userkeywords uk2
on uk1.keyword_id = uk2.keyword_id
and uk1.user_id <> uk2.user_id
join user u2
on u2.id = uk2.user_id
where uk1.user_id = 1
group by
uk2.name
order by
count(*) desc
This should do it.
select uk.user_id, u.name
from userkeywords uk
left join user u on u.id = uk.user_id
where uk.keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by uk.user_id
order by count(uk.keyword_id) desc) AND uk.user_id != 1
Also, JOIN provides better performance.
I would use an inner join to select the correct rows:
SELECT *
FROM user
INNER JOIN (
SELECT * FROM userkeyword
WHERE keyword_id IN (
SELECT keyword_id
FROM userkeyword
WHERE user_id=1
)
) uk
ON user.id = uk.user_id
GROUP BY u.id
ORDER BY count(*) DESC;

MySql query for crosstable select

packages:
package_id, user_id
transactions:
product_id,user_id,package_id
I want to select rows in transactions that have package_id null or 0
but only when there are other transactions with same user_id where package id is greater than 0
is that possible. Sorry for bad English... I tried goggling it, but couldn't guess a correct question for Google query.
I know that title is not ok, so anybody please propose a better one.
Gwynnbleid1
Perhaps this...I try to avoid sub-selects at all costs, so it can get crazy and a bit confusing, and I never truly know if it works until I try.
SELECT DISTINCT T.*
FROM packages P
LEFT JOIN transactions T
ON P.user_id = T.user_id
LEFT JOIN transactions T2
ON T.user_id = T2.user_id
WHERE ( T.package_id IS NULL
OR T.package_id = 0 )
AND T2.package_id > 0
This query will give you all the transactions for package_id > 0 for users who have at least one other transaction with a NULL or 0 package_id.
SELECT * FROM transactions WHERE user_id IN
(SELECT user_id FROM transactions WHERE package_id IS NULL OR package_id = 0)
AND package_id > 0;
SELECT A.* FROM
(SELECT * FROM TRANSACTIONS WHERE (package_id = 0 or package_id is null)) A
(SELECT * FROM TRANSACTIONS WHERE package_id is not null) B
WHERE A.userID = B.userID