MS SQL update statement using comma delimited values - sql

I'm trying to create a SQL Update statement that will read comma separated values in a text box field & update a table (1 column referencing ID) with these comma separated values.
Specifically I have a table of products that I want to create a form that updates the "Inventory" column in that table.
I update my inventory spreadsheet daily and it would be great if I can copy/paste the CSV of inventory for all my products paste into a textbox hit update and have my table actualized.
I'm not sure how to do this as I need to reference each products unique ID and update that ID with the matching value for inventory stock.
Any ideas, examples highly appreciated.

If you are using SQL Server 2016 then you can use string_split as I stated in the comment above
select * from string_split('1,2,3,4,5',',')
However:
If you are using earlier version of sql server < 2016 then you can create a function that returns a table then from there you can join to the table you need to update:
CREATE FUNCTION dbo.SplitStringToValues
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E42(N) AS (SELECT 1 FROM E4 a, E2 b),
cteTally(N) AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(#List,1)))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
WHERE (SUBSTRING(#List,t.N,1) = #Delimiter OR t.N = 0))
SELECT Item = SUBSTRING(#List, s.N1, ISNULL(NULLIF(CHARINDEX(#Delimiter,#List,s.N1),0)-s.N1,8000))
FROM cteStart s;
go
Then use the function like so
SELECT * FROM SplitStringToValues('1,2,3,5',',') as t
UPDATE:
To get the next row's value next to the id as you have asked in the comment you could do something like this below. It is using the LEAD function introduced in SQL SERVER 2012:
SELECT Id, Value
FROM (SELECT
ROW_NUMBER() over (order by(select 1)) as cnt,
t.item AS Id,
Lead(t.item)
OVER (
ORDER BY (SELECT 1)) Value
FROM dbo.Splitstringtovalues('10,20,30,40,50,10,20,30,40,50,60,70', ',')
t)
keyValue
WHERE keyValue.value IS NOT NULL
and cnt % 2 = 1

If you are using SQL Server 2016 then you can use string_split
select * from string_split('1,2,3,4,5',',')

Related

SQL breakup string for use with WHERE IN

I have a table in SQL Server with a column that stores a series of characters. I would like to be able to send a series of characters to a stored procedure and use it for the WHERE IN. Example below
ID | series
---+-------
1 | U
2 | B
3 | R
4 | UB
5 | BR
I would like a return as follows:
Parameter | Return IDs
----------+------------
U | 1
R | 3
U, R | 1, 3
I can format the parameter any way, UR or U, R or whatever. I could break this up in application and call the procedure N times but I would rather not so that I can use an order by in the query.
Here's one way if you can not use a table valued parameter.
declare #table table (ID int, series varchar(16))
insert into #table
values
(1,'U'),
(2,'B'),
(3,'R'),
(4,'UB'),
(5,'BR')
declare #input varchar(4000) = 'U,R'
select
t.*
from #table t
cross apply dbo.DelimitedSplit8K(#input,',') x
where
x.Item = t.series
OR without Cross Apply
;with cte as
(select *
from dbo.DelimitedSplit8K(#input,',') x )
select
t.*
from #table t
inner join cte on Item = t.series
Here is the function which has proven to be a fast method of splitting strings:
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
On way to achieve this is to pass in a delimited string and then create a function to take in your parameter, splits out the values and returns a table value. Then all you need to do is create a stored procedure similar to.
CREATE PROCEDURE MyProc
(
#MyParameter NVARCHAR(3000)
)
AS
SELECT
ID,
Series
FROM
MyTable
WHERE
Series IN (SELECT ID FROM dbo.MyFunctionToSplitDelimitedString(#MyParameter,',')

Returning while-loop values as multiple rows in SQL Server instead of multiple result sets

I have the following T-SQL, used to generate some random values:
DECLARE #cnt INT = 0;
WHILE #cnt < 100
BEGIN
select
Random_String =
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
from
(select x='0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+') a
SET #cnt = #cnt + 1;
END;
This works well enough, except every string is returned as, what looks like, an independent result set.
Is there a way to refactor that query to return every value as one row in the same result set?
Environment is MS SQL Server 2008.
Thanks!
The best way to do this kind of thing is to forget about looping in t-sql. Using a numbers or tally table is a much better way to go about this.
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select
Random_String =
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
from
(select x='0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+') a
cross join cteTally t
where t.N < = 100
Here's a way to do it using a tally table:
;With Tally (N) As
(
Select 0 Union All
Select 1 Union All
Select 2 Union All
Select 3 Union All
Select 4 Union All
Select 5 Union All
Select 6 Union All
Select 7 Union All
Select 8 Union All
Select 9
), Numbers (N) As
(
Select Row_Number() Over (Order By A.N) Num
From Tally A -- 10
Cross Join Tally B -- 100
Cross Join Tally C -- 1000
Cross Join Tally D -- 10000
Cross Join Tally E -- 100000
), LookupString (X) As
(
Select '0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+'
)
Select Random_String = substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
From LookupString
Cross Join Numbers
Where N <= 100

SQL Server SQL Get list of unused ids

I have a table that ranges from 1-100000 but there are gaps in the ids where items have been deleted. I want a SQL statement that will return me a list of all unused ids in the table so I can get a list of items that were deleted.
I want the list but randomizing the list is a bonus actually. I think it can be done with a rand function...
I'd like to keep it ansi SQL if possible to maintain portability but if not, then that's ok...
You can do this with a use of Tally Table.
Create our sample data.
CREATE TABLE #ids(
id INT IDENTITY(1, 1)
)
SET IDENTITY_INSERT #ids ON
--Insert 100,000 rows
INSERT INTO #ids(id)
SELECT TOP 100000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM sys.columns a
CROSS JOIN sys.columns b
SET IDENTITY_INSERT #ids OFF;
-- Randomly delete 1000 rows
WITH cte AS(
SELECT TOP 1000 id
FROM #ids
ORDER BY NEWID()
)
DELETE FROM cte
Using a Tally Table, create a list of numbers from 1 - 100,000. Then use NOT EXISTS to get the unused ids. To randomize the list, add on ORDER BY NEWID() clause.
DECLARE #min INT = 1,
#max INT = 100000
;WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b),
Tally(N) AS(
SELECT TOP(#max) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM E8
)
SELECT N
FROM Tally t
WHERE NOT EXISTS(
SELECT 1 FROM #ids WHERE id = t.N
)
ORDER BY NEWID() -- Sorts the result in a random order

SQL to get sequence of phone numbers

I have table called PhoneNumbers with columns Phone and Range as below
here in the phone column i have a phone numbers and in range column i have a range of values i need the phone numbers to be included.For the first phone number 9125678463 I need to include the phone numbers till the range 9125678465 ie (9125678463,9125678464,9125678465).Similarly for other phone numbers too.here is the sample destination table should look like
How can i write the sql to get this?
Thanks in advance
I have a solution which goes a classic way BUT: it does not need recursions and it does not need any loops! And it works even if your range has length of 3 or 5, or whatever...
first i create a table with numbers (from 1 to 1 million in this example - you can adopt this in TOP () clause):
SELECT TOP (1000000) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO dbo.Numbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX idx_numbers ON dbo.Numbers(n)
;
if you have that table it's pretty simple:
;WITH phonenumbers
AS
(
SELECT phone,
[range],
CAST(RIGHT(phone,LEN([range])) AS INT) AS number_to_increase,
CAST(LEFT(phone,LEN(phone)-LEN([range])) + REPLICATE('0',LEN([range])) AS BIGINT) AS base_number
FROM PhoneNumbers
)
SELECT p.base_number + num.n
FROM phonenumbers p
INNER JOIN dbo.Numbers num ON num.n BETWEEN p.number_to_increase AND p.[range]
You don't have to use a CTE like here - it's just to see a bit clearer what the idea behind this approach is. Maybe this suits for you
You can use CTE like this:
;WITH CTE (PhoneNumbers, [Range], i) AS (
SELECT CAST(Phone AS bigint), [Range], CAST(1 AS bigint)
FROM yourTable
UNION ALL
SELECT CAST(PhoneNumbers + 1 AS bigint), [Range], i + 1
FROM CTE
WHERE (PhoneNumbers + 1) % 10000 <= [Range]
)
SELECT PhoneNumbers
FROM CTE
ORDER BY PhoneNumbers
Here is one example of using a tally table. In my system I have that set of ctes as a view so I never have to write it again.
if OBJECT_ID('tempdb..#PhoneNumbers') is not null
drop table #PhoneNumbers;
create table #PhoneNumbers
(
Phone char(10)
, Range smallint
)
insert #PhoneNumbers
select 9135678463, 8465 union all
select 3279275678, 5679 union all
select 6372938103, 8105;
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select *
from #PhoneNumbers p
join cteTally t on t.N >= RIGHT(Phone, 4) and t.N <= Range
order by p.Phone
One more approach:
--Creating dummy table
select '9999991234' phone, '1237' rang into #tbl
union
select '9999995689', '5692'
SELECT [phone] low
,(CAST(9999995689/10000 AS bigINT) * 10000 + [Rang]) high
into #tbl1
FROM #tbl
--Creating 'numbrs' to have numbers between 0 & 9999 i.e. max range
select (rn-1)rn
into #numbrs
from
(select row_number() over (partition by null order by A.object_id) rn from sys.objects A
cross join sys.objects B)A
where rn between 0 and 9999
select (low + rn)phn from #numbrs cross join #tbl1
where (low + rn) between low and high

How to populate a table with 5 million rows in MS SQL Server?

I'm working on an app that should find 26-letter code in char(26) column out of 5,760,000 rows. I need to know how long it's going to take. I'm using MS SQL Server 2012 Express.
I have a database which has only one table, myTable:
Idcolumn integer
CodeColumn char(26)
DateAndTimeColumn datetime
Column 'CodeColumn' has an index.
IdColumn is simply integer ID.
CodeColumn has "00592098715648275649283746" format (this is an example).
DateAndTimeColumn is a timestamp.
I would like to populate this table with data to do some tests and to find out how long it is going to take to get an answer from the database. I don't know how to write proper tsql statement to populate my table with 5,760,000 rows. Especially that second column is very long. How can I populate the table to get my table populated?
Let's say the data should be like this when I use statement
SELECT IdColumn, CodeColumn, DateAndTimeColumn FROM myTable;
Output:
1 00000000000000000000000001 2014-11-19 15:46:50.843
2 00000000000000000000000002 2014-11-19 15:46:54.310
3 00000000000000000000000003 2014-11-19 15:46:56.060
and so on ... till 5,760,000 rows.
How can I do that?
;WITH Numbers AS
(
SELECT TOP (5760000)
IdColumn = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
CROSS JOIN sys.all_objects AS s3
)
INSERT INTO dbo.YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM Numbers;
Here is another way to do this using Lamak's excellent example. The only difference is this will create a 10 million row cte with zero reads. When you use sys.all_objects it can get extremely slow because of all the I/O.
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E6(N) AS (SELECT 1 from E4 a, E2 b, E1 c),
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E6
)
INSERT INTO dbo.YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM cteTally
where cteTally.N <= 5760000
Very similar to Lamak answer, this will not depends on your database structure:
;WITH Numbers AS
(
SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION
SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION
SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION
SELECT 10
),
Joins AS
(
SELECT TOP (5760000)
IdColumn = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[id]))
FROM Numbers AS s1 --10
CROSS JOIN Numbers AS s2 --100
CROSS JOIN Numbers AS s3 --1.000
CROSS JOIN Numbers AS s4 --10.000
CROSS JOIN Numbers AS s5 --100.000
CROSS JOIN Numbers AS s6 --1.000.000
CROSS JOIN Numbers AS s7 --10.000.000
)
INSERT INTO #YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM Joins;
It just generate a list of numbers from 1 to 10 and then it crossjoins all the way to 10^7.