Create Dynamic Array - sql

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.

Related

Generate a unique column sequence value based on a query handling concurrency

I have a requirement to automatically generate a column's value based on another query's result. Because this column value must be unique, I need to take into consideration concurrent requests. This query needs to generate a unique value for a support ticket generator.
The template for the unique value is CustomerName-Month-Year-SupportTicketForThisMonthCount.
So the script should automatically generate:
AcmeCo-10-2019-1
AcmeCo-10-2019-2
AcmeCo-10-2019-3
and so on as support tickets are created. How can ensure that AcmeCo-10-2019-1 is not generated twice if two support tickets are created at the same time for AcmeCo?
insert into SupportTickets (name)
select concat_ws('-', #CustomerName, #Month, #Year, COUNT())
from SupportTickets
where customerName = #CustomerName
and CreatedDate between #MonthStart and #MonthEnd;
One possibility:
Create a counter table:
create table Counter (
Id int identify(1,1),
Name varchar(64)
Count1 int
)
Name is a unique identifier for the sequence, and in your case name would be CustomerName-Month-Year i.e. you would end up with a row in this table for every Customer/Year/Month combination.
Then write a stored procedure similar to the following to allocate a new sequence number:
create procedure [dbo].[Counter_Next]
(
#Name varchar(64)
, #Value int out -- Value to be used
)
as
begin
set nocount, xact_abort on;
declare #Temp int;
begin tran;
-- Ensure we have an exclusive lock before changing variables
select top 1 1 from dbo.Counter with (tablockx);
set #Value = null; -- if a value is passed in it stuffs us up, so null it
-- Attempt an update and assignment in a single statement
update dbo.[Counter] set
#Value = Count1 = Count1 + 1
where [Name] = #Name;
if ##rowcount = 0 begin
set #Value = 10001; -- Some starting value
-- Create a new record if none exists
insert into dbo.[Counter] ([Name], Count1)
select #Name, #Value;
end;
commit tran;
return 0;
end;
You could look into using a TIME type instead of COUNT() to create unique values. That way it is much less likely to have duplicates. Hope that helps

Explain syntax: select #au_id = au_id from #mytemp (Equals sign in SELECT clause) [duplicate]

What are the differences between the SET and SELECT statements when assigning variables in T-SQL?
Quote, which summarizes from this article:
SET is the ANSI standard for variable assignment, SELECT is not.
SET can only assign one variable at a time, SELECT can make multiple assignments at once.
If assigning from a query, SET can only assign a scalar value. If the query returns multiple values/rows then SET will raise an error. SELECT will assign one of the values to the variable and hide the fact that multiple values were returned (so you'd likely never know why something was going wrong elsewhere - have fun troubleshooting that one)
When assigning from a query if there is no value returned then SET will assign NULL, where SELECT will not make the assignment at all (so the variable will not be changed from its previous value)
As far as speed differences - there are no direct differences between SET and SELECT. However SELECT's ability to make multiple assignments in one shot does give it a slight speed advantage over SET.
I believe SET is ANSI standard whereas the SELECT is not. Also note the different behavior of SET vs. SELECT in the example below when a value is not found.
declare #var varchar(20)
set #var = 'Joe'
set #var = (select name from master.sys.tables where name = 'qwerty')
select #var /* #var is now NULL */
set #var = 'Joe'
select #var = name from master.sys.tables where name = 'qwerty'
select #var /* #var is still equal to 'Joe' */
When writing queries, this difference should be kept in mind :
DECLARE #A INT = 2
SELECT #A = TBL.A
FROM ( SELECT 1 A ) TBL
WHERE 1 = 2
SELECT #A
/* #A is 2*/
---------------------------------------------------------------
DECLARE #A INT = 2
SET #A = (
SELECT TBL.A
FROM ( SELECT 1 A) TBL
WHERE 1 = 2
)
SELECT #A
/* #A is null*/
Aside from the one being ANSI and speed etc., there is a very important difference that always matters to me; more than ANSI and speed. The number of bugs I have fixed due to this important overlook is large. I look for this during code reviews all the time.
-- Arrange
create table Employee (EmployeeId int);
insert into dbo.Employee values (1);
insert into dbo.Employee values (2);
insert into dbo.Employee values (3);
-- Act
declare #employeeId int;
select #employeeId = e.EmployeeId from dbo.Employee e;
-- Assert
-- This will print 3, the last EmployeeId from the query (an arbitrary value)
-- Almost always, this is not what the developer was intending.
print #employeeId;
Almost always, that is not what the developer is intending. In the above, the query is straight forward but I have seen queries that are quite complex and figuring out whether it will return a single value or not, is not trivial. The query is often more complex than this and by chance it has been returning single value. During developer testing all is fine. But this is like a ticking bomb and will cause issues when the query returns multiple results. Why? Because it will simply assign the last value to the variable.
Now let's try the same thing with SET:
-- Act
set #employeeId = (select e.EmployeeId from dbo.Employee e);
You will receive an error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
That is amazing and very important because why would you want to assign some trivial "last item in result" to the #employeeId. With select you will never get any error and you will spend minutes, hours debugging.
Perhaps, you are looking for a single Id and SET will force you to fix your query. Thus you may do something like:
-- Act
-- Notice the where clause
set #employeeId = (select e.EmployeeId from dbo.Employee e where e.EmployeeId = 1);
print #employeeId;
Cleanup
drop table Employee;
In conclusion, use:
SET: When you want to assign a single value to a variable and your variable is for a single value.
SELECT: When you want to assign multiple values to a variable. The variable may be a table, temp table or table variable etc.
Surround everything in select with ().
Make sure you are only returning 1 item
eg
ET #sql_update = (select left(#sql_update, len(#sql_update)-1))
SET #Telephone2 = (SELECT REPLACE(LTRIM(REPLACE(#Telephone2, '0', ' ')), ' ', '0'))

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

Incremental count column based on another column contents

I need to populate a column with the running count based on another column contents. The table is like this:
count seq_num
1 123-456-789
1 123-456-780
1 123-456-990
2 123-456-789
2 123-456-990
So, as the seq_num column changes, the counter resets to '1' and as the column repeats, the counter increments by 1.
This is using SQL2000, and the seq_num field is varchar.
Any ideas?
If you're inserting, you can use a subquery:
insert into
table (count, seq_num)
values
((select count(*)+1 from table where seq_num = #seq)
,#seq)
Otherwise, you'll need to have a date on there or some way of telling it how to determine what was first:
update table
set count =
(select count(*)+1 from table t2
where t2.seq_num = table.seq_num
and t2.insertdate < table.insertdate)
if you need to be able to continue updating this in the future, you might try this. It's a few steps but would fix it AND set it up for future use. (probably need to check my syntax - I mess with ORacle more now, so I may have mixed up some things - but the logic should work.)
first, create a table to contain the current counter level per sequence:
Create newTable (counter int, sequence varchar)
then, fill it with data like this:
insert into newTable
(select distinct 0 as Counter, sequence
from table)
This will put each sequence number in the table one time and the counter for each will be set at 0.
Then, create an update proc with TWO update statements and a bit of extra logic:
Create procedere counterUpdater(#sequence varchar) as
Declare l_counter as int;
select l_counter = counter
from newTable
where sequence = #sequence
--assuming you have a primary key in the table.
Declare #id int;
Select top 1 #id = id from table
where sequence = #sequence
and counter is null;
--update the table needing edits.
update table
set counter = l_counter + 1
where id = #id
--update the new table so you can keep track of which
--counter you are on
update newTable
set counter = l_counter + 1
where id = #id
Then run a proc to execute this proc for each record in your table.
Now you should have a "newTable" filled with the currently used counter for each record in the table. Set up your insert proc so that anytime a new record is created, if it is a sequence not already in the newTable, you add it with a count of 1 and you put a count of 1 in the main table. If the sequence DOES already exist, use the logic above (increment the count already in use the "newTable" and place that count as the counter value in the newTable and the mainTable.
Basically, this method decided to use memory in place of querying the existing table. It will become most beneficial if you have a large table with lots of repeated sequence numbers. If your sequence numbers only happen two or three times, you probably want to do a query instead when you update and then later insert:
First, to update:
--find out the counter value
Declare l_counter int
select l_counter = max(counter)
from table where sequence = #sequence
update table
set counter = l_counter + 1
where id = (select top 1 id from table where sequence = #sequence
and counter is null)
then run that for each record.
Then, when inserting new records:
Declare l_counter int
select l_counter = max(counter)
from table where sequence = #sequence
IsNull(l_counter, 0)
Insert into table
(counter, sequence) values (l_counter + 1, #sequence)
Again, I'm positive I've mixed-and-matched my syntaxes here, but the concepts should work. OF course, it's a "one at a time" approach instead of set based, so it might be a little inefficient, but it will work.

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