Join result of two queries with same column in oracle - sql

I am using Oracle. I am currently working on one table with two different query output. I want to combine two output in single output, I have tried Union all and union but no luck.
with D as
(
Select
VP.HOMELABORLEVELNM4 as DEPT,
SUM(X.DURATIONSECSQTY/3600.0) as ACTL_HR,
SUM(X.WAGEAMT) as ACTL_DLR,
to_char(X.APPLYDTM,'YYYY-MM') AS MONTHLY,
VP.HOMELABORLEVELDSC4 as DESCRIPTION,
NULL as DAILY,
NULL as DEPT1,
NULL as ACTL_HR1,
NULL as ACTL_DLR1
from VP_EMPLOYEEV42 VP,
WFCTOTAL X
where
VP.PERSONID = X.EMPLOYEEID and
X.APPLYDTM between '01-DEC-18' and '31-DEC-18' and
X.EMPLOYEEID in (select personid from PERSONCSTMDATA where CUSTOMDATADEFID ='154' and PERSONCSTMDATATXT = 'USKEANE')
group by VP.HOMELABORLEVELNM4, VP.HOMELABORLEVELDSC4, to_char(X.APPLYDTM,'YYYY-MM')
union all
Select
NULL as DEPT,
NULL as ACTL_HR,
NULL as ACTL_DLR,
NULL as MONTHLY,
VP.HOMELABORLEVELDSC4 as DESCRIPTION,
to_char(X.APPLYDTM) as DAILY,
VP.HOMELABORLEVELNM4 as DEPT1,
SUM(X.DURATIONSECSQTY/3600.0) as ACTL_HR1,
SUM(X.WAGEAMT) as ACTL_DLR1
from VP_EMPLOYEEV42 VP,
WFCTOTAL X
where
VP.PERSONID = X.EMPLOYEEID and
X.APPLYDTM = '31-DEC-18' and
X.EMPLOYEEID in (select personid from PERSONCSTMDATA where CUSTOMDATADEFID ='154' and PERSONCSTMDATATXT = 'USKEANE')
group by VP.HOMELABORLEVELNM4, VP.HOMELABORLEVELDSC4, to_char(X.APPLYDTM)
)
select D.DEPT DEPT,
SUM(D.ACTL_HR) ACTL_HR,
SUM(D.ACTL_DLR) ACTL_DLR,
D.MONTHLY MONTHLY,
D.DESCRIPTION DESCRIPTION,
D.DAILY DAILY,
D.DEPT1 DEPT1,
SUM(D.ACTL_HR1) ACTL_HR1,
SUM(D.ACTL_DLR1) ACTL_DLR1
from D
group by D.DEPT, D.MONTHLY, D.DAILY, D.DESCRIPTION, D.DEPT1
order by DESCRIPTION
it is giving me output like this
-DEPT-HR-DLR-MONTHLY-DESC-DAILY-DEPT-HR-DLR-
-1-12-12-11/1-Manu-NULL-NULL-NULL-NULL-
-NULL-NULL-NULL-NULL-Manu-17-1-12-12-

As long as you have null value in any of the fields you want to group on, you will receive it as a separate row. I think you'd want to review your needed output, then we can try to paraphrase it with code to you.
Hint: you may want to look into JOIN, and group only on D.MONTHLY, D.DAILY, D.DESCRIPTION, D.DEPT1, because your DEPT column is missing in one of the tables.

I think your goal (which is not quite clear to me) might be easier to achieve following this pattern:
with Query1 as (select fields from table where conditions are met),
Query2 as (select fields from table where conditions are met)
select fields
from Query1
outer join Query2
on Query1.identifier_for_match=Query2.identifier_for_match
where optional conditions are true
Note - the 'identifier_for_match' might be employeeid in your case (which would make it a required part of the Query1/Query2 resultset) - you have to look at the model and figure out how the query should combine the rows.
Also - an answer fitting your tables is easier to provide if the DDL for the tables is provided and some data for the same (including the desired output)

Related

SQL Aggregating data based on condition containing the key fields for aggregation

