How to find exact matching values in sql - sql

How can I find the exact match in case of duplicate values in SQL
I am using the below query to find customers which are having write access based on the below values.
user **A write** user - {UI.ACCESS, API.ACCESS} and
user **B read** user - {UI.ACCESS, API.ACCESS, UI.READONLY, API.READONLY}
Query -
select ul.USERNAME,sp.PERMISSION_N,cus.ID
from CUSTOMER cus
join USER_L ul on cus.ID = ul.CUSTOMER_ID
join USER_ROLE ur on ul.ID = ur.USER_ID
join SECURITY_ROLE sr on ur.SECURITY_ROLE_ID = sr.SECURITY_ROLE_ID
join SECURITY_PERMISSION sp on sr.SECURITY_PERMISSION_ID = sp.ID
where sp.PERMISSION_NAME in ('UI.ACCESS','API.ACCESS')
above query return the B user as well but I am expecting only A.

You want to check across all the rows in the SECURITY_PERMISSION table that none of them have the forbidden roles.
You have tagged both Oracle and MySQL. In Oracle, you can use:
SELECT ul.USERNAME,
sp.permission_names,
cus.ID
FROM CUSTOMER cus
INNER JOIN USER_L ul
ON (cus.ID = ul.CUSTOMER_ID)
INNER JOIN USER_ROLE ur
ON (ul.ID = ur.USER_ID)
INNER JOIN SECURITY_ROLE sr
ON (ur.SECURITY_ROLE_ID = sr.SECURITY_ROLE_ID)
INNER JOIN (
SELECT id,
LISTAGG(permission_name, ',')
WITHIN GROUP (ORDER BY permission_name)
AS permission_names
FROM SECURITY_PERMISSION
GROUP BY id
HAVING COUNT(
CASE
WHEN PERMISSION_NAME in ('UI.ACCESS','API.ACCESS')
THEN 1
END
) > 0
AND COUNT(
CASE
WHEN PERMISSION_NAME in ('UI.READONLY', 'API.READONLY')
THEN 1
END
) = 0
) sp
ON (sr.SECURITY_PERMISSION_ID = sp.ID)
Which, for the sample data:
CREATE TABLE customer (id) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE user_l (id, customer_id, username) AS
SELECT LEVEL, LEVEL, 'User' || LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE user_role (id, user_id, security_role_id) AS
SELECT LEVEL, LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE security_role (security_role_id, security_permission_id) AS
SELECT LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE security_permission (id, permission_name) AS
SELECT 1, 'UI.ACCESS' FROM DUAL UNION ALL
SELECT 1, 'API.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'UI.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'API.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'UI.READONLY' FROM DUAL UNION ALL
SELECT 2, 'API.READONLY' FROM DUAL;
Outputs:
USERNAME
PERMISSION_NAMES
ID
User1
API.ACCESS,UI.ACCESS
1
db<>fiddle here
For MySQL, you would need to replace LISTAGG with something to aggregate strings such as GROUP_CONCAT.

Related

Excluding a row that contains a specific value

I want to exclude people who have joined a specific group. For example, if some students signed up for an Orchestra club, and I want to retrieve a list of students who did NOT sign up for orchestra, how do I do so?
I am unable to simply do a Group By clause because some students may have joined multiple clubs, and would bypass the Where condition and still show up in the query,
as shown here.
I am thinking about using a CASE statement in the SELECT clause to flag the person as '1' if they have joined Orchestra, and '0' if they have not, but I'm struggling to write an aggregate CASE function, which would cause issues from the GROUP BY clause.
Any thoughts on how to flag people with a certain row value?
Apparently my table didn't get saved onto SQLFiddle so you can paste the code below on your own screen:
CREATE TABLE activity ( PersonId, Club) as
select 1, 'Soccer' from dual union
select 1, 'Orchestra' from dual union
select 2, 'Soccer' from dual union
select 2, 'Chess' from dual union
select 2, 'Bball' from dual union
select 3, 'Orchestra' from dual union
select 3, 'Chess' from dual union
select 3, 'Bball' from dual union
select 4, 'Soccer' from dual union
select 4, 'Bball' from dual union
select 4, 'Chess' from dual;
Use the HAVING clause instead of using WHERE, with case expression :
HAVING max(case when column = ‘string’ then 1 else 0 end) = 0
Add this after your group by .
How about selecting a list of user ids from the activity table and excluding it:
SELECT * FROM users WHERE id NOT IN
(SELECT PersonId FROM activity WHERE Club = 'Orchestra');
You could use a subquery to return a list of people to exclude.
-- Returns person 2 and 4.
SELECT
PersonId
FROM
activity
WHERE
PersonId NOT IN
(
-- People to exclude.
SELECT
PersonId
FROM
activity
WHERE
Club = 'Orchestra'
)
GROUP BY
PersonId
;
EDIT Removed superfluous distinct in subquery - thanks #mathguy.
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =1; --> get some students signed up for an Orchestra club
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =0; --> get students not signed up for an Orchestra club

