SQL Lookup function - sql

I have a Table that has employee info and another that has Manager assignments.
The manager info table references employee table by employee id in addition to managers.
I have been able to left join the table but I want to display the name rather than just employee ID.
SELECT CONCAT_WS(' ',[FirstName],[LastName]) AS FullName,
[EmployeeID]
,[Status]
,[LastName]
,[FirstName]
,[ManagersTbl].[ManagerIDf]
FROM [EmployeesTbl]
LEFT JOIN [ManagersTbl] on [EmployeesTbl].[EmployeeID] = [ManagersTbl].[EmployeeIDf]
WHERE Status = 'A'
Manager Table
_____________
| EmployeeIDf | ManagerIDf |
-----------------------------
001T | 005C
002J | 005C
_______________________________________________
Employee Table
______________
| EmployeeID | FirstName | LastName | Status |
-----------------------------------------------
001T | Tom | Spanks | A
002J | John | Doe | A
005C | Cruisin | Bruisin | A
_______________________________________________
End Result needed
_________________
|EmployeeID | StaffName | ManagerName |
------------------------------------------
001T | Tom Spanks | Crusin Bruisin
002J | John Doe | Crusin Bruisin

Please try this,
SELECT
EmployeeID
, CONCAT_WS(' ',A.[FirstName],A.[LastName]) AS StaffName
,CONCAT_WS(' ',B.[FirstName],B.[LastName]) AS ManagerName
FROM [EmployeesTbl] A
LEFT JOIN [ManagersTbl] M on A.[EmployeeID] = M.[EmployeeIDf]
LEFT JOIN [EmployeesTbl] B
ON M.[Manageridf]
=B.[EmployeeID]
WHERE Status = 'A'

There are numerous routes you can take, one possible solution is to join between your two tables to get your StaffName and then use an inline correlated subquery to get the ManagerName
select EmployeeId,
concat_ws(' ',FirstName,LastName) StaffName,
(select concat_ws(' ',FirstName,LastName) from EmployeeTable em where em.EmployeeID=m.ManagerIDf) ManagerName
from ManagerTable m join EmployeeTable e on e.EmployeeID=m.EmployeeIDf
where e.status='A'

you can use concatenation and join with two table by primary key employeeid.
concatenation function or + sign to concat in sql .

Related

A better way to aggregate into a default value