I am new to SQL (Oracle SQL if it makes a difference) but it so happens I have to use it. I need to aggregate data by some key fields (CustId, AppId). I also have some AppDate, PDate and Amount.Initial data
What I need to do is aggregate but for each key field combination I need to aggregate the data from other rows with the following conditions:
CustID = CustID aka take only information for this custID
AppId != AppId aka take only information for application different than the current one.
AppDate >= PDate aka take only information available at time of application
From a quick look at SQL language my approach was the use of:
select CustId, AppId, Sum(case when
custid=custid and Appid!=Appid and AppDate >= PDate then Amount else 0 end) as SumAmount
From Table
Group by CustId AppId
Unfortunately, the result I get are all 0 for SumAmount. My guess it is because of the last 2 conditions.
The results I want to get from the example table are: Results
Also, I would probably add condition that AppDate - AppDate of other AppID > 6months exclude those from the aggregated amounts.
P.S. I am really sorry for the substandard formatting and probably bad code. I am not really experienced on how to do it.
Edit: I've found a solution as follows:
select distinct a.CustId, a.AppId, a.AppDate, b.PDate, b.Amount
from table a
inner join (select CustId, AppId, Amount, PDate from Table) b
on a.CustId = b.CustId and a.AppId != b.AppId
where a.AppDate >= b.PDate
After that I aggregate by AppId summing the amount.
Basically, I just append the same information based on a condition and since I get a lot of full duplicates I deduplicate with distinct.
I've found a solution as follows:
select distinct a.CustId, a.AppId, a.AppDate, b.PDate, b.Amount
from table a
inner join (select CustId, AppId, Amount, PDate from Table) b
on a.CustId = b.CustId and a.AppId != b.AppId
where a.AppDate >= b.PDate
After that I aggregate by AppId summing the amount.
Basically, I just append the same information based on a condition and since I get a lot of full duplicates I deduplicate with distinct.

TSQL "where ... group by ..." issue that needs solution like "having ..."

I have 3 sub-tables of different formats joined together with unions if this affects anything into full-table. There I have columns "location", "amount" and "time". Then to keep generality for my later needs I union full-table with location-table that has all possible "location" values and other fields are null into master-table.
I query master-table,
select location, sum(amount)
from master-table
where (time...)
group by location
However some "location" values are dropped because sum(amount) is 0 for those "location"s but I really want to have full list of those "location"s for my further steps.
Alternative would be to use HAVING clause but from what I understand HAVING is impossible here because i filter on "time" while grouping on "location" and I would need to add "time" in grouping which destroys the purpose. Keep in mind that the goal here is to get sum(amount) in each "location"
select location, sum(amount)
from master-table
group by location, time
having (time...)
To view the output:
with the first code I get
loc1, 5
loc3, 10
loc6, 1
but I want to get
loc1, 5
loc2, 0
loc3, 10
loc4, 0
loc5, 0
loc6, 1
Any suggestions on what can be done with this structure of master-table? Alternative solution to which I have no idea how to code would be to add numbers from the first query result to location-table (as a query, not actual table) with the final result query that I've posted above.
What you want will require a complete list of locations, then a left-outer join using that table and your calculated values, and IsNull (for tsql) to ensure you see the 0s you expect. You can do this with some CTEs, which I find valuable for clarity during development, or you can work on "putting it all together" in a more traditional SELECT...FROM... statement. The CTE approach might look like this:
WITH loc AS (
SELECT DISTINCT LocationID
FROM location_table
), summary_data as (
SELECT LocationID, SUM(amount) AS location_sum
FROM master-table
GROUP BY LocationID
)
SELECT loc.LocationID, IsNull(location_sum,0) AS location_sum
FROM loc
LEFT OUTER JOIN summary_data ON loc.LocationID = summary_data.LocationID
See if that gets you a step or two closer to the results you're looking for.
I can think of 2 options:
You could move the WHERE to a CASE WHEN construction:
-- Option 1
select
location,
sum(CASE WHEN time <'16:00' THEN amount ELSE 0 END)
from master_table
group by location
Or you could JOIN with the possible values of location (which is my first ever RIGHT JOIN in a very long time 😉):
-- Option 2
select
x.location,
sum(CASE WHEN m.time <'16:00' THEN m.amount ELSE 0 END)
from master_table m
right join (select distinct location from master_table) x ON x.location = m.location
group by x.location
see: DBFIDDLE
The version using T-SQL without CTEs would be:
SELECT l.location ,
ISNULL(m.location_sum, 0) as location_sum
FROM master-table l
LEFT JOIN (
SELECT location,
SUM(amount) as location_sum
FROM master-table
WHERE (time ... )
GROUP BY location
) m ON l.location = m.location
This assumes that you still have your initial UNION in place that ensures that master-table has all possible locations included.
It is the where clause that excludes some locations. To ensure you retain every location you could introduce "conditional aggregation" instead of using the where clause: e.g.
select location, sum(case when (time...) then amount else 0 end) as location_sum
from master-table
group by location
i.e. instead of excluding some rows from the result, place the conditions inside the sum function that equate to the conditions you would have used in the where clause. If those conditions are true, then it will aggregate the amount, but if the conditions evaluate to false then 0 is summed, but the location is retained in the result.