Oracle 'where clause' to become shorter

Lets assume 'table1' has three columns:
'key',
'singleID',
'multipleIDs'
Rows would be like:
1,'8736', '1234;6754;9785;6749'
2,'7446', '9959;7758;6485;9264'
To search for all rows which have an id either in 'singleID' or as part of
the concatenated IDs in the 'multipleIDs' I would:
select key from table1 where
singleID = '8888' or multipleIDs like '%8888%';
When searching not only for one ID (8888) like in this statement but for 100 it would be necessary to repeate the where clause 100 times with different id like:
select key from table1 where
singleID = '8888' or multipleIDs like '%8888%' or
singleID = '9999' or multipleIDs like '%9999%' or
....;
The IDs to search for are taken dynamically from another query like
select id from table2;
The query shall
be created dynamically since the number of IDs might vary.
Like this the SQL statement would become quite long.
Is there a nice and short way to express that in Oracle SQL? PLSQL perhaps?
Something like this?
This is the test version:
with sv_qry
as
(
SELECT trim(regexp_substr(search_values, '[^,]+', 1, LEVEL)) val
FROM (select '1234,7446' as search_values
from dual
)
CONNECT BY LEVEL <= regexp_count(search_values, ',')+1
)
, table1_qry
as
(select 1 as id,'8736' as single_id, '1234;6754;9785;6749' as multiple_id from dual
union all
select 2,'7446' as single_id, '9959;7758;6485;9264' as multiple_id from dual
)
select *
from table1_qry
inner join
sv_qry
on single_id = val or multiple_id like '%'||val||'%'
And this would be with a table called table1:
with sv_qry
as
(
SELECT trim(regexp_substr(search_values, '[^,]+', 1, LEVEL)) val
FROM (select '1234,7446' as search_values
from dual
)
CONNECT BY LEVEL <= regexp_count(search_values, ',')+1
)
select *
from table1
inner join
sv_qry
on single_id = val or multiple_id like '%'||val||'%'
Partial credit goes here:
Splitting string into multiple rows in Oracle
You can express the query like this :
select key
from table1 a
join ( select id from table2 where id in ('yyyy','xxxx','zzzz',...) b
on a.singleId = b.id or a.multipleID like '%'||b.id||'%';

select statement with subqueries against two databases

I have the below code to show what I am "trying" to accomplish in a stored procedure:
select * from
(
select to_char(sum(aa.amount))
from additional_amount aa, status st
where aa.int_tran_id = st.int_tran_id
and st.stage in ('ACHPayment_Confirmed')
and aa.entry_timestamp > (
select to_date(trunc(last_day(add_months(sysdate,-1))+1), 'DD-MON-RR') AS "day 1"
from dual
)
)
UNION ALL
(
select distinct it.debit_acct as "debit_accounts"
from internal_transactions it
where it.debit_acct IN ( select texe_cnasupro
from service.kndtexe, service.kndtctc
where texe_cncclipu = tctc_cncclipu
and tctc_cntipcli = 'C'
)
)
union all
(select distinct it.credit_acct as "credit_account"
from internal_transactions it
where it.credit_acct IN (select texe_cnasupro
from service.kndtexe, service.kndtctc
where texe_cncclipu = tctc_cncclipu
and tctc_cntipcli = 'C'
)
)
;
Output:
TO_CHAR(SUM(AA.AMOUNT))
----------------------------------------
130250292.22
6710654504
0000050334
2535814905
0007049560
5 rows selected
The top row of the output is what I need in the SP as output based on the below two queries which I am guessing needs to be sub-queried against the top select statement.
The top select is to select the sum of the amount a table with a join against another table for filtering (output:130250292.22).
The second and third selects is actually to check that the accounts in the internal_transactions table are signed up for the corresponding two tables in the service db which is a different db on the same server(owned by the same application).
The tables in the "service" db do not have the same common primary keys as in the first select which is against the same database.
Thank you for your help!
I don't understand your question, but I do know you can simplify this bit:
to_date(trunc(last_day(add_months(sysdate,-1))+1), 'DD-MON-RR') AS "day 1"
to this
trunc (sysdate, 'mm')
and you don't need a SELECT from DUAL to do that either.
and aa.entry_timestamp > trunc (sysdate, 'mm')

SQL hierarchy count totals report

I'm creating a report with SQL server 2012 and Report Builder which must show the total number of Risks at a high, medium and low level for each Parent Element.
Each Element contains a number of Risks which are rated at a certain level. I need the total for the Parent Elements. The total will include the number of all the Child Elements and also the number the Element itself may have.
I am using CTEs in my query- the code I have attached isn't working (there are no errors - it's just displaying the incorrect results) and I'm not sure that my logic is correct??
Hopefully someone can help. Thanks in advance.
My table structure is:
ElementTable
ElementTableId(PK) ElementName ElementParentId
RiskTable
RiskId(PK) RiskName RiskRating ElementId(FK)
My query:
WITH cte_Hierarchy(ElementId, ElementName, Generation, ParentElementId)
AS (SELECT ElementId,
NAME,
0,
ParentElementId
FROM Extract.Element AS FirtGeneration
WHERE ParentElementId IS NULL
UNION ALL
SELECT NextGeneration.ElementId,
NextGeneration.NAME,
Parent.Generation + 1,
Parent.ElementId
FROM Extract.Element AS NextGeneration
INNER JOIN cte_Hierarchy AS Parent
ON NextGeneration.ParentElementId = Parent.ElementId),
CTE_HighRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS HighRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'High'
GROUP BY r.ElementId),
CTE_LowRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS LowRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'Low'
GROUP BY r.ElementId),
CTE_MedRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS MedRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'Medium'
GROUP BY r.ElementId)
SELECT rd.ElementId,
rd.ElementName,
rd.ParentElementId,
Generation,
HighRisk,
MedRisk,
LowRisk
FROM cte_Hierarchy rd
LEFT OUTER JOIN CTE_HighRisk h
ON rd.ElementId = h.ElementId
LEFT OUTER JOIN CTE_MedRisk m
ON rd.ElementId = m.ElementId
LEFT OUTER JOIN CTE_LowRisk l
ON rd.ElementId = l.ElementId
WHERE Generation = 1
Edit:
Sample Data
ElementTableId(PK) -- ElementName -- ElementParentId
1 ------------------- Main --------------0
2 --------------------Element1-----------1
3 --------------------Element2 ----------1
4 --------------------SubElement1 -------2
RiskId(PK) RiskName RiskRating ElementId(FK)
a -------- Financial -- High ----- 2
b -------- HR --------- High ----- 3
c -------- Marketing -- Low ------- 2
d -------- Safety -----Medium ----- 4
Sample Output:
Element Name High Medium Low
Main ---------- 2 ---- 1 -------1
Here is your sample tables
SELECT * INTO #TABLE1
FROM
(
SELECT 1 ElementTableId, 'Main' ElementName ,0 ElementParentId
UNION ALL
SELECT 2,'Element1',1
UNION ALL
SELECT 3, 'Element2',1
UNION ALL
SELECT 4, 'SubElement1',2
)TAB
SELECT * INTO #TABLE2
FROM
(
SELECT 'a' RiskId, 'Fincancial' RiskName,'High' RiskRating ,2 ElementId
UNION ALL
SELECT 'b','HR','High',3
UNION ALL
SELECT 'c', 'Marketing','Low',2
UNION ALL
SELECT 'd', 'Safety','Medium',4
)TAB
We are finding the children of a parent, its count of High,Medium and Low and use cross join to show parent with all the combinations of its children's High,Medium and Low
UPDATE
The below variable can be used to access the records dynamically.
DECLARE #ElementTableId INT;
--SET #ElementTableId = 1
And use the above variable inside the query
;WITH CTE1 AS
(
SELECT *,0 [LEVEL] FROM #TABLE1 WHERE ElementTableId = #ElementTableId
UNION ALL
SELECT E.*,e2.[LEVEL]+1 FROM #TABLE1 e
INNER JOIN CTE1 e2 on e.ElementParentId = e2.ElementTableId
AND E.ElementTableId<>#ElementTableId
)
,CTE2 AS
(
SELECT E1.*,E2.*,COUNT(RiskRating) OVER(PARTITION BY RiskRating) CNT
from CTE1 E1
LEFT JOIN #TABLE2 E2 ON E1.ElementTableId=E2.ElementId
)
,CTE3 AS
(
SELECT DISTINCT T1.ElementName,C2.RiskRating,C2.CNT
FROM #TABLE1 T1
CROSS JOIN CTE2 C2
WHERE T1.ElementTableId = #ElementTableId
)
SELECT *
FROM CTE3
PIVOT(MIN(CNT)
FOR RiskRating IN ([High], [Medium],[Low])) AS PVTTable
SQL FIDDLE
RESULT
UPDATE 2
I am updating as per your new requirement
Here is sample table in which I have added extra data to test
SELECT * INTO #ElementTable
FROM
(
SELECT 1 ElementTableId, 'Main' ElementName ,0 ElementParentId
UNION ALL
SELECT 2,'Element1',1
UNION ALL
SELECT 3, 'Element2',1
UNION ALL
SELECT 4, 'SubElement1',2
UNION ALL
SELECT 5, 'Main 2',0
UNION ALL
SELECT 6, 'Element21',5
UNION ALL
SELECT 7, 'SubElement21',6
UNION ALL
SELECT 8, 'SubElement22',7
UNION ALL
SELECT 9, 'SubElement23',7
)TAB
SELECT * INTO #RiskTable
FROM
(
SELECT 'a' RiskId, 'Fincancial' RiskName,'High' RiskRating ,2 ElementId
UNION ALL
SELECT 'b','HR','High',3
UNION ALL
SELECT 'c', 'Marketing','Low',2
UNION ALL
SELECT 'd', 'Safety','Medium',4
UNION ALL
SELECT 'e' , 'Fincancial' ,'High' ,5
UNION ALL
SELECT 'f','HR','High',6
UNION ALL
SELECT 'g','HR','High',6
UNION ALL
SELECT 'h', 'Marketing','Low',7
UNION ALL
SELECT 'i', 'Safety','Medium',8
UNION ALL
SELECT 'j', 'Safety','High',8
)TAB
I have written the logic in query
;WITH CTE1 AS
(
-- Here you will find the level of every elements in the table
SELECT *,0 [LEVEL]
FROM #ElementTable WHERE ElementParentId = 0
UNION ALL
SELECT ET.*,CTE1.[LEVEL]+1
FROM #ElementTable ET
INNER JOIN CTE1 on ET.ElementParentId = CTE1.ElementTableId
)
,CTE2 AS
(
-- Filters the level and find the major parant of each child
-- ie, 100->150->200, here the main parent of 200 is 100
SELECT *,CTE1.ElementTableId MajorParentID,CTE1.ElementName MajorParentName
FROM CTE1 WHERE [LEVEL]=1
UNION ALL
SELECT CTE1.*,CTE2.MajorParentID,CTE2.MajorParentName
FROM CTE1
INNER JOIN CTE2 on CTE1.ElementParentId = CTE2.ElementTableId
)
,CTE3 AS
(
-- Since each child have columns for main parent id and name,
-- you will get the count of each element corresponding to the level you have selected directly
SELECT DISTINCT CTE2.MajorParentName,RT.RiskRating ,
COUNT(RiskRating) OVER(PARTITION BY MajorParentID,RiskRating) CNT
FROM CTE2
JOIN #RiskTable RT ON CTE2.ElementTableId=RT.ElementId
)
SELECT MajorParentName, ISNULL([High],0)[High], ISNULL([Medium],0)[Medium],ISNULL([Low],0)[Low]
FROM CTE3
PIVOT(MIN(CNT)
FOR RiskRating IN ([High], [Medium],[Low])) AS PVTTable
SQL FIDDLE