For this example I have three tables (individual, business, and ind_to_business). Individual has information on people. Business has information on businesses. And ind_to_business has information on which people are linked to which business. Here are their DDL:
CREATE TABLE individual
(
ID INTEGER PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE business
(
ID INTEGER PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE ind_to_business
(
ID INTEGER PRIMARY KEY,
IND_ID REFERENCES individual(id),
BUS_ID REFERENCES business(id),
START_DT DATE NOT NULL,
END_DT DATE
);
I'm looking for the best way to display one row for each person. If they are linked to one business, I want to display the the business's ENTERPRISE_ID. If they are linked to more than one business, I want to display the default value 'Multiple'. They will always be linked to a business, so there is no LEFT JOIN necessary. They can also be linked to a business more than once (Leaving and coming back). Multiple records for the same business would be aggregated.
So for the following sample data:
Individual:
+----+------------+---------------+
| ID | NAME | ENTERPRISE_ID |
+----+------------+---------------+
| 1 | John Smith | 53a23B7 |
| 2 | Jane Doe | 63f2a35 |
+----+------------+---------------+
Business:
+----+----------+---------------+
| ID | NAME | ENTERPRISE_ID |
+----+----------+---------------+
| 3 | ABC Corp | 2a34d9b |
| 4 | XYZ Inc | 34bf21e |
+----+----------+---------------+
ind_to_business
+----+--------+--------+-------------+-------------+
| ID | IND_ID | BUS_ID | START_DT | END_DT |
+----+--------+--------+-------------+-------------+
| 5 | 1 | 3 | 01-JAN-2000 | 31-DEC-2002 |
| 6 | 1 | 3 | 01-JAN-2015 | |
| 7 | 2 | 3 | 01-JAN-2000 | |
| 8 | 2 | 4 | 01-MAR-2006 | 05-JUN-2010 |
| 9 | 2 | 4 | 15-DEC-2019 | |
+----+--------+--------+-------------+-------------+
I would expect the following output:
+---------+------------+------------+
| IND_ID | NAME | LINKED_BUS |
+---------+------------+------------+
| 53a23B7 | John Smith | 2a34d9b |
| 63f2a35 | Jane Doe | Multiple |
+---------+------------+------------+
Here is my current query:
SELECT DISTINCT
sub.ind_id,
sub.name,
DECODE(sub.bus_count, 1, sub.bus_id, 'Multiple') AS LINKED_BUS
FROM (SELECT i.enterprise_id AS IND_ID,
i.name,
b.enterprise_id AS BUS_ID,
COUNT(DISTINCT b.enterprise_id) OVER (PARTITION BY i.id) AS BUS_COUNT
FROM individual i
INNER JOIN ind_to_business i2b ON i.id = i2b.ind_id
INNER JOIN business b ON i2b.bus_id = b.id) sub;
My query works, but this is running on a large dataset and taking a long time to run. I'm wondering if anyone has any ideas on how improve this so that there isn't so much wasted processing (i.e Needing to do a DISTINCT on the final result or doing COUNT(DISTINCT) in the inline view only to use that value in the DECODE above).
I've also created a DBFiddle for this question. (Link)
Thanks in advance for any input.
You could try and use a correlated subquery. This removes the need for outer distinct:
SELECT
i.enterprise_id ind_id,
i.name,
(
SELECT DECODE(COUNT(DISTINCT b.enterprise_id), 1, MIN(bus_id), 'Multiple')
FROM ind_to_business i2b
INNER JOIN business b ON i2b.bus_id = b.id
WHERE i2b.ind_id = i.id
) linked_bus
FROM individual i
You can join with the aggregated ind_to_business per individual. One way to do this:
select i.id, i.name, coalesce(b.enterprise_id, 'Multiple')
from individual i
join
(
select
ind_id,
case when min(bus_id) = max(bus_id) then min(bus_id) else null end as bus_id
from ind_to_business
group by ind_id
) ib on ib.ind_id = i.id
left join business b on b.id = ib.bus_id
order by i.id;
First you should sub-query to get all needed dimensions and then do all your final aggregation using CASE statement.
select
ind_id,
name,
case
when count(*) > 1 then 'Multiple'
else ind_id
end as linked_bus
from
(
select
distinct i.enterprise_id as ind_id,
i.name,
b.enterprise_id as bus_id
from individual i
join ind_to_business i2b
on i.id = i2b.ind_id
join business b
on i2b.bus_id = b.id
) vals
group by
ind_id,
name
order by
ind_id
No need of using DISTINCT twice. You could use subquery factoring and put the in-line view in WITH clause, and make the data set DISTINCT in the subquery itself.
WITH data AS
(
SELECT distinct
i.enterprise_id AS IND_ID,
i.name,
b.enterprise_id AS BUS_ID
FROM individual i
JOIN ind_to_business i2b ON i.id = i2b.ind_id
JOIN business b ON i2b.bus_id = b.id
)
SELECT ind_id,
name,
case
when count(*) = 1 then MIN(bus_id)
else 'Multiple'
end AS LINKED_BUS
FROM data
GROUP BY ind_id, name;
IND_ID NAME LINKED_BUS
---------- ---------- -------------------------
53a23B7 John Smith 2a34d9b
63f2a35 Jane Doe Multiple

Identifying duplicate records in SQL along with primary key

I have a business case scenario where I need to do a lookup into our SQL "Users" table to find out email addresses which are duplicated. I was able to do that by the below query:
SELECT
user_email, COUNT(*) as DuplicateEmails
FROM
Users
GROUP BY
user_email
HAVING
COUNT(*) > 1
ORDER BY
DuplicateEmails DESC
I get an output like this:
user_email DuplicateEmails
--------------------------------
abc#gmail.com 2
xyz#yahoo.com 3
Now I am asked to list out all the duplicate records in a single row of its own and display some additional properties like first name , last name and userID. All this information is stored in this table "Users". I am having difficulty doing so. Can anyone help me or put me toward right direction?
My output needs to look like this:
user_email DuplicateEmails FirstName LastName UserID
------------------------------------------------------------------------------
abc#gmail.com 2 Tim Lentil timLentil
abc#gmail.com 2 John Doe johnDoe12
xyz#yahoo.com 3 brian boss brianTheBoss
xyz#yahoo.com 3 Thomas Hood tHood
xyz#yahoo.com 3 Mark Brown MBrown12
There are several ways you could do this. Here is one using a cte.
with FoundDuplicates as
(
SELECT
uter_email, COUNT(*) as DuplicateEmails
FROM
Users
GROUP BY
uter_email
HAVING
COUNT(*) > 1
)
select fd.user_email
, fd.DuplicateEmails
, u.FirstName
, u.LastName
, u.UserID
from Users u
join FoundDuplicates fd on fd.uter_email = u.uter_email
ORDER BY fd.DuplicateEmails DESC
Use count() over( Partition by ), example
You can solve it like:
DECLARE #T TABLE
(
UserID VARCHAR(20),
FirstName NVARCHAR(45),
LastName NVARCHAR(45),
UserMail VARCHAR(45)
);
INSERT INTO #T (UserMail, FirstName, LastName, UserID) VALUES
('abc#gmail.com', 'Tim', 'Lentil', 'timLentil'),
('abc#gmail.com', 'John', 'Doe', 'johnDoe12'),
('xyz#yahoo.com', 'brian', 'boss', 'brianTheBoss'),
('xyz#yahoo.com', 'Thomas', 'Hood', 'tHood'),
('xyz#yahoo.com', 'Mark', 'Brown', 'MBrown12');
SELECT *, COUNT (1) OVER (PARTITION BY UserMail) MailCount
FROM #T;
Results:
+--------------+-----------+----------+---------------+-----------+
| UserID | FirstName | LastName | UserMail | MailCount |
+--------------+-----------+----------+---------------+-----------+
| timLentil | Tim | Lentil | abc#gmail.com | 2 |
| johnDoe12 | John | Doe | abc#gmail.com | 2 |
| brianTheBoss | brian | boss | xyz#yahoo.com | 3 |
| tHood | Thomas | Hood | xyz#yahoo.com | 3 |
| MBrown12 | Mark | Brown | xyz#yahoo.com | 3 |
+--------------+-----------+----------+---------------+-----------+
Use a window function like this:
SELECT u.*
FROM (SELECT u.*, COUNT(*) OVER (PARTITION BY user_email) as numDuplicateEmails
FROM Users
) u
WHERE numDuplicateEmails > 1
ORDER BY numDuplicateEmails DESC;
I think this will also work.
WITH cte (
SELECT
*
,DuplicateEmails = ROW_NUMBER() OVER (Partition BY user_email ORder by user_email)
FROM Users
)
Select * from CTE
where DuplicateEmails > 1

Pick exact records

INSERT INTO filerecord (fname,
lname,
transdate,
memberid)
VALUES ('tyler',
'smith',
TO_DATE ('07/01/2016', 'mm/dd/yyyy'),
'111');
I get the following result:
fname lname email
fernando hernandez fh#yahoo.com
fernando hernandez ts#hotmail.com
IT should not display Tyler Smith's Name & Email due to the transdate clause. I am looking for someone to get me only the Fernando Result.
I cannot change the structure of the tables.
You are getting a Cartesian product.
You have two entries with memberid = '111' in both tables, so this condition
AND b.memberid = e.memberid
filters you down to 4 rows --
one with Tyler Smith joined to Tyler Smith's email
one with Fernando Hernandez joined to Fernando Hernandez's e-mail,
one with Tyler Smith joined to Fernando Hernandez's email and
one with Fernando Hernandez joined to Tyler Smith's email
Then, this condition:
AND b.transdate >= TO_DATE ('07/02/2016', 'mm/dd/yyyy');
excludes two of those but not the other two.
You need a better data model and/or a more exact way to join your two tables.
In general, anytime you join two tables, one of the tables should have all of its primary key columns specified to avoid this type of error. (There are exceptions to that, as always).
It is not. I just created a new table and checked.
Table member
create table member (programid varchar(10), memberid int, fname varchar(10), lname varchar(10), email varchar(25))
Content
+-----------+----------+----------+-----------+----------------+
| PROGRAMID | MEMBERID | FNAME | LNAME | EMAIL |
+-----------+----------+----------+-----------+----------------+
| BLUE | 111 | tyler | smith | ts#hotmail.com |
| GREEN | 111 | fernando | hernandez | fh#yahoo.com |
+-----------+----------+----------+-----------+----------------+
Table filerecord
create table filerecord (fname varchar(10), lname varchar(10), transdate date, memberid int);
Content
+-------+-------+-----------+----------+
| FNAME | LNAME | TRANSDATE | MEMBERID |
+-------+-------+-----------+----------+
| tyler | smith | 7/1/2016 | 111 |
+-------+-------+-----------+----------+
Last Query
SELECT b.fname, b.lname, e.email
FROM filerecord b, member e
WHERE b.memberid IN ('111')
AND b.memberid = e.memberid
AND b.transdate >= TO_DATE ('07/02/2016', 'mm/dd/yyyy');
Result -
no rows selected.
Since memberid is not a unique identifier, there no reason to expect good things from a join condition like b.memberid = e.memberid.
Instead, you seem to suggest (or believe) that (memberid, fname, lname) should be a unique identifier. So, use it in the join condition:
... WHERE ... AND b.memberid = e.memberid
AND b.fname = e.fname
AND b.lname = e.lname
...
Like so:
SQL> SELECT b.fname, b.lname, e.email
2 FROM filerecord b, member e
3 WHERE b.memberid IN ('111')
4 AND b.memberid = e.memberid
5 AND b.fname = e.fname
6 AND b.lname = e.lname
7 AND b.transdate >= TO_DATE ('07/02/2016', 'mm/dd/yyyy');
FNAME LNAME EMAIL
-------------------- -------------------- --------------------
fernando hernandez fh#yahoo.com
1 row selected.

How to get the previous row-text

First of all: I am a SQL beginner and I use SQL Server 2008.
The tables as it is now, is written as:
SELECT
Transaction.description, Person.name
FROM
Transaction, Person, SystemUser
WHERE
Person.personnumber = SystemUser.personnumber
AND Transaction.art_ID = SystemUser.art_ID
ORDER BY
Transaction.description
where personnumber is PK nvarchar (could look like N0890) where the last numbers of it grows with +1 for every new person.
art_ID (Transaction) is PK smallint, art_ID (SystemUser) is smallint, description is nvarchar.
I want to get the text from the previous row, in the same column, so that I can manipulate the text to be clear and make the result-table look more simple.
Example as it is now:
|Transactions | Persons |
|-------------------|----------|
|Statistic | Ursula |
|Statistic | Peter |
|Statistic | Alan |
|Settlement | Christie |
|Settlement | Tania |
|Deptor department | Jack |
|Economy department | Rickie |
|Economy department | Annie |
|Economy department | Tom |
|Economy department | Seth |
How I want it to be:
|Transactions | Persons |
|-------------------|----------|
|Statistic | Ursula |
| | Peter |
| | Alan |
|Settlement | Christie |
| | Tania |
|Deptor department | Jack |
|Economy department | Rickie |
| | Annie |
| | Tom |
| | Seth |
as in select case when description = description - 1 row then ''
I have searched for examples and every one of them are based on integers, not varchar/nvarchar), and I keep getting errors when i try to do it with varchars. Such as With CTE, min() and max().
Do you have any ideas of what function I can use or how to set up the select-statement to do as I want?
First use a rank function to identify just one of them:
SELECT Transaction.description, Person.name,
RANK() OVER (PARTITION BY Transaction.description ORDER BY Person.name) As R
FROM Transaction, Person, SystemUser
WHERE Person.personnumber = SystemUser.personnumber
AND Transaction.art_ID = SystemUser.art_ID
ORDER BY Transaction.description, Person.name
Notice the lines you want to see have 1 against them? Use that:
SELECT
CASE WHEN R=1 THEN Transaction.description ELSE '' END description,
Person.name
FROM
(
SELECT Transaction.description, Person.name,
RANK() OVER (PARTITION BY Transaction.description ORDER BY Person.name) As R
FROM Transaction, Person, SystemUser
WHERE Person.personnumber = SystemUser.personnumber
AND Transaction.art_ID = SystemUser.art_ID
) Subtable
ORDER BY Transaction.description, Person.name
I think following SQL should work
CREATE TABLE #TempTable (rowrank INT, description VARCHAR(256), name VARCHAR(256));
INSERT INTO #TempTable (rowrank, description, name)
VALUES
Select RANK() OVER (ORDER BY Transaction.description)
,Transaction.description
,name
FROM Transaction, Person, SystemUser
WHERE Person.personnumber = SystemUser.personnumber
AND Transaction.art_ID = SystemUser.art_ID
ORDER BY Transaction.description
SELECT
CASE
WHEN prev.RANK = TT.RANK
THEN ""
ELSE TT.Description
END AS Description,
name
FROM #TempTable TT
LEFT JOIN #TempTable prev ON prev.rownum = TT.rownum - 1

get table join with column value

How can i join tables using column value?
I have three tables as listed below:
messages_table
-----------------------------------------------------------------------------
msg_id | msg_sub | msg_to | to_user_type | msg_from | from_user_type
-----------------------------------------------------------------------------
001 | test | 88 | manager | 42 | admin
002 | test2 | 88 | manager | 94 | manager
admin_table
-------------------------—
admin_id | admin_name
-------------------------—
001 | Super Admin
manager_table
---------------------------
manager_id | manager_name
---------------------------
88 | Mandela
94 | Kristen
How can i get the desired output as shown below with SQL query. I.e.
Join tables with respect to column values when the following criteria is met:
If user_type = admin then it should join with admin_table.
If user_type = manager then it should join with manager_table.
Desired output:
-----------------------------------------------------
msg_id | msg_sub | msg_to_name | msg_from_name
-----------------------------------------------------
001 | test | Mandela | Super Admin
002 | test2 | Mandela | Kristen
I.e. Get the join sql query based on column value.
EDIT:
I want to fetch the datafrom sql query not form the serverside coding.
I tried this query from here, i.e. Winfred's Idea ( Answered )
However, I could not understand it.
msg_by_usertype is the column based, where the value manager then it should select manager_table and if it is admin the to admin_table
As far as I understood your question, you can try this:
SELECT msg_id,
msg_body,
usersBy.userName AS msg_by,
usersTo.userName AS msg_to,
msg_by_usertype
FROM messages
INNER JOIN
(SELECT admin_id As id, admin_name as userName
FROM admin_table
UNION
SELECT manager_id As id, manager_name as userName
FROM manager_table ) usersTo ON msg_to = usersTo.id
INNER JOIN
(SELECT admin_id As id, admin_name as userName
FROM admin_table
UNION
SELECT manager_id As id, manager_name as userName
FROM manager_table ) usersBy ON msg_by = usersBy.id
Here is an SQL Fiddle to see how it works. (It only works if you cant have an admin who has the same id like a manager. Id should be unique in both tables.)
Please use the below SQL
SELECT msg_id,
msg_body,
usersBy.userName AS msg_by,
usersTo.userName AS msg_to,
msg_by_usertype
FROM messages
INNER JOIN
(SELECT admin_id As id, admin_name as userName,'admin' as usertype
FROM admin_table
UNION
SELECT manager_id As id, manager_name as userName,'manager' as usertype
FROM manager_table ) usersTo
ON msg_to = usersTo.id and msg_by_usertype = usersTo.usertype
if I understand your question correctly, you want a result like this?
MSG_ID MSG_BODY MSG_TO BY MSG_BY_USERTYPE
---------- ---------- ---------- ----------- ---------------
001 test adm1 managone manager
002 sadff adm1 adm3? admin
If so, you could use this
SELECT MSG_ID, MSG_BODY, MSG_TO,
CASE
WHEN MSG_BY_USERTYPE = 'admin' THEN COALESCE(
(SELECT ADMIN_NAME FROM ADMIN_TABLE
WHERE MSG_BY = ADMIN_ID), RTRIM(MSG_BY) CONCAT '?')
WHEN MSG_BY_USERTYPE = 'manager' THEN COALESCE(
(SELECT MANAGER_NAME FROM MANAGER_TABLE
WHERE MSG_BY = MANAGER_ID), RTRIM(MSG_BY) CONCAT '?')
ELSE ' '
END AS BY,
MSG_BY_USERTYPE
FROM MESSAGES