Select rows based on multiple conditions - sql

I have a table from which I have to select some rows based on the following conditions.
If more than one row exists with same DocumentRef, then select all the rows if BlockNumber is empty for all rows
If more than one row exists with same DocumentRef, then select only 1 row (ordered by DocumentId asc) with BlockNumber IS NOT EMPTY
If only one row exists with DocumentRef, select it irrespective of anything
Table:
I was trying to group it by DocumentRef and filter with having but having can only have aggregate functions. I think I will have to provide multiple conditions in having separated by OR. Please give me some direction.

Use window functions:
select t.*
from (select t.*,
sum(case when blocknumber is not null then 1 else 0 end) over (partition by documentref) as num_bn_notnull,
rank() over (partition by documentref
order by (case when blocknumber is not null then documentid end) desc nulls last
) as rnk
from t
) t
where num_bn_notnull = 0 or
rnk = 1;
Or, you can use exists clauses:
select t.*
from t
where not exists (select 1
from t t2
where t2.documentref = t.documentref and
t2.blocknumber is not null
) or
t.documentid = (select max(t2.documentid)
from t t2
where t2.documentref = t.documentref and
t2.blocknumber is not null
);
This can take advantage of an index on (documentref, blocknumber, documentid).
Actually, by a quirk of the SQL language, I think this works as well:
select t.*
from t
where t.documentid >= any (select t2.documentid
from t t2
where t2.documentref = t.documentref and
t2.blocknumber is not null
order by t2.documentid
fetch first 1 row only
);
The subquery returns an empty set if all blocknumbers are NULL. By definition, any document id matches the condition on an empty set.

Join the table to a query that returns for each documentref the maximum documentid for all the blocknumbers that are not null or null if they are all null:
select t.*
from tablename t inner join (
select
documentref,
max(case when blocknumber is not null then documentid end) maxid
from tablename
group by documentref
) d on d.documentref = t.documentref
and t.documentid = coalesce(d.maxid, t.documentid)
See the demo.
Results:
> DOCUMENTID | DOCUMENTREF | WARDID | BLOCKNUMBER
> ---------: | ----------: | -----: | ----------:
> 203962537 | 100000126 | B | A
> 203962538 | 100000130 | B | A
> 203962542 | 100000151 | null | null
> 203962543 | 100000151 | null | null
> 203962544 | 100000180 | B | A
> 203962546 | 100000181 | B | A
> 203962551 | 100000185 | null | null
> 203962552 | 100000186 | B | A

Related

Return the result of a record pair with same identifier and one field with/without null value

I have these data records:
ImageId ParentId
image1 Null // wanna have
image1 1 // wanna have
image3 2
image2 1
How should my query be written that I get the records with same ImageId AND the other row of the Pair result having always ParentId value Null?
If you only want the ImageID where there is a row with ParentID null and a row with ParentId = 1 here are 2 queries that you can use.
select a.ImageId
from table_name a
join table_name b on a.ImageId = b.ImageId
where a.ParentID is null
and b.ParentID = 1;
| ImageId |
| :------ |
| image1 |
select ImageId
from table_name
where ParentID is null or ParentID = 1
group by ImageId
having count(distinct coalesce(ParentID,999)) = 2;
GO
| ImageId |
| :------ |
| image1 |
db<>fiddle here
We can try to use CTE to find ParentId IS NULL ImageId and then use that to judgment by EXISTS
;WITH CTE AS (
SELECT *
FROM T
WHERE ParentId IS NULL
)
SELECT ImageId,ParentId
FROM (
SELECT *,
COUNT(*) OVER(PARTITION BY ParentId) cnt
FROM T
) t1
WHERE cnt > 1 AND
EXISTS (SELECT 1 FROM CTE c WHERE c.ImageId = t1.ImageId)
UNION ALL
SELECT ImageId,ParentId
FROM CTE
or we can use count window function with your logic to make it simple.
SELECT ImageId,ParentId
FROM (
SELECT *,
COUNT(*) OVER(PARTITION BY ParentId) cnt
FROM T
) t1
WHERE (cnt > 1 AND
EXISTS (
SELECT 1 FROM T tt
WHERE tt.ImageId = t1.ImageId
AND tt.ParentId IS NULL
)
)
OR ParentId IS NULL
sqlfiddle
EDIT
I saw you edit your question, we can just use COUNT window function to get duplicate rows by PARTITION BY your identifier columns
SELECT ImageId,ParentId
FROM (
SELECT *,
COUNT(*) OVER(PARTITION BY ImageId) cnt
FROM T
) t1
WHERE cnt > 1
sqlfiddle
If what you want to do is to find the images having one or more null values and one of more not null values we can use the fact that COUNT() does not include null values.
'count(ParentID) > 0` checks that there is at least one not null value of ParentID
count(coalesce(ParentID,1)) <> count(ParentID) checks that there is at least one null value of ParentID, which will be counted in the first expression because it will have been changed to 1 by coalesce.
We use string_agg to show the parentID's present for the image.
select
ImageId,
String_agg(ParentID,',') "ParentID's"
from table_name
group by ImageID
having count(ParentID) > 0
and count(coalesce(ParentID,1)) <> count(ParentID)
GO
ImageId | ParentID's
:------ | :---------
image1 | 1,2
image3 | 2
db<>fiddle here

