Suppose I have following table RIGHTS with data:
ID NAME OWNER_ID ACL_ID ACL_NAME
--------------------------------------------------
100 Entity_1 1 1 g1
100 Entity_1 2 2 g2
100 Entity_1 3 3 g3
200 Entity_2 1 1 g1
200 Entity_2 2 2 g2
300 Entity_3 1 1 g1
300 Entity_3 2 2 g2
300 Entity_3 4 NULL NULL
400 Entity_4 1 1 g1
400 Entity_4 2 2 g2
400 Entity_4 3 3 g3
400 Entity_4 4 NULL NULL
500 Entity_5 4 NULL NULL
500 Entity_5 5 NULL NULL
500 Entity_5 6 NULL NULL
600 Entity_6 NULL NULL NULL
How to select all (ID, NAME) records for which there is no even single ACL_ID=NULL row except those rows with OWNER_ID=NULL. In this particular example I want to select 3 rows:
(100, Entity_1) - because all 3 rows with ACL_ID != NULL (1, 2, 3)
(200, Entity_2) - because all 2 rows with ACL_ID != NULL (1, 2)
(600, Entity_6) - because OWNER_ID=NULL
For now I use SQL Server, but I want it works on Oracle as well if it possible.
UPDATE
I apologize I had to mention that this table data is just a result of a query with joins, so it has to be taken into account:
SELECT DISTINCT
EMPLOYEE.ID
,EMPLOYEE.NAME
, OWNERS.OWNER_ID as OWNER_ID
, GROUPS.GROUP_ID as ACL_ID
, GROUPS.NAME as ACL_NAME
from EMPLOYEE
inner join ENTITIES on ENTITIES.ENTITY_ID = ID
left outer join OWNERS on (OWNERS.ENTITY_ID = ID and OWNERS.OWNER_ID != 123)
left outer join GROUPS on OWNERS.OWNER_ID = GROUPS.GROUP_ID
where
ENTITIES.STATUS != 'D'
Try this:
select s.id, s.name
from
(select id,name,max(coalesce(owner_id,-1)) owner_id, min(coalesce(acl_id,-1)) acl_id
from yourtable
group by id,name) as s
where s.owner_id = -1
or (s.owner_id > -1 and s.acl_id > -1)
We use COALESCE to default null values to -1 (assuming the columns are integers), and then get the minimum values of owner_id and acl_id per unique id-name combination. If the maximum value of owner_id is -1, then the owner column is null. Likewise, if minimum value of acl_id is -1, then at least one null valued row exists. Based on these 2 conditions, we filter the list to get the required id-name pairs. Note that in this case, I simply chose -1 as the default value because I assume you don't use negative numbers as IDs. If you do, you can choose a suitable, "impossible" value as the default for the COALESCE function.
This should work on SQL Server and Oracle.
Here's my solution on Oracle.
SELECT DISTINCT
EMPLOYEE.ID
,EMPLOYEE.NAME
, OWNERS.OWNER_ID as OWNER_ID
, GROUPS.GROUP_ID as ACL_ID
, GROUPS.NAME as ACL_NAME
from EMPLOYEE
inner join ENTITIES on ENTITIES.ENTITY_ID = ID
left outer join OWNERS on (OWNERS.ENTITY_ID = ID and OWNERS.OWNER_ID != 123)
left outer join GROUPS on OWNERS.OWNER_ID = GROUPS.GROUP_ID
where ENTITIES.STATUS != 'D'
and EMPLOYEE.ID not in (select id from EMPLOYEE
where GROUPS.GROUP_ID is null
and OWNERS.OWNER_ID is not null);
You simply need to append the inner subquery from my earlier answer and you will get your solution.
Using simple filters in where clause:
with tab(ID,NAME,OWNER_ID,ACL_ID,ACL_NAME) as (
select 100, 'Entity_1', 1,1, 'g1' from dual union all
select 100, 'Entity_1', 2,2, 'g2' from dual union all
select 100, 'Entity_1', 3,3, 'g3' from dual union all
select 200, 'Entity_2', 1,1, 'g1' from dual union all
select 200, 'Entity_2', 2,2, 'g2' from dual union all
select 300, 'Entity_3', 1,1, 'g1' from dual union all
select 300, 'Entity_3', 2,2, 'g2' from dual union all
select 300, 'Entity_3', 4,NULL, NULL from dual union all
select 400, 'Entity_4', 1,1, 'g1' from dual union all
select 400, 'Entity_4', 2,2, 'g2' from dual union all
select 400, 'Entity_4', 3,3, 'g3' from dual union all
select 400, 'Entity_4', 4,NULL,NULL from dual union all
select 500, 'Entity_5', 4,NULL,NULL from dual union all
select 500, 'Entity_5', 5,NULL,NULL from dual union all
select 500, 'Entity_5', 6,NULL,NULL from dual union all
select 600, 'Entity_6', NULL,NULL,NULL from dual)
--------------------------------
---End of data preparation here
--------------------------------
select a.id, a.name
from tab a
where ((a.ACL_ID is not null and a.ACL_NAME is not NULL) or a.OWNER_ID is null)
and not exists (select 'x'
from tab b
where b.id = a.id
and (b.ACL_ID is null or b.ACL_NAME is null)
and b.owner_id is not null)
group by a.id, a.name;
Output:
ID NAME
------------
200 Entity_2
100 Entity_1
600 Entity_6
But I still wonder, what would be you logic where there is data like :
ID NAME OWNER_ID ACL_ID ACL_NAME
--------------------------------------------------
600 Entity_1 null null null
600 Entity_1 2 null null
??????????
Related
I have rows that look like .
OrderNo OrderStatus SomeOtherColumn
A 1
A 1
A 3
B 1 X
B 1 Y
C 2
C 3
D 2
I want to return all orders that have only one possible value of orderstatus. For e.g Here order B has only order status 1 SO result should be
B 1 X
B 1 Y
Notes:
Rows can be duplicated with same order status. For e.g. B here.
I am interested in the order having a very peculiar status for e.g. 1 here and not having any other status. So if B had a status of 3 at any point of time it is disqualified.
You can use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t.orderno = t2.orderno and t.OrderStatus = t2.OrderStatus
);
If you just want the orders where this is true, you can use group by and having:
select orderno
from t
group by orderno
having min(OrderStatus) = max(OrderStatus);
If you only want a status of 1 then add max(OrderStatus) = 1 to the having clause.
Here is one way to do it. It does not handle the case where the status can be NULL; if that is possible, you will need to explain how you want it handled.
SQL> create table test_data ( orderno, status, othercol ) as (
2 select 'A', 1, null from dual union all
3 select 'A', 1, null from dual union all
4 select 'A', 3, null from dual union all
5 select 'B', 1, 'X' from dual union all
6 select 'B', 1, 'Y' from dual union all
7 select 'C', 2, null from dual union all
8 select 'C', 3, null from dual union all
9 select 'D', 2, null from dual
10 );
Table created.
SQL> variable input_status number
SQL> exec :input_status := 1
PL/SQL procedure successfully completed.
SQL> column orderno format a8
SQL> column othercol format a8
SQL> select orderno, status, othercol
2 from (
3 select t.*, count(distinct status) over (partition by orderno) as cnt
4 from test_data t
5 )
6 where status = :input_status
7 and cnt = 1
8 ;
ORDERNO STATUS OTHERCOL
-------- ---------- --------
B 1 X
B 1 Y
One way to handle NULL status (if that may happen), if in that case the orderno should be rejected (not included in the output), is to define the cnt differently:
count(case when status != :input_status or status is null then 1 end)
over (partition by orderno) as cnt
and in the outer query change the WHERE clause to a single condition,
where cnt = 0
Count distinct OrderStatus partitioned by OrderNo and show only rows where number equals one:
select OrderNo, OrderStatus, SomeOtherColumn
from ( select t.*, count(distinct orderstatus) over (partition by orderno) cnt
from t )
where cnt = 1
SQLFiddle demo
Just wanted to add something to Gordon's answer, using a stats function:
select orderno
from t
group by orderno
having variance(orderstatus) = 0;
ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
2 Purchase null
2 Return 5
2 Exchange 1
3 Purchase 34
3 Return 4
3 Exchange 2
4 Purchase 12
4 Exchange 2
Above is sample data. What I want to return is:
ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
3 Purchase 34
3 Return 4
3 Exchange 2
So if a field is null in total or the values of Purchase, Return and Exchange are not all present for that ID, ignore that ID completely. How can I go about doing this?
You can use exists. I think you intend:
select t.*
from t
where exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Purchase' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Exchange' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Return' and t2.total is not null
);
There are ways to "simplify" this:
select t.*
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.total is not null
);
I would write this as a join, without subqueries:
SELECT pur.id, pur.total AS Purchase, exc.total AS Exchange, ret.total AS Return
FROM MyTable as pur
INNER JOIN MyTable AS exc ON exc.id=pur.id AND exc.type='Exchange'
INNER JOIN MyTable AS ret ON ret.id=pur.id AND ret.type='Return'
WHERE pur.type='Purchase'
The inner join means that if any of the three rows with different values are not found for a given id, then no row is included in the result.
Analytic functions are a good way to solve this kind of problems. The base table is read just once, and no joins (explicit or implicit, as in EXISTS conditions or correlated subqueries) are needed.
In the solution below, we count distinct values of 'Purchase', 'Exchange' and 'Return' for each id while ignoring other values (assuming that is indeed the requirement), and separately count total nulls in the total column for each id. Then it becomes a trivial matter to select just the "desired" rows in an outer query.
with
test_data ( id, type, total ) as (
select 1, 'Purchase', 12 from dual union all
select 1, 'Return' , 2 from dual union all
select 1, 'Exchange', 5 from dual union all
select 2, 'Purchase', null from dual union all
select 2, 'Return' , 5 from dual union all
select 2, 'Exchange', 1 from dual union all
select 3, 'Purchase', 34 from dual union all
select 3, 'Return' , 4 from dual union all
select 3, 'Exchange', 2 from dual union all
select 4, 'Purchase', 12 from dual union all
select 4, 'Exchange', 2 from dual
)
-- end of test data; actual solution (SQL query) begins below this line
select id, type, total
from ( select id, type, total,
count( distinct case when type in ('Purchase', 'Return', 'Exchange')
then type end
) over (partition by id) as ct_type,
count( case when total is null then 1 end
) over (partition by id) as ct_total
from test_data
)
where ct_type = 3 and ct_total = 0
;
Output:
ID TYPE TOTAL
-- -------- -----
1 Exchange 5
1 Purchase 12
1 Return 2
3 Exchange 2
3 Purchase 34
3 Return 4
This also should work fine even if new values are added to type column
select * from t where
ID not in(select ID from t where
t.total is null or t.[Type] is null)
I have a table A below which has the columns "a_id" and "b_id". I need to get all the a_id's which has both (100,200) as b_id values
A
a_id b_id
---- ------
1 100
1 200
2 100
3 100
4 100
4 200
5 100
5 300
I need to get output like below
a_id b_id
---- ------
1 100
1 200
4 100
4 200
You will have to JOIN with the same table. Something like below
select a.a_id, a.b_id
from A a
join A b on a.a_id = b.a_id
where a.b_id = 100
and b.b_id = 200;
Proof of concept. This solution returns all the rows for a given a_id, as long as there is at least one row with b_id=100 and another one with b_id=200 for this a_id. If the same a_id also has other rows with different values for b_id (if that is possible in the inputs), those rows will be returned also. The same if there are multiple rows with b_id=100 - all duplicates will be returned.
Depending on the actual requirement (which is still unclear at this moment - see my Comment to the OP), an even simpler solution with GROUP BY and HAVING is possible - if only the a_id values are needed, not the original rows.
with
table_a ( a_id, b_id ) as (
select 1, 100 from dual union all
select 1, 200 from dual union all
select 2, 100 from dual union all
select 3, 100 from dual union all
select 4, 100 from dual union all
select 4, 200 from dual union all
select 5, 100 from dual union all
select 5, 300 from dual
)
-- end of test data; SQL query begins below this line
select a_id, b_id
from (
select a_id, b_id,
count(case b_id when 100 then 1 end) over (partition by a_id) as ct_1,
count(case b_id when 200 then 1 end) over (partition by a_id) as ct_2
from table_a
)
where ct_1 > 0 and ct_2 > 0
;
A_ID B_ID
---- ----
1 100
1 200
4 100
4 200
I have table With ID,Sub_ID and value coloumns
ID SUB_ID Value
100 1 100
100 2 150
101 1 100
101 2 150
101 3 200
102 1 100
SUB ID can vary from 1..maxvalue( In this example it is 3). I need Sum of values for each Sub_ID. If SUB_ID is less than MAXVALUE for a particlaur ID then it should take MAX(SUB_ID) of each ID As shown below ( In this example for ID=100 for SUB_ID 3 it should take 150 i.e 2<3 so value=150))
SUB_ID SUM(values) Remarks
1 300 (100+100+100)
2 400 (150+150+100)
3 450 (150+200+100)
This can be easily done in PL/SQL . Can we use SQL for the same using Model Clause or any other options
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableA ( ID, SUB_ID, Value ) AS
SELECT 100, 1, 100 FROM DUAL
UNION ALL SELECT 100, 2, 150 FROM DUAL
UNION ALL SELECT 101, 1, 100 FROM DUAL
UNION ALL SELECT 101, 2, 150 FROM DUAL
UNION ALL SELECT 101, 3, 200 FROM DUAL
UNION ALL SELECT 102, 1, 100 FROM DUAL
Query 1:
WITH sub_ids AS (
SELECT LEVEL AS sub_id
FROM DUAL
CONNECT BY LEVEL <= ( SELECT MAX( SUB_ID ) FROM TableA )
),
max_values AS (
SELECT ID,
MAX( VALUE ) AS max_value
FROM TableA
GROUP BY ID
)
SELECT s.SUB_ID,
SUM( COALESCE( a.VALUE, m.max_value ) ) AS total_value
FROM sub_ids s
CROSS JOIN
max_values m
LEFT OUTER JOIN
TableA a
ON ( s.SUB_ID = a.SUB_ID AND m.ID = a.ID )
GROUP BY
s.SUB_ID
Results:
| SUB_ID | TOTAL_VALUE |
|--------|-------------|
| 1 | 300 |
| 2 | 400 |
| 3 | 450 |
Try this
SELECT SUB_ID,SUM(values),
(SELECT DISTINCT SUBSTRING(
(
SELECT '+'+ CAST(values AS VARCHAR)
FROM table_Name AS T2
WHERE T2.SUB_ID = d.SUB_ID
FOR XML PATH ('')
),2,100000)[values]) as values
FROm table_Name d
GROUP BY SUB_ID
How about something like this:
select max_vals.sub_id, sum(nvl(table_vals.value,max_vals.max_value)) as sum_values
from (
select all_subs.sub_id, t1.id, max(t1.value) as max_value
from your_table t1
cross join (select sub_id from your_table) all_subs
group by all_subs.sub_id, t1.id
) max_vals
left outer join your_table table_vals
on max_vals.id = table_vals.id
and max_vals.sub_id = table_vals.sub_id
group by max_vals.sub_id;
The inner query gets you a list of all sub_id/id combinations and their fall-back values. The out query uses an nvl to use the table value if it exists and the fall-back value if it doesn't.
So I am trying to pull rows from a table where there are more than one version for an ID that has at least one person for the ID that is not null but the versions that come after it are null.
So, if i had a statement like:
select ID, version, person from table1
the output would be:
ID Version Person
-- ------- ------
1 1 Tom
1 2 null
1 3 null
2 1 null
2 2 null
2 3 null
3 1 Mary
3 2 Mary
4 1 Joseph
4 2 null
4 3 Samantha
The version number can have an infinite value and is not limited.
I want to pull ID 1 version 2/3, and ID 4 Version 2.
So in the case of ID 2 where the person is null for all three rows I don't need these rows. And in the case of ID 3 version 1 and 2 I don't need these rows because there is never a null value.
This is a very simple version of the table I am working with but the "real" table is a lot more complicated with a bunch of joins already in it.
The desired output would be:
ID Version Person
-- ------- ------
1 2 null
1 3 null
4 2 null
The result set that I am looking for is where in a previous version for the same ID there was a person listed but is now null.
You are seeking all rows where the person is not null and that id has null rows, and the not null person version is less than the null version for the same person id:
Edited predicate based on comment
with sample_data as
(select 1 id, 1 version, 'Tom' person from dual union all
select 1, 2, null from dual union all
select 1, 3, null from dual union all
select 2, 1, null from dual union all
select 2, 2, null from dual union all
select 2, 3, null from dual union all
select 3, 1, 'Mary' from dual union all
select 3, 2, 'Mary' from dual union all
select 4, 1, 'Joseph' from dual union all
select 4, 2, null from dual union all
select 4, 3, 'Samantha' from dual)
select *
from sample_data sd
where person is null
and exists
(select 1 from sample_data
where id = sd.id
and person is not null
and version < sd.version);
/* Old predicate
and id in
(select id from sample_data where person is not null);
*/
I think this query translates pretty nicely into what you asked for?
List all the rows (R) where the person is null, but only if a previous row (P) with a non-null name exists.
select *
from table1 r
where r.person is null
and exists(
select 'x'
from table1 p
where p.id = r.id
and p.version < r.version
and p.person is not null
);
I believe the below should work.
select ID, listagg(version, ', ') within group (order by version) as versions
from table1 t1
where 0 < (select count(*) from table1 t1A where t1A.ID = t1.ID and t1A.version is not null)
and 0 < (select count(*) from table1 t1B where t1B.ID = t1.ID and t1B.version is null)
and person is null
group by ID
This should do what you want:
select id, version, person
from
(
select id, version, person,
lag(person, 1) ignore nulls
over (partition by id
order by version) as x
from table1
) dt
where person is null
and x is not null