Trying to get results from 3 different tables using sql or linq query - sql

I'm very new to SQL/LINQ and could really use your help.
I have 3 tables:
Invitation table
| InviteId | Name | Email | SalesAgentId | |
|----------|----------|--------------|--------------|---|
| 9 | John Doe | john#doe.com | 1 | |
SalesAgent table
| SalesAgentId | UserId | Active |
|--------------|--------|--------------|
| 1 | 2 | true |
User table
| UserId | Name | Email |
|--------|--------------------------------------|-----------------------------------|
| 2 | Sales Guy | sales#agent.com |
Trying to generate a list of a given SalesAgent that should contain that SalesAgent's info along with info of the user that has been invited by the sales agent (from the Invitation table)
Goal:
Trying to Get a list of users (both from the Invitation table and User table that are tied to the SalesAgent Id (either the user is the sales agent himself, or the user was invited by the sales agent).
So in this scenario, the goal is to get the result table (result) as:
ResultId: If there is a record in User table, then use the SalesAgentId, but if the record is coming from Invitation table, then use the InviteId for this column.
Name/Email: Same logic; if record is in User table, use the Name/Email from User table, if not, then use the info from Invitation table.
InvitedUser: If record is in User table, then this should be false, but if its coming from Invitation table, then this should be true.
| ResultId | Name | Email | InvitedUser |
|----------|-----------|-----------------|-------------|
| 1 | Sales Guy | sales#agent.com | false |
| 9 | John Doe | john#doe.com | true |
I'm not sure what kind of join would help me in this scenario, more or less I need to combine results from 2 queries (I think). Ultimate goal is to get a linq query version of this select, but I think LINQ/EfCore doesn't support Concat when using different data stores.
I'd really appreciate any type of help, or a pointer in the right direction; many thanks in advance.

The output you show looks like result of a UNION query so consider:
SELECT Invitation.InviteID AS ResultID, Invitation.Name, Invitation.Email, True AS InvitedUser
FROM Invitation
UNION
SELECT SalesAgent.SalesAgentID, User.Name, User.Email, False AS InvitedUser
FROM [User] INNER JOIN SalesAgent ON User.UserID = SalesAgent.UserID
WHERE (((User.Name) Not In (SELECT [Name] FROM [Invitation])));

Related

Better way to grant access to data within a table based on user?

I'm trying design a system for an API that grants users access to a data table Data based on a permission table Permissions which is related to a group table Group. When a user makes a request for data (from the Data table), my API should only return rows from the Data table based on the values within the columns of the Data table that they have been granted to
view.
By default, a user will have no access to any rows when requesting data through my API. However, I'd like to grant access to Data based on values in columns.
For example If my Data table contains information about news articles and has columns title, news_source, posted_date, and other similar columns
id | title | news_source | posted_date | ...
-----+----------+-----------------------+-------------+------
1 | ... | NYTimes | 2019-12-30 |
2 | ... | BBC | 2019-12-30 |
3 | ... | BBC | 2019-12-30 |
4 | ... | Washington Post | 2019-12-30 |
5 | ... | NYTimes | 2019-12-30 |
6 | ... | NYTimes | 2020-01-01 |
7 | ... | Boston Globe | 2020-01-01 |
In this example, I'd like to grant a group access to get data only from NYTimes, posted after 2020-01-01, etc...
To do this, I've implemented the schema below
+-----+ +--------------+
|Group|<-------|Permission |
+-----+ +--------------+
|name | |group_id |
|... | |column_name |
+-----+ |text_value |
|date_value |
+--------------+
For Group, name is just the name of the group and the ellipse represents some other non-relevant columns. In Permissions, I have the foreign key to Group (group_id), the name of the column in the Data table that I'm accessing (column_name), and the value I'm granting access to (text_value or date_value depending on the column I'm referencing).
Right now, when a user makes a request for data, I run this SQL to apply the permissions (if the user's group has id = 1).
SELECT * FROM Data d
INNER JOIN Permission p1 ON p1.group_id = 1 AND p1.column_name = 'news_source' AND p1.text_value = d.news_source
INNER JOIN Permission p2 ON p2.group_id = 1 AND p2.column_name = 'posted_date' AND p2.date_value >= d.posted_date;
This will work, but I was wondering if there was a better more organized way to go about this. I feel there would be a lot of redundancy in this model across multiple groups with the same permissions.

Complex Unmatched Records Query

I have users submitting records to a table called USERS. To simplify here are the 3 fields: ID, END TIME, VERSION.
I need to identify records that are in the USERS table that and are NOT listed in the MASTER table. However it's possible for a user to submit the same ID more than once. So then I added to check for different END TIME and version number. Here is the code I have so far:
SELECT DISTINCT USER.ID, USER.EndTime, USER.Version
FROM [USER] LEFT JOIN MASTER ON USER.[ID] = MASTER.[ID]
WHERE (([MASTER.ID] Is Null OR [USER.ENDTIME] <> [MASTER.ENDTIME]));
Sample USER TABLE:
|---------------------|------------------|------------------|
| ID | ENDTIME | VERSION |
|---------------------|------------------|------------------|
| 11111 |9/18/18 6:16 PM | 1 |
|---------------------|------------------|------------------|
| 11111 |9/18/18 6:20 PM | 1 |
|---------------------|------------------|------------------|
| 11111 |9/18/18 6:23 PM | 2 |
|---------------------|------------------|------------------|
Sample MASTER TABLE: (Records I have already imported)
|---------------------|------------------|------------------|
| ID | ENDTIME | VERSION |
|---------------------|------------------|------------------|
| 11111 |9/18/18 6:16 PM | 1 |
|---------------------|------------------|------------------|
| 11111 |9/18/18 6:20 PM | 1 |
|---------------------|------------------|------------------|
Using the two data sets above and the SQL Statement above all 3 USER Records are returned when I only want to see the Version 2 record from 6:23PM
How can I add more criteria to my SQL Statement to ensure all of the following conditions identify the record as new?
Here are the conditions I need:
1) The ID exists in the user table but not in the master.
2) The ID exists in both tables, but the end times do not match but the version numbers do.
3) The ID exists in both tables, and the version numbers do not match (this would guarantee the end times do not match also).
I have tried various combinations of AND and OR and cannot get this to work correctly in all scenarios. Can someone please provide an example of how to accomplish this?
Try with:
SELECT
USER.ID, USER.ENDTIME, USER.VERSION
FROM
[USER]
LEFT JOIN
[MASTER]
ON (USER.ID = MASTER.ID AND USER.ENDTIME = MASTER.ENDTIME AND USER.VERSION = MASTER.VERSION)
WHERE
MASTER.ID Is Null