Joining a list of values with table rows in SQL

Suppose I have a list of values, such as 1, 2, 3, 4, 5 and a table where some of those values exist in some column. Here is an example:
id name
1 Alice
3 Cindy
5 Elmore
6 Felix
I want to create a SELECT statement that will include all of the values from my list as well as the information from those rows that match the values, i.e., perform a LEFT OUTER JOIN between my list and the table, so the result would be like follows:
id name
1 Alice
2 (null)
3 Cindy
4 (null)
5 Elmore
How do I do that without creating a temp table or using multiple UNION operators?
If in Microsoft SQL Server 2008 or later, then you can use Table Value Constructor
Select v.valueId, m.name
From (values (1), (2), (3), (4), (5)) v(valueId)
left Join otherTable m
on m.id = v.valueId
Postgres also has this construction VALUES Lists:
SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter)
Also note the possible Common Table Expression syntax which can be handy to make joins:
WITH my_values(num, str) AS (
VALUES (1, 'one'), (2, 'two'), (3, 'three')
)
SELECT num, txt FROM my_values
With Oracle it's possible, though heavier From ASK TOM:
with id_list as (
select 10 id from dual union all
select 20 id from dual union all
select 25 id from dual union all
select 70 id from dual union all
select 90 id from dual
)
select * from id_list;
the following solution for oracle is adopted from this source. the basic idea is to exploit oracle's hierarchical queries. you have to specify a maximum length of the list (100 in the sample query below).
select d.lstid
, t.name
from (
select substr(
csv
, instr(csv,',',1,lev) + 1
, instr(csv,',',1,lev+1 )-instr(csv,',',1,lev)-1
) lstid
from (select ','||'1,2,3,4,5'||',' csv from dual)
, (select level lev from dual connect by level <= 100)
where lev <= length(csv)-length(replace(csv,','))-1
) d
left join test t on ( d.lstid = t.id )
;
check out this sql fiddle to see it work.
Bit late on this, but for Oracle you could do something like this to get a table of values:
SELECT rownum + 5 /*start*/ - 1 as myval
FROM dual
CONNECT BY LEVEL <= 100 /*end*/ - 5 /*start*/ + 1
... And then join that to your table:
SELECT *
FROM
(SELECT rownum + 1 /*start*/ - 1 myval
FROM dual
CONNECT BY LEVEL <= 5 /*end*/ - 1 /*start*/ + 1) mypseudotable
left outer join myothertable
on mypseudotable.myval = myothertable.correspondingval
Assuming myTable is the name of your table, following code should work.
;with x as
(
select top (select max(id) from [myTable]) number from [master]..spt_values
),
y as
(select row_number() over (order by x.number) as id
from x)
select y.id, t.name
from y left join myTable as t
on y.id = t.id;
Caution: This is SQL Server implementation.
fiddle
For getting sequential numbers as required for part of output (This method eliminates values to type for n numbers):
declare #site as int
set #site = 1
while #site<=200
begin
insert into ##table
values (#site)
set #site=#site+1
end
Final output[post above step]:
select * from ##table
select v.id,m.name from ##table as v
left outer join [source_table] m
on m.id=v.id
Suppose your table that has values 1,2,3,4,5 is named list_of_values, and suppose the table that contain some values but has the name column as some_values, you can do:
SELECT B.id,A.name
FROM [list_of_values] AS B
LEFT JOIN [some_values] AS A
ON B.ID = A.ID