sql Find data that does NOT exist in a subquery - sql

I'm trying to find all records that do not match my criteria.
I have a one-to-many table where for each key combo (wbs1, wbs2, wbs3) there may be many records. I'm trying to query this table for each combo, and return the combo (wbs1, wbs2, wbs3) only when the table has no records that match my "in" criteria.
But at the moment, my query seems to be returning all or nothing.
select distinct wbs1, wbs2, wbs3
from dbo.Registry
where not exists ( select wbs1, wbs2, wbs3, InspType
from Registry
where ISNULL(InspType,'') IN ('Test 1','Test 2')
)
What I'd like in the return is a list of wbs1,wbs2,wbs3 where in the table "Registry" there are no entries that match the Test (InspType) criteria.
Thanks!

It looks like you're pretty close, and the subquery likely isn't even necessary, but this modification should get you the correct results. But as-written, you need a way to refer to the outer query, thus the dbo.Registry r alias.
select distinct wbs1, wbs2, wbs3
from dbo.Registry r
where not exists ( select 1
from Registry
where ISNULL(InspType,'') IN ('Test 1','Test 2')
and (wbs1 = r.wbs1 or wbs2 = r.wbs2 or wbs3 = r.wbs3)
)
This is assuming Microsoft Sql Server of some flavor, so I don't know if it'd work in MySql or Oracle or whatever.
But couldn't you also just do
select distinct wbs1, wbs2, wbs3
from dbo.Registry
where ISNULL(InspType, '') != 'Test 1'
and ISNULL(InspType, '') != 'Test 2'
and avoid the subquery entirely?

This sounds like a "set-within-sets" subquery, which I like solving with aggregation and logic in the having clause. The following will return all combinations that are missing one or both tests:
select wbs1, wbs2, wbs3
from dbo.Registry r
group by wbs1, wbs2, wbs3
having sum(case when InspType = 'Test 1' then 1 else 0 end) = 0 or
sum(case when InspType = 'Test 2' then 1 else 0 end) = 0;

Related

SQL - expand dataset into lookup table?

I currently have a legacy table that looks like the one below.
This is a set of rules that our business has stored over the years. the issue is the "all" and "both" values really should be separated out into rows so they can be queried more efficiently.
For example, the contract length column can only ever be between 1 and 5, the type column can only ever be "gas" or "water" and the sales channel "internal" or "external". Instead of saying all or both, another row should exist with the specific rule and the table should look like the below.
So this will have a row for every variation in the first table.
I didn't think it would be a long task to manually do myself. but I was wrong :)
Does anyone have any idea on how to achieve this quickly in SQL? I would say what I have tried so far...but I am completely stumped on this one so am wondering if it can even be done at all?
This could be done in a single sql statement, but for the sake of your mental health and the ability to check interim result sets before you get to the final output is probably a lot healthier and less risky.
I would approach this with a UNION query, one set of UNIONs for each column that should be split out to more granular rows.
For instance for contractlength:
SELECT Supplier, 1, Type, SalesChannel FROM yourtable WHERE contractLength in ('1', 'All')
UNION ALL
SELECT Supplier, 2, Type, SalesChannel FROM yourtable WHERE contractLength in ('2', 'All')
UNION ALL
SELECT Supplier, 3, Type, SalesChannel FROM yourtable WHERE contractLength in ('3', 'All')
UNION ALL
SELECT Supplier, 4, Type, SalesChannel FROM yourtable WHERE contractLength in ('4', 'All')
UNION ALL
SELECT Supplier, 5, Type, SalesChannel FROM yourtable WHERE contractLength in ('5', 'All')
You can write those results out to a temp table, and then build your query for type on top of it writing to a new temp table.
SELECT Supplier, contractLength, 'Gas', SalesChannel FROM previousTempTable WHERE type in ('Gas','Both')
UNION ALL
SELECT Supplier, contractLength, 'Water', SalesChannel FROM previousTempTable WHERE type in ('Gas','Both')
Rinse and repeat for SalesChannel.
There's other more elegant ways to solve this with some SELECT DISTINCT and cross joins, but your list of values for each column is limited and this solution I'm proposing feels like a quick easy way to get your data in shape. It's also easy to understand if this is auditable data or the process needs to be repeated.
You don't need to query your table multiple times, or use temp tables. You can do this pretty elegantly with conditional unpivots, by using CROSS APPLY
SELECT
t.Supplier,
c1.ContractLength,
c2.Type,
c3.SalesChannel
FROM YourTable t
CROSS APPLY (
SELECT t.ContractLength
WHERE t.ContractLength <> 'All'
UNION ALL
SELECT *
FROM (VALUES
(1),(2),(3),(4),(5)
) v(ContractLength)
WHERE t.ContractLength = 'All'
) c1
CROSS APPLY (
SELECT t.Type
WHERE t.Type <> 'Both'
UNION ALL
SELECT *
FROM (VALUES
('Gas'),('Water')
) v(Type)
WHERE t.Type = 'Both'
) c2
CROSS APPLY (
SELECT t.SalesChannel
WHERE t.SalesChannel <> 'Both'
UNION ALL
SELECT *
FROM (VALUES
('Internal'),('External')
) v(SalesChannel)
WHERE t.SalesChannel = 'Both'
) c3;
A somewhat less efficient, but more compact, version of the same, is to use normal joins against the VALUES clauses
SELECT
t.Supplier,
c1.ContractLength,
c2.Type,
c3.SalesChannel
FROM YourTable t
JOIN (VALUES
(1),(2),(3),(4),(5)
) c1(ContractLength)
ON c1.ContractLength = t.ContractLength OR t.ContractLength = 'All'
JOIN (VALUES
('Gas'),('Water')
) c2(Type)
ON c2.Type = t.Type OR t.Type = 'Both'
JOIN (VALUES
('Internal'),('External')
) c3(SalesChannel)
ON c3.SalesChannel = t.SalesChannel OR t.SalesChannel = 'Both';

