SQL Server : join only one table - sql-server

Let's say that I have those four tables.
PEOPOLE
ID NAME SURNAME COMPANY UNIT GROUPS
--------------------------------------------
1 Michael Backer 1 1 1
2 Travis Morgan 2 2 2
3 George Marshall 3 3 3
COMPANY
ID NAME
------------
1 Coca Cola
2 Pepsi
3 Sprite
WORKUNIT
ID NAME
-------------
1 Finances
2 Marketing
3 Sales
GROUPS (both values can be null)
ID NAME FLOOR
-------------------------
1 Risks 5
2 NULL NULL
3 Secretariat NULL
Expected results
NAME SURNAME COMPANYNAME WORKUNIT GROUPS FLOOR
-----------------------------------------------
Michael Backer Coca Cola Finances Risks 5
Travis Morgan Pepsi Marketing NULL NULL
George Marshall Sprite Sales Secretariat NULL
So far I write this query with no success:
SELECT
people.NAME, people.SURNAME, company.NAME,
workunit.NAME, groups.NAME, groups.FLOOR
FROM
company, workunit, groups, people
LEFT JOIN
groups on people.GROUP = GROUPS.id
WHERE
company.id = people.company AND
workunit.id = people.unit AND
groups.id = people.group AND
groups.floor = 'something from textbox';
I am not familiar with combining join statements in more than one table, so please help me out because I'm stuck.

Just write like this:
SELECT
people.NAME,
people.SURNAME,
company.NAME,
workunit.NAME,
groups.NAME,
groups.FLOOR
FROM
people
INNER JOIN GROUP
ON people.GROUP=GROUPS.id
INNER JOIN company
ON people.company=company.id
INNER JOIN UNIT
ON people.UNIT =UNIT.id
WHERE
GROUPS.floor='something from textbox';

I verified your query in SQL Server. You need to modify it a little bit, at least, to see it executing, in this way:
SELECT
people.NAME, people.SURNAME, company.NAME,
workunit.NAME, groups.NAME, groups.FLOOR
FROM
company, workunit, people
LEFT JOIN
groups on people.GROUP = GROUPS.id
WHERE
company.id = people.company AND
workunit.id = people.unit AND
groups.id = people.group AND
groups.floor = 'something from textbox';
Anyway, your query doesn't work because, even if you want the results in the way you described using a LEFT JOIN, you are setting the condition:
AND groups.floor = 'something from textbox';
In this way, you won't be able to see the records where there isn't a match with the GROUPS table, because you're saying explicitly that you want the records where the value of the column floor must be equal to 'something from textbox'.
If you want also the non-matches you can replace the condition with:
OR groups.floor = 'something from textbox';
but you will have some duplicates and a group by statement won't work anyway.
Then, you have to check if you really want those expected results.
I didn't understand why you want also the non-matches, even if you are looking for the precise result, using groups.floor = 'something from textbox'. If you want to display the results in a GUI, the non-matches would be useless information for a user, in my opinion.

SELECT people.NAME, people.SURNAME, company.NAME, workunit.NAME, groups.NAME, groups.FLOOR
FROM company, workunit, groups LEFT JOIN people on people.GROUP=GROUPS.id
WHERE
company.id=people.company AND
workunit.id=people.unit AND
groups.id=people.group AND
groups.floor='something from textbox';

Related

SQL Server: aggregate counts grouped by user based on csv values in a text field