Recursive subtraction from two separate tables to fill in historical data

I have two datasets hosted in Snowflake with social media follower counts by day. The main table we will be using going forward (follower_counts) shows follower counts by day:
This table is live as of 4/4/2020 and will be updated daily. Unfortunately, I am unable to get historical data in this format. Instead, I have a table with historical data (follower_gains) that shows net follower gains by day for several accounts:
Ideally - I want to take the follower_count value from the minimum date in the current table (follower_counts) and subtract the sum of gains (organic + paid gains) for each day, until the minimum date of the follower_gains table, to fill in the follower_count historically. In addition, there are several accounts with data in these tables, so it would need to be grouped by account. It should look like this:
I've only gotten as far as unioning these two tables together, but don't even know where to start with looping through these rows:
WITH a AS (
SELECT
account_id,
date,
organizational_entity,
organizational_entity_type,
vanity_name,
localized_name,
localized_website,
organization_type,
total_followers_count,
null AS paid_follower_gain,
null AS organic_follower_gain,
account_name,
last_update
FROM follower_counts
UNION ALL
SELECT
account_id,
date,
organizational_entity,
organizational_entity_type,
vanity_name,
localized_name,
localized_website,
organization_type,
null AS total_followers_count,
organic_follower_gain,
paid_follower_gain,
account_name,
last_update
FROM follower_gains)
SELECT
a.account_id,
a.date,
a.organizational_entity,
a.organizational_entity_type,
a.vanity_name,
a.localized_name,
a.localized_website,
a.organization_type,
a.total_followers_count,
a.organic_follower_gain,
a.paid_follower_gain,
a.account_name,
a.last_update
FROM a
ORDER BY date desc LIMIT 100
UPDATE: Changed union to union all and added not exists to remove duplicates. Made changes per the comments.
NOTE: Please make sure you don't post images of the tables. It's difficult to recreate your scenario to write a correct query. Test this solution and update so that I can make modifications if necessary.
You don't loop through in SQL because its not a procedural language. The operation you define in the query is performed for all the rows in a table.
with cte as (SELECT a.account_id,
a.date,
a.organizational_entity,
a.organizational_entity_type,
a.vanity_name,
a.localized_name,
a.localized_website,
a.organization_type,
(a.follower_count - (b.organic_gain+b.paid_gain)) AS follower_count,
a.account_name,
a.last_update,
b.organic_gain,
b.paid_gain
FROM follower_counts a
JOIN follower_gains b ON a.account_id = b.account_id
AND b.date < (select min(date) from
follower_counts c where a.account.id = c.account_id)
)
SELECT b.account_id,
b.date,
b.organizational_entity,
b.organizational_entity_type,
b.vanity_name,
b.localized_name,
b.localized_website,
b.organization_type,
b.follower_count,
b.account_name,
b.last_update,
b.organic_gain,
b.paid_gain
FROM cte b
UNION ALL
SELECT a.account_id,
a.date,
a.organizational_entity,
a.organizational_entity_type,
a.vanity_name,
a.localized_name,
a.localized_website,
a.organization_type,
a.follower_count,
a.account_name,
a.last_update,
NULL as organic_gain,
NULL as paid_gain
FROM follower_counts a where not exists (select 1 from
follower_gains c where a.account_id = c.account_id AND a.date = c.date)
You could do something like this, instead of using the variable you can just wrap it another bracket and write at end ) AS FollowerGrowth
DECLARE #FollowerGrowth INT =
( SELECT total_followers_count
FROM follower_gains
WHERE AccountID = xx )
-
( SELECT TOP 1 follower_count
FROM follower_counts
WHERE AccountID = xx
ORDER BY date ASCENDING )

