Can you specify 'unlimited' when using "Select TOP (#variable) * From"? - sql

In SQL Server 2005 or later you can use following syntax to return a a variable number of rows:
Declare #Limit Int
Set #Limit=5
Select Top(#Limit) * From TableName
Is there some magic value, that you can use to let it return all rows? (Thinking of parametrized stored procedure here). Setting limit as 0 will just return no rows, and using negative value will generate run-time error.
I am pretty sure this is not possible, but I haven't found a definite answer. Having this work without If/Else block and duplicating the rather complicated query would be nice.

The simple answer would be to convert to bigint and use its maximum possible value (9223372036854775807). Since that many rows cannot possibly be part of any table, you can be sure you won't miss anything.

To avoid rewriting the query, which I'm assuming is more complicated than the SQL in the question, you could return the row count to replace the variable in a certain case, for example, if the variable = 0, then return all:
declare #Limit Int
set #Limit = 0
if #Limit = 0
// get the row count from the table you are querying if #Limit = 0
select #Limit = count(1) from TABLE_NAME
// then use the value in your query as before
select top(#Limit) * from TABLE_NAME

A way I would do this is to conditionally use TOP when limit is not -1 so that when you want all results it can be done.
Declare #Limit Int
Set #Limit=5
IF #Limit = -1
SELECT * FROM tbl_Products
ELSE
Select Top(#Limit) * From tbl_Products

Instead of using a number use TOP 100 Percent in your top clause. Let the percentage handle the numbers.
SELECT TOP 100 PERCENT *
FROM TABLE_Name
If you plan to use this inside a stored procedure simply use IF..ELSE Logic as follows .....
CREATE PROCEDURE TestProc
#Top_N INT = NULL
AS
BEGIN
SET NOCOUNT ON;
IF (#Top_N IS NULL)
BEGIN
SELECT * FROM TABLE_NAME
END
ELSE
BEGIN
SELECT TOP (#Top_N) * FROM TABLE_NAME
END
END
Using Percent pass default value of 100% as follows
CREATE PROCEDURE TestProc
#Top_N INT = 100
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP (#Top_N) PERCENT * FROM TABLE_NAME
END

Related

Create Dynamic Array

I have a table name Tankdet which has two columns TCO and Tanks.
Here is the stored procedure code to return the count of leased, owned and principle tanks:
ALTER PROCEDURE [dbo].[sp_Dash_LeaseInformation]
AS
BEGIN
DECLARE #leased INT
DECLARE #owned INT
DECLARE #principal INT
SET NOCOUNT ON
SET #owned = (SELECT COUNT(*) FROM Tankdet WHERE ownleasetank='owned')
SET #leased = (SELECT COUNT(*) FROM Tankdet WHERE ownleasetank='leased')
SET #principal = (SELECT COUNT(*) FROM Tankdet WHERE ownleasetank='principle')
SELECT [Leased]=#leased,[Owned]=#owned,[Principal]=#principal
SET NOCOUNT OFF
END
The table looks like this
and it returns the values like,
leased = 4,owned = 4,principle = 7
The values which is used to show the tank count in my frontend.
The problem is here I display the whole count of principle tanks and returns it to my frontend.
Now I have the problem of creating array in getting the Principle Tank count as separate for each TCO, and I have to return it as:
SELECT [Leased]=#leased, [Owned]=#owned, [vibe]=#vibe, [baru]=#baru,[sarath]=#sarath, [karthi]=#karthi, [nth...]=#nth.....
The nth is because the TCO values may get added in future for purpose, and the selected values have to return like:
leased=4,owned=4,vibe=2,baru=3,sarath=1,karthi=1,nth= n......,
Tables (and resultsets) have a fixed number of columns and a variable number of rows. So simply return
SELECT ownleasetank, COUNT(*) TankCount
FROM Tankdet
GROUP BY ownleasetank
That will return one row per ownleasetank value along with the count.

how to incorporate dynamic column name in sql query

I have a table, which has columns, say
Week1,Week2, Week3 and so on.
I have a stored procedure, and based on the number input, i want to select that column.
Example, if input is 4 then I want to make the query,
select *
from table_name
where Week4=<something>
Is there any way to do this other than using dynamic query? Because this dynamic thing will be just a small part of a huge query.
The comments about normalization are right, but if you have no choice, you can use "or" clauses:
declare #inputvalue int;
set #int = 1;
select *
from <table>
where (week1 = <something> and #inputvalue = 1)
or (week2 = <something> and #inputvalue = 2)
or (week3 = <something> and #inputvalue = 3)
or (week4 = <something> and #inputvalue = 4)
This will be very slow if the tables are of any size, as you won't be using any indexes. I wouldn't suggest doing this unless you're absolutely unable to change the table structure.
I realize this isn't what you asked for, but I figured I'd point out to some people who find this what you mean by doing this as a dynamic query.
You'd just write a procedure and hold the field name in there. Assuming that the naming standard is the same, so the input value would be the week# (1,2,7,27, 123, etc.) and the field name would directly correspond (Week1, Week2, Week7, Week27, Week123, etc.)
create or replace procedure myweek(week_in varchar2)
is
dyn_sql varchar2(1000);
begin
dyn_sql := 'select * from table_name where week'||week_in||' = ''something;'' '
execute immediate dyn_sql;
end;
/
Then to call it you'd just do something like :
exec myweek(27); and it would generate the sql:
select * from table_name where week27 = 'something';

Create a function for generating random number in SQL Server trigger

I have to create a function in a SQL Server trigger for generating random numbers after insert. I want to update the column with that generated random number please help what I have missed in my code.
If you know other ways please suggest a way to complete my task.
This my SQL Server trigger:
ALTER TRIGGER [dbo].[trgEnquiryMaster]
ON [dbo].[enquiry_master]
AFTER INSERT
AS
declare #EnquiryId int;
declare #ReferenceNo varchar(50);
declare #GenReferenceNo NVARCHAR(MAX);
select #EnquiryId = i.enquiry_id from inserted i;
select #ReferenceNo = i.reference_no from inserted i;
BEGIN
SET #GenReferenceNo = 'CREATE FUNCTION functionRandom (#Reference VARCHAR(MAX) )
RETURNS VARCHAR(MAX)
As
Begin
DECLARE #r varchar(8);
SELECT #r = coalesce(#r, '') + n
FROM (SELECT top 8
CHAR(number) n FROM
master..spt_values
WHERE type = P AND
(number between ascii(0) and ascii(9)
or number between ascii(A) and ascii(Z)
or number between ascii(a) and ascii(z))
ORDER BY newid()) a
RETURNS #r
END
'
EXEC(#GenReferenceNo)
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
-- update statements for trigger here
UPDATE enquiry_master
SET reference_no ='updated'
WHERE enquiry_id = #EnquiryId
END
To generate random numbers, just call CRYPT_GEN_RANDOM which was introduced in SQL Server 2008:
SELECT CRYPT_GEN_RANDOM(5) AS [Hex],
CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(5), 2) AS [HexStringWithout0x],
CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(10)) AS [Translated-ASCII],
CONVERT(NVARCHAR(20), CRYPT_GEN_RANDOM(20)) AS [Translated-UCS2orUTF16]
returns:
Hex HexStringWithout0x Translated-ASCII Translated-UCS2orUTF16
0x4F7D9ABBC4 0ECF378A7A ¿"bü<ݱØï 붻槬㟰添䛺⯣왚꒣찭퓚
If you are ok with just 0 - 9 and A - F, then the CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(5), 2) is all you need.
Please see my answer on DBA.StackExchange on a similar question for more details:
Password generator function
The UPDATE statement shown in the "Update" section of that linked answer is what you want, just remove the WHERE condition and add the JOIN to the Inserted pseudo-table.
The query should look something like the following:
DECLARE #Length INT = 10;
UPDATE em
SET em.[reference_no] = rnd.RandomValue
FROM dbo.enquiry_master em
INNER JOIN Inserted ins
ON ins.enquiry_id = em.enquiry_id
CROSS APPLY dbo.GenerateReferenceNo(CRYPT_GEN_RANDOM((em.[enquiry_id] % 1) + #Length)) rnd;
And since the function is slightly different, here is how it should be in order to get both upper-case and lower-case letters:
CREATE FUNCTION dbo.GenerateReferenceNo(#RandomValue VARBINARY(20))
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
WITH base(item) AS
(
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL UNION ALL
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL
), items(item) AS
(
SELECT NULL
FROM base b1
CROSS JOIN base b2
)
SELECT (
SELECT TOP (LEN(#RandomValue))
SUBSTRING('1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm',
(CONVERT(TINYINT, SUBSTRING(#RandomValue, 1, 1)) % 62) + 1,
1) AS [text()]
FROM items
FOR XML PATH('')
) AS [RandomReferenceNo];
GO
And please follow the usage shown above, passing in CRYPT_GEN_RANDOM((em.[enquiry_id] % 1) + #Length), not: CRYPT_GEN_RANDOM(#RefferenceNOLength).
Other notes:
#marc_s already explained the one-row vs multiple-rows flaw and how to fix that.
not only is a trigger not the place to create a new object (i.e. the function), that function wouldn't have worked anyway since the call to newid() (in the ORDER BY) is not allowed in a function.
You don't need to issue two separate SELECTs to set two different variables. You could do the following:
SELECT #EnquiryId = i.enquiry_id,
#ReferenceNo = i.reference_no
FROM TableName i;
Passing strings into a function requires quoting those strings inside of single-quotes: ASCII('A') instead of ASCII(A).
UPDATE
The full Trigger definition should be something like the following:
ALTER TRIGGER [dbo].[trgEnquiryMaster]
ON [dbo].[enquiry_master]
AFTER INSERT
AS
BEGIN
DECLARE #Length INT = 10;
UPDATE em
SET em.[reference_no] = rnd.RandomValue
FROM dbo.enquiry_master em
INNER JOIN Inserted ins
ON ins.enquiry_id = em.enquiry_id
CROSS APPLY dbo.GenerateReferenceNo(
CRYPT_GEN_RANDOM((em.[enquiry_id] % 1) + #Length)
) rnd;
END;
A trigger should be very nimble and quick - it is no place to do heavy and time-intensive processing, and definitely no place to create new database objects since (a) the trigger is executed in the context of the code causing it to fire, and (b) you cannot control when and how often the trigger is fired.
You need to
define and create your function to generate that random value during database setup - once, before any operations are executed on the database
rewrite your trigger to take into account that multiple rows could be inserted at once, and in that case, the Inserted table will contain multiple rows which all have to be handled.
So your trigger will look something like this (with several assumptions by me - e.g. that enquiry_id is the primary key on your table - you need this to establish the INNER JOIN between your data table and the Inserted pseudo table:
ALTER TRIGGER [dbo].[trgEnquiryMaster]
ON [dbo].[enquiry_master]
AFTER INSERT
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
-- update statements for trigger here
UPDATE enq
SET reference_no = dbo.GenerateRandomValue(.....)
FROM enquiry_master enq
INNER JOIN inserted i ON enq.enquiry_id = i.enquiry_id

Why my T-SQL (WHILE) does not work?

In my code, I need to test whether specified column is null and the most close to 0 as possible (it can holds numbers from 0 to 50) so I have tried the code below.
It should start from 0 and for each value test the query. When #Results gets null, it should return. However, it does not work. Still prints 0.
declare #hold int
declare #Result int
set #hold0
set #Result=0
WHILE (#Result!=null)
BEGIN
select #Result=(SELECT Hold from Numbers WHERE Name='Test' AND Hold=#hold)
set #hold=#hold+1
END
print #hold
First, you can't test equality of NULL. NULL means an unknown value, so you don't know whether or not it does (or does not) equal any specific value. Instead of #Result!=NULL use #result IS NOT NULL
Second, don't use this kind of sequential processing in SQL if you can at all help it. SQL is made to handle sets, not process things sequentially. You could do all of this work with one simple SQL command and it will most likely run faster anyway:
SELECT
MIN(hold) + 1
FROM
Numbers N1
WHERE
N1.name = 'Test' AND
NOT EXISTS
(
SELECT
*
FROM
Numbers N2
WHERE
N2.name = 'Test' AND
N2.hold = N1.hold + 1
)
The query above basically tells the SQL Server, "Give me the smallest hold value plus 1 (MIN(hold) + 1) in the table Numbers where the name is test (name = 'Test') and where the row with name of 'Test' and hold of one more that that does not exist (the whole "NOT EXISTS" part)". In the case of the following rows:
Name Hold
-------- ----
Test 1
Test 2
NotTest 3
Test 20
SQL Server finds all of the rows with name of "Test" (1, 2, 20) then finds which ones don't have a row with name = Test and hold = hold + 1. For 1 there is a row with Test, 2 that exists. For Test, 2 there is no Test, 3 so it's still in the potential results. For Test, 20 there is no Test, 21 so that leaves us with:
Name Hold
-------- ----
Test 2
Test 20
Now SQL Server looks for MIN(hold) and gets 2 then it adds 1, so you get 3.
SQL Server may not perform the operations exactly as I described. The SQL statement tells SQL Server what you're looking for, but not how to get it. SQL Server has the freedom to use whatever method it determines is the most efficient for getting the answer.
The key is to always think in terms of sets and how do those sets get put together (through JOINs), filtered (through WHERE conditions or ON conditions within a join, and when necessary, grouped and aggregated (MIN, MAX, AVG, etc.).
have you tried
WHILE (#Result is not null)
BEGIN
select #Result=(SELECT Hold from Numbers WHERE Name='Test' AND Hold=#hold)
set #hold=#hold+1
END
Here's a more advanced version of Tom H.'s query:
SELECT MIN(N1.hold) + 1
FROM Numbers N1
LEFT OUTER JOIN Numbers N2
ON N2.Name = N1.Name AND N2.hold = N1.hold + 1
WHERE N1.name = 'Test' AND N2.name IS NULL
It's not as intuitive if you're not familiar with SQL, but it uses identical logic. For those who are more familiar with SQL, it makes the relationship between N1 and N2 easier to see. It may also be easier for the query optimizer to handle, depending on your DBMS.
Try this:
declare #hold int
declare #Result int
set #hold=0
set #Result=0
declare #max int
SELECT #max=MAX(Hold) FROM Numbers
WHILE (#hold <= #max)
BEGIN
select #Result=(SELECT Hold from Numbers WHERE Name='Test' AND Hold=#hold)
set #hold=#hold+1
END
print #hold
While is tricky in T-SQL - you can use this for (foreach) looping through (temp) tables too - with:
-- Foreach with T-SQL while
DECLARE #tempTable TABLE (rownum int IDENTITY (1, 1) Primary key NOT NULL, Number int)
declare #RowCnt int
declare #MaxRows int
select #RowCnt = 1
select #MaxRows=count(*) from #tempTable
declare #number int
while #RowCnt <= #MaxRows
begin
-- Number from given RowNumber
SELECT #number=Number FROM #tempTable where rownum = #RowCnt
-- next row
Select #RowCnt = #RowCnt + 1
end

SQL - Counting Returned Records

I'm building a stored procedure. This stored procedure needs to insert a record if a record with a specific value does not exist. If the value does exist, I need to update the record. The problem I'm having is determining if a record with the given value exists or not. I am using the following code:
DECLARE #record1ID as char(36)
SET #record1ID = (SELECT TOP 1 ID FROM Person WHERE [Role]='Manager')
DECLARE #record2ID as char(36)
SET #record2ID = (SELECT TOP 1 d.ID FROM Department d WHERE d.[ManagerID]=#record1ID)
-- If #record2ID is set update record, otherwise add record
-- how do I setup this if/else statement?
Thank you!
If this were a SQL Server as it looks like, you could do a count like this:
declare #rec_counter as int
set #rec_counter = 0
select #rec_counter = count(*) FROM Department d WHERE d.[ManagerID]=#record1
if (#rec_counter > 0)
begin
-- do whatever here
end
IF (EXISTS YOUR_SELECT)
BEGIN ...
or
IF (#record2ID IS NULL)
BEGIN ...
or use select count(*) instead of selecting a value