I'm not sure how best to describe my problem in words without before/after examples, which is probably why I can't find an existing solution here.
I have 'tags' in a user defined field in our tickets database to represent errors made when handling said ticket. The field could be empty, could have one entry, or multiple entries. They should be comma separated, but I have no ability to validate text as it's being entered. The temporary table #Categories below shows the current 'tags' I expect to appear in this field.
I can get my desired results with the approach below, but I feel like this is clumsy and there's almost certainly a more elegant solution that doesn't require running the same update command over and over with a manual change of the Category it's matching on. This also will require manual updating if a new 'tag' is introduced (or if somebody typo's a tag and thus doesn't result in a match).
My desired results are a list of users, with a count of 'errors', i.e. the occurrences of those tags across all the user defined fields of all tickets within the date range specified (including 0), like shown below. The ideal solution would inherently include new users & new categories (tags) without manual intervention so I'd see them on the resultant report this will be used for.
User
Category
Errors
jdoe
Board
0
jdoe
Budget
1
jdoe
Conflict
0
jdoe
Contact
2
jdoe
Dupe
1
jdoe
Item
0
jdoe
SkipDispatch
0
jdoe
SLAMiss
0
jdoe
SubType
5
jdoe
Type
0
jdoe
whitespace
0
jsmith
Board
0
jsmith
Budget
0
jsmith
Conflict
1
jsmith
Contact
0
jsmith
Dupe
0
jsmith
Item
2
jsmith
SkipDispatch
0
jsmith
SLAMiss
0
jsmith
SubType
0
jsmith
Type
0
jsmith
whitespace
1
-- I want to include all users and all category combinations, so I build a table
-- with those combinations first to ensure NULL entries are still represented (as zero)
CREATE TABLE #Categories (Category varchar(30))
INSERT INTO #Categories (Category)
VALUES ('Agreement')
,('Board')
,('Budget')
,('Conflict')
,('Contact')
,('Dupe')
,('Item')
,('SkipDispatch')
,('SLAMiss')
,('Subtype')
,('Type')
,('Whitespace')
CREATE TABLE #Errors
(
User varchar(50),
Category varchar(30),
Errors Int
)
INSERT INTO #Errors (User, Category, Errors)
SELECT DISTINCT
A.User, D.Category, 0
FROM
Tickets_SLA_Workflow A
LEFT JOIN
Tickets C ON A.Tickets_RecID = C.Tickets_RecID
LEFT JOIN
Tickets_User_Defined_Field_Value B ON C.Tickets_RecID = B.Tickets_RecID
CROSS JOIN
#Categories D
WHERE
B.User_Defined_Field_RecID = 28
AND A.Date_Responded BETWEEN #Start AND #End
-- Next I update that table with the actual counts of occurrences of the tag "Board"
UPDATE #Errors
SET Errors = Board.Errors
FROM #Errors A
LEFT JOIN
(SELECT A.User, COUNT(B.User_Defined_Field_Value) Errors
FROM Tickets_SLA_Workflow A
LEFT JOIN Tickets C ON A.Tickets_RecID = C.Tickets_RecID
LEFT JOIN Tickets_User_Defined_Field_Value B ON C.Tickets_RecID = B.Tickets_RecID
WHERE B.User_Defined_Field_RecID = 28
AND A.Date_Responded_UTC BETWEEN #Start AND #End
AND B.User_Defined_Field_Value LIKE '%Board%'
GROUP BY A.User) Board on A.User = Board.User
WHERE
A.Category = 'Board'
AND A.User = Board.User
-- Then I repeat for tag "Budget" ... and so on through all the categories
UPDATE #Errors
SET Errors = Budget.Errors
FROM #Errors A
LEFT JOIN
(SELECT A.User, COUNT(B.User_Defined_Field_Value) Errors
FROM Tickets_SLA_Workflow A
LEFT JOIN Tickets C ON A.Tickets_RecID = C.Tickets_RecID
LEFT JOIN Tickets_User_Defined_Field_Value B ON C.Tickets_RecID = B.Tickets_RecID
WHERE B.User_Defined_Field_RecID = 28
AND A.Date_Responded_UTC BETWEEN #Start AND #End
AND B.User_Defined_Field_Value LIKE '%Budget%'
GROUP BY A.User) Budget ON A.User = Budget .User
WHERE
A.Category = 'Budget'
AND A.User = Budget.User
Your design is denormalized, as each category should really be in a separate row. But we can transform it by splitting the categories by commas using STRING_SPLIT.
You can then simply aggregate by user and category.
SELECT
wf.[User],
s.value AS Category,
COUNT(*) Errors
FROM Tickets_SLA_Workflow wf
JOIN Tickets t ON wf.Tickets_RecID = t.Tickets_RecID
JOIN Tickets_User_Defined_Field_Value fv
ON t.Tickets_RecID = fv.Tickets_RecID
AND fv.User_Defined_Field_RecID = 28
CROSS APPLY STRING_SPLIT (fv.User_Defined_Field_Value, ',') s
WHERE wf.Date_Responded_UTC BETWEEN #Start AND #End
GROUP BY
wf.User,
s.value;
If not all the catgories are correctly separated by commas, but you have an actual list of categories, then you could get almost the same result by using LIKE. The downside is that overlapping categories (such as Contact and Contacts) will return twice.
SELECT
wf.[User],
c.Category,
COUNT(*) Errors
FROM Tickets_SLA_Workflow wf
JOIN Tickets t ON wf.Tickets_RecID = t.Tickets_RecID
JOIN Tickets_User_Defined_Field_Value fv
ON t.Tickets_RecID = fv.Tickets_RecID
AND fv.User_Defined_Field_RecID = 28
JOIN Categories c ON fv.User_Defined_Field_Value LIKE '%' + c.Category + '%'
WHERE wf.Date_Responded_UTC BETWEEN #Start AND #End
GROUP BY
wf.User,
c.Category;
Note: avoid using meaningless aliases such as A and B
Note: LEFT JOIN followed by WHERE on that table becomes an INNER JOIN

