history gives me too many subquery results - sql

I appreciate your help, I hope I'm able to provide adequate information.
I need to roll back owners associated with quite a few entries in our asset table (one owner got applied to a lot of systems). I have a history table with the information I need, and an employee table that gets wedged in there because for some reason the history table stores names rather than employee_id's.
Source data:
asset_table has asset_id, employee_id, asset_tag
employee_table has employee_id, name
history_table has asset_id, old_name, new_name
update asset_table
set employee_id = (select employee_id
from employee_table
where name like (select old_name
from history_table
where asset_table.asset_id=history_table.asset_id
and new_name like 'tobe replaced'))
However, the subquery turns up more than one result per line.
What am I missing to limit that subquery results?
MSSQL server 2012

I guess you need someone who was the last in history:
update at set
employee_id = e.employee_id
from asset_table at
cross apply
(
select top (1)
h.old_name
from history_table h
where at.asset_id = h.asset_id
and h.new_name = 'tobe replaced'
order by h.<date or id or something> desc
) h
inner join employee_table e
on e.name = h.old_name
TOP 1 will fix "more than one result" problem.
Note, LIKE without % works exactly the same way as =.

You can write update statement is as below by using JOIN instead of sub query, Provide table structure and data for more correct query:
update at set at.employee_id = et.employee_id
from asset_table at
inner join history_table ht on at.asset_id = ht.asset_id
and ht.new_name like '%tobe replaced%'
inner join employee_table et on et.name = ht.old_name
and et.employee_id = at.employee_id

Related

SQL Joins and Corelated subqueries with column data

