Need help on query to select data from union of two tables in Orcacle - sql

Need help in below query:
My requirement is, if role_id is existing for a user in si_org_roles ( first union), then no need to pick up data from audit tables
( its used for list of user deleted from a role) and note that a user can have multiple org - role, ie. org-role to role is many - one relationship.
SELECT m.user_id, m.org_role_id, m.last_update_date,ro.role_id
FROM si_user_org_roles m,si_org_roles ro where m.org_role_id = ro.org_role_id
and ro.role_id = 100074
union
SELECT au.user_id_o,
au.org_role_id_o,
au.last_update_date_o,
ru.role_id
FROM si_user_org_roles_au au,si_org_roles ru
WHERE au.transaction_type = 'Delete'
and au.org_role_id_o = ru.org_role_id

How about this:
SELECT m.user_id,
m.org_role_id,
m.last_update_date,
ro.role_id
FROM si_user_org_roles m,
si_org_roles ro
where m.org_role_id = ro.org_role_id
and ro.role_id = 100074
union
SELECT au.user_id_o,
au.org_role_id_o,
au.last_update_date_o,
ru.role_id
FROM si_user_org_roles_au au,si_org_roles ru
WHERE au.transaction_type = 'Delete'
and au.org_role_id_o = ru.org_role_id
and not exists(select 'X'
from si_user_org_roles m,
si_org_roles ro
where m.org_role_id = ro.org_role_id
and ro.org_role_id = ru.org_role_id
and ro.role_id = 100074)
Totally untested, since you didn't provide table definitions or sample data.
The idea here is that the first half (before union) does the select based on role_id, if there is no data available, the first query will return no rows. Then the second half (after union) goes after the audit data, but, I added the exists clause. The exists clause essentially checks if the first half returned data, and if it did, prevents the second half from returning data.
Hope that makes sense.

You can use NOT EXISTS clause to skip the selection from the audit table.
NOT EXISTS (SELECT role_id
FROM si_org_roles
WHERE role_id = 100074); -- to check the role_id is avlailable in si_org_roles table.
Try like this,
SELECT m.user_id, m.org_role_id, m.last_update_date,ro.role_id
FROM si_user_org_roles m,
si_org_roles ro
WHERE m.org_role_id = ro.org_role_id
AND ro.role_id = 100074
UNION
SELECT au.user_id_o, au.org_role_id_o, au.last_update_date_o, ru.role_id
FROM si_user_org_roles_au au,
si_org_roles ru
WHERE au.transaction_type = 'Delete'
AND au.org_role_id_o = ru.org_role_id
AND NOT EXISTS (SELECT role_id
FROM si_org_roles
WHERE role_id = 100074);

Related

Find a Subset of Records or Find All

I'm working on a similar problem to check if a column contains ALL the values of another column - Mysql
This CTE is part of a bigger query. CTE_ProjekteRollen contains a subset of ProjektParamRolle and can contain zero or more records. I want a list of ProjektParam where the items in CTE_ProjekteRollen are all present (when joined with ProjektParamRolle). My solution works in all cases where CTE_ProjektRollen is not empty.
CTE_FilteredByRolle as (
select pp.ID_ProjektParam
from Basis.ProjektParam pp
join Basis.ProjektParamRolle ppr
on pp.ID_ProjektParam = ppr.ID_ProjektParam
join CTE_ProjektRollen pr
on ppr.Rolle = pr.Rolle
group by pp.ID_ProjektParam
having Count(pp.ID_ProjektParam) = (
select Count(Rolle)
from CTE_ProjektRollen))
What do I have to change to get all ProjektParam (joined with ProjektParamRolle), if CTE_ProjektRollen is empty?
Edit: I think I phrased my question wrong, because I didn't understand it fully. #Kendle's solution works for what I described, but I actually needed all ID_ProjektParam (not joined with ProjektParamRolle).
The actual CTE that worked for me was
CTE_FilteredByRolle as (
select pp.ID_ProjektParam
from Basis.ProjektParam pp
where (
select Count(ppr.Rolle)
from Basis.ProjektParamRolle ppr
join CTE_ProjektRollen pr
on ppr.Rolle = pr.Rolle
where ppr.ID_ProjektParam = pp.ID_ProjektParam) = (
select Count(Rolle)
from CTE_ProjektRollen))
We can use a CASE to check whether the table is empty. If it is empty we return the number to which we are comparing, so it will always be true.
CTE_FilteredByRolle as (
select pp.ID_ProjektParam
from Basis.ProjektParam pp
join Basis.ProjektParamRolle ppr
on pp.ID_ProjektParam = ppr.ID_ProjektParam
join CTE_ProjektRollen pr
on ppr.Rolle = pr.Rolle
group by pp.ID_ProjektParam
having Count(distinct pp.ID_ProjektParam)
= case when(select Count(distinct Rolle) from CTE_ProjektRollen)) = 0
then Count(distinct pp.ID_ProjektParam)
else (select Count(distinct Rolle) from CTE_ProjektRollen))
end;