Select records when 2 column's data will match

I have two tables as shown below:
-----------------------
|EmpNo|Complaint |
-----------------------
|9091 |Change required|
|9092 |No change |
|9093 |Changes done |
-----------------------
Above table contains employee number and his complaints.
I have one another table which contains employee all kind of details as shown below.
-------------------------------
|EmpNo|EmailID |EmpBossNO|
-------------------------------
|9091 |abc#gmail.com|9092 |
|9092 |xyz#gmail.com|9093 |
|9093 |mno#gmail.com|9099 |
-------------------------------
Here, if Empno:9091 will raise any complain then a mail will send to his boss that the complain is raise by your employee and you have to accept it so I want to get EmailID of employee's boss and for that I want one SQL query. I tried the query shown here, but it doesn't work.
select EmpEmailID
from tblComplaint
inner join tblEmpMaster on tblEmpMaster.EmpNo = tblComplaint.EmpPSNo
where tblComplaint.EmpPSNo = tblEmpMaster.EmpBossNo
I want output like.. if complaint is raised by EmpNo:9091 then it will return EmailID of his boss which is xyz#gmail.com.
You are on the right track with a join between the tblComplaint and tblEmpMaster tables. But, you need an additional join to tblEmpMaster to bring in the boss' email for each employee complaint.
SELECT
c.EmpNo,
c.Complaint,
COALESCE(e2.EmailID, 'NA') AS boss_email
FROM tblComplaint c
INNER JOIN tblEmpMaster e1
ON c.EmpNo = e1.empNo
LEFT JOIN tblEmpMaster e2
ON e1.EmpBossNO = e2.EmpNo;
Demo
I used a left self join above, in case a given employee does not have a boss (e.g. for the highest ranking boss). In this case, I display NA for the boss email.
You should self join tblEmpMaster
select boss.EmpEmailID
from tblComplaint
inner join tblEmpMaster emp on emp.EmpNo = tblComplaint.EmpPSNo
inner join tblEmpMaster boss on boss.EmpNo = emp.EmpBossNO
where tblComplaint.EmpPSNo = 9091
DB Fiddle
you can even use sub queries to get the Email_Id of the boss as shown below
SELECT Email_Id
FROM EMP_Details
WHERE Emp_No IN (
SELECT Boss_Id
FROM Emp_Details) AND
Emp_No IN (
SELECT Emp_No
FROM Emp_Complaints)

Gettings grouped sums from related tables into linq query columns