SQL: multiple counts from same table

I am having a real problem trying to get a query with the data I need. I have tried a few methods without success. I can get the data with 4 separate queries, just can't get hem into 1 query. All data comes from 1 table. I will list as much info as I can.
My data looks like this. I have a customerID and 3 columns that record who has worked on the record for that customer as well as the assigned acct manager
RecID_Customer___CreatedBy____LastUser____AcctMan
1-------1374----------Bob Jones--------Mary Willis------Bob Jones
2-------1375----------Mary Willis------Bob Jones--------Bob Jones
3-------1376----------Jay Scott--------Mary Willis-------Mary Willis
4-------1377----------Jay Scott--------Mary Willis------Jay Scott
5-------1378----------Bob Jones--------Jay Scott--------Jay Scott
I want the query to return the following data. See below for a description of how each is obtained.
Employee___Created__Modified__Mod Own__Created Own
Bob Jones--------2-----------1---------------1----------------1
Mary Willis------1-----------2---------------1----------------0
Jay Scott--------2-----------1---------------1----------------1
Created = Counts the number of records created by each Employee
Modified = Number of records where the Employee is listed as Last User
(except where they created the record)
Mod Own = Number of records for each where the LastUser = Acctman
(account manager)
Created Own = Number of Records created by the employee where they are
the account manager for that customer
I can get each of these from a query, just need to somehow combine them:
Select CreatedBy, COUNT(CreatedBy) as Created
FROM [dbo].[Cust_REc] GROUP By CreatedBy
Select LastUser, COUNT(LastUser) as Modified
FROM [dbo].[Cust_REc] Where LastUser != CreatedBy GROUP By LastUser
Select AcctMan, COUNT(AcctMan) as CreatePort
FROM [dbo].[Cust_REc] Where AcctMan = CreatedBy GROUP By AcctMan
Select AcctMan, COUNT(AcctMan) as ModPort
FROM [dbo].[Cust_REc] Where AcctMan = LastUser AND NOT AcctMan = CreatedBy GROUP By AcctMan
Can someone see a way to do this? I may have to join the table to itself, but my attempts have not given me the correct data.
The following will give you the results you're looking for.
select
e.employee,
create_count=(select count(*) from customers c where c.createdby=e.employee),
mod_count=(select count(*) from customers c where c.lastmodifiedby=e.employee),
create_own_count=(select count(*) from customers c where c.createdby=e.employee and c.acctman=e.employee),
mod_own_count=(select count(*) from customers c where c.lastmodifiedby=e.employee and c.acctman=e.employee)
from (
select employee=createdby from customers
union
select employee=lastmodifiedby from customers
union
select employee=acctman from customers
) e
Note: there are other approaches that are more efficient than this but potentially far more complex as well. Specifically, I would bet there is a master Employee table somewhere that would prevent you from having to do the inline view just to get the list of names.
this seems pretty straight forward. Try this:
select a.employee,b.created,c.modified ....
from (select distinct created_by from data) as a
inner join
(select created_by,count(*) as created from data group by created_by) as b
on a.employee = b.created_by)
inner join ....
This highly inefficient query may be a rough start to what you are looking for. Once you validate the data then there are things you can do to tidy it up and make it more efficient.
Also, I don't think you need the DISTINCT on the UNION part because the UNION will return DISTINCT values unless UNION ALL is specified.
SELECT
Employees.EmployeeID,
Created =(SELECT COUNT(*) FROM Cust_REc WHERE Cust_REc.CreatedBy=Employees.EmployeeID),
Mopdified =(SELECT COUNT(*) FROM Cust_REc WHERE Cust_REc.LastUser=Employees.EmployeeID AND Cust_REc.CreateBy<>Employees.EmployeeID),
ModOwn =
CASE WHEN NOT Empoyees.IsManager THEN NULL ELSE
(SELECT COUNT(*) FROM Cust_REc WHERE AcctMan=Employees.EmployeeID)
END,
CreatedOwn=(SELECT COUNT(*) FROM Cust_REc WHERE AcctMan=Employees.EmployeeID AND CReatedBy=Employees.EMployeeID)
FROM
(
SELECT
EmployeeID,
IsManager=CASE WHEN EXISTS(SELECT AcctMan FROM CustRec WHERE AcctMan=EmployeeID)
FROM
(
SELECT DISTINCT
EmployeeID
FROM
(
SELECT EmployeeID=CreatedBy FROM Cust_Rec
UNION
SELECT EmployeeID=LastUser FROM Cust_Rec
UNION
SELECT EmployeeID=AcctMan FROM Cust_Rec
)AS Z
)AS Y
)
AS Employees
I had the same issue with the Modified column. All the other columns worked okay. DCR example would work well with the join on an employees table if you have it.
SELECT CreatedBy AS [Employee],
COUNT(CreatedBy) AS [Created],
--Couldn't get modified to pull the right results
SUM(CASE WHEN LastUser = AcctMan THEN 1 ELSE 0 END) [Mod Own],
SUM(CASE WHEN CreatedBy = AcctMan THEN 1 ELSE 0 END) [Created Own]
FROM Cust_Rec
GROUP BY CreatedBy

