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

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

Related

TSQL: Insert Into Table with if condition

I've made a small simple table here:
declare #live bit = 1
declare #temp table(id int, title varchar(30))
insert into #temp (id, title)
select 1, 'myTitle1'
union select 2, 'myTitle2'
union select 3, 'myTitle3'
select * from #temp
Output:
id title
-------------
1 myTitle1
2 myTitle2
3 myTitle3
Now I want the title attribute to be dependent from #live
I'll show it in pseudo-code:
declare #live bit = 1
declare #temp table(id int, title varchar(30))
insert into #temp (id, title)
select 1, IF (#live == 1) THEN 'myTitle1_live' ELSE 'myTitle1'
union select 2, IF (#live == 1) THEN 'myTitle2_live' ELSE 'myTitle2'
union select 3, IF (#live == 1) THEN 'myTitle3_live' ELSE 'myTitle3'
select * from #temp
How would this look in sql syntax?
I think you just want a conditional expression:
select id,
(case when #live = 1 then concat(title, '_live') else title end)
from #temp;
If the data is already in the table, then you would use update:
update t
set #title = concat(title, '_live')
from #temp t
where #live = 1;

Conditionally modify query based on parameter

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.

return data and count as json toghether

Consider following table:
[Id: 1, Name: "A1"], [Id: 2, Name: "A2"], ... [Id: 100, Name: "A100"]
I need data with Id <= 50, with paging. So I write
select *
from data
where Id <= 50 order by Id 0 rows fetch next 10 rows only
And then I execute another query
select count(1)
from data
where Id <= 50
Then I return json like this to client
{
count:50,
values:[{Id:1, Name:'A1'}, ..., {Id:10,Name:'A10'}]
}
How can I create that json in SQL Server 2016 using 1 select and roundtrip to db only?
This works...is it what you are after is unclear.
DECLARE #X TABLE(Id INT)
INSERT #X SELECT 1
INSERT #X SELECT 2
INSERT #X SELECT 3
SELECT
*
FROM
#X
INNER JOIN(SELECT YourCount=COUNT(*) FROM #X)AS X ON 1=1
And here is a more efficient way when the filter clause is known.
DECLARE #X TABLE(Id INT)
INSERT #X SELECT 1
INSERT #X SELECT 2
INSERT #X SELECT 3
DECLARE #MyFilterCount INT = (SELECT COUNT(*) FROM #X WHERE 1=1)
SELECT
MyCount=#MyFilterCount
,*
FROM
#X
I found the solution.
select (select count(1) from data where Id <= 50) as total,
(select * from data where Id <= 50 order by Id 0 rows fetch next 10 rows only FOR JSON PATH) as values
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
It results into
{
count:50,
values:[{Id:1, Name:'A1'}, ..., {Id:10,Name:'A10'}]
}

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.

Subtract all values from data

Table data look like this
id val
1 4
2 2
3 1
I want result of subtract valu of val field in one sql statement.
like it should be like 4-2-1 = 1 if order by id asc, 1-2-4 = -5 if order by id desc.
You can try this
DECLARE #Table TABLE(
ID INT,
Val INT
)
INSERT INTO #Table (ID,Val) SELECT 1, 4
INSERT INTO #Table (ID,Val) SELECT 2, 2
INSERT INTO #Table (ID,Val) SELECT 3, 1
SELECT SUM(Val * CASE WHEN RowID = 1 THEN 1 ELSE -1 END)
FROM (
SELECT *,
ROW_NUMBER() OVER (ORDER BY ID) RowID
FROM #Table
) sub
You can declare a variable and increment it in the select statement:
declare #sum float
select #sum = case when #sum is null then value else #sum - value end
from YourTable
order by id
select #sum
To reverse the subtraction order, change order by id to order by id desc.
if you want to use just the sql without temp tables or variables:
select fromid.val - sumid.val
from (
select val
from t
where id = (
select min(id)
from t
)
) fromid cross join (
select sum(val) as val
from t
where id > (
select min(id)
from t
)
) sumid