Conditional statements in "WHERE" in SQL Server

What I want to achieve is to have a switch case in the where clause. I want to test if this statement returns something, if it returns null, use this instead.
Sample:
SELECT [THIS_COLUMN]
FROM [THIS_TABLE]
WHERE (IF THIS [ID] RETURNS NULL THEN DO THIS SUBQUERY)
What I mean is that it will do this query first.
SELECT [THIS_COLUMN]
FROM [THIS_TABLE]
WHERE [ID] = 'SOMETHING'
If this returns NULL, do this query instead:
SELECT [THIS_COLUMN]
FROM [THIS_TABLE]
WHERE ID = (SELECT [SOMETHING] FROM [OTHER_TABLE]
WHERE [SOMETHING_SPECIFIC] = 'SOMETHING SPECIFIC')
Note that the expected results from the intended query varies from 30 rows up to 15k rows. Hope it helps.
Adding more information:
The results for this query will be used for another query but will just focus on this query.
Providing a real case scenario:
[THIS_COLUMN] is expected to have a list of VALUES.
[THIS_TABLE] contains the latest data only(let's say 1 year's worth of data) while the [OTHER_TABLE] contains the historical data.
What I want to achieve is when I query for a data that is not with in the 1 year's worth of data, IE 'SOMETHING' is not with in the 1 year scope(or in my case it returns NULL), I will use the other query where I query the 'SOMETHING_SPECIFIC'(Or may be 'SOMETHING' from the first statement makes more sense) from the historical table.
If I as reading through the lines correctly, this might work:
SELECT THIS_COLUMN
FROM dbo.THIS_TABLE TT
WHERE TT.ID = 'SOMETHING'
OR TT.ID = (SELECT OT.SOMETHING
FROM dbo.OTHER_TABLE OT
WHERE OT.SOMETHING_SPECIFIC = 'SOMETHING SPECIFIC'
AND NOT EXISTS (SELECT 1
FROM dbo.THIS_TABLE sq
WHERE sq.ID = 'SOMETHING'
AND THIS_COLUMN IS NOT NULL))
Note, however, that this could easily not be particularly performant.
You an use union all and not exists:
select this_column
from this_table
where id = 'something'
union all
select this_column
from this_table
where
not exists (select this_column from this_table where id = 'something')
and id = (select something from other_table where something_specific = 'something specific')
The first union member attempts to find rows that match the first condition, while the other one uses the subquery - the not exists prevents the second member to return something if the first member found a match.
90% of the time you can use a query-batch (i.e. a sequence of T-SQL statements) in a single SqlCommand object or SQL Server client session, so with that in-mind you could do this:
DECLARE #foo nvarchar(50) = (
SELECT
[THIS_COLUMN]
FROM
[THIS_TABLE]
WHERE
[ID] = 'SOMETHING'
);
IF #foo IS NULL
BEGIN
SELECT
[THIS_COLUMN]
FROM
[THIS_TABLE]
WHERE
[ID] = (
SELECT
[SOMETHING]
FROM
[OTHER_TABLE]
WHERE
[SOMETHING_SPECIFIC] = 'SOMETHING SPECIFIC'
)
END
ELSE
BEGIN
SELECT #foo AS [THIS_COLUMN];
END
That said, SELECT ... FROM ... WHERE x IN ( SELECT y FROM ... ) is a code-smell in a query - you probably need to rethink your solution entirely.

