replacing TSQL NOT EXISTS in SQL-92 - sql

I am have following code which works well in TSQL:
BEGIN
IF NOT EXISTS (select * from tblDCUSTOM where id = 'All Customers')
BEGIN
INSERT INTO tblDCUSTOM
(ID
,Name
,English
)
SELECT 'All Customers','All Customers','All Customers'
END
END
Now, I need to have this functionality in an custom environment, where SQL-92 is used - so no EXISTS (edit: not true, EXISTS works in SQL-92) or BEGIN-END is possible. Any Ideas?

As per very first comment;
INSERT INTO tblDCUSTOM
(ID
,Name
,English
)
SELECT 'All Customers','All Customers','All Customers'
WHERE (SELECT COUNT(*) FROM tblDCUSTOM where id = 'All Customers') >= 1
If TOP is supported this might be better
INSERT INTO tblDCUSTOM
(ID
,Name
,English
)
SELECT 'All Customers','All Customers','All Customers'
WHERE (SELECT TOP 1 1 as F FROM tblDCUSTOM where id = 'All Customers') IS NOT NULL
I must warn you, many have tried to make a 'database agnostic' system. It's not worth it.

This is the correct answer, the EXISTS statement IS actually supported:
Put the condition in the WHERE: INSERT ... SELECT ... WHERE NOT EXISTS
(...). This is arguably better practice even in T-SQL, to make the
operation atomic.

Related

Complex Conditional Query in Where Clause

In my company, each department has their own statuses for purchase orders. I'm trying to load only the pertinent POs for a specific user's department. I keep getting errors. It seems like it should be correct, though. I initially attempted to use a Case statement, but it appears that SQL can only do a simple return from a case, so I'm now attempting If statements. Current error is Incorrect Syntax near the keyword IF. It looks right to me. It's as if the incorrect syntax is that it is in the IN parentheses.
declare #Dept nvarchar(12);
set #Dept = 'IT'
SELECT *
FROM TBL_ORDERS
WHERE ORD_STATUS IN
(
IF #Dept = 'PURCH'
BEGIN
SELECT distinct * FROM (VALUES ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4')) AS X(a)
END
ELSE
IF #Dept = 'ADMIN'
BEGIN
SELECT distinct * FROM (VALUES ('ADStat1')) AS X(a)
END
ELSE
IF #Dept = 'IT'
BEGIN
SELECT distinct * FROM (VALUES ('ADStat1'), ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4'), ('ITStat1'), ('ITStat2')) AS X(a)
END
ELSE END
)
There's a conceptual problem to address here first, and then we can look at how to actually do what you want.
select is starting a statement here. Statements end with a semicolon. You can't put statements inside other statements. What you are doing is the same as trying to do something like this in c#:
int i = if (true) 1; else 2;;
You can of course use expressions inside statements:
int i = true ? 1 : 2;
Moreover, select is a declarative statement. You can't do imperative flow of control inside a declarative language construct. You're mixing metaphors, as it were. To understand the declarative/imperative distinction see this question and in particular (in my opinion) this answer.
So the first thing to do is wrap your head around the declarative nature of SQL statements like select. Yes, T-SQL also includes imperative constructs like if and while, but you can't do imperative inside declarative.
You can use conditional expressions (and other expressions) inside a declarative statement:
select name,
case
when name = 'date' then 'this is the date row'
else 'this is not the date row'
end
from sys.types;
In this example the declarative select says what to do with all of the rows returned by the from clause. I don't write a while loop or a for loop in order to instruct the computer to loop over each row and provide instructions inside the loop. The from returns all the rows, and the select declares what I want to do with each of them. The case expression will be evaluated against every row in sys.types.
OK, so what about your specific question? There's many ways to write the code. Here is one way that is very similar to your current structure. First I conditionally (imperatively!) populate a temp table with the statuses I want. Then I declaratively use that temp table as my filter:
create table #statuses
(
statusname varchar(32)
);
declare #dept nvarchar(12) = 'IT';
if (#dept = 'IT')
begin
insert #statuses (statusname) values
('ADStat1'), ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4'), ('ITStat1'), ('ITStat2');
end
else if (#dept = 'PURCH')
begin
insert #statuses (statusname) values
('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4');
end
else if (#dept = 'ADMIN')
begin
insert #statuses (statusname) values
('ADStat1');
end
select *
from tbl_orders
where ord_status in (select statusName from #statuses);
Can I do it without the temp table? Sure. Here's one way:
declare #dept nvarchar(12) = 'IT';
select *
from tbl_orders
where (#dept = 'ADMIN' and ord_status = 'ADStat1')
or (#dept = 'PURCH' and ord_status in ('PurchStat1', 'PurchStat2', 'PurchStat3', 'PurchStat4'))
or (#dept = 'IT' and ord_status in ('ADStat1', 'PurchStat1', 'PurchStat2', 'PurchStat3', 'PurchStat4', 'ITStat1', 'ITStat2'));
Here we evaluate a different in depending on the value of #dept. Clearly only one of them actually needs to be evaluated, and the other two don't really need to be there, depending on which value of #dept is provided. Adding an option (recompile) can be beneficial in cases like this. For more information about option (recompile) look here and here.
If for a reason storing those status/department data in tables is not possible you can use union
declare #Dept nvarchar(12);
set #Dept = 'IT'
SELECT *
FROM TBL_ORDERS
WHERE ORD_STATUS IN
(
SELECT distinct *
FROM (VALUES ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4')) AS X(a)
WHERE #Dept ='PURCH'
union all
SELECT distinct *
FROM (VALUES ('ADStat1')) AS X(a)
WHERE #Dept ='ADMIN'
union all
SELECT distinct *
FROM (VALUES ('ADStat1'), ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4'), ('ITStat1'), ('ITStat2')) AS X(a)
WHERE #Dept ='IT'
)

Check if a temp table exists when I only know part of the name?

I have a function for checking if certain tables exist in my database, using part of the table name as a key to match (my table naming conventions include unique table name prefixes). It uses a select statement as below, where #TablePrefix is a parameter to the function and contains the first few characters of the table name:
DECLARE #R bit;
SELECT #R = COUNT(X.X)
FROM (
SELECT TOP(1) 1 X FROM sys.tables WHERE [name] LIKE #TablePrefix + '%'
) AS X;
RETURN #R;
My question is, how can I extend this function to work for #temp tables too?
I have tried checking the first char of the name for # then using the same logic to select from tempdb.sys.tables, but this seems to have a fatal flaw - it returns a positive result when any temp table exists with a matching name, even if not created by the current session - and even if created by SPs in a different database. There does not seem to be any straightforward way to narrow the selection down to only those temp tables that exist in the context of the current session.
I cannot use the other method that seems universally to be suggested for checking temp tables - IF OBJECT('tempdb..#temp1') IS NOT NULL - because that requires me to know the full name of the table, not just a prefix.
create table #abc(id bit);
create table #abc_(id bit);
create table #def__(id bit);
create table #xyz___________(id bit);
go
select distinct (left(t.name, n.r)) as tblname
from tempdb.sys.tables as t with(nolock)
cross join (select top(116) row_number() over(order by(select null)) as r from sys.all_objects with(nolock)) as n
where t.name like '#%'
and object_id('tempdb..'+left(t.name, n.r)) is not null;
drop table #abc;
drop table #abc_;
drop table #def__;
drop table #xyz___________;
Try something like this:
DECLARE #TablePrefix VARCHAR(50) = '#temp';
DECLARE #R BIT, #pre VARCHAR(50) = #TablePrefix + '%';
SELECT #R = CASE LEFT ( #pre, 1 )
WHEN '#' THEN (
SELECT CASE WHEN EXISTS ( SELECT * FROM tempdb.sys.tables WHERE [name] LIKE #pre ) THEN 1
ELSE 0
END )
ELSE (
SELECT CASE WHEN EXISTS ( SELECT * FROM sys.tables WHERE [name] LIKE #pre ) THEN 1
ELSE 0
END )
END;
SELECT #R AS TableExists;

DELETE EXCEPT TOP 1

Is there any way to delete all the rows in a table except one (random) row, without specifying any column names in the DELETE statement?
I'm trying to do something like this:
CREATE TABLE [dbo].[DeleteExceptTop1]([Id] INT)
INSERT [dbo].[DeleteExceptTop1] SELECT 1
INSERT [dbo].[DeleteExceptTop1] SELECT 2
INSERT [dbo].[DeleteExceptTop1] SELECT 3
SELECT * FROM [dbo].[DeleteExceptTop1]
DELETE
FROM [dbo].[DeleteExceptTop1]
EXCEPT
SELECT TOP 1 * FROM [dbo].[DeleteExceptTop1]
SELECT * FROM [dbo].[DeleteExceptTop1]
The final SELECT should yield one row (could be any of the three).
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT newid())) AS RN
FROM [dbo].[DeleteExceptTop1]
)
DELETE FROM CTE
WHERE RN > 1
Or similar to #abatishchev's answer but with more variety in the ordering and avoiding deprecated constructs.
DECLARE #C INT
SELECT #C = COUNT(*) - 1
FROM [dbo].[DeleteExceptTop1]
IF #c > 0
BEGIN
WITH CTE AS
(
SELECT TOP(#C) *
FROM [dbo].[DeleteExceptTop1]
ORDER BY NEWID()
)
DELETE FROM CTE;
END
Or a final way that uses EXCEPT and assumes no duplicate rows and that all columns are of datatypes compatible with the EXCEPT operator
/*Materialise TOP 1 to ensure only evaluated once*/
SELECT TOP(1) *
INTO #T
FROM [dbo].[DeleteExceptTop1]
ORDER BY NEWID()
;WITH CTE AS
(
SELECT *
FROM [dbo].[DeleteExceptTop1] T1
WHERE EXISTS(
SELECT *
FROM #T
EXCEPT
SELECT T1.*)
)
DELETE FROM CTE;
DROP TABLE #T
Try:
declare #c int
select #c = count(*) - 1 from [dbo].[DeleteExceptTop1]
IF #c > 0
BEGIN
set RowCount #c
delete from [dbo].[DeleteExceptTop1]
END
No.
You need to use a column name (such as that of the primary key) to identify which rows you want to remove.
"random row" has no meaning in SQL except its data. If you want to delete everything except some row, you must differentiate that row from the others you with to DELETE
EXCEPT works by comparing the DISTINCT values in the row.
EDIT: If you can specify the primary key then this is a trivial matter. You can simply DELETE where the PK <> your "random" selection or NOT IN your "random" selection(s).
EDIT: Apparently I'm wrong about the need to specify any column name, you can do it using the assigned ROW_NUMBER.. But I'm not going to delete my answer because it references your use of EXCEPT which was discussed in the comments. You cannot do it without deriving some column name like that from ROW_NUMBER
You could do something like this (SQL 2008)
DECLARE #Original TABLE ([Id] INT)
INSERT INTO #Original(ID) VALUES(1)
INSERT INTO #Original(ID) VALUES(2)
INSERT INTO #Original(ID) VALUES(3)
SELECT * FROM #Original;
WITH CTE AS
(SELECT ROW_NUMBER() OVER(ORDER BY ID) AS ROW, ID FROM #Original)
DELETE #Original
FROM #Original O
INNER JOIN CTE ON O.ID = CTE.ROW
WHERE ROW > 1
SELECT * FROM #Original
It seems like the simplest answer may be the best. The following should work:
Declare #count int
Set #count=(Select count(*) from DeleteExceptTop1)-1
Delete top (#count) from DeleteExceptTop1
I know it has been answered but what about?
DELETE
FROM [dbo].[DeleteExceptTop1]
Where Id not in (
SELECT TOP 1 * FROM [dbo].[DeleteExceptTop1])

Test ALL rows exists

It is very simple to test when row exists or not.
if exists(select * from dbo.APQP_head where zestaw=#zestaw)
I want to test in my query whether all rows satisfy the condition.
I need use some query like this
if All exists(select * from dbo.APQP_head where zestaw=#zestaw and type=3)
But this syntax is not correct.
if NOT exists(select * from dbo.APQP_head where zestaw<>#zestaw OR type<>3)
--all rows satisfy the condition
if your columns can be nullable, then
if NOT exists(select * from dbo.APQP_head where zestaw<>#zestaw OR type<>3)
AND NOT exists(select * from dbo.APQP_head where zestaw IS NULL OR type IS NULL)
This may perform better than an OR because it keep the AND and uses semi-joins
IF NOT EXISTS (
SELECT zestaw, [type] FROM #foo
EXCEPT
SELECT zestaw, [type] FROM #foo where zestaw=#zestaw and type=3
)
-- all rows etc
Edit, quick and dirty test (SQL Server 2008 R2 Express on workstation), the EXCEPT uses more IO (2 touches) but less CPU (more efficient plan)
If you replace #zestaw with a constant, the NOT EXISTS .. OR .. wins
CREATE TABLE excepttest (zestaw int, [type] int);
INSERT excepttest VALUES (1, 3);
GO
INSERT excepttest SELECT excepttest.* FROM excepttest
GO 21
SELECT COUNT(*) FROM excepttest
GO
CREATE INDEX IX_Test ON excepttest (zestaw, [type]);
GO
DECLARE #zestaw int = 1;
SET STATISTICS IO ON
SET STATISTICS TIME ON
if NOT exists(select * from excepttest where zestaw<>#zestaw OR [type]<>3)
SELECT 'all match'
ELSE
SELECT 'some match';
IF NOT EXISTS (
SELECT zestaw, [type] FROm excepttest
EXCEPT
SELECT zestaw, [type] FROm excepttest where zestaw=#zestaw and [type]=3
)
SELECT 'all match'
ELSE
SELECT 'some match';
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
DROP TABLE excepttest
You can invert the conditions in the WHERE clause and the whole exists expression:
if NOT exists select(select * from dbo.APQP_head where zestaw <> #zestaw OR type <> 3)
This uses a well known fact that NOT(A1 AND A2 AND ... An) == NOT(A1) OR NOT(A2)... OR NOT(An).
if not exists(select * from dbo.APQP_head where not (zestaw=#zestaw and type=3))
this is a solution for some problems:
select distinct AccAccountId from AccAccountTafsilType where AccAccountId
in (select Id From AccountForSooratVaziatFunc(5))
and AccAccountId in (select AccAccountId from AccAccountTafsilType where
TafsilTypeId in (select Id from AccTafsilType where ComplexId = 5 and
Code = 115))
and AccAccountId in (select AccAccountId from AccAccountTafsilType where
TafsilTypeId in (select Id from AccTafsilType where ComplexId = 5 and
Code = 116))

is it possible to select EXISTS directly as a bit?

I was wondering if it's possible to do something like this (which doesn't work):
select cast( (exists(select * from theTable where theColumn like 'theValue%') as bit)
Seems like it should be doable, but lots of things that should work in SQL don't ;) I've seen workarounds for this (SELECT 1 where... Exists...) but it seems like I should be able to just cast the result of the exists function as a bit and be done with it.
No, you'll have to use a workaround.
If you must return a conditional bit 0/1 another way is to:
SELECT CAST(
CASE WHEN EXISTS(SELECT * FROM theTable where theColumn like 'theValue%') THEN 1
ELSE 0
END
AS BIT)
Or without the cast:
SELECT
CASE
WHEN EXISTS( SELECT 1 FROM theTable WHERE theColumn LIKE 'theValue%' )
THEN 1
ELSE 0
END
SELECT CAST(COUNT(*) AS bit) FROM MyTable WHERE theColumn like 'theValue%'
When you cast to bit
0 -> 0
everything else -> 1
And NULL -> NULL of course, but you can't get NULL with COUNT(*) without a GROUP BY
bit maps directly to boolean in .net datatypes, even if it isn't really...
This looks similar but gives no row (not zero) if no matches, so it's not the same
SELECT TOP 1 CAST(NumberKeyCOlumn AS bit) FROM MyTable WHERE theColumn like 'theValue%'
You can use IIF and CAST
SELECT CAST(IIF(EXISTS(SELECT * FROM theTable
where theColumn like 'theValue%'), 1, 0) AS BIT)
I'm a bit late on the uptake for this; just stumbled across the post. However here's a solution which is more efficient & neat than the selected answer, but should give the same functionality:
declare #t table (name nvarchar(16))
declare #b bit
insert #t select N'Simon Byorg' union select N'Roe Bott'
select #b = isnull((select top 1 1 from #t where name = N'Simon Byorg'),0)
select #b whenTrue
select #b = isnull((select top 1 1 from #t where name = N'Anne Droid'),0)
select #b whenFalse
You can also do the following:
SELECT DISTINCT 1
FROM theTable
WHERE theColumn LIKE 'theValue%'
If there are no values starting with 'theValue' this will return null (no records) rather than a bit 0 though
SELECT IIF(EXISTS(SELECT * FROM theTable WHERE theColumn LIKE 'theValue%'), 1, 0)
No it isn't possible. The bit data type is not a boolean data type. It is an integer data type that can be 0,1, or NULL.
Another solution is to use ISNULL in tandem with SELECT TOP 1 1:
SELECT ISNULL((SELECT TOP 1 1 FROM theTable where theColumn like 'theValue%'), 0)
I believe exists can only be used in a where clause, so you'll have to do a workaround (or a subquery with exists as the where clause). I don't know if that counts as a workaround.
What about this:
create table table1 (col1 int null)
go
select 'no items',CONVERT(bit, (select COUNT(*) from table1) ) -- returns 'no items', 0
go
insert into table1 (col1) values (1)
go
select '1 item',CONVERT(bit, (select COUNT(*) from table1) ) --returns '1 item', 1
go
insert into table1 (col1) values (2)
go
select '2 items',CONVERT(bit, (select COUNT(*) from table1) ) --returns '2 items', 1
go
insert into table1 (col1) values (3)
go
drop table table1
go