Append counter with another column of query if counter is greater than one

I am struggling to find simple solution for below query
select id,
(select count(1) from table2 where table2.Id = table1.Id and table2.IsActive = 1) as TotalCount,
groupid from table1
Now I want to add one more field FinalGroupId in this query.
FinalGropId = If Totalcount is greater than 1 and groupid is not null then append count with Groupid or else return groupid .
below is expected result.
----------------------------------------------------------
Id | TotalCount | GroupId |FinalGroupId
---------------------------------------------------------
1 | 1 | 11111 | 11111
2 | 2 | 22222 | 22222-2
3 | 1 | 33333 | 33333
4 | 3 | 44444 | 44444-3
5 | 3 | null | null
How to find FinalGroupId in optimized way?
Without access to some sample data its a bit of a gamble, but try this out.
SELECT
Id,
TotalCount,
GroupId,
CASE
WHEN GroupId is not null AND TotalCount > 1 THEN GroupId || '-' TotalCount
WHEN GroupId is not null AND TotalCount = 1 THEN GroupId
ELSE null END as FinalGroupId
FROM
(
SELECT
Id,
GroupId,
SUM( CASE WHEN IsActive = 1 THEN 1 ELSE 0 END ) as TotalCount
FROM
table
GROUP BY
Id, GroupId
) g
Hmmmm . . .
I might suggest left join and aggregation, such to simplify the expressions:
select t1.id, t1.groupid, count(t2.id) as cnt,
concat(t1.groupid,
case when count(t2.id) > 0 then concat('-', count(t2.id)) end
) as newcol
from table1 t1 left join
table2 t2
on t2.id = t1.id
group by t1.id, t1.groupid;
concat() is convenient for this purpose for two reasons:
It ignores NULL values.
It automatically converts numbers to strings.

MS-SQL select rows that have no records as null