I would like to query a table (Accounts) and also as part of the query get totals (Total) from another table (AccountPositions). How can I rewrite this sql query as a linq query? Is it as easy as adding a group by statement... but the group by usage is confusing me.
select
(select sum(ap.Quantity * ap.LastPrice) from AccountPositions ap
where ap.AccountId = a.Accountid and ap.IsOpen = 0) as Total,
a.AccountId, a.Username, a.FirstName, a.LastName, a.AccountNumber
from Accounts a
where a.IsTrading = 1
Something like???
var query = (from a in _context.Accounts
join ap in _context.AccountPositions on ap.AccountId = a.AccountId
where a.IsTrading == true && and ap.IsOpen == true
select new {
Total = ap.Sum(r => r.Quantity * r.LastPrice),
AccountId = a.AccountId,
Username = a.Username,
FirstName = a.FirstName,
LastName = a.LastName,
AccountNumber = a.AccountNumber
});
Desired Result:
Total AccountId Username FirstName LastName AccountNumber
2500 496 brice Brian Rice U399445
160 508 jdoe John Doe U555322
Tables:
Accounts
AccountId Username FirstName LastName AccountNumber IsTrading
496 brice Brian Rice U399445 1
498 bmarley Bob Marley U443992 0
508 jdoe John Doe U555332 1
AccountPositions
AccountPositionId AccountId Quantity LastPrice IsOpen
23 496 10 200 1
24 496 15 48 0
25 508 8 20 1
26 498 18 35 1
27 496 5 100 1
How can I rewrite this sql query as a linq query? Is it as easy as adding a group by statement...
It's even easier than that, because the SQL query in question uses single aggregate returning correlated subquery in the select clause, so the translation to LINQ is literally one to one - just use the corresponding C# operators and remember that in LINQ select goes last. And aggregate methods like Sum are outside the LINQ query syntax:
var query =
from a in _context.Accounts
where a.IsTrading
select new
{
Total = (from ap in _context.AccountPositions
where ap.AccountId == a.AccountId && ap.IsOpen
select ap.Quantity * ap.LastPrice).Sum(),
a.AccountId,
a.Username,
a.FirstName,
a.LastName,
a.AccountNumber
};
But LINQ allows you to mix query syntax with method syntax, so the Sum part can be written more naturally using the later like this:
Total = _context.AccountPositions
.Where(ap => ap.AccountId == a.AccountId && ap.IsOpen)
.Sum(ap => ap.Quantity * ap.LastPrice),
Finally, if you are using Entity Framework, the relationship between the Account and AccountPosition will be expressed by something like
public ICollection<AccountPosition> AccountPositions { get; set; }
navigation property inside Account class. Which allows to forget about the join (correlation) conditions like ap.AccountId == a.AccountId - they will be applied automatically, and concentrate just on your query logic (see Don’t use Linq’s Join. Navigate!), e.g.
Total = a.AccountPositions.Where(ap => ap.IsOpen).Sum(ap => ap.Quantity * ap.LastPrice),
Inside LINQ query, the EF collection navigation property represents the so called grouped join - an unique LINQ feature (at least doesn't have direct equivalent in SQL). In LINQ query syntax it's expressed as join + into clauses. Let say you need more than one aggregate. Grouped join will help you to achieve that w/o repeating the correlation part.
So with your model, the query using grouped join will start like this:
from a in _context.Accounts
join ap in _context.AccountPositions on a.AccountId equals ap.AccountId into accountPositions
From here you have 2 variables - a representing an Account and accountPositions representing the set of the AccountPosition related to that Account.
But you are interested only in open positions. In order to avoid repeating that condition, you can use another LINQ query syntax construct - the let clause. So the query continues with both filters:
where a.IsTrading
let openPositions = accountPositions.Where(ap => ap.IsOpen)
Now you have all the information to produce the final result, for instance:
select new
{
// Aggregates
TotalPrice = openPositions.Sum(ap => ap.Quantity * ap.LastPrice),
TotalQuantity = openPositions.Sum(ap => ap.Quantity),
OpenPositions = openPositions.Count(),
// ...
// Account info
a.AccountId,
a.Username,
a.FirstName,
a.LastName,
a.AccountNumber
};

SQL query to get data from two rows connected by key

I have a question that seems very simple to me at first but I'm relatively new to SQL and I can't solve it.
I have two tables: 'Applicants' and 'Family Units'.
Applicants:
ApplicantID | FirstName
----------- | ----------
1 | John
2 | Mary
Family Units:
ApplicantID | UnitID| Note
1 | 10 | Member
2 | 10 | Mother
I need to bring in one table Applicant Name and Mother Name.
Mother applicantID should be determined by having same UnitID in Family Units table and Mother Note in the same table (and other details they are not relevant here).
I tried this query:
That obviously doesn't work correct, I get applicant name instead of applicant's mother's name.
Need you help, links to articles and explanations also will be great because I feel that I'm missing something very basic.
; with cte as
(
select fu.*,a.name from
FamilyUnit fu
join applicant a on a.id = fu.id
where type = 'Mother'
)
select a.id,a.name,c.name
from applicant a
join FamilyUnit fu on fu.id = a.id
join cte c on c.unit = fu.unit
where c.id <> a.id
Im not so sure if this is correct: just try it
select * from
(select * from #family_units where note = 'member') as a
left join
(select * from #family_units where note = 'mother') as b
on a.unitid = b.unitid
left join #applicant_table as c
on a.ApplicantID =c.ApplicantID
Test result:
1 10 Member 2 10 Mother 1 John

SQL 2005 returning unique results with subquery

I have a database Table a (EMAILS) where EmailID is the Primary Key
EmailID Email_To Email_From Email_Subject Email_Registered Email_Read
If a user creates an email it is registered in this table.
For example, the user "Dave" who has id 3 sends an email to "John" who has id 4
So this would give
EmailID Email_To Email_From Email_Subject Email_Registered Email_Read
10 4 3 TEST 2/23/2016 11:00 False
To return results I do this select (joining the user profile database)
SELECT PROFILE_1.SellerID, PROFILE_1.Seller_UserName, EMAILS.EmailID, EMAILS.Email_From, EMAILS.Email_To, EMAILS.Email_Subject,
EMAILS.Email_Registered, EMAILS.Email_Read,
(SELECT Seller_UserName AS Epr2
FROM PROFILE
WHERE (SellerID = EMAILS.Email_To)) AS Expr2
FROM PROFILE AS PROFILE_1 LEFT OUTER JOIN
EMAILS ON EMAILS.Email_From = PROFILE_1.SellerID
WHERE (EMAILS.Email_From IS NOT NULL) AND (PROFILE_1.Seller_UserName = 'Dave')
ORDER BY EMAILS.Email_Registered DESC
So John Replies to Dave's email and it goes into the EMAILS_THREAD table and is registered as
EmailThreadID EmailID Email_To Email_From Email_Registered Email_Read
1 10 3 4 2/23/2016 11:05 False
What I am trying to do is a select that
SELECTS from EMAILS where Email_From is from Dave and return in the results the top 1 result from EMAIL_THREADS that is sent to Dave (based on Email_Registered) with the same EmailID as the EMAILS.EmailID if there is a entry in EMAIL_THREADS.
So in other words return the result of the EMAIL table and latest corresponding result in the EMAIL_THREADS table if there is one.
I hope this makes sense.
I've tried a ton of combinations and I can't figure this out.
At first I thought it was a subquery or a join or a group by...but i can't seem to nail the select and how it is structured.
Looking for some help.
Here is my last attempt
SELECT PROFILE_1.SellerID, PROFILE_1.Seller_UserName, EMAILS.EmailID, EMAILS.Email_From, EMAILS.Email_To, EMAILS.Email_Subject,
EMAILS.Email_Registered, EMAILS.Email_Read,
(SELECT Seller_UserName AS Epr2
FROM PROFILE
WHERE (SellerID = EMAILS.Email_To)) AS Expr2
FROM PROFILE AS PROFILE_1 LEFT OUTER JOIN
EMAILS ON EMAILS.Email_From = PROFILE_1.SellerID CROSS JOIN
(SELECT TOP (1) EMAILS_THREAD.Email_From, EMAILS_THREAD.Email_To, EMAILS_THREAD.Email_Registered, EMAILS_THREAD.Email_Read
FROM EMAILS_THREAD LEFT OUTER JOIN
EMAILS AS EMAILS_1 ON EMAILS_THREAD.EmailID = EMAILS_1.EmailID) AS derivedtbl_1
WHERE (EMAILS.Email_From IS NOT NULL) AND (PROFILE_1.Seller_UserName = 'Dave')
ORDER BY EMAILS.Email_Registered DESC
But it's not returning anything from EMAILS_THREADS at all.
SELECTS from EMAILS where Email_From is from Dave and return in the results the top 1 result from EMAIL_THREADS that is sent to Dave (based on Email_Registered) with the same EmailID as the EMAILS.EmailID if there is a entry in EMAIL_THREADS.Hope this helps
with cte
as
(select
* from emails
where email_from=3
)
select * from cte mt --only for dave
outer apply
(
select top 1* from
emial_threads st where mt.mail_from=st.mail_to ---send to dave
and mt.emailid=st.emailid) b
Haven't been able to find a solution to this so I am paying a SQL professional to resolve this for me. Thanks for the input from Tab and The Gameiswar. Much appreciated.

Resources