How to Get a Count of Records Using Partitioning in Oracle

I have the following query:
SELECT
F.IID,
F.E_NUM AS M_E_NUM,
MCI.E_NUM AS MCI_E_NUM,
F.C_NUM AS M_C_NUM,
MCI.C_NUM AS MCI_C_NUM,
F.ET_ID AS M_ET_ID,
EDIE.ET_ID AS ED_INDV_ET_ID,
COUNT(*) OVER (PARTITION BY F.IID) IID_COUNT
FROM FT_T F JOIN CEMEI_T MCI ON F.IID = MCI.IID
JOIN EDE_T EDE ON MCI.E_NUM = EDE.E_NUM
JOIN EDIE_T EDIE ON EDIE.IID = F.IID AND EDIE.ET_ID = EDE.ET_ID
WHERE
F.DEL_F = 'N'
AND MCI.EFF_END_DT IS NULL
AND MCI.TOS = 'BVVB'
AND EDE.PTEND_DT IS NULL
AND EDE.DEL_S = 'N'
AND EDE.CUR_IND = 'A'
AND EDIE.TAR_N = 'Y'
AND F.IID IN
(
SELECT DISTINCT IID
FROM FT_T
WHERE GROUP_ID = 'BG'
AND DEL_F = 'N'
AND (IID, E_NUM) NOT IN
(
SELECT IID, E_NUM FROM CEMEI_T
WHERE TOS = 'BVVB' AND EFF_END_DT IS NULL
)
);
I am basically grabbing information from several tables and creating a flat record of them.
Everything works accordingly except now I need to find out whether there are two records in FT_T table with identical IID's and display that count as part of the result set.
I tried to use partitioning but all the rows in the result set return a single count even though there are ones that have 2 records with identical IID's in FT_T.
The reason I initially said that I'm gathering information from several tables is due to the fact that FT_T might not have all the information I need if two records are not available for the same IID, so I have to retrieve them from other tables JOINed in the query. However, I need to know which FT_T.IID's have two records in FT_T (or greater than one).
Perhaps you need to calculate the count before the join and filtering:
SELECT . . .
FROM (SELECT F.*,
COUNT(*) OVER (PARTITION BY F.IID) as IID_CNT
FROM FT_T F
) JOIN
CEMEI_T MCI
ON F.IID = MCI.IID JOIN
EDE_T EDE
ON MCI.E_NUM = EDE.E_NUM JOIN
EDIE_T EDIE
ON EDIE.IID = F.IID AND EDIE.ET_ID = EDE.ET_ID
. . .
this is merely a comment/observation, but formatting is needed
You use of in(...) with select distinct and not in(...,...) seems complex and could be a problem if some values are NULL. I suggest you consider using EXISTS and NOT EXISTS instead. e.g.
AND EXISTS (
SELECT
NULL
FROM FT_T
WHERE F.IID = FT_T.IID
AND FT_T.GROUP_ID = 'BG'
AND FT_T.DEL_F = 'N'
AND NOT EXISTS (
SELECT
NULL
FROM CEMEI_T
WHERE FT_T.IID = CEMEI_T.IID
AND FT_T.E_NUM = CEMEI_T.E_NUM
AND CEMEI_T.TOS = 'BVVB'
AND CEMEI_T.EFF_END_DT IS NULL
)
)