Conditional insert in SQL for an order system

I have a question regarding a conditional insert.
I want to be able to add the same ID in the ("tafelID" = tableID) column, ONLY if the ("betaalstatus" = pay status) is equal to true. What query do i have to use ?
I'm new to SQL. my query is below.
INSERT INTO Rekening (tafelID, betaalstatus) Select ('7', 'False')
WHERE not exists ( select 'False' )
you can replace "<...>" with the data in the nested select statement in FROM clause. you can use nested Case When statement for complex conditions.
INSERT INTO Rekening (tafelID, betaalstatus)
Select Case WHEN betaalstatus = true THEN tableID ELSE tafelID END tafelID, betaalstatus
FROM (SELECT <betaalstatus> AS betaalstatus, <tableID> AS tableID, <tafelID> AS tafelID)
-- i.e. (SELECT true AS betaalstatus, 7 AS tableID, 8 AS tafelID)

No column name showing up in SQL

In SQL Server 2008 but column name does not appear. I need to put in an empty string as the rows are populated manually in the report.
(SELECT '' As 'Total No of people')
It seems to show up as (No column name)
You can have
SELECT ID as 'ID',
(SELECT <....> FROM table WHERE <...> ) AS 'Total No of people'
FROM somewhere
You have to put the column name after the ) for the inner select
I will say it works correctly! http://sqlfiddle.com/#!3/d41d8/18149
But perhaps your problem is that you do (technically using a subquery)
SELECT ID, (SELECT '' As 'Total No of people') FROM SomeWhere
and that is wrong...
SELECT ID, '' As 'Total No of people' FROM SomeWhere
or
SELECT ID, (SELECT '') As 'Total No of people' FROM SomeWhere
but there is no reason for the inner SELECT
make sure you put in tight order : SELECT '' As 'Total No of people' from PEOPLE

SQL Oracle: Replace an empty result with word

I'm working on this problem for several days. I have a oracle database.
The problem must be resolved in one query. No Function, Pocedure, ...
I want to make a select. When he has results, post them. Else there should be "empty result".
select case
when count(*) = 0
then 'no Entry'
else MAX(Member)--all Members should be here
END as Member
from tableMember
where Membergroup = 'testgroup';
The problem is that Oracle wants an Agregat function by the else. So I only get one value if the result is not "no entry". I need all values.
Everybody who can help me is welcome and makes me happy.
not sure what do you try to achieve, perhaps this
select member from tablemember where Membergroup = 'testgroup'
union
select 'no Entry'
from dual
where NOT EXISTS ( select member from tablemember where membergroup = 'testgroup')
;
There's no need for two aggregate queries, you just need to check whether max(member) is null. I'd do it this way to make it clear what's going on.
select case when max_member is null then 'no entry' else max_member end as member
from ( select max(member) as max_member
from tablemember
where membergroup = 'testgroup'
)
If, however, you want to return all members you can do something like the following:
select member
from tablemember
where membergroup = 'testgroup'
union all
select 'no entry'
from dual
where not exists ( select 1 from tablemember where membergroup = 'testgroup')
If you RIGHT JOIN your query with a query for the empty set you will always get one row and will get all the rows if your query returns data. This is less expensive (faster) than a UNION or UNION ALL with a NOT EXISTS because it does not require multiple scans of the data.
SELECT nvl(a.member,b.member) member
FROM (SELECT member FROM tablemember WHERE membergroup='????') a
RIGHT JOIN (SELECT 'no Entry' member FROM dual) b ON 1=1;
Test Environment:
DROP TABLE tablemember;
CREATE TABLE tablemember AS
(
SELECT TO_CHAR(level) member
, DECODE(mod(level, 5), 0, 'testgroup', 'othergroup') membergroup
FROM dual CONNECT BY level <= 50
);
You can use some aggregate functions and NVL for achieve you goal:
SELECT MIN('VALUE 1') AS p1, MIN('VALUE 2') AS p2 FROM DUAL WHERE 1=0
result of this query is:
NULL, NULL
next, replace empty values by desired strings:
SELECT
NVL(MIN('1'), 'empty value 1') AS p1,
NVL(MIN('STRING VALUE'), 'empty value 2') AS p2,
NVL(MIN((select 'subquery result' from dual)), 'empty subquery result') as p3
FROM
DUAL
WHERE
1=0
But, you can't mix numbers and strings in fields.
Try this:
DECLARE C INTEGER;
SELECT COUNT(*) INTO C FROM tableMember WHERE Membergroup = 'testgroup';
IF C > 0
THEN
SELECT * FROM tableMember;
ELSE
SELECT 'No results!' FROM tableMember;
END IF;