How can I select no rows if any row in the result set meets a certain condition?
For instance:
Id|SomeColumn|Indicator
1 | test | Y
1 | test1 | Y
1 | test2 | X
2 | test1 | Y
2 | test2 | Y
3 | test1 | Y
Say I wanted to select all rows where Id = 1 unless there is a row with an indicator = X
Currently I am doing something like this
SELECT * FROM SOMETABLE WHERE ID = 1 AND INDICATOR = 'Y' AND ID NOT IN (SELECT ID WHERE INDICATOR = 'X')
But that feels really clunky and I feel like there could be a better way to be doing this. Is there or am I just being overly sensitive
Something like this ?
SELECT *
FROM SOMETABLE
WHERE ID = 1
AND NOT EXISTS (SELECT 1 FROM SOMETABLE WHERE INDICATOR = 'X')
or, if you want the X to discriminate only on the same id:
SELECT *
FROM SOMETABLE t1
WHERE t1.ID = 1
AND NOT EXISTS (SELECT 1 FROM SOMETABLE t2 WHERE t1.ID = t1.ID AND INDICATOR = 'X')
There are not too many options to do this. Another option is to use EXISTS.
SELECT *
FROM SOMETABLE s1
WHERE ID = 1 AND INDICATOR = 'Y'
AND NOT EXISTS (SELECT TOP 1 ID FROM SOMETABLE s2 WHERE s1.ID = s2.ID AND INDICATOR = 'X')
Another option, assuming that there's an enforced order in indicator column.
DECLARE #T TABLE
(
ID INT
, someColumn VARCHAR(5)
, Indicator CHAR(1)
)
INSERT INTO #T
( ID, someColumn, Indicator )
VALUES ( 1, 'test', 'Y' ),
( 1, 'test1', 'Y' ),
( 1, 'test2', 'X' ),
( 2, 'test1', 'Y' ),
( 2, 'test2', 'Y' ),
( 3, 'test1', 'Y' )
SELECT t.ID
, t.someColumn
, t.Indicator
FROM #T t
JOIN (SELECT ID
FROM #T t2
GROUP BY t2.ID
HAVING MIN(indicator) >= 'Y') q ON q.ID = t.ID
Not sure if it's any less clunky, but it may perform better since it's using positive exclusion rather than negative.
Related
For example: I have a table with these records below
1 A
2 A
3 B
4 C
...
and I need to migrate these record in to another table
1 AA
2 AB
3 B
4 C
...
Meaning if the record is duplicate, it will automatically add one more letter alphabetically.
Just a slightly different approach
Example
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',char(64+row_number() over ( partition by SomeCol order by ID ))) )
From #YourTable
Returns
ID SomeCol NewVal
1 A AA
2 A AB
3 B B
4 C C
EDIT - Requested UPDATE
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',replace(char(63+row_number() over ( partition by SomeCol order by ID )),'#','')) )
From #YourTable
Returns
ID SomeCol NewVal
1 A A
2 A AA
3 B B
4 C C
We might be able to handle this requirement with the help of a calendar table mapping secondary letters to duplicate sequence counts:
WITH letters AS (
SELECT 1 AS seq, 'A' AS let UNION ALL
SELECT 2, 'B' UNION ALL
SELECT 3, 'C' UNION ALL
...
SELECT 26, 'Z' UNION ALL
...
),
cte AS (
SELECT id, let, ROW_NUMBER() OVER (PARTITION BY let ORDER BY id) rn,
COUNT(*) OVER (PARTITION BY let) cnt
FROM yourTable
)
SELECT t1.id, t1.let + CASE WHEN t1.cnt > 1 THEN t2.let ELSE '' END AS let
FROM cte t1
LEFT JOIN letters t2
ON t1.id = t2.seq
ORDER BY t1.id;
Demo
I have a query that is structured like this:
SELECT
"result1"
, "result2"
, "result3"
FROM
(
SELECT number, position
FROM values val
FULL JOIN values2 val2 ON val.id = val2.id
WHERE val.code = 'example'
AND val.number IS NOT NULL
)
PIVOT
(
MAX(number)
FOR position IN(
1 AS "result1",
2 AS "result2",
3 AS "result3"
)
);
For the case of it returning no values, I want it to return null-values instead of a empty row.
How would I achieve this? I was not able to make commonly suggested solutions work, because of the pivot.
E:
The result I get:
No row returned:
The result I want:
One row returned with null values
The problem is not with the PIVOT it is with the sub-query before the PIVOT and your question can be reduced to:
How can I get the query (renaming your identifiers to have legal values):
SELECT num,
position
FROM values1 val
FULL OUTER JOIN values2 val2
ON val.id = val2.id
WHERE val.code = 'example'
AND val.num IS NOT NULL
to always return at least one row?
If you have the test data:
CREATE TABLE values1 ( id, code, num ) AS
SELECT 1, 'example', NULL FROM DUAL UNION ALL
SELECT 2, 'not_example', 1 FROM DUAL;
CREATE TABLE values2 ( id, position ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL;
Then there is no row that will match the filter conditions in your WHERE clause and there will be zero rows to PIVOT so the query will always return zero rows. In this case then you can use UNION ALL to add a row:
SELECT num,
position
FROM values1 val
FULL OUTER JOIN values2 val2
ON val.id = val2.id
WHERE val.code = 'example'
AND val.num IS NOT NULL
UNION ALL
SELECT NULL,
NULL
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM value1
WHERE code = 'example'
AND num IS NOT NULL
)
Which will output:
NUM | POSITION
---: | -------:
null | null
And then wrapped with the pivot:
SELECT *
FROM
(
SELECT num,
position
FROM values1 val
FULL OUTER JOIN values2 val2
ON val.id = val2.id
WHERE val.code = 'example'
AND val.num IS NOT NULL
UNION ALL
SELECT NULL,
NULL
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM values1
WHERE code = 'example'
AND num IS NOT NULL
)
)
PIVOT ( MAX(num) FOR position IN (
1 AS "result1",
2 AS "result2",
3 AS "result3"
));
Outputs:
result1 | result2 | result3
------: | ------: | ------:
null | null | null
db<>fiddle here
I solved my issue by creating a view out of this query and calling the view like this:
SELECT NVL(MIN(result1), null)
, NVL(MIN(result2), null)
, NVL(MIN(result3), null)
FROM schema.view;
DataTable:
Any time an ID has a record where Type = A and a record where Type = B, I want to not include the record where Type = A.
SELECT * FROM DataTable
WHERE Type <> 'A'
This query does not work, because I still want to show the record where Type = A for ID 500.
You can use count() window analytic function with partitioning by ID column and then filter out rows with type equals A whenever count is greater than one :
with t2 as
(
select t.*, count(*) over (partition by ID order by ID,Qty ) as cnt
from t
)
select type,ID,Qty
from t2
where ( type <> 'A' and cnt > 1 ) or cnt <= 1
Demo
You can use UNION and NOT EXISTS as follows:
select * from DataTable where Type <> 'A'
union
select * from DataTable t1 where Type = 'A'
and not exists(select 1 from DataTable t2 where t1.id = t2.id and Type = 'B')
With NOT EXISTS:
select t.* from tablename t
where Type <> 'A'
or not exists (
select 1 from tablename
where Type = 'B' and id = t.id
)
See the demo.
Results:
> type | id | qty
> :--- | --: | --:
> B | 400 | 91
> C | 400 | 91
> A | 500 | 105
Add a NOT condition which excludes records where both type is 'A' and there exists a record with the same ID and of TYPE 'B'.
SELECT *
FROM DataTable DT1
WHERE not (
DT1.[Type] = 'A'
and exists (select 1 from DataTable DT2 where DT2.id = DT1.id and DT2.[Type] = 'B')
)
Working example:
declare #Test table ([Type] char(1), ID int, Qty int)
insert into #Test ([Type], ID, Qty)
select 'A', 400, 91 union all
select 'B', 400, 91 union all
select 'C', 400, 91 union all
select 'A', 500, 105;
SELECT *
FROM #Test DT1
WHERE not (
DT1.[Type] = 'A'
and exists (select 1 from #Test DT2 where DT2.id = DT1.id and DT2.[Type] = 'B')
)
PS - if its SQL Server you should be single quoting your strings, not double quoting them.
I am currently working in a database with the following structure:
Var | Value | ID
--------------
A | 1 | 1
B | 2 | 1
C | 3 | 1
A | 2 | 2
B | 4 | 2
C | 6 | 2
What I am trying to achieve is to subtract the value of Var C from the other Var's (B and C) sharing the same ID as Var C. In this case the output would be:
Var | Value | ID
--------------
A | -2 | 1
B | -1 | 1
C | 3 | 1
A | -4 | 2
B | -2 | 2
C | 6 | 2
To be honest I have absolutely no idea how to start on achieving this. I am familiar with many other programming languages, but SQL is still a challenge with difficult/specific queries.
Do a self join:
select t1.var,
case when t1.var = 'C' then t1.value
else t1.value - t2.value
end as value,
t1.id
from tablename t1
join tablename t2 ON t1.id = t2.id
where t2.var = 'C'
Note that value is a reserved word in ANSI SQL, so it should be delimited as "Value".
You could pre-analyse the "C" Values and then use this to remove them?
DECLARE #Data TABLE (
[Var] VARCHAR(1),
Value INT,
ID INT);
INSERT INTO #Data SELECT 'A', 1, 1;
INSERT INTO #Data SELECT 'B', 2, 1;
INSERT INTO #Data SELECT 'C', 3, 1;
INSERT INTO #Data SELECT 'A', 2, 2;
INSERT INTO #Data SELECT 'B', 4, 2;
INSERT INTO #Data SELECT 'C', 6, 2;
WITH CValues AS (
SELECT
ID,
Value
FROM
#Data
WHERE
[Var] = 'C')
SELECT
d.[Var],
CASE WHEN d.[Var] != 'C' THEN d.Value - c.Value ELSE d.Value END AS Value,
d.ID
FROM
#Data d
LEFT JOIN CValues c ON c.ID = d.ID;
...but yes, a self-join is probably a better solution:
DECLARE #Data TABLE (
[Var] VARCHAR(1),
Value INT,
ID INT);
INSERT INTO #Data SELECT 'A', 1, 1;
INSERT INTO #Data SELECT 'B', 2, 1;
INSERT INTO #Data SELECT 'C', 3, 1;
INSERT INTO #Data SELECT 'A', 2, 2;
INSERT INTO #Data SELECT 'B', 4, 2;
INSERT INTO #Data SELECT 'C', 6, 2;
SELECT
d.[Var],
CASE WHEN d.[Var] != 'C' THEN d.Value - c.Value ELSE d.Value END AS Value,
d.ID
FROM
#Data d
LEFT JOIN #Data c ON c.[Var] = 'C' AND c.ID = d.ID;
This is kind of hard to explain in words but here is an example of what I am trying to do in SQL. I have a query which returns the following records:
ID Z
--- ---
1 A
1 <null>
2 B
2 E
3 D
4 <null>
4 F
5 <null>
I need to filter this query so that each unique record (based on ID) appears only once in the output and if there are multiple records for the same ID, the output should contain the record with the value of Z column being non-null. If there is only a single record for a given ID and it has value of null for column Z the output still should return that record. So the output from the above query should look like this:
ID Z
--- ---
1 A
2 B
2 E
3 D
4 F
5 <null>
How would you do this in SQL?
You can use GROUP BY for that:
SELECT
ID, MAX(Z) -- Could be MIN(Z)
FROM MyTable
GROUP BY ID
Aggregate functions ignore NULLs, returning them only when all values on the group are NULL.
If you need to return both 2-B and 2-E rows:
SELECT *
FROM YourTable t1
WHERE Z IS NOT NULL
OR NOT EXISTS
(SELECT * FROM YourTable t2
WHERE T2.ID = T1.id AND T2.z IS NOT NULL)
SELECT ID
,Z
FROM YourTable
WHERE Z IS NOT NULL
DECLARE #T TABLE ( ID INT, Z CHAR(1) )
INSERT INTO #T
( ID, Z )
VALUES ( 1, 'A' ),
( 1, NULL )
, ( 2, 'B' ) ,
( 2, 'E' ),
( 3, 'D' ) ,
( 4, NULL ),
( 4, 'F' ),
( 5, NULL )
SELECT *
FROM #T
; WITH c AS (SELECT ID, r=COUNT(*) FROM #T GROUP BY ID)
SELECT t.ID, Z
FROM #T t JOIN c ON t.ID = c.ID
WHERE c.r =1
UNION ALL
SELECT t.ID, Z
FROM #T t JOIN c ON t.ID = c.ID
WHERE c.r >=2
AND z IS NOT NULL
This example assumes you want two rows returned for ID = 2.
with tmp (id, cnt_val) as
(select id,
sum(case when z is not null then 1 else 0 end)
from t
group by id)
select t.id, t.z
from t
inner join tmp on t.id = tmp.id
where tmp.cnt_val > 0 and t.z is not null
or tmp.cnt_val = 0 and t.z is null
WITH CTE
AS (
SELECT id
,z
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY coalesce(z, '') DESC
) rn
FROM #T
)
SELECT id
,z
FROM CTE
WHERE rn = 1