How to query on another query result?

I have a problem as I am not so strong on queries.
I have a query with consists of a union of two select queries :
SELECT em.emp_code,
em.emp_name,
COALESCE(SUM(pe.hours_allotted),0) AS hours,
pe.dated
FROM employee_master em
LEFT JOIN project_employee pe ON (pe.Emp_code = em.emp_code)
WHERE (dated >= '2011-03-14'
AND dated < '2011-03-20' )
OR dated IS NULL
GROUP BY em.emp_code
UNION
(SELECT em.emp_code,
em.emp_name,
'0' AS hours,
pe.dated
FROM employee_master em
LEFT JOIN project_employee pe ON (pe.Emp_code = em.emp_code)
WHERE (dated >= '2011-03-14'
AND dated < '2011-03-20' )
OR dated IS NOT NULL
GROUP BY em.Emp_code)
ORDER BY emp_name;
Now the result sets are returning for example as:
ecode ename hours
----------------------
201 Alak basu 10
201 alak basu 0
The first result is from first select statement of the union where hours = 10
and hours = 0 is from second select statement of union.
What I want is:
ecode ename hours
----------------------------
201 alak basu 10
Like in the case there should be only one result per ecode. How to group it like summing up the hours on as group by ecode so that it gives me only one result as above?
You can always do something like:
select emp_code, min(emp_name) as emp_name, sum(hours)
from (
<your original query here>
) as e
group by emp_code
order by emp_name;
If the desired result is to sum all hours for a single employee code into a single row, and the second query after the UNION will only ever return zero hours, it seems like the best solution here is to get rid of the UNION.
EDIT: After further clarification, here's what I think the SQL should probably look like:
SELECT em.emp_code,
em.emp_name,
COALESCE(pe.hours, 0) AS hours
FROM employee_master em
LEFT JOIN (
SELECT emp_code,
SUM(hours_allotted) AS hours
FROM project_employee
WHERE dated >= '2011-03-14' AND
dated < '2011-03-20'
GROUP BY emp_code
) pe ON (pe.emp_code = em.emp_code)
ORDER BY em.emp_name;
What it's doing:
Perform a subquery to filter all project_employee entries to the ones within the specified date range. (Note that there is no need for NULL or NOT NULL checks at all here. Either the date is in range, or it is not.)
Sum the hours for each employee code generated in the subquery.
Take all employees in the employee_master table, and search for matching entries in the filtered, summed project_employee subquery result set. (Since it is a LEFT JOIN, every employee in the master table will have an entry, even if none of the filtered project_employee entries matched.)
In the case that there is no match, the pe.hours column will be NULL, causing the COALESCE to revert to its second value of zero.
Order results by emp_name.