I am trying automatically increment the alpha numeric field (say for productid in product table).
But I am getting an error (see below).
Could somebody please look into this error or any other approach to achieve this task?
My table details:
create table tblProduct
(
id varchar(15)
)
create procedure spInsertInProduct
AS
Begin
DECLARE #PId VARCHAR(15)
DECLARE #NId INT
DECLARE #COUNTER INT
SET #PId = 'PRD00'
SET #COUNTER = 0
--This give u max numeric id from the alphanumeric id
SELECT #NId = cast(substring(id, 3, len(id)) as int) FROM tblProduct group by left(id, 2) order by id
--here u increse the vlaue to numeric id by 1
SET #NId = #NId + 1
--GENERATE ACTUAL APHANUMERIC ID HERE
SET #PId = #PId + cast(#NId AS VARCHAR)
INSERT INTO tblProduct(id)values (#PId)
END
I am gettin the following error:
Msg 8120, Level 16, State 1, Procedure spInsertInProduct, Line 10
Column 'tblProduct.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Msg 8120, Level 16, State 1, Procedure spInsertInProduct, Line 10
Column 'tblProduct.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.**
SELECT #NId = max(
cast(substring(id, 4, len(id)) as int)
)
FROM tblProduct;
This assumes your substring function is returning the numeric portion of your id. I made changes since in other examples your id's start with PRD.
Side note: There is no reason to have you Product ID's start with PRD in the database. If this were an identity field, you could set it to increment by 1 and in any display just have: 'PRD' + Cast(ID as varchar25)) as ProductID. Maybe it is not that simple of all ID's do not start with the same three letters.
Your line
SELECT #NId = cast(substring(id, 3, len(id)) as int)
FROM tblProduct
group by left(id, 2)
order by id
is not doing what you were wanting it to. It is failing because you can't include id in the select directly, because you're grouping by left(id, 2), not id itself. You cannot put something into the select statement when grouping, unless it is part of the Group By, or an aggregate (such as SUM and MAX.)
(Edit: Corrected, Left and Substring are not 0 based--to get the PRD tag and such, we need substring 4, and left 3.)
The correct way of doing this would be:
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct
group by left(id, 3)
order by left(id, 3)
I have tested your stored procedure in SQL Server 2000 and Query Analyzer it works very well. Just I have removed create table code from that.
Related
My question is similar to this.
I made a scalar function like follows:
CREATE FUNCTION [dbo].[MyFunction](#table [TableModel] READONLY)
RETURNS DECIMAL(18, 6) AS
BEGIN
DECLARE #sql NVARCHAR(MAX), #params NVARHCAR(MAX), #value DECIMAL(16, 8);
SELECT #sql = formula, #params = params FROM formulas WHERE id = (SELECT TOP 1 id_formula FROM #table)
EXEC sp_executesql #sql, #params, #table=#table, #value=#value OUTPUT;
RETURN #value;
END
Where a formula SQL could be something like:
SELECT #value = SUM(value) / AVG(value) FROM #table
And it could have more columns if needed.
The model table looks like so:
CREATE TYPE [dbo].[TableModel] AS TABLE(
[formula] INT NOT NULL,
[value] DECIMAL(16, 8) NOT NULL
)
And I want to use it like so:
SELECT od.id, od.col1, od.col2,
dbo.MyFunction((SELECT id.formula, id.value FROM #raw_data id WHERE id.id = od.id)) as result
FROM #data od
GROUP BY od.id, od.col1, od.col2
Where the ID unique and multiple rows will have the same id.
Basically, what I'm trying to do in a single query I want to call a function that has a table parameter. But I want this table to be a subquery.
I'm aware that you can call the function with a table variable as mentioned in this answer.
Is this possible in any way? I'm having this error at executing:
Msg 116, Level 16, State 1, Line 30
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
I am working on SQL Server 2008 and trying to update a table. I just need the first one hundred rows to be updated with a 15 digit sequence. I have tried the following:
Declare #id varchar
Set #id = 2435435345962
UPDATE TOP (100) Table1
SET #id = Column_nm = #id + 1
GO
but I am getting the following error message:
Msg 426, Level 16, State 1, Line 3
The length 1 of the receiving variable is less than the length 15 of the column 'Column_nm'.
Several things. You want start in 0 so you can add 00-99.
So lets start with the base
WITH cte as (
SELECT TOP 100 *,
ROW_NUMBER() OVER (ORDER BY somefield) -1 as rn -- row_number start with 1.
FROM Table1
ORDER BY somefield
)
UPDATE cte
SET anotherfield = '1234567890ABC' + REPLACE(STR(rn, 2, 0), ' ', '0')
^^ 13 chars ^^ two digits number
The error message is about length of your variable. In SQL Server the default length of varchar is 1 (from msdn.microsoft.com i have only the french link sorry).
Declare #id varchar(15)
Set #id = 2435435345962
UPDATE top (100) Table1
SET #id = Column_nm = #id + 1
GO
You must declare id with length of 15 to put your string in this variable.
I have two MSSQL2008 tables like this:
I have problem on the unit conversion logic.
The result I expect like this :
1589 cigar = 1ball, 5slop, 8box, 2pcs
52 pen = 2box, 12pcs
Basically I'm trying to take number (qty) from one table and to convert (split) him into the units which I defined in other table!
Note : Both table are allowed to add new row and new data (dinamic)
How can I get these results through a SQL stored procedure?
i totally misunderstand the question lest time so previous answer is removed (you can see it in edit but it's not relevant for this question)... However i come up with solution that may solve your problem...
NOTE: one little think about this solution, if you enter the value in second table like this
+--------+-------+
| Item | qty |
+--------+-------+
| 'cigar'| 596 |
+--------+-------+
result for this column will be
598cigar = 0ball, 5slop, 8box, 0pcs
note that there is a ball and pcs is there even if their value is 0, that probably can be fix if you don't want to show that value but I let you to play with it...
So let's back to solution and code. Solution have two stored procedures first one is the main and that one is the one you execute. I call it sp_MainProcedureConvertMe. Here is a code for that procedure:
CREATE PROCEDURE sp_MainProcedureConvertMe
AS
DECLARE #srcTable TABLE(srcId INT IDENTITY(1, 1), srcItem VARCHAR(50), srcQty INT)
DECLARE #xTable TABLE(xId INT IDENTITY(1, 1), xVal1 VARCHAR(1000), xVal2 VARCHAR(1000))
DECLARE #maxId INT
DECLARE #start INT = 1
DECLARE #sItem VARCHAR(50)
DECLARE #sQty INT
DECLARE #val1 VARCHAR(1000)
DECLARE #val2 VARCHAR(1000)
INSERT INTO #srcTable (srcItem, srcQty)
SELECT item, qty
FROM t2
SELECT #maxId = (SELECT MAX(srcId) FROM #srcTable)
WHILE #start <= #maxId
BEGIN
SELECT #sItem = (SELECT srcItem FROM #srcTable WHERE srcId = #start)
SELECT #sQty = (SELECT srcQty FROM #srcTable WHERE srcId = #start)
SELECT #val1 = (CAST(#sQty AS VARCHAR) + #sItem)
EXECUTE sp_ConvertMeIntoUnit #sItem, #sQty, #val2 OUTPUT
INSERT INTO #xTable (xVal1, xVal2)
VALUES (#val1, #val2)
SELECT #start = (#start + 1)
CONTINUE
END
SELECT xVal1 + ' = ' + xVal2 FROM #xTable
GO
This stored procedure have two variables as table #srcTable is basically your second table but instead of using id of your table it's create new srcId which goes from 1 to some number and it's auto_increment it's done because of while loop to avoid any problems when there is some deleted values etc. so we wanna be sure that there wont be any skipped number or something like that.
There is few more variables some of them is used to make while loop work other one is to store data. I think it's not hard to figure out from code what are they used for...
While loop iterate throughout all rows from #srcTable take values processing them and insert them into #xTable which basically hold result.
In while loop we execute second stored procedure which have a task to calculate how many unit of something is there in specific number of item. I call her sp_ConvertMeIntoUnit and here is a code for her:
CREATE PROCEDURE sp_ConvertMeIntoUnit
#inItemName VARCHAR(50),
#inQty INT,
#myResult VARCHAR(5000) OUT
AS
DECLARE #rTable TABLE(rId INT IDENTITY(1, 1), rUnit VARCHAR(50), rQty INT)
DECLARE #yTable TABLE(yId INT IDENTITY(1, 1), yVal INT, yRest INT)
DECLARE #maxId INT
DECLARE #start INT = 1
DECLARE #quentity INT = #inQty
DECLARE #divider INT
DECLARE #quant INT
DECLARE #rest INT
DECLARE #result VARCHAR(5000)
INSERT INTO #rTable(rUnit, rQty)
SELECT unit, qty
FROM t1
WHERE item = #inItemName
ORDER BY qty DESC
SELECT #maxId = (SELECT MAX(rId) FROM #rTable)
WHILE #start <= #maxId
BEGIN
SELECT #divider = (SELECT rQty FROM #rTable WHERE rId = #start)
SELECT #quant = (#quentity / #divider)
SELECT #rest = (#quentity % #divider)
INSERT INTO #yTable(yVal, yRest)
VALUES (#quant, #rest)
SELECT #quentity = #rest
SELECT #start = (#start + 1)
CONTINUE
END
SELECT #result = COALESCE(#result + ', ', '') + CAST(y.yVal AS VARCHAR) + r.rUnit FROM #rTable AS r INNER JOIN #yTable AS y ON r.rId = y.yId
SELECT #myResult = #result
GO
This procedure contain three parametars it's take two parameters from the first one and one is returned as result (OUTPUT). In parameters are Item and Quantity.
There are also two variables as table #rTable we stored values as #rId which is auto increment and always will go from 1 to some number no matter what is there Id's in the first table. Other two values are inserted there from the first table based on #inItemName parameter which is sanded from first procedure... From the your first table we use unit and quantity and stored them with rId into table #rTable ordered by Qty from biggest number to lowest. This is a part of code for that
INSERT INTO #rTable(rUnit, rQty)
SELECT unit, qty
FROM t1
WHERE item = #inItemName
ORDER BY qty DESC
Then we go into while loop where we do some maths. Basically we store into variable #divider values from #rTable. In the first iteration we take the biggest value calculate how many times it's contain into the number (second parameter we pass from first procedure is qty from the yours second table) and store it into #quant than we also calculate modulo and store it into variable #rest. This line
SELECT #rest = (#quentity % #divider)
After that we insert our values into #yTable. Before we and with iteration in while loop we assign #quentity variable value of #rest value because we need to work just with the remainder not with whole quantity any more. In second iteration we take next (the second greatest number in our #rTable) number and procedure repeat itself...
When while loop finish we create a string. This line here:
SELECT #result = COALESCE(#result + ', ', '') + CAST(y.yVal AS VARCHAR) + r.rUnit FROM #rTable AS r INNER JOIN #yTable AS y ON r.rId = y.yId
This is the line you want to change if you want to exclude result with 0 (i talk about them at the beginning of answer)...
And at the end we store result into output variable #myResult...
Result of this stored procedure will return string like this:
+--------------------------+
| 1ball, 5slop, 8box, 2pcs |
+--------------------------+
Hope I didn't miss anything important. Basically only think you should change here is the name of the table and their columns (if they are different) in first stored procedure instead t2 here
INSERT INTO...
SELECT item, qty
FROM t2
And in second one instead of t1 (and column if needed) here..
INSERT INTO...
SELECT unit, qty
FROM t1
WHERE item = #inItemName
ORDER BY qty DESC
Hope i help a little or give you an idea how this can be solved...
GL!
You seem to want string aggregation – something that does not have a simple instruction in Transact-SQL and is usually implemented using a correlated FOR XML subquery.
You have not provided names for your tables. For the purpose of the following example, the first table is called ItemDetails and the second one, Items:
SELECT
i.item,
i.qty,
details = (
SELECT
', ' + CAST(d.qty AS varchar(10)) + d.unit
FROM
dbo.ItemDetails AS d
WHERE
d.item = i.item
FOR XML
PATH (''), TYPE
).value('substring(./text()[1], 3)', 'nvarchar(max)')
FROM
dbo.Items AS i
;
For the input provided in the question, the above query would return the following output:
item qty details
----- ----------- ------------------------------
cigar 1598 1pcs, 1000ball, 12box, 100slop
pen 52 1pcs, 20box
You can further arrange the data into strings as per your requirement. I would recommend you do it in the calling application and use SQL only as your data source. However, if you must, you can do the concatenation in SQL as well.
Note that the above query assumes that the same unit does not appear more than once per item in ItemDetails. If it does and you want to aggregate qty values per unit before producing the detail line, you will need to change the query a little:
SELECT
i.item,
i.qty,
details = (
SELECT
', ' + CAST(SUM(d.qty) AS varchar(10)) + d.unit
FROM
dbo.ItemDetails AS d
WHERE
d.item = i.item
GROUP BY
d.unit
FOR XML
PATH (''), TYPE
).value('substring(./text()[1], 3)', 'nvarchar(max)')
FROM
dbo.Items AS i
;
I have a following Table Where I need to loop through the records and add values to a variable
Declare #Variable1 INT
SET #Variable1=0
Declare #totalval INT
SET #totalval=0
While (#Variable1<=20)
BEGIN
SET #totalval=#totalval+(Select Salary from EmpTable Where EmpID=9)
PRINT #totalval
SET #Variable1= Variable1+1
END
GO
I cant print the value...I am using SQL server 2005
Thank you all
You could use this instead:
;WITH a AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) num
, *
FROM EmpTable
WHERE EmpID = 9
)
SELECT #totalval = SUM(Salary)
, #Variable1 = COUNT(*)
FROM a
WHERE num <= 20
This would be one of usual ways of doing such task and it's more efficient than looping. ROW_NUMBER() OVER (ORDER BY (SELECT 0)) gives ordinal numbers to records but doesn't change sort.
This is also working (for your requirement):
Fiddle demo here
declare #total int, #records int = 20
select top( #records) #total=isnull(#total,0) + salary
from EmpTable
where empid = 9
-- order by salary (you may need to order by something)
The problem is this line:
SET #Variable1= Variable1+1
that doesn't work, change it to this:
SET #Variable1= #Variable1+1
if you were to leave it you should be getting this error:
Msg 207, Level 16, State 1, Line 11
Invalid column name 'Variable1'.
Can somebody help me with this little task? What I need is a stored procedure that can find duplicate letters (in a row) in a string from a table "a" and after that make a new table "b" with just the id of the string that has a duplicate letter.
Something like this:
Table A
ID Name
1 Matt
2 Daave
3 Toom
4 Mike
5 Eddie
And from that table I can see that Daave, Toom, Eddie have duplicate letters in a row and I would like to make a new table and list their ID's only. Something like:
Table B
ID
2
3
5
Only 2,3,5 because that is the ID of the string that has duplicate letters in their names.
I hope this is understandable and would be very grateful for any help.
In your answer with stored procedure, you have 2 mistakes, one is missing space between column name and LIKE clause, second is missing single quotes around search parameter.
I first create user-defined scalar function which return 1 if string contains duplicate letters:
EDITED
CREATE FUNCTION FindDuplicateLetters
(
#String NVARCHAR(50)
)
RETURNS BIT
AS
BEGIN
DECLARE #Result BIT = 0
DECLARE #Counter INT = 1
WHILE (#Counter <= LEN(#String) - 1)
BEGIN
IF(ASCII((SELECT SUBSTRING(#String, #Counter, 1))) = ASCII((SELECT SUBSTRING(#String, #Counter + 1, 1))))
BEGIN
SET #Result = 1
BREAK
END
SET #Counter = #Counter + 1
END
RETURN #Result
END
GO
After function was created, just call it from simple SELECT query like following:
SELECT
*
FROM
(SELECT
*,
dbo.FindDuplicateLetters(ColumnName) AS Duplicates
FROM TableName) AS a
WHERE a.Duplicates = 1
With this combination, you will get just rows that has duplicate letters.
In any version of SQL, you can do this with a brute force approach:
select *
from t
where t.name like '%aa%' or
t.name like '%bb%' or
. . .
t.name like '%zz%'
If you have a case sensitive collation, then use:
where lower(t.name) like '%aa%' or
. . .
Here's one way.
First create a table of numbers
CREATE TABLE dbo.Numbers
(
number INT PRIMARY KEY
);
INSERT INTO dbo.Numbers
SELECT number
FROM master..spt_values
WHERE type = 'P'
AND number > 0;
Then with that in place you can use
SELECT *
FROM TableA
WHERE EXISTS (SELECT *
FROM dbo.Numbers
WHERE number < LEN(Name)
AND SUBSTRING(Name, number, 1) = SUBSTRING(Name, number + 1, 1))
Though this is an old post it's worth posting a solution that will be faster than a brute force approach or one that uses a scalar udf (which generally drag down performance). Using NGrams8K this is rather simple.
--sample data
declare #table table (id int identity primary key, [name] varchar(20));
insert #table([name]) values ('Mattaa'),('Daave'),('Toom'),('Mike'),('Eddie');
-- solution #1
select id
from #table
cross apply dbo.NGrams8k([name],1)
where charindex(replicate(token,2), [name]) > 0
group by id;
-- solution #2 (SQL 2012+ solution using LAG)
select id
from
(
select id, token, prevToken = lag(token,1) over (partition by id order by position)
from #table
cross apply dbo.NGrams8k([name],1)
) prep
where token = prevToken
group by id; -- optional id you want to remove possible duplicates.
another burte force way:
select *
from t
where t.name ~ '(.)\1';