I have the following 3 tables:
A) Unit information [Unit]
+-----------+---------+-------+
| record_ID | SN | Data1 |
+-----------+---------+-------+
| 1 | 123 123 | info |
+-----------+---------+-------+
B) Test related information [TestingData]
+---------+------------------+-----------+
| SN | Info1 | Data1 |
+---------+------------------+-----------+
| 123 123 | Some information | Some data |
+---------+------------------+-----------+
C) Join table to more testing information [Link]
+--------+---------+
| LinkID | SN |
+--------+---------+
| 1 | 123 123 |
+--------+---------+
D) Testing details [Tests]
+--------+---------------------+-----------+
| LinkID | testdate | Testname |
+--------+---------------------+-----------+
| 1 | 10.05.2015 22:22:00 | AD1 |
+--------+---------------------+-----------+
Now the tricky part:
I need to get the last record for each LinkID where test name = AD1 for example.
I managed to create a query that does just this, except that it returns no rows in case there was no AD1 named test:
SELECT * FROM (
SELECT
ROW_NUMBER() OVER(PARTITION BY rf2.linkID ORDER BY rf2.testDate DESC) AS rn
,rf2.*, rs.*
FROM dbo.Unit rs
FULL OUTER JOIN dbo.TestingData rf ON ( rs.SN = rf.SN )
FULL OUTER JOIN dbo.Link rf1 ON ( rf.SN = rf1.SN)
FULL OUTER JOIN dbo.Tests rf2 ON ( rf1.linkID = rf2.linkID )
WHERE rf2.testType = 'AD1'
) T
WHERE rn = 1
ORDER BY SN ASC;
How can I extend (if possible at all) above query so that I will also get rows with null values in case there has not been a testType of AD1?
Reason behind this is that I will need to integrate this part to a larger report, which I will integrate through a join on SN.
I need to get the last record for each LinkID where test name = AD1 for example.
I can't figure out why your question has references to four tables, when one table has all the information you need. The full joins are even less explanable.
This gets the rows from tests that you want:
select top (1) with ties t.*
from tests t
where t.testName = 'AD1'
order by row_number() over (partition by t.linkid order by t.testdate desc)
union all
select t.*
from tests t
where not exists (select 1 from tests t2 where t2.linkid = t.linkid and t.testName = 'AD1');
You can also do this with window functions:
select t.*
from (select t.*,
row_number() over (partition by linkid, testname order by testdate desc) as seqnum,
sum(case when testName = 'AD1' then 1 else 0 end) over (partition by linkid) as num_ad1
from tests t
) t
where (l.num_ad1 > 0 and testName = 'AD1' and seqnum = 1) or
(l.num_ad1 = 0);
You can JOIN in the other tables, for additional columns you may want. There is no need for FULL JOIN, a LEFT JOIN should suffice.
You are saying where test name = AD1 so I think you should check Testname instead of testType in the WHERE clause as below:
SELECT * FROM (
SELECT
ROW_NUMBER() OVER(PARTITION BY rf2.linkID ORDER BY rf2.testDate DESC) AS rn
,rf2.*, rs.*
FROM dbo.Unit rs
FULL OUTER JOIN dbo.TestingData rf ON ( rs.SN = rf.SN )
FULL OUTER JOIN dbo.Link rf1 ON ( rf.SN = rf1.SN)
FULL OUTER JOIN dbo.Tests rf2 ON ( rf1.linkID = rf2.linkID )
WHERE rf2.Testname = 'AD1' -- You should use `Testname` instead of `testType`
) T
WHERE rn = 1
ORDER BY SN ASC;
OUTPUT:
rn LinkID testdate Testname record_ID SN Data1
1 1 2015-10-05 22:22:00.000 AD1 1 123 123 info

How can i check the order of column values(by date) for every unique id?