I am facing an issue in terms of understanding the joins. Lets say for an example we have two tables employee and sales and now I have a query where we have sales of an employee using the id of the employee
select e.employeename
,s.city
,SUM(s.sales)
from employee e
left join (select sales,eid from sales) s on s.eid = e.id
group by 1,2
I'd like to understand why s.city wasn't showing up? and also would like to understand what is this concept called? Is it co related sub queries on Joins? Please help me down over here.
select
e.employeename
,s.city
,SUM(s.sales)
from employee e
left join (select sales,eid,city from sales) s on s.eid = e.id
group by 1,2
in the left join above you have to add city as well. The query Imagine select sales,eid,city from sales is a table itself and then from this table you are selecting city (your second column s.city) this will run error as your table doesn't have a city column yet.
It is much easier to use CTE (common table expressions than CTE's) You can also do the above question as
select
e.employeename
,s.city
,SUM(s.sales)
from employee e
left join sales as s
on e.id = s.id
group by 1,2
here I have added e.id = s.id instead of s.id = e.id it is better to reference the key of the main table first.
you could use CTE (although used when you have to do a lot of referencing but you can see how it works):
With staging as (
select
e.employeename
,s.city
,s.sales
from employee e
left join sales as s
on e.id = s.id
),
sales_stats as (
select
staging.employeename,
staging.city,
sum(staging.sales)
from staging
group by 1,2
#here you will select from staging again consider staging as a separate table so you will have to have all the columns in the staging that you want to use further. Also you will have to reference columns using staging.x
)
select * from sales_stats
-- here you could have combined the steps but I wanted to show you how cte works, Hope this works for you

Oracle Sql Duplicate rows when joining new table

I am using oracle sql to join tables. I use the following code:
SELECT
T.TRANSACTION_KEY,
PR.ACCOUNT_KEY,
T.ACCT_CURR_AMOUNT,
T.EXECUTION_LOCAL_DATE_TIME,
TC.DESCRIPTION,
T.OPP_ACCOUNT_NAME,
T.OPP_COUNTRY,
PT.PARTY_TYPE_DESC,
P.PARTY_NAME,
P.CUSTOM_SMALL_STRING_02,
CO.COUNTRY_NAME,
LE.LIST_CD
FROM TRANSACTIONS T
LEFT JOIN TRANSACTION_CODE TC
ON T.TRANSACTION_CODE = TC.ENTITY
LEFT JOIN PARTY_ACCOUNT_RELATION PR
ON T.ACCOUNT = PR.ACCOUNT
LEFT JOIN PARTY P
ON PR.PARTY_KEY = P.PARTY_KEY
LEFT JOIN PARTY_TYPE PT
ON P.PARTY_TYPE = PT.ENTITY
LEFT JOIN COUNTRY CO
ON T.OPP_COUNTRY = CO.ENTITY
LEFT JOIN LISTED_ENTITY LE
ON CO.COUNTRY = LE.ENTITY_KEY
WHERE
PR.PARTY_KEY = '111111111' and T.EXECUTION_LOCAL_DATE_TIME>'2017-01-01';
It works fine until now but I want to join another table which has a column in common(ENTITY_KEY) with PARTY_ACCOUNT_RELATION table (ACCOUNT_KEY) and I want to include some of the new table's columns but when I do that, it becomes dublicated. I am adding the following lines before "where" statment:
LEFT JOIN EVALUATE_RULE ER
ON PR.ACCOUNT_KEY = ER.ENTITY_KEY
Does anyone know where the problem is?
If joining another table into an existing query causes the existing rows to be duplicated, it is because the table being joined in has duplicate values in the columns that are being used as keys for the join
In your case, if you do
SELECT ENTITY_KEY FROM EVALUATE_RULE GROUP BY ENTITY_KEY HAVING COUNT(*) > 1
You'll see which entity_keys are duplicated. When these duplicates are joined to the existing data, the existing data has to be doubled up to permit both rows from EVALUATE_RULE with the same ENTITY_KEY to exist in the result set
You must either de-dupe the table, or put other clauses into your ON condition to further restrict the rows coming from EVALUATE_RULE.
For example, after adding EVALUATE_RULE and putting ER.* in your SELECT list, imagine that you can see that the rows from ER are status = 'old' and status = 'current' but you know you only want the current ones.. So put AND er.status = 'current' in your ON clause
Your comment indicates that multiple records differ by some column you don't care about, so this technique will just select only one row:
LEFT JOIN
(SELECT e.*, ROW_NUMBER() OVER(PARTITION BY e.entity_key ORDER BY e.name) as rown FROM evaluate_rule e) er
ON
er.entity_key = pr.account_key and
er.rown = 1
If you want info on why this works, run that sql in isolation:
SELECT e.*, ROW_NUMBER() OVER(PARTITION BY e.entity_key ORDER BY e.name) as rown FROM evaluate_rule e
ORDER BY e.entity_key -- i added this to make it more clear what is going on. You don't need it in your main query
It just assigns a number to each row in the table, the number restarts at 1 every time entity_key changes, so we can then select all those with rown = 1
If it turns out you DO want something specific like "the latest row from evaluate_rule", you can use something like this:
SELECT e.*, ROW_NUMBER() OVER(PARTITION BY e.entity_key ORDER BY e.created_date DESC) as rown FROM evaluate_rule e
Now the latest created_date row will always have rown = 1
So far as I can understain from your description, table EVALUATE_RULE has moro records with ACCOUNT_KEY=ENTITY_KEY.
You can change your query section:
LEFT JOIN EVALUATE_RULE ER ON PR.ACCOUNT_KEY = ER.ENTITY_KEY
to
LEFT JOIN (SELECT DISTINCT ENTITY_KEY FROM EVALUATE_RULE) ER ON PR.ACCOUNT_KEY = ER.ENTITY_KEY
If you post structure of EVALUATE_RULE (indicating PK columns) I can change my answer to let you includ EVALUATE_RULE columns in final query.

How to get the value of max() group when in subquery?

So i woud like to find the department name or department id(dpmid) for the group that has the max average of age among the other group and this is my query:
select
MAX(avg_age) as 'Max average age' FROM (
SELECT
AVG(userage) AS avg_age FROM user_data GROUP BY
(select dpmid from department_branch where
(select dpmbid from user_department_branch where
user_data.userid = user_department_branch.userid)=department_branch.dpmbid)
) AS query1
this code show only the max value of average age and when i try to show the name of the group it will show the wrong group name.
So, How to show the name of max group that has subquery from another table???
You may try this..
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##' ,department_name)) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I've done some change on ipothesys that the department name is placed in a "department" anagraphical table.. so, as it needed put in join a table in plus, then I changed your query, eventually if the department name is placed (but I don't thing so) in the branch_department table you can add the field and its treatment to your query
update
In adjunct to as said, if you wanto to avoid identical average cases you can furtherly make univocal the averages by appending a rownum id in this way:
select MAX(avg_age) as max_avg, SUBSTRING_INDEX(MAX(avg_age_dep),'##',-1) as max_age_dep from
(
SELECT
AVG(userage) as avg_age, CONCAT( AVG(userage), CONCAT('##', CONCAT( #rownum:=#rownum+1, CONCAT('##' ,department_name)))) as avg_age_dep
FROM user_data
inner join user_department_branch
on user_data.userid = user_department_branch.userid
inner join department_branch
on department_branch.dpmbid = user_department_branch.dpmbid
inner join department
on department.dpmid = department_branch.dpmid
,(SELECT #rownum:=0) r
group by department_branch.dpmid
) tab_avg_age_by_dep
;
I took a shot at what I think you are looking for. The following will give you the department branch with the highest average age. I assumed the department_branch table had a department_name field. You may need an additional join to get the department.
SELECT db.department_name, udb.dpmid, AVG(userage) as `Average age`
FROM user_data as ud
JOIN user_department_branch as udb
ON udb.userid = ud.userid
JOIN department_branch as db
ON db.dpmbid = udb.dpmbid
GROUP BY udb.dpmid
ORDER BY `Average age` DESC
LIMIT 1

Create filter for most recent date using combined columns

I have created a filtering application in Access that references four simple tables:
Employee: Emp_ID, FirstName, LastName
Skill: Skill_ID, SkillName, SkillDescription, SkillGroup
Employee_Skill: Entry_ID, Emp_ID, Skill_ID, LevelofExperience, Dateupdated
SkillGroupName:SkillGroup_ID SkillGroupName`
Basically the idea of this database is to track employee skills and how the level of experience improves (or not!) over time. The problem I am facing is that I want the application to filter by the most recently updated combination of Skill and Employee. I have found the query that will allow for me to use the two columns as a distinct entity:
SELECT DISTINCT Emp_ID, Skill_ID FROM Employee_Skill
WHERE (SELECT MAX(DateUpdated)From Employee_Skill);
And it works perfectly on its own, but I don't know how to incorporate it either into my main query, which simply joins together the necessary columns for an easier end user experience. It does not visibly show Emp_ID or Skill_ID. It also doesn't in the VBA for the application. (-1 = Include all History; 0 = Only include most updated.)
Update:
I have been able to select the distinct combination of Employee and Skill through my main query by doing this:
SELECT
Employee.FirstName,
Employee.LastName,
Max(Employee_Skill.LevelOfExperience) AS LevelOfExperience,
Skill.SkillName,
Max(Employee_Skill.DateUpdated) AS DateUpdated,
Max(SkillGroup.SkillGroupName) AS SkillGroupName
FROM
SkillGroup INNER JOIN
(Skill INNER JOIN
(Employee INNER JOIN
Employee_Skill ON
Employee.Emp_ID = Employee_Skill.Emp_ID) ON
Skill.Skill_ID = Employee_Skill.Skill_ID) ON
SkillGroup.SkillGroup_ID = Skill.SkillGroup
WHERE
Employee.Active=True
GROUP BY
Employee.FirstName,
Employee.LastName,
Skill.SkillName
ORDER BY
Max(Employee_Skill.LevelOfExperience) DESC;
However, my forms and reports built on this query are stuck with only the option of seeing the most updated version. I am really hoping to have a dynamic form that removes the constraints as desired.
Not sure what you're doing with Max(Employee_Skill.LevelOfExperience) or Max(SkillGroup.SkillGroupName) but I think you need to stick with querying for the detail rows and then include another column marking the Max(Employee_Skill.DateUpdated) filter, like:
SELECT
Employee.FirstName,
Employee.LastName,
Employee_Skill.LevelOfExperience,
Skill.SkillName,
Employee_Skill.DateUpdated,
SkillGroup.SkillGroupName,
iif(max_dateUpdated=dateupdated,1,0) as is_max_DateUpdated
FROM
SkillGroup INNER JOIN
(Skill INNER JOIN
(Employee INNER JOIN
Employee_Skill ON
Employee.Emp_ID = Employee_Skill.Emp_ID) ON
Skill.Skill_ID = Employee_Skill.Skill_ID) ON
SkillGroup.SkillGroup_ID = Skill.SkillGroup inner join
(select
empID,
max(dateupdated) as max_dateUpdated
from
Employee_Skill
group by
empID) mx on
Employee.empID = mx.empID
WHERE
Employee.Active=True

How to select records with no matches in the foreign table (Left outer join)

I have one table that holds my ressources:
Ressource | Ressource-ID
And a table that holds the associations
Ressource-ID | Employee-ID
How to select the ressources of an Employee that are available, i.e. not in the association table?
I've tried this, but it's not working:
select r.ress, r.ress_id
FROM Ressource r
LEFT outer JOIN Ressource_Employee_Association a ON r.ress_id = a.ress_id
WHERE a.emp_id = 'ID00163efea66b' and a.ress_id IS NULL
Any ideas?
Thanks
Thomas
After writing my above comments, and looking at the proposed solutions: I think I've got some more understanding of what you are trying to do.
Assuming you have unlimited quantity of resources in your resources table, you want to select the un-assigned resources per employee (based on their non-existence for any specific employee in the resource association table).
In order to accomplish this (and get a comprehensive list of employees) you'll need a 3rd table, in order to reference the complete list of employees. You'll also need to CROSS JOIN all of the resources onto the list of employees (assuming every employee has access to every resource), then you LEFT JOIN (LEFT OUTER JOIN whatever) your association list onto the query where the resource_id and employee_id match the resource_id in the resources table, and the employee_id in the employees table (respectively). THEN you add your where clause that filters out all the records that assign an employee to a resource. This leaves you with the resources that are available to the employee, which they also do not have signed out. This is convoluted to say, so hopefully the query sheds more light:
SELECT e.employee_id, e.employee, r.res_id, r.res
FROM employees e
CROSS JOIN resources r
LEFT JOIN assigned_resources ar
ON ar.employee_id = e.employee_id AND r.res_id = ar.res_id
WHERE ar.res_id IS NULL
If you don't have an employees table, you can accomplish the same by using the assigned resources table, but you will be limited to selecting employees who already have some resources allocated. You'll need to add a GROUP BY query because of the possible existence of multiple employee definitions in the association table. Here's what that query would look like:
SELECT e.employee_id, r.res_id, r.res
FROM assigned_resources e
CROSS JOIN resources r
LEFT JOIN assigned_resources ar
ON ar.employee_id = e.employee_id AND r.res_id = ar.res_id
WHERE ar.res_id IS NULL
GROUP BY e.employee_id, r.res_id
Does this work?
select r.ress, r.ress_id
from resource r
where not exists
(
select 1 from ressource_emplyee_association a
where a.emp_id = '...' and a.ress_id = r.ress_id
)
EDIT
Before that I had the following, but changed it according to the comments below:
select r.ress, r.ress_id
from resource r
where not exists
(
select top 1 1 from ressource_emplyee_association a
where a.emp_id = '...' and a.ress_id = r.ress_id
)
The WHERE clause is applied after the LEFT JOIN. This means that you are currently trying to get results where there is NO matching record in Ressource_Employee_Association, but where the emp_id equals 'ID00163efea66b'.
But if there is no matching record, how can emp_id be anything other than NULL?
One option is to move part of the WHERE clause into the join...
SELECT
r.ress, r.ress_id
FROM
Ressource r
LEFT OUTER JOIN
Ressource_Employee_Association a
ON r.ress_id = a.ress_id
AND a.emp_id = 'ID00163efea66b'
WHERE
a.ress_id IS NULL
This will list all resources that are not associated to employee 'ID00163efea66b'.
EDIT
Your comment implies that what you want is...
- A view listing all employees
- For each employee list each resource that they DON'T have
This requires an extra table listing all of your employees.
SELECT
*
FROM
Employee
CROSS JOIN
Ressource
WHERE
NOT EXISTS (SELECT * FROM Ressource_Employee_Association
WHERE emp_id = Employee.id
AND ress_id = Ressource.id)
SELECT *
FROM Ressource
WHERE ress_id IN (
SELECT ress_id,
FROM Ressource
MINUS
SELECT ress_id
FROM Ressource_Employee_Association
WHERE emp_id = 'ID00163efea66b'
);