How do you merge rows from 2 SQL tables without duplicating rows?

I guess this query is a little basic and I should know more about SQL but haven't done much with joins yet which I guess is the solution here.
What I have is a table of people and a table of job roles they hold. A person can have multiple jobs and I wish to have one set of results with a row per person containing their details and their job roles.
Two example tables (people and job_roles) are below so you can understand the question easier.
People
id | name | email_address | phone_number
1 | paul | paul#example.com | 123456
2 | bob | bob#example.com | 567891
3 | bart | bart#example.com | 987561
job_roles
id | person_id | job_title | department
1 | 1 | secretary | hr
2 | 1 | assistant | media
3 | 2 | manager | IT
4 | 3 | finance clerk | finance
4 | 3 | manager | IT
so that I can output each person and their roles like such
Name: paul
Email Address: paul#example.com
Phone: 123456
Job Roles:
Secretary for HR department
Assistant for media department
_______
Name: bob
Email address: bob#example.com
Phone: 567891
Job roles:
Manager for IT department
So how would I get each persons information (from the people table) along with their job details (from the job_roles table) to output like the example above. I guess it would be some kind of way of merging their jobs and their relevant departments into a jobs column that can be split up for output, but maybe there is a better way and what would the sql look like?
Thanks
Paul
PS it would be a mySQL database if that makes any difference
It looks like a straight-forward join:
SELECT p.*, j.*
FROM People AS p INNER JOIN Roles AS r ON p.id = r.person_id
ORDER BY p.name;
The remainder of the work is formatting; that's best done by a report package.
Thanks for the quick response, that seems a good start but you get multiple rows per person like (you have to imagine this is a table as you don't seem to be able to format in comments):
id | Name | email_address | phone_number | job_role | department
1 | paul | paul#example.com | 123456 | secretary | HR
1 | paul | paul#example.com | 123456 | assistant | media
2 | bob | bob#example.com | 567891 | manager | IT
I would like one row per person ideally with all their job roles in it if that's possible?
It depends on your DBMS, but most available ones do not support RVAs - relation-valued attributes. What you'd like is to have the job role and department part of the result like a table associated with the user:
+----+------+------------------+--------------+------------------------+
| id | Name | email_address | phone_number | dept_role |
+----+------+------------------+--------------+------------------------+
| | | | | +--------------------+ |
| | | | | | job_role | dept | |
| 1 | paul | paul#example.com | 123456 | | secretary | HR | |
| | | | | | assistant | media | |
| | | | | +--------------------+ |
+----+------+------------------+--------------+------------------------+
| | | | | +--------------------+ |
| | | | | | job_role | dept | |
| 2 | bob | bob#example.com | 567891 | | manager | IT | |
| | | | | +--------------------+ |
+----+------+------------------+--------------+------------------------+
This accurately represents the information you want, but is not usually an option.
So, what happens next depends on your report generation tool. Using the one I'm most familiar with, (Informix ACE, part of Informix SQL, available from IBM for use with the Informix DBMSs), you would simply ensure that the data is sorted and then print the name, email address and phone number in the 'BEFORE GROUP OF id' section of the report, and in the 'ON EVERY ROW' section you would process (print) just the role and department information.
It is often a good idea to separate the report formatting from the data retrieval operations; this is an example of where it is necessary unless your DBMS has unusual features to help with the formatting of selected data.
Oh dear that sounds very complicated and not something I could run easily on a mySQL database in a PHP page?
The RVA stuff - you're right, that is not for MySQL and PHP.
On the other hand, there are millions of reports (meaning results from queries that are formatted for presentation to a user) that do roughly this. The technical term for them is 'Control-Break Report', but the basic idea is not hard.
You keep a record of the 'id' number you last processed - you can initialize that to -1 or 0.
When the current record has a different id number from the previous number, then you have a new user and you need to start a new set of output lines for the new user and print the name, email address and phone number (and change the last processed id number). When the current record has the same id number, then all you do is process the job role and department information (not the name, email address and phone number). The 'break' occurs when the id number changes. With a single level of control-break, it is not hard; if you have 4 or 5 levels, you have to do more work, and that's why there are reporting packages to handle it.
So, it is not hard - it just requires a little care.
RE:
I was hoping SQL could do something
clever and join the rows together
nicely so I had essentially a jobs
column with that persons jobs in it.
You can get fairly close with
SELECT p.id, p.name, p.email_address, p.phone_number,
group_concat(concat(job_title, ' for ', department, ' department') SEPARATOR '\n') AS JobRoles
FROM People AS p
INNER JOIN job_roles AS r ON p.id = r.person_id
GROUP BY p.id, p.name, p.email_address, p.phone_number
ORDER BY p.name;
Doing it the way you're wanting would mean the result set arrays could have infinite columns, which would be very messy. for example, you could left join the jobs table 10 times and get job1, job2, .. job10.
I would do a single join, then use PHP to check if the name ID is the same from 1 row to the next.
One way might be to left outer join the tables and then load them up into an array using
$people_array =array();
while($row1=mysql_fetch_assoc($extract1)){
$people_array[] = $row1;
}
and then loop through using
for ($x=0;$x<=sizeof($people_array;)
{
echo $people_array[$x][id];
echo $people_array[$x][name];
for($y=0;$y<=$number_of_roles;$y++)
{
echo $people_array[$x][email_address];
echo $people_array[$x][phone_number];
$x++;
}
}
You might have to play with the query a bit and the loops but it should do generally what you want.For it to work as above every person would have to have the same number of roles, but you may be able to fill in the blanks in your table

Retrieve comma delimited data from a field

I've created a form in PHP that collects basic information. I have a list box that allows multiple items selected (i.e. Housing, rent, food, water). If multiple items are selected they are stored in a field called Needs separated by a comma.
I have created a report ordered by the persons needs. The people who only have one need are sorted correctly, but the people who have multiple are sorted exactly as the string passed to the database (i.e. housing, rent, food, water) --> which is not what I want.
Is there a way to separate the multiple values in this field using SQL to count each need instance/occurrence as 1 so that there are no comma delimitations shown in the results?
Your database is not in the first normal form. A non-normalized database will be very problematic to use and to query, as you are actually experiencing.
In general, you should be using at least the following structure. It can still be normalized further, but I hope this gets you going in the right direction:
CREATE TABLE users (
user_id int,
name varchar(100)
);
CREATE TABLE users_needs (
need varchar(100),
user_id int
);
Then you should store the data as follows:
-- TABLE: users
+---------+-------+
| user_id | name |
+---------+-------+
| 1 | joe |
| 2 | peter |
| 3 | steve |
| 4 | clint |
+---------+-------+
-- TABLE: users_needs
+---------+----------+
| need | user_id |
+---------+----------+
| housing | 1 |
| water | 1 |
| food | 1 |
| housing | 2 |
| rent | 2 |
| water | 2 |
| housing | 3 |
+---------+----------+
Note how the users_needs table is defining the relationship between one user and one or many needs (or none at all, as for user number 4.)
To normalise your database further, you should also use another table called needs, and as follows:
-- TABLE: needs
+---------+---------+
| need_id | name |
+---------+---------+
| 1 | housing |
| 2 | water |
| 3 | food |
| 4 | rent |
+---------+---------+
Then the users_needs table should just refer to a candidate key of the needs table instead of repeating the text.
-- TABLE: users_needs (instead of the previous one)
+---------+----------+
| need_id | user_id |
+---------+----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 1 | 2 |
| 4 | 2 |
| 2 | 2 |
| 1 | 3 |
+---------+----------+
You may also be interested in checking out the following Wikipedia article for further reading about repeating values inside columns:
Wikipedia: First normal form - Repeating groups within columns
UPDATE:
To fully answer your question, if you follow the above guidelines, sorting, counting and aggregating the data should then become straight-forward.
To sort the result-set by needs, you would be able to do the following:
SELECT users.name, needs.name
FROM users
INNER JOIN needs ON (needs.user_id = users.user_id)
ORDER BY needs.name;
You would also be able to count how many needs each user has selected, for example:
SELECT users.name, COUNT(needs.need) as number_of_needs
FROM users
LEFT JOIN needs ON (needs.user_id = users.user_id)
GROUP BY users.user_id, users.name
ORDER BY number_of_needs;
I'm a little confused by the goal. Is this a UI problem or are you just having trouble determining who has multiple needs?
The number of needs is the difference:
Len([Needs]) - Len(Replace([Needs],',','')) + 1
Can you provide more information about the Sort you're trying to accomplish?
UPDATE:
I think these Oracle-based posts may have what you're looking for: post and post. The only difference is that you would probably be better off using the method I list above to find the number of comma-delimited pieces rather than doing the translate(...) that the author suggests. Hope this helps - it's Oracle-based, but I don't see .

What is a better way to store status updates in a database?

I'm developing a web application that will enable users to post short status updates similar to Twitter. The only way I can think of storing these posts is to have a large "status_updates" table that stores EVERY user's status updates:
--------------------------------------
| table: status_updates |
-------------------------------------|
| id | user_who_posted | update_text |
--------------------------------------
This method requires something like this SQL query to get each user's updates:
SELECT * FROM status_updates where user_who_posted="username"
and I think that wouldn't be very inefficient. Is there a better way of doing this?
Build a user table, and have the user_id be an integer foreign key to that user table. Then, build an index on the user_id field to allow for rapid retrieval.
In short:
status_updates:
--------------------------------------
| status_id | user_id | status |
--------------------------------------
| 1 | 1 | Woot! |
--------------------------------------
| 2 | 1 | Yeah! |
--------------------------------------
| 3 | 2 | Hello! |
--------------------------------------
users:
--------------------------
| user_id | username |
--------------------------
| 1 | 'Joe' |
--------------------------
| 2 | 'John' |
--------------------------
Then, to retrieve, you would do this:
select
u.username,
s.status
from
status_updates s
inner join users u on
s.user_id = u.user_id
where
u.username = 'John'
This will retrieve:
-------------------------
| username | status |
-------------------------
| John | Hello! |
-------------------------
Do with that what you will. That will be very performant on millions of rows, so long as you build your indexes right. What RDBMS are you using, so I can point you to the right spot for that?
This actually can be very efficient as long as you properly set up an index for the status_updates table on user.
If you are truly worried about the table becoming very, very large you may want to look into horizontal partitioning of your database(s).
It would be quicker to not have a string as part of your search criteria, and instead have your user replaced with a surrogate key:
SELECT update_text
FROM status_updates
INNER JOIN users
ON status_updates.user_id = users.user_id
WHERE users.username = 'username'
Obviously, indexing and potentially partitioning your table could be useful for scalability.