Only return value that matches the ID on table 1

I have tried all possible joins and sub-queries but I cant get the data to only return one value from table 2 that exactly matches the vendor ID. If I dont have the address included in the query, I get one hit for the vendor ID. How can I make it so that when I add the address, I only want the one vendor that I get prior to adding the address.
The vendor from table one must be VEN-CLASS IS NOT NULL.
This was my last attempt using subquery:
SELECT DISTINCT APVENMAST.VENDOR_GROUP,
APVENMAST.VENDOR,
APVENMAST.VENDOR_VNAME,
APVENMAST.VENDOR_CONTCT,
APVENMAST.TAX_ID,
Subquery.ADDR1
FROM (TEST.dbo.APVENMAST APVENMAST
INNER JOIN
(SELECT APVENADDR.ADDR1,
APVENADDR.VENDOR_GROUP,
APVENADDR.VENDOR,
APVENMAST.VEN_CLASS
FROM TEST.dbo.APVENADDR APVENADDR
INNER JOIN TEST.dbo.APVENMAST APVENMAST
ON (APVENADDR.VENDOR_GROUP = APVENMAST.VENDOR_GROUP)
AND (APVENADDR.VENDOR = APVENMAST.VENDOR)
WHERE (APVENMAST.VEN_CLASS IS NOT NULL)) Subquery
ON (APVENMAST.VENDOR_GROUP = Subquery.VENDOR_GROUP)
AND (APVENMAST.VENDOR = Subquery.VENDOR))
INNER JOIN TEST.dbo.APVENLOC APVENLOC
ON (APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP)
AND (APVENMAST.VENDOR = APVENLOC.VENDOR)
WHERE (APVENMAST.VEN_CLASS IS NOT NULL)
Try this:
SELECT APVENMAST.VENDOR_GROUP
, APVENMAST.VENDOR
, APVENMAST.VENDOR_VNAME
, APVENMAST.VENDOR_CONTCT
, APVENMAST.TAX_ID
, APVENADDR.ADDR1
FROM TEST.dbo.APVENMAST APVENMAST
INNER JOIN (
select VENDOR_GROUP, VENDOR, ADDR1
, row_number() over (partition by VENDOR_GROUP, VENDOR order by ADDR1) r
from TEST.dbo.APVENADDR
) APVENADDR
ON APVENADDR.VENDOR_GROUP = APVENMAST.VENDOR_GROUP
AND APVENADDR.VENDOR = APVENMAST.VENDOR
AND APVENADDR.r = 1
--do you need this table; you're not using it...
--INNER JOIN TEST.dbo.APVENLOC APVENLOC
--ON APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP
--AND APVENMAST.VENDOR = APVENLOC.VENDOR
WHERE APVENMAST.VEN_CLASS IS NOT NULL
--if the above inner join was to filter results, you can do this instead:
and exists (
select top 1 1
from TEST.dbo.APVENLOC APVENLOC
ON APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP
AND APVENMAST.VENDOR = APVENLOC.VENDOR
)
I found another column in the APVENLOC table that I can filter on to get the unique vendor. Turns out if the vendor address is for the main office, the vendor location is set blank.
Easier than I thought it would be!
SELECT DISTINCT APVENMAST.VENDOR_GROUP,
APVENMAST.VENDOR,
APVENMAST.VENDOR_VNAME,
APVENADDR.ADDR1,
APVENMAST.VENDOR_SNAME,
APVENADDR.LOCATION_CODE,
APVENMAST.VEN_CLASS
FROM TEST.dbo.APVENMAST APVENMAST
INNER JOIN TEST.dbo.APVENADDR APVENADDR
ON (APVENMAST.VENDOR_GROUP = APVENADDR.VENDOR_GROUP)
AND (APVENMAST.VENDOR = APVENADDR.VENDOR)
WHERE (APVENADDR.LOCATION_CODE = ' ')
Shaji

Select statement to show the corresponding user with the lowest/highest amount?

I want to write a select statement output that, among other things, has both a lowest_bid and highest_bid column. I know how to do that bit, but want I also want is to show the user (user_firstname and user_lastname combined into their own column) as lowest_bidder and highest_bidder. What I have so far is:
select item_name, item_reserve, count(bid_id) as number_of_bids,
min(bid_amount) as lowest_bid, ???, max(big_amount) as highest_bid,
???
from vb_items
join vb_bids on item_id=bid_item_id
join vb_users on item_seller_user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
sort by item_reserve
(The ???'s are where the columns should go, once I figure out what to put there!)
This seems like good use of window functions. I've assumed a column vb_bids.bid_user_id. If there's no link between a bid and a user, you can't answer this question
With x as (
Select
b.bid_item_id,
count(*) over (partition by b.bid_item_id) as number_of_bids,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount desc
) as high_row,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount
) as low_row,
b.bid_amount,
u.user_firstname + ' ' + u.user_lastname username
From
vb_bids b
inner join
vb_users u
on b.bid_user_id = u.user_id
Where
b.bid_status = 'ok'
)
Select
i.item_name,
i.item_reserve,
min(x.number_of_bids) number_of_bids,
min(case when x.low_row = 1 then x.bid_amount end) lowest_bid,
min(case when x.low_row = 1 then x.username end) low_bidder,
min(case when x.high_row = 1 then x.bid_amount end) highest_bid,
min(case when x.high_row = 1 then x.username end) high_bidder
From
vb_items i
inner join
x
on i.item_id = x.bid_item_id
Where
i.item_sold = 'no'
Group By
i.item_name,
i.item_reserve
Order By
i.item_reserve
Example Fiddle
In order to get the users, I broke out the aggregates into their own tables, joined them by the item_id and filtered them by a derived value that is either the min or max of bid_amount. I could have joined to vb_bids for a third time, and kept the aggregate functions, but that would've been redundant.
This will fail if you have two low bids of the exact same amount for the same item, since the join is on bid_amount. If you use this, then you'd want to created an index on vb_bids covering bid_amount.
select item_name, item_reserve, count(bid_id) as number_of_bids,
low_bid.bid_amount as lowest_bid, low_user.first_name + ' ' + low_user.last_name,
high_bid.bid_amount as highest_bid, high_user.first_name + ' ' + high_user.last_name
from vb_items
join vb_bids AS low_bid on item_id = low_bid.bid_item_id
AND low_bid.bid_amount = (
SELECT MIN(bid_amount)
FROM vb_bids
WHERE bid_item_id = low_bid.bid_item_id)
join vb_bids AS high_bid on item_id = high_bid.bid_item_id
AND high_bid.bid_amount = (
SELECT MAX(bid_amount)
FROM vb_bids
WHERE bid_item_id = high_bid.bid_item_id)
join vb_users AS low_user on low_bid.user_id=user_id
join vb_users AS high_user on high_bid.user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
group by item_name, item_reserve,
low_bid.bid_amount, low_user.first_name, low_user.last_name,
high_bid.bid_amount, high_user.first_name, high_user.last_name
order by item_reserve
I am a big fan of using Common Table Expressions (CTEs) for situations like this, because of the following advantages:
Separating different parts of the logic, adding to readability, and
Reducing complexity (for example, the need to GROUP BY a large number of fields, or to repeat the same join multiple times.)
So, my suggested approach would be something like this:
-- semi-colon must precede CTE
;
-- collect bid info
WITH item_bids AS (
SELECT
i.item_id, i.item_name, i.item_reserve, b.bid_id, b.bid_amount,
(u.first_name + ' ' + u.last_name) AS bid_user_name
FROM vb_items i
JOIN vb_bids b ON i.item_id = b.bid_item_id
JOIN vb_users u ON b.user_id = u.user_id
WHERE b.bid_status = 'ok'
AND i.item_sold = 'no'
),
-- group bid info
item_bid_info AS (
SELECT item_id, item_name, item_reserve
COUNT(bid_id) AS number_of_bids, MIN(bid_amount) AS lowest_bid, MAX(bid_amount) AS highest_bid
FROM item_bids
GROUP BY item_id, item_name, item_reserve
)
-- assemble final result
SELECT
bi.item_name, bi.item_reserve, bi.number_of_bids,
bi.low_bid, low_bid.bid_user_name AS low_bid_user,
bi.high_bid, high_bid.bid_user_name AS high_bid_user
FROM item_bid_info bi
JOIN item_bids AS low_bid ON bi.lowest_bid = low_bid.bid_amount AND bi.item_id = low_bid.bid_item_id
JOIN item_bids AS high_bid ON bi.lowest_bid = high_bid.bid_amount AND bi.item_id = high_bid.bid_item_id
ORDER BY bi.item_reserve;
Note that the entire SQL statement (from the starting WITH all the way down to the final semi-colon after the ORDER BY) is a single statement, and is evaluated by the optimizer as such. (Some people think each part is evaluated separately, like temp tables, and then all the rows are joined together at the end in a final step. That's not how it works. CTEs are just as efficient as sub-queries.)
Also note that this approach does a JOIN on the bid amount, so if there are identical bids for a single item, it will fail. (Seems like that should be an invalid state anyway, though, right?) Also you may have efficiency concerns depending on:
The size of your table
Whether the lookup can use an index
You could address both issues by including a unique constraint (which has the added advantage of indexing the foreign key bid_item_id as well; always a good practice):
ALTER TABLE [dbo].[vb_bids] ADD CONSTRAINT [UK_vbBids_item_amount]
UNIQUE NONCLUSTERED (bid_item_id, bid_amount)
GO
Hope that helps!

Using Count() and Sum() correctly in SQL?

Ok, so I hope I can explain this question well enough, because I feel like this is going to be a tough one.
I have two tables I'm working with today. These look like:
#pset table (PersonID int, SystemID int, EntitlementID int, TargetID int)
#Connector table (TargetName varchar(10), fConnector bit)
The first table stores records that tell me, oh this person has this system, which is composed of these entitlements, whom have these targets. A little complicated, but stay with me. The second stores the TargetName and then whether or not that target has a connector in my not-so-theoretical system.
What I'm trying to do is merge these two tables so that I can see the target flag for each row in #pset. This will help me later as you'll see.
If each entitlement in a system has a connector to the target (the flag is true for all of them), then I'd like to know.
All the others should go into a different table.
This is what I tried to do, but it didn't work. I need to know where I went wrong. Hopefully someone with more experience than me will be able to answer.
-- If the count(123) = 10 (ten rows with SystemID = 123) and the sum = 10, cool.
select pset.*, conn.fConnector from #pset pset
inner join vuTargets vt
on vt.TargetID = pset.TargetID
inner join #conn conn
on conn.TargetName = vt.TargetName
group by ProfileID, SystemRoleID, EntitlementID, TargetID, fConnector
having count(SystemID) = sum(cast(fConnector as int))
order by ProfileID
and
-- If the count(123) = 10 (ten rows with SystemID = 123) and the sum <> 10
select pset.*, conn.fConnector from #pset pset
inner join vuTargets vt
on vt.TargetID = pset.TargetID
inner join #conn conn
on conn.TargetName = vt.TargetName
group by ProfileID, SystemRoleID, EntitlementID, TargetID, fConnector
having count(SystemID) <> sum(cast(fConnector as int))
order by ProfileID
Unfortunately, these do not work :(
Edit
Here is a screenshot showing the problem. Notice ProfileID 1599 has a SystemID of 1126567, but one of the entitlements doesn't have a connector! How can I get both of these rows into the second query? (above)
Your basic problem is that you're trying to roll up to two different record sets.
The initial set (the SELECT and GROUP BY clauses) is saying that you want one record for every difference in the set [ProfileId, SystemId, EntitlementId, TargetId, fConnector].
The second set (the HAVING clause) is saying that you want, for every row in the inital set, to compare it's COUNT of records with the SUM of the connections. However, because you've asked for grouping down to the individual flag, this has the effect of getting a single row for each flag (assuming 1-to-1 relationships). Effectively, you're saying - 'Hey, if this target has a connection? Yeah, I want it'.
What you appear to want is a roll up to the SystemId value. To do that, you will need to change your SELECT and GROUP BY clauses to only include the set [ProfileId, SystemId]. This will return only those rows (keyed from profile and system) who has all targets 'connected'. You will not be able to see the individual entitlements, targets, and whether they are connected (you will be able to infer that they will all be/not be connected, however).
EDIT:
In the interests of full disclosure, here is how you'd get something similar to your original results set, where it lists all EntitlementIds and TargetIds:
WITH all_connections as (SELECT pset.ProfileId, pset.SystemRoleId
FROM #pset pset
INNER JOIN vuTargets vt
ON vt.TargetId = pset.TargetId
INNER JOIN #conn conn
ON conn.TargetName = vt.TargetName
GROUP BY pset.ProfileId, pset.SystemRoleId
HAVING COUNT(pset.SystemRoleId)
= SUM(CAST(fConnector as INT)))
SELECT pset.*
FROM #pset pset
JOIN all_connections conn
ON conn.ProfileId = pset.ProfileId
AND conn.SystemRoleId = pset.SystemRoleId
This should get you a listing, down to the TargetId, of ProfileId/SystemRoleId keys where all EntitlementIds and TargetIds have a connection (or, flip the CTE = to <> for those where not all do).
Edit: fixed my original queries, updated the description as well
You can split this up: first find the TargetIDs that have an fConnector of 0. Then find the PersonID, SystemID pairs that have any target equal to the ones you found. Then select the relevant data: (this finds the PersonID, SystemID pair where at least one entitlement does not have a connector to the target)
with abc as (
select PersonID, SystemID
from pset P
where TargetID in (
select TargetID
from vuTargets V join connector C on V.TargetName = C.TargetName
where C.fConnector = 0
)
)
select P.PersonID, P.SystemID, P.EntitlementID, P.TargetID, C.fConnector
from pset P
join abc on ((P.PersonID = abc.PersonID) and (P.SystemID = abc.SystemID))
join vuTargets V on P.TargetID = V.TargetID
join connector C on V.TargetName = C.TargetName
The query to find the PersonID, SystemID pairs where all entitlements have a connector to the target is similar:
with abc as (
select PersonID, SystemID
from pset P
where TargetID in (
select TargetID
from vuTargets V join connector C on V.TargetName = C.TargetName
where C.fConnector = 0
)
)
select P.PersonID, P.SystemID, P.EntitlementID, P.TargetID, C.fConnector
from
pset P
join abc on ((P.PersonID <> abc.PersonID) or (P.SystemID <> abc.SystemID))
join vuTargets V on P.TargetID = V.TargetID
join connector C on V.TargetName = C.TargetName
The difference is in the join with the temp table (<> vs =). This is very similar to zero's answer, but doesn't use counts or sums.