Conditionally modify query based on parameter - sql

I have this query (something like a case statement which I can use and fix it)
select *
from mytable
where 1=1
and (isNull(ID, 0) = 0 OR UtilityID IN (9,40))
I also want to add another statement
select *
from mytable
where 1=1
and UtilityID NOT IN (9,40)
Everything is happening in a procedure, so want to use a variable like declare #something so if that is passed as 1, use the first statement and the if 0 is passed, use the latter one.

While I appreciate the genius in Dale's answer I find this more readable:
IF #something = 0
BEGIN
select *
from mytable
where ID IS NULL OR ID = 0 OR UtilityID IN (9,40);
END
IF #something = 1
BEGIN
select *
from mytable
where UtilityID NOT IN (9,40);
END
It's procedure code, so use IF to direct the control flow. Also expanded and simplified your where clauses

I think I understand your logic, ignoring the 1=1 (which does nothing) you want to only allow id = 0 when #something = 1. This should do it:
declare #something bit = 0;
declare #mytable table (ID int, UtilityID int);
insert into #mytable (ID, UtilityID)
select 0, 1 union all
select 1, 2 union all
select 2, 9 union all
select 3, 40;
select *
from #mytable
where (
(#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
or (#something = 0 and (UtilityID not in (9,40)))
);
A more performant approach for a larger query could be:
select *
from #mytable
where (#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
union all
select *
from #mytable
where (#something = 0 and (UtilityID not in (9,40)));
PS: Hopefully your ID cannot ever by null - it should have a constraint on it.

Related

In SQL , how to build a loop that copies a row number of times

could someone please help? My starting table looks like this with 2 fields:
Name Counter
dave 2
Joe 3
I want my result to look like this:
Name Counter
dave 1
dave 2
joe 1
joe 2
joe 3
Essentially creating n number of records base on the counter and starts at 1. I tried to do a loop using counter as a variable, but the code just runs nonstop.. could someone help?
A procedural SQL Server solution:
declare #input table
(
name nvarchar(100)
,wantedrows int
,processed bit
,id uniqueidentifier
);
declare #output table
(
name nvarchar(100)
,rownum int
);
insert into #input
select 'Dave',3,0,newid()
union
select 'Joe',2,0,newid();
while exists(select * from #input where processed = 0)
begin
declare #currentid uniqueidentifier = (select top 1 id from #input where processed = 0);
declare #currentwantedrows int = (select wantedrows from #input where id = #currentid);
declare #i int = 0;
while #i < #currentwantedrows
begin
insert into #output
select name,#i+1
from #input
where id = #currentid;
set #i = #i + 1;
end;
update #input set processed = 1 where id = #currentid;
end
select name,wantedrows from #input;
select * from #output;
You can use a number-table or following trick using a system view to build a sequence:
WITH Nums AS
(
SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT Name, Counter = n
FROM Nums n CROSS JOIN Table1 t1
WHERE n BETWEEN 1 AND Counter
ORDER BY Name, Counter;
Demo
This view has only about 2000 rows, so if you need more you could use a number-table.
http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
( presuming SQL-Server )
Is a hundred copies enough?
create table #c (num)
insert into #c (num)
select 0 union
select 1 union
select 2 union
select 3 union
select 4 union
select 5 union
select 6 union
select 7 union
select 8 union
select 9
select T.Name, c1.num * 10 + c0.num + 1
from T, #c c1, #c c0
where c1.num * 10 + c0.num < T.Counter
drop table #c
You didn't say which version of Sybase. The old ones I've worked on didn't allow derived tables so I had to throw the values into a temp table. But you can see how to extend the idea. This may not be the best approach if this is something you need to do more than once though.

How can I always return 3 values in this stored procedure?

I have an association table. The rows look something like: id, objectID, thingID.
I need a stored procedure to perform a select statement that will return 3 values:
item1ID, item2ID, item3ID
So the query will look something like:
SELECT TOP 3 objectID WHERE thingID = 7 -- (or something)
There may not always be three rows returned, however.
What would the stored proc look like that returned the rows as values, but zeroes for the remaining rows if 3 are not returned?
examples:
data
id: 1, objectID: 12, thingID: 2
id: 2, objectID: 13, thingID: 2
id: 3, objectID: 14, thingID: 3
id: 4, objectID: 15, thingID: 3
id: 5, objectID: 16, thingID: 3
results where thingID = 2
item1ID: 12, item2ID: 13, item3ID: 0
results where thingID = 3
item1ID: 14, item2ID: 15, item3ID: 16
Similar to other answers but using sql table variable instead of temp table.
SQL table variables are cleaned up when the proc completes.
create proc ReturnTop3
as
begin
declare #returnTable as table (
objectId int
)
declare #count int
insert into #returnTable
SELECT TOP 3 objectID WHERE thingID = 7
set #count = (select COUNT(*) from #returnTable)
while (#count < 3)
begin
insert into #returnTable select 0
select #count = #count + 1
end
select * from #returnTable
end
You can create a temp table and do it this way
Create table #test (Id int);
INSERT INTO #test
SELECT TOP 3 objectID WHERE thingID = 7
WHILE (SELECT COUNT(1) from #test) < 3
BEGIN
INSERT INTO #test
VALUES (0)
END
SELECT * FROM #test
DROP TABLE #test
Something like this?
DECLARE #InputThingID INT = 2
DECLARE #ID AS INT, #ItemID1 AS INT = 0, #ItemID2 AS INT = 0, #ItemID3 AS Int = 0
SELECT * INTO #Temp FROM MyTable WHERE ThingID=#InputThingID
SELECT TOP 1 #ID=ID, #ItemID1 = ObjectID FROM #Temp
DELETE FROM #Temp WHERE ID=#ID
SELECT TOP 1 #ID=ID, #ItemID2 = ObjectID FROM #Temp
DELETE FROM #Temp WHERE ID=#ID
SELECT TOP 1 #ID=ID, #ItemID3 = ObjectID FROM #Temp
DELETE FROM #Temp WHERE ID=#ID
SELECT #ItemID1 AS ItemID1, #ItemID2 AS ItemID2, #ItemID3 AS ItemID3
DROP TABLE #Temp
You could do something like this See fiddle
http://sqlfiddle.com/#!3/ed8ca/10
select TOP 3 ID, thing
from
(
select 0 as srt,ID,thing from tbl
where thing = 2
union all
select 1 as srt,0 as ID, 0 as thing
union all
select 2 as srt,0 as ID, 0 as thing
union all
select 3 as srt,0 as ID, 0 as thing
) as t
order by srt,thing
this may be the nicest and most universal, but probably also most difficult solution. The aggregation MAX function is needed, but doesn't affect the result.
SELECT [1] AS Item1ID,[2] AS Item2ID, [3] AS Item3ID FROM
(SELECT ROW_NUMBER()
OVER(order by id) rownum,objectId FROM data where thingId = 3) AS rows
PIVOT(
MAX(objectId)
FOR rownum IN ([1],[2],[3])
) as piv

Select with IN and Like

I have a very interesting problem. I have an SSRS report with a multiple select drop down.
The drop down allows to select more than one value, or all values.
All values is not the problem.
The problem is 1 or the combination of more than 1 option
When I select in the drop down 'AAA' it should return 3 values: 'AAA','AAA 1','AAA 2'
Right now is only returning 1 value.
QUESTION:
How can make the IN statement work like a LIKE?
The Drop down select
SELECT '(All)' AS team, '(All)' AS Descr
UNION ALL
SELECT 'AAA' , 'AAA'
UNION ALL
SELECT 'BBB' , 'BBB'
Table Mytable
ColumnA Varchar(5)
Values for ColumnA
'AAA'
'AAA 1'
'AAA 2'
'BBB'
'BBB 1'
'BBB 2'
SELECT * FROM Mytable
WHERE ColumnA IN (SELECT * FROM SplitListString(#Team, ',')))
Split function
CREATE FUNCTION [dbo].[SplitListString]
(#InputString NVARCHAR(max), #SplitChar CHAR(1))
RETURNS #ValuesList TABLE
(
param NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #ListValue NVARCHAR(max)
DECLARE #TmpString NVARCHAR(max)
DECLARE #PosSeparator INT
DECLARE #EndValues BIT
SET #TmpString = LTRIM(RTRIM(#InputString));
SET #EndValues = 0
WHILE (#EndValues = 0) BEGIN
SET #PosSeparator = CHARINDEX(#SplitChar, #TmpString)
IF (#PosSeparator) > 1 BEGIN
SELECT #ListValue = LTRIM(RTRIM(SUBSTRING(#TmpString, 1, #PosSeparator -1 )))
END
ELSE BEGIN
SELECT #ListValue = LTRIM(RTRIM(#TmpString))
SET #EndValues = 1
END
IF LEN(#ListValue) > 0 BEGIN
INSERT INTO #ValuesList
SELECT #ListValue
END
SET #TmpString = LTRIM(RTRIM(SUBSTRING(#TmpString, #PosSeparator + 1, LEN(#TmpString) - #PosSeparator)))
END
RETURN
END
You can't. But, you can make the like work like the like:
select *
from mytable t join
SplitListString(#Team, ',') s
on t.ColumnA like '%'+s.param+'%'
That is, move the split list to an explicit join. Replace with the actual column name returned by the function, and use the like function.
Or, if you prefer:
select *
from mytable t cross join
SplitListString(#Team, ',') s
where t.ColumnA like '%'+s.param+'%'
The two versions are equivalent and should produce the same execution plan.
Better approach would be to have a TeamsTable (teamID, teamName, ...) and teamMembersTable (teamMemberID, teamID, teamMemberDetails, ...).
Then you an build your dropdown list as
SELECT ... FROM TeamsTable ...;
and
SELECT ... FROM teamMembersTable WHERE teamID IN (valueFromYourDropDown);
Or you can just store your teamID or teamName (or both) in your (equivalent of) teamMembersTable
You're not going to get IN to work the same as LIKE without a lot of work. You could do something like this though (and it would be nice to see some of your actual data though so we could give better solutions):
SELECT *
FROM table
WHERE LEFT(field,3) IN #Parameter
If you'd like better performance, create a code field on your table and update it like this:
UPDATE table
SET codeField = LEFT(field,3)
Then just add an index on that field and run this query to get your results:
SELECT *
FROM table
WHERE codeField IN #Parameter

SQL Replace an empty SQL SELECT with word

I'm trying to solve the following problem:
I would like to make a select, when the result is empty it should be replaced with 'empty'
Else the result should be there.
That is my try:
select case (count*)
when 0 then 'empty'
ELSE
THEVALUEOFTHECOLUM
END AS RESULT
from Database.table where CarID = 12;
Thanks for every comment.
This should work, but you might have to convert the second occurrence of COUNT(*) to VARCHAR depending on the database used:
SELECT
CASE WHEN COUNT(*) = 0
THEN 'empty'
ELSE COUNT(*) -- CONVERT, TO_CHAR, ...
END AS result
FROM Database.table where CarID = 12;
SELECT
CASE
WHEN Q.countvalue = 0 THEN 'Empty'
ELSE CONVERT(NVARCHAR(10), Q.countvalue)
END AS RESULT
FROM
(
SELECT COUNT(*) AS countvalue
FROM Database.table WHERE CarID = 12
) AS Q
This feels hacky to me, but it will return the column data.
It is not one query, but it's still setwise.
declare #tmp table (id int)
declare #cnt int
insert into #tmp select col from table
select #cnt = count(*) from #tmp
if(#cnt = 0)
begin
select 'empty'
end
else
begin
select * from #tmp
end
Is it possible to code it with one query?
If there are no results -> no result found
else
Show all results, not only one
declare #tmp table (id int)
declare #cnt int
insert into #tmp select col from table
select #cnt = count(*) from #tmp
if(#cnt = 0)
begin
select 'empty'
end
else
begin
select * from #tmp
end

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