I have this table, Activity:
| ID | Date of activity | activity |
|----|---------------------|----------|
| 1 | 2016-05-01T13:45:03 | a |
| 1 | 2016-05-02T13:45:03 | b |
| 1 | 2016-05-03T13:45:03 | a |
| 1 | 2016-05-04T13:45:03 | b |
| 2 | 2016-05-01T13:45:03 | b |
| 2 | 2016-05-02T13:45:03 | b |
and this table:
| id | Right order |
|----|-------------|
| 1 | yes |
| 2 | no |
How can I check for every ID if the order of the activities is sumiliar to this order for example ?
a b a b a b ..
of course i'll check according to activity date
In SQL Server 2012+ you could use common table expression with lag(), and then the min() of a case expression that follows your logic like so:
;with cte as (
select *
, prev_activity = lag(activity) over (partition by id order by date_of_activity)
from t
)
select id
, right_order = min(case
when activity = 'a' and isnull(prev_activity,'b')<>'b' then 'no'
when activity = 'b' and isnull(prev_activity,'b')<>'a' then 'no'
else 'yes'
end)
from cte
group by id
rextester demo: http://rextester.com/NQQF78056
returns:
+----+-------------+
| id | right_order |
+----+-------------+
| 1 | yes |
| 2 | no |
+----+-------------+
Prior to SQL Server 2012 you can use outer apply() to get the previous activity instead of lag() like so:
select id
, right_order = min(case
when activity = 'a' and isnull(prev_activity,'b')<>'b' then 'no'
when activity = 'b' and isnull(prev_activity,'b')<>'a' then 'no'
else 'yes'
end)
from t
outer apply (
select top 1 prev_activity = i.activity
from t as i
where i.id = t.id
and i.date_of_activity < t.date_of_activity
order by i.date_of_activity desc
) x
group by id
EDITED - Allows for variable number of Patterns per ID
Perhaps another approach
Example
Declare #Pat varchar(max)='a b'
Declare #Cnt int = 2
Select ID
,RightOrder = case when rtrim(replicate(#Pat+' ',Hits/#Cnt)) = (Select Stuff((Select ' ' +activity From t Where id=A.id order by date_of_activity For XML Path ('')),1,1,'') ) then 'Yes' else 'No' end
From (Select ID,hits=count(*) from t group by id) A
Returns
ID RightOrder
1 Yes
2 No
select id,
case when sum(flag)=0 and cnt_per_id%2=0
and max(case when rnum=1 then activity end) = 'a'
and max(case when rnum=2 then activity end) = 'b'
and min_activity = 'a' and max_activity = 'b'
then 'yes' else 'no' end as RightOrder
from (select t.*
,row_number() over(partition by id order by activitydate) as rnum
,count(*) over(partition by id) as cnt_per_id
,min(activity) over(partition by id) as min_activity
,max(activity) over(partition by id) as max_activity
,case when lag(activity) over(partition by id order by activitydate)=activity then 1 else 0 end as flag
from tbl t
) t
group by id,cnt_per_id,max_activity,min_activity
Based on the explanation the following logic has to be implemented for rightorder.
Check if the number of rows per id are even (Remove this condition if there can be an odd number of rows like a,b,a or a,b,a,b,a and so on)
First row contains a and second b, min activity is a and max activity is b for an id.
Sum of flags (set using lag) should be 0

Can row_number() ignore null in oracle

I have data like this
---------------------------
| code | other column
---------------------------
| C | a
| null | a
| A | a
| null | a
| null | a
----------------------------
How can i write query to get row_number without counting null column.
----------------------------------
| id | code | other column |
----------------------------------
| 1 | C | a
| | null | a
| 2 | A | a
| | null | a
| | null | a
----------------------------------
Well, not specifically. But you can get what you want by using conditional logic:
select (case when code is not null
then row_number() over (partition by (case when code is not null then 1 else 0 end)
order by . . .
)
end) as id
It is not clear to me what the order by is for the row_number() which is what the . . . means.
If you need to order on code (descendent in your example) with NULLs last:
select
decode(code,null,null,row_number() over (order by code DESC NULLS LAST)) rn,
code
from test;
If you need to order on OTHER column:
select
decode(code,null,null,row_number() over (order by decode(code,null,null,'x') NULLS LAST, other DESC)) rn,
code, other
from test;
You can use row_number on the desired subset and then union all the other records:
select row_number() over (order by sortkey) as id, code, other_column
from mytable
where code is not null
union all
select null as id, code, other_column
from mytable
where code is null
order by sortkey;
Another easy way would be:
Select
CASE WHEN code IS NOT NULL THEN ROW_NUMBER() OVER (PARTITION BY code order by code)
ELSE NULL END id,
code,
other_column
FROM table;
Example, in my case using IS NOT NULL did not work for me, I had to change it to an expression:
SELECT A.ITEMNAME,
CASE
WHEN (SELECT T1.MAXSTOCK
FROM DATOS T1
WHERE T1.MAXSTOCK) > 5 THEN
ROW_NUMBER() OVER(PARTITION BY CASE
WHEN (SELECT T1.MAXSTOCK
FROM DATOS T1
WHERE T1.MAXSTOCK) <= 5 /*here should go IS NOT NULL*/
THEN
1
END ORDER BY A.ITEMNAME)
ELSE
NULL
END AS #ROW
FROM TABLE A