How to find if user relation exists on another table - sql

I have a table called users and another table called relationship which has a column of two userId. I want to find an output if the user has a relationship with another user, (for this example, it is a user with userId = 2). If they do have a relationship, I want my output to be true, else false. I am having some trouble and wondering if anyone can help me.
I tried something like this:
SELECT DISTINCT
userid,
CASE WHEN user_one_id = '2' THEN true ELSE false END AS has_user_two
FROM users
LEFT JOIN relationship ON userid = user_two_id;
However, I am getting duplicates...
My Query output that is wrong
userid | has_user_two|
---------------------+
1 | true |
1 | false |
2 | false |
3 | false |
4 | true |
5 | true |
Users
userid
-----------
1
2
3
4
5
relationship
user_one_id| user_two_id
-----------+-------------
1 | 1
3 | 1
3 | 2
My output should look something like...
userid | has_user_two|
---------------------+
1 | true |
2 | false |
3 | false |
4 | false |
5 | false |
Any help would be appreciated.

Use EXISTS:
SELECT u.userid,
(EXISTS (SELECT 1
FROM relationship r
WHERE r.user_one_id = 2 AND r.user_two_id = u.userid
)
) as has_user_two
FROM users u;
Note that you don't need a CASE expression in Postgres. A boolean expression can be -- well -- a boolean column.

Related

SQL-Retrieve only the Total count of occurences of a specific column [duplicate]

Think I have a table with two fields: ID and State. State value (that is boolean) can be 0 or 1. ID isn't unique so the table looks like this:
ID | State |
-----------------
1 | true |
-----------------
1 | false |
-----------------
2 | false |
-----------------
3 | true |
-----------------
1 | true |
Now, I want to count every rows group by ID field and have State as two different columns in resultset. So it should look like this:
ID | TrueState | FalseState |
------------------------------------
1 | 2 | 1 |
------------------------------------
2 | 0 | 1 |
------------------------------------
3 | 1 | 0 |
How to do that?
This is a pivot query, which mysql doesn't support. The workarounds get ugly fast, but since you're only going to be generating two new columns, it won't be horribly ugly, just mildly unpleasant:
SELECT SUM(State = True) AS TrueState, SUM(State = False) AS FalseState,
SUM(State is NULL) AS FileNotFoundState
...
Basically state = true will evaluate to boolean true/false, which MySQL will type-cast to an integer 0 or 1, which can them be SUM()med up.

Setting value of boolean columns based on existence of value in set

I have a SQL table of the format (INTEGER, json_array(INTEGER)).
I need to return results from the table that have two boolean columns. One is set to true iff a 1 appears in the json_array, and the other true iff a two appears in the array. Obviously there is not mutual exclusion.
For example, if the data were this:
-------------------------------
| ID | VALUES |
-------------------------------
| 12 | [1, 4, 6, 11] |
_______________________________
| 74 | [0, 1, 2, 5] |
-------------------------------
I would hope to get back:
-------------------------------
| ID | HAS1 | HAS2 |
-------------------------------
| 12 | true | false |
_______________________________
| 74 | true | true |
-------------------------------
I have managed to extract the json data out of the values column using json_each, but am unsure how to proceed.
If I recall correctly, SQLite max aggregate function supports boolean, therefore you can simply group by your data:
select
t1.id,
max(case json_each.value when 1 then true else false end) as has1,
max(case json_each.value when 2 then true else false end) as has2
from
yourtable t1,
json_each(t1.values)
group by
t1.id

SQL Query (Display All 'x' Where 'x' Is Not In Table '2' for field 'y' and has 'z' flag)

I need to return all 'contacts' that do not appear in the 'delegate' table for 'event name' but do have flags in the 'contacts' table that can selected by the user for the search.
I know the query can be broken in to 2 parts.
Are they already attending this event (Does their email appear in 'delegates' table with delegates.event field matching 'event' on the user form)
WHERE (
d.Event <> [Forms]![usf_FindCampaignContacts]![FCC_EventName]
Do they match the criteria (Have they got the HR flag in 'contacts' table)
AND (c.[HR-DEL] = [Forms]![usf_FindCampaignContacts]![FCC_HRD] OR IsNull([Forms]![usf_FindCampaignContacts]![FCC_HRD]));
Based on the 2 things that the query is required to do I have written the following code...
SELECT
c.[First Name], c.[Last Name], c.Email, d.Event, c.Suppress, c.[HR-DEL]
FROM tbl_Contacts AS c LEFT JOIN tbl_Delegates AS d ON c.Email = d.Email
WHERE (
d.Event <> [Forms]![usf_FindCampaignContacts]![FCC_EventName]
And
c.Suppress = False
)
AND (c.[HR-DEL] = [Forms]![usf_FindCampaignContacts]![FCC_HRD] OR IsNull([Forms]![usf_FindCampaignContacts]![FCC_HRD]));
[FCC_HRD] refers to the user selected input on the form, I tried to use a <> to remove matching records but I feel this is where the compile error is so I changed these to and/or statements and this part now returns results with the matching flags (Success)
Other issue with attempting to do it this way is even if it worked it would remove anyone who was listed in the delegates/sponsor table. Which is why I added the <> statement for the Event as it only needs to remove them off the list for the named event. Again this works perfectly well (Success)
Final issue is the results are clearly being pulled from the 'delegates' table not the 'contacts' table as both parts above work but only display the results that match criteria in delegates table not from contacts.
Here is the query/table relationships
Here is the user form (This is not the final design)
Below are the 3 tables that are used in the query (2 direct, 1 linked)
Contacts (c)
+----+------------+---------------+-------------------------+--------+----------+
| ID | First Name | Last Name | Email | HR-DEL | Suppress |
+----+------------+---------------+-------------------------+--------+----------+
| 1 | A | Platt | a.platt#fake.com | TRUE | TRUE |
| 2 | D | Farr | d.farr#fake.com | TRUE | FALSE |
| 3 | Y | Helle | y.helle#fake.com | TRUE | FALSE |
| 4 | S | Oliphant | soliphant#fake.com | TRUE | FALSE |
| 5 | J | Bedell-Pearce | jbedell-pearce#fake.com | TRUE | FALSE |
| 6 | J | Walker | j.walker#fake.com | FALSE | FALSE |
| 7 | S | Rug | s.rug#fake.com | FALSE | FALSE |
| 8 | D | Brown | d.brown#fake.com | FALSE | FALSE |
| 9 | R | Cooper | r.cooper#fake.com | TRUE | FALSE |
| 10 | M | Morrall | m.morrall#fake.com | TRUE | FALSE |
+----+------------+---------------+-------------------------+--------+----------+
Delegates (d)
+----+-------------------------+-------+
| ID | Email | Event |
+----+-------------------------+-------+
| 1 | a.platt#fake.com | 2 |
| 2 | d.farr#fake.com | 1 |
| 3 | y.helle#fake.com | 4 |
| 4 | soliphant#fake.com | 3 |
| 6 | jbedell-pearce#fake.com | 2 |
+----+-------------------------+-------+
Events (not direct but used to check event name drop-down on user form vs event number in delegates)
+----+------------+
| ID | Event Name |
+----+------------+
| 1 | Test 1 |
| 2 | Test 2 |
| 3 | Test 3 |
| 4 | Test 4 |
+----+------------+
Based on form selection and this sample data I need to return the following:
All contacts who are flagged 'HR' TRUE, not suppressed or going to event named 'test 2' (Should be 5 - I always return the names of 'delegates' not going to the event only = 3)
Final results should be:
+----+------------+-----------+--------------------+--------+----------+
| ID | First Name | Last Name | Email | HR-DEL | Suppress |
+----+------------+-----------+--------------------+--------+----------+
| 2 | D | Farr | d.farr#fake.com | TRUE | FALSE |
| 3 | Y | Helle | y.helle#fake.com | TRUE | FALSE |
| 4 | S | Oliphant | soliphant#fake.com | TRUE | FALSE |
| 9 | R | Cooper | r.cooper#fake.com | TRUE | FALSE |
| 10 | M | Morrall | m.morrall#fake.com | TRUE | FALSE |
+----+------------+-----------+--------------------+--------+----------+
At the moment it appears to be pulling results from the wrong table (d not c). I attempted to change to OUTER join type but that returned with a FROM syntax error.
If I understand it correctly, basically you want to do this:
SELECT A.foo
FROM A
LEFT JOIN B
ON A.bar = B.bar
WHERE
<complex condition, partly involving B>
This cannot work. By including B in the global WHERE condition, you turn the LEFT JOIN into an INNER JOIN, and so you will only ever get records that match between A and B.
You can either move the filter on B into the JOIN condition:
SELECT A.foo
FROM A
LEFT JOIN B
ON (A.bar = B.bar)
AND (B.bamboozle = 42)
WHERE
A.columns = things
or LEFT JOIN a filtered subquery:
SELECT A.foo
FROM A
LEFT JOIN
(SELECT bar, columns FROM B
WHERE B.bamboozle = 42) AS B1
ON A.bar = B1.bar
WHERE
A.columns = things
So in your query, this is the bamboozle part you will need to move:
d.Event <> [Forms]![usf_FindCampaignContacts]![FCC_EventName]

View Table over Language/Client/Status Table

I would like to simplify my data with a view table, MainView but am having a hard time figuring it out.
I have a Fact table that is specific to clients, language, and status. The ID in the Fact table comes from a FactLink table that just has an FactLinkID column. The Status table has an Order column that needs to be shown in the aggregate view instead of the StatusID. My Main table references the Fact table in multiple columns.
The end goal will be to be able to query the view table by the compound index of LanguageID, StatusOrder, ClientID more simply than I was before, grabbing the largest specified StatusOrder and the specified ClientID or ClientID 1. So, that is what I was hoping to simplify with the view table.
So,
Main
ID | DescriptionID | DisclaimerID | Other
----+---------------+--------------+-------------
50 | 1 | 2 | Blah
55 | 4 | 3 | Blah Blah
Fact
FactID | LanguageID | StatusID | ClientID | Description
-------+------------+----------+----------+------------
1 | 1 | 1 | 1 | Some text
1 | 2 | 1 | 1 | Otro texto
1 | 1 | 3 | 2 | Modified text
2 | 1 | 1 | 1 | Disclaimer1
3 | 1 | 1 | 1 | Disclaimer2
4 | 1 | 1 | 1 | Some text 2
FactLink
ID
--
1
2
3
4
Status
ID | Order
---+------
1 | 10
2 | 100
3 | 20
MainView
MainID | StatusOrder | LanguageID | ClientID | Description | Disclaimer | Other
-------+-------------+------------+----------+---------------+-------------+------
50 | 10 | 1 | 1 | Some text | Disclaimer1 | Blah
50 | 10 | 2 | 1 | Otro texto | NULL | Blah
50 | 20 | 1 | 2 | Modified text | NULL | Blah
55 | 10 | 1 | 1 | Some text 2 | Disclaimer2 | Blah Blah
Here's how I implemented it with just a single column that references the Fact table:
DROP VIEW IF EXISTS dbo.KeywordView
GO
CREATE VIEW dbo.KeywordView
WITH SCHEMABINDING
AS
SELECT t.KeywordID, f.ClientID, f.Description Keyword, f.LanguageID, s.[Order] StatusOrder
FROM dbo.Keyword t
JOIN dbo.Fact f
ON f.FactLinkID = t.KeywordID
JOIN dbo.Status s
ON f.StatusID = s.StatusID
GO
CREATE UNIQUE CLUSTERED INDEX KeywordIndex
ON dbo.KeywordView (KeywordID, ClientID, LanguageID, StatusOrder)
My previous query queried for everything except for that StatusOrder. But adding in the StatusOrder seems to complicate things. Here's my previous query without the StatusOrder. When I created a view on a table with just a single Fact linked column it greatly simplified things, but extending that to two or more columns has proven difficult!
SELECT
Main.ID,
COALESCE(fDescription.Description, dfDescription.Description) Description,
COALESCE(fDisclaimer.Description, dfDisclaimer.Description) Disclaimer,
Main.Other
FROM Main
LEFT OUTER JOIN Fact fDescription
ON fDescription.FactLinkID = Main.DescriptionID
AND fDescription.ClientID = #clientID
AND fDescription.LanguageID = #langID
AND fDescription.StatusID = #statusID -- This actually needs to get the largest `StatusOrder`, not the `StatusID`.
LEFT OUTER JOIN Fact dfDescription
ON dfDescription.FactLinkID = Main.DescriptionID
AND dfDescription.ClientID = 1
AND dfDescription.LanguageID = #langID
AND dfDescription.StatusID = #statusID
... -- Same for Disclaimer
WHERE Main.ID = 50
Not sure if this the most performant or elegant way to solve this problem. But I finally thought of a way to do it. The problem with the solution below is that it can no longer be indexed. So, now to figure out how to do that without having to wrap it in a derived table.
SELECT
x.ID,
x.StatusOrder,
x.LanguageID,
x.ClientID,
x.Other,
MAX(x.Description),
MAX(x.Disclaimer)
FROM (
SELECT
Main.ID,
s.StatusOrder,
f.LanguageID,
f.ClientID,
f.Description,
NULL Disclaimer,
Main.Other
FROM Main
JOIN Fact f
ON f.FactID = Main.DescriptionID
JOIN Status s ON s.StatusID = f.StatusID
UNION ALL
SELECT
Main.ID,
s.StatusOrder,
f.LanguageID,
f.ClientID,
NULL Description,
f.Description Disclaimer,
Main.Other
FROM Main
JOIN Fact f
ON f.FactID = Main.DisclaimerID
JOIN Status s ON s.StatusID = f.StatusID
) x
GROUP BY x.ID, x.StatusOrder, x.LanguageID, x.ClientID, x.Other

Active Record Query, return Users with over a certain number of check_ins at a restaurant

In my db I have Users that have check_ins. A check_in is tied to one restaurant with restaurant_id. What's the most efficient way to get all Users who have checked in at a given restaurant more then X times?
To write effect Active Record queries, you must first know how to write effective SQL queries. As with any programming problem, the first step is to break it down into smaller tasks.
TL;DR
Don't do two queries when you just need one.
users_with_check_in_counts = User.select('users.*, COUNT(*) AS check_in_count')
.joins('LEFT OUTER JOIN check_ins ON users.id = check_ins.user_id')
.where(check_ins: { restaurant_id: 1 })
.group(:id)
.having('check_in_count > ?', 3)
.all
# => [ #<User id=2, name="Nick", ..., check_in_count=4>,
# #<User id=4, name="Jordan", ..., check_in_count=4> ]
nick = users_with_check_in_counts.first
puts nick.check_in_count
# => 4
Prelude
Your check_ins table probably looks something like this:
id | restaurant_id | user_id | ...
-----+---------------+---------+-----
1 | 1 | 1 | ...
2 | 1 | 2 |
3 | 1 | 2 |
4 | 1 | 2 |
5 | 1 | 2 |
6 | 1 | 3 |
7 | 1 | 3 |
8 | 1 | 3 |
9 | 1 | 4 |
10 | 1 | 4 |
11 | 1 | 4 |
12 | 1 | 4 |
13 | 2 | 1 |
... | ... | ... | ...
In the above table we have 12 check-ins at the restaurant with restaurant_id = 1. The user with user_id = 1 checked in once, 2 checked in four times, 3 checked in twice, and 4 checked in four times.
The naïve way
The naive way to do this would be to break it down into the following tasks:
Get the check_ins records for the restaurant:
SELECT * FROM check_ins WHERE restaurant_id = 1;
Get the number of check-ins for each user for the restaurants by grouping by user_id and counting the number of records in each group:
SELECT check_ins.*, COUNT(user_id) AS check_in_count
FROM check_ins
WHERE restaurant_id = 1
GROUP BY user_id
Restrict the results to groups with at least than N records, e.g. N = 3:
SELECT check_ins.*, COUNT(user_id) AS check_in_count
FROM check_ins
WHERE restaurant_id = 1
GROUP BY user_id
HAVING check_in_count >= 3
Translate that into an Active Record query:
check_in_counts = CheckIn.where(restaurant_id: 1).group(:user_id)
.having("user_count > ?", 3).count
# => { 2 => 4, 4 => 4 }
Write a second query to get the associated users:
User.find(check_in_counts.keys)
# => [ #<User id=2, ...>, #<User id=4, ...> ]
That works, but there's something smelly about it—oh, it's that we're using a relational database. If we have a query that gets records from check_ins, we should just get the related users in the same query.
A better way
Now, it's relatively obvious that we could take our SQL query from (3) above and add a JOIN users ON check_ins.user_id = users.id to get the associated users records, but that leaves us in a bind because we still want Active Record to give us User objects, not CheckIn objects. To do that we need a different query, one that starts with users and joins check_ins.
To get there, we use LEFT OUTER JOIN:
SELECT *
FROM users
LEFT OUTER JOIN check_ins ON users.id = check_ins.user_id
WHERE restaurant_id = 1;
The above query will give us results like this:
id | name | ... | restaurant_id | user_id
----+--------+-----+---------------+---------
1 | Sarah | 1 | 1 | 1
2 | Nick | 1 | 1 | 2
2 | Nick | 1 | 1 | 2
2 | Nick | 1 | 1 | 2
2 | Nick | 1 | 1 | 2
3 | Carmen | 1 | 1 | 3
3 | Carmen | 1 | 1 | 3
3 | Carmen | 1 | 1 | 3
4 | Jordan | 1 | 1 | 4
4 | Jordan | 1 | 1 | 4
4 | Jordan | 1 | 1 | 4
4 | Jordan | 1 | 1 | 4
This looks familiar: it has all of the data from check_ins, with the data from users added on to each row. That's what LEFT OUTER JOIN does. Now, just like before, we can use GROUP BY to group by user IDs and COUNT to count the records in each group, with HAVING to restrict the results to users with a certain number of check-ins:
SELECT users.*, COUNT(*) AS check_in_count
FROM users
LEFT OUTER JOIN check_ins ON users.id = check_ins.user_id
WHERE restaurant_id = 1
GROUP BY users.id
HAVING check_in_count >= 3;
This gives us:
id | name | ... | check_in_count
----+--------+-----+----------------
2 | Nick | ... | 4
4 | Jordan | | 4
Perfect!
Finally...
Now all we have to do is translate this into an Active Record query. It's pretty straightforward:
users_with_check_in_counts = User.select('users.*, COUNT(*) AS check_in_count')
.joins('LEFT OUTER JOIN check_ins ON users.id = check_ins.user_id')
.where(check_ins: { restaurant_id: 1 })
.group(:id)
.having('check_in_count > ?', 3)
.all
# => [ #<User id=2, name="Nick", ..., check_in_count=4>,
# #<User id=4, name="Jordan", ..., check_in_count=4> ]
nick = users_with_check_in_counts.first
puts nick.check_in_count
# => 4
And best of all, it performs just one query.
Bonus: Scope it
That's a pretty long Active Record query. If there's only one place in your app where you're going to have a query like this, it might be okay to use it that way. If I were you, though, I would turn it into a scope:
class User < ActiveRecord::Base
scope :with_check_in_count, ->(opts) {
opts[:at_least] ||= 1
select('users.*, COUNT(*) AS check_in_count')
.joins('LEFT OUTER JOIN check_ins ON users.id = check_ins.user_id')
.where(check_ins: { restaurant_id: opts[:restaurant_id] })
.group(:id)
.having('check_in_count >= ?', opts[:at_least])
}
# ...
end
Then:
User.with_check_in_count(at_least: 3, restaurant_id: 1)
# ...or just...
User.with_check_in_count(restaurant_id: 1)
I cannot check this with your exact model schema, but something like this should work:
check_in_counts = CheckIn.group(:user_id).having(restaurant_id: 3).having('COUNT(id) > 10').count
This will return a Hash with user_id => check_in_count values, which you can use to fetch all the User objects:
users = User.find(check_in_counts.keys)