SQL Server: how to update a column with a value that is in that column when another number in another column is >1 - sql

I have a table with the following data:
Part Comp level item_nbr
-------------------------------
abc ab 1 1
null cd 2 2
null ef 3 3
cde gh 1 4
null ij 2 5
null kl 3 6
null mn 4 7
I would like to update the nulls to the value in each level 1, so every level that is >1 is updated with the level one value.
Part Comp level
---------------------
abc ab 1
abc cd 2
abc ef 3
cde gh 1
cde ij 2
cde kl 3
cde mn 4
I am at a loss as to how to achieve this on a very large dataset. Any help would be greatly appreciated!
To explain another way,
part level
abc 1
2
3
Then the next row is populated with another part
efg 1
2
2
etc.
Further clarification:
I need the string"abc" to be filled down with the string "abc" while the column fields below are null. The next row has a string of efg and the following column fields below are null, again, those fields should be filled down with the value "efg" and so on.
The level field = 1 will always have a part number, but all the other levels report up to the level 1 part, so should be populated identically. And repeat.
Hope this makes sense.

Use an updatable CTE with window functions:
with toupdate as (
select t.*,
max(part) over (partition by itm_nbr_not_null) as new_part
from (select t.*,
max(case when part is not null then item_nbr end) over (order by item_nbr) as itm_nbr_not_null
from t
) t
)
update toupdate
set part = new_part
where part is null;
You can run the CTE to see what is happening.

well, from your question what I understand is, you need to update the null column's value until you get a not null value. and you want to continue it up to the last row of the table.
for that scenario, I created a stored procedure, where I read the value of every n-th cell if it is null I changing it with the prev. cell's value, when the cell was not null.
Approach:
create a temporary table/ table variable.
add an extra column, which is basically identity, which will help to rank the column.
iterate a loop until the maximum row is reached.
in each iteration, read the cell value for the i-th row
4.1 if it is not null put it in a temporary variable.
4.2 else, replace/update the i-th cell's value with the temporary variable
continue it, until you reached up to the last row of the table/table variable.
look at my following snippets:
create proc DemoPost
as
begin
declare #table table(serial_no int identity(1,1), name varchar(30), text varchar(30), level int)
insert #table
select Name, Text, Level from Demo
declare #max as int = (select max(serial_no) from #table)
--select #max
declare #i as int =0
declare #temp as varchar(30)
declare #text as varchar(30)
while #i < #max
begin
set #i = #i +1
set #temp = (select name from #table where serial_no = #i)
-- if #temp is not null, fetch its value, otherwise, update/replace it with
-- previously gotten not-null cell's value.
if #temp is not null
begin
set #text = (select name from #table where serial_no = #i)
end
else
begin
update #table
set name = #text where serial_no = #i
end
end
select name, text, level from #table
end

You can update it using temporary table according to the given scenario i thought item_nbr is unique in row Hope this will help
SELECT *
INTO #TEMP
FROM URTablehere
DECLARE #PRev VARCHAR(MAX)
WHILE ( SELECT COUNT(*)
FROM URTablehere
) > 0
BEGIN
DECLARE #ID INT
DECLARE #Part VARCHAR(MAX)
DECLARE #Num INT
SELECT TOP ( 1 )
#ID = level ,
#Part = Part ,
#Num = item_nbr
FROM #TEMP
IF ( #ID = 1 )
BEGIN
SELECT #PRev = #Part
END
IF ( #ID > 1
AND #Part IS NULL
)
BEGIN
UPDATE URTablehere
SET Part = #PRev
WHERE item_nbr = #Num
END
DELETE
FROM #TEMP WHERE item_nbr=#Num
END

Related

loop through rows and delete those with a specific value

I have a question. I have for example this table in database:
row column visible
-----------------------
1 1 no
1 2 no
1 3 no
1 4 no
2 1 yes
2 2 yes
2 3 yes
2 4 yes
I want to loop through this table and to verify if all the rows have visibility "no". If all the row have the visibility no, I want that my new table to be :
row column visible
-----------------------
1 1 yes
1 2 yes
1 3 yes
1 4 yes
I am thinking at a cursor in a stored procedure like this:
create procedure someProcedure
as
declare #visible varchar(5)
declare #column int
declare #position int
declare scan cursor for
select column, position, visible
from table
where row between (select min(row) from table)
and (select max(row) from table)
order by row, column
but i don't know how to do this, I'm very new to sql,thank you
Cursors are slow and inefficient, and are very rarely needed. You don't need a cursor for this, you can use an updatable CTE to update the yes rows.
CREATE OR ALTER PROCEDURE someProcedure
AS
WITH cte AS (
SELECT *,
AnyVisible = COUNT(CASE WHEN Visible = 'yes' THEN 1 END) OVER (PARTITION BY row)
FROM [table] t
)
DELETE FROM cte
WHERE AnyVisible = 0;
WITH cte AS (
SELECT *,
rn = DENSE_RANK() OVER (ORDER BY row)
FROM [table] t
)
UPDATE cte
SET row = rn;
GO
I am not a 100% sure what you are trying to achieve but here is a small template for the cursor.
create procedure someProcedure as
declare #column int
declare #position int
declare #visible varchar(5)
declare scan cursor for
select column, position, visible
from table
where row between (select min(row) from table)
and (select max(row) from table)
order by row, column
--Open the cursor
open scan
-- fill variables with first position of cursor
fetch next from scan into #column, #position, #visible
-- start looping unit end
while(##FETCH_STATUS = 0)
begin
/*
Do things here
*/
-- move to next position in cursor
fetch next from scan into #column, #position, #visible
end
close scan
deallocate scan

Repeat query if no results came up

Could someone please advise on how to repeat the query if it returned no results. I am trying to generate a random person out of the DB using RAND, but only if that number was not used previously (that info is stored in the column "allready_drawn").
At this point when the query comes over the number that was drawn before, because of the second condition "is null" it does not display a result.
I would need for query to re-run once again until it comes up with a number.
DECLARE #min INTEGER;
DECLARE #max INTEGER;
set #min = (select top 1 id from [dbo].[persons] where sector = 8 order by id ASC);
set #max = (select top 1 id from [dbo].[persons] where sector = 8 order by id DESC);
select
ordial,
name_surname
from [dbo].[persons]
where id = ROUND(((#max - #min) * RAND() + #min), 0) and allready_drawn is NULL
The results (two possible outcomes):
Any suggestion is appreciated and I would like to thank everyone in advance.
Just try this to remove the "id" filter so you only have to run it once
select TOP 1
ordial,
name_surname
from [dbo].[persons]
where allready_drawn is NULL
ORDER BY NEWID()
#gbn that's a correct solution, but it's possible it's too expensive. For very large tables with dense keys, randomly picking a key value between the min and max and re-picking until you find a match is also fair, and cheaper than sorting the whole table.
Also there's a bug in the original post, as the min and max rows will be selected only half as often as the others, as each maps to a smaller interval. To fix generate a random number from #min to #max + 1, and truncate, rather than round. That way you map the interval [N,N+1) to N, ensuring a fair chance for each N.
For this selection method, here's how to repeat until you find a match.
--drop table persons
go
create table persons(id int, ordial int, name_surname varchar(2000), sector int, allready_drawn bit)
insert into persons(id,ordial,name_surname,sector, allready_drawn)
values (1,1,'foo',8,null),(2,2,'foo2',8,null),(100,100,'foo100',8,null)
go
declare #min int = (select top 1 id from [dbo].[persons] where sector = 8 order by id ASC);
declare #max int = 1+ (select top 1 id from [dbo].[persons] where sector = 8 order by id DESC);
set nocount on
declare #results table(ordial int, name_surname varchar(2000))
declare #i int = 0
declare #selected bit = 0
while #selected = 0
begin
set #i += 1
insert into #results(ordial,name_surname)
select
ordial,
name_surname
from [dbo].[persons]
where id = ROUND(((#max - #min) * RAND() + #min), 0, 1) and allready_drawn is NULL
if ##ROWCOUNT > 0
begin
select *, #i tries from #results
set #selected = 1
end
end

Sorting results of SQL query just like IN parameter list

I'm doing a query which looks something like
SELECT id,name FROM table WHERE id IN (2,1,4,3)
I'd like to get
id name
2 B
1 A
4 D
3 C
but I'm getting
1 A
2 B
3 C
4 D
Is there any way to sort the query results in the same way as the list I'm including after IN?
Believe me, I have a practical reason that I would need it for ;)
SELECT id,name FROM table WHERE id IN (2,1,4,3)
ORDER BY CASE id
WHEN 2 THEN 1
WHEN 1 THEN 2
WHEN 4 THEN 3
WHEN 3 THEN 4
ELSE 5
END
This might solve your problem.
Solution 2, insert your list into a temp table and get them a running sequence
id, seq(+1 every new row added)
-----------------
2 1
1 2
4 3
3 4
then join 2 table together and order by this seq.
Okay, I did it myself. It's a bit mad but it works ;)
DECLARE #IDs varchar(max)
DECLARE #nr int
DECLARE #znak varchar(1)
DECLARE #index int
DECLARE #ID varchar(max)
SET #IDs='7002,7001,7004,7003'
SET #nr=1
SET #index=1
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
CREATE TABLE #temp (nr int, id int)
--fill temp table with Ids
WHILE #index<=LEN(#Ids)
BEGIN
set #znak=''
set #ID=''
WHILE #znak<>',' AND #index<=LEN(#Ids)
BEGIN
SET #znak= SUBSTRING(#IDs,#index,1)
IF #znak<>',' SET #ID=#ID+#znak
SET #index=#index+1
END
INSERT INTO #temp(nr,id) VALUES (#nr,CAST(#ID as int))
SET #nr=#nr+1
END
-- select proper data in wanted order
SELECT MyTable.* FROM MyTable
INNER JOIN #temp ON MyTable.id=#temp.id
ORDER BY #temp.nr

Complex SQL selection query

I have a stored procedure which needs a different if condition to work properly.
The procedure has 2 parameter namely, #CategoryID and #ClassID, which basically come from a UI tree view functionality. #CategoryID corresponds to the parent nodes, while #ClassID corresponds to the child nodes.
Based upon the above parameters I need to make a selection(Column Code) from a table which has CategoryID and ClassID as columns.
Now there are 2 scenarios:
Scenario 1
#CategoryID:A
#ClassID:B (which is a child node of CategoryID A)
Result needed: Codes corresponding to only ClassID B, which is basically the intersection
Scenario 2
#CategoryID:A
#ClassID: C (which is not a child node for CategoryID A)
Result needed: Codes corresponding to the CategoryID A, as well as ClassID B, basically a union
The procedure which I wrote gives me correct answer for the second scenario, but the first scenario it fails. Below is my procedure:
ALTER PROCEDURE [dbo].[uspGetCodes]
#CategoryID varchar(50),
#ClassID varchar(50)
AS
BEGIN
BEGIN TRY
DECLARE #SQLQuery NVARCHAR(MAX)
SET #SQLQuery=N'SELECT Code FROM dbo.ClassToCategoryMapping WHERE '
IF (#CategoryID IS NULL OR #CategoryID='')
BEGIN
SET #SQLQuery=#SQLQuery + 'ClassId IN ('+#ClassID+')'
PRINT(#SQLQuery)
EXEC(#SQLQuery)
END
ELSE IF (#ClassID IS NULL OR #ClassID='')
BEGIN
SET #SQLQuery=#SQLQuery+'CategoryID IN ('+#CategoryID+')'
PRINT(#SQLQuery)
EXEC(#SQLQuery)
END
ELSE
BEGIN
SET #SQLQuery=#SQLQuery+'(CategoryID IN ('+#CategoryID+') OR ClassId IN ('+#ClassID+') )'
PRINT(#SQLQuery)
EXEC(#SQLQuery)
END
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS 'ErrorNumber', ERROR_MESSAGE() AS 'ErrorMessage', ERROR_SEVERITY() AS 'ErrorSeverity', ERROR_STATE() AS 'ErrorState', ERROR_LINE() AS 'ErrorLine'
RETURN ERROR_NUMBER()
END CATCH
END
The Last Else part actually does an 'OR', which gives me the union of the Codes for CategoryID's and ClassID's irrespective whether the given ClassID is a child of the given CategoryID or not.
My question over here would be, how to write the condition to achieve both the scenarios.
Latest Sample Data:
Scenario 1
#CategoryId=2,5, #ClassID=10 (Here 10 is the child while 2 is the parent, CategoryID 2 corresponds to ClassID's 10, 11, 12)
Expected Result: 10, 26, 27 (26 and 27 correspond to the CategoryID 5)
Scenario 2
#CategoryID=2, #ClassID=13,15 (13 and 15 is the child of a different parent, CategoryID 2 corresponds to ClassID's 10, 11 ,12)
Expected Result: 10, 11, 12, 13, 15
Data in Table dbo.ClasstoCategoryMapping will be somewhat as below:
CategoryID ClassID Code
2 10 200
2 11 201
2 12 202
5 26 501
5 27 502
6 15 601
6 16 602
6 17 603
7 20 701
7 21 702
7 22 703
I guess I have made my question quite clear, if no then, folks can ask me to edit it. I would be happy to do so. I urge the experts to assist me in this problem. Any pointers too will be quite appreciated.
Regards
Anurag
If I understand the question correctly, what you require in your result set is:
(all supplied classid) + (all classid for supplied categoryid with no matching supplied classid)
That would translate to the following:
CREATE PROCEDURE [dbo].[uspGetCodes]
(
#CategoryID varchar(50),
#ClassID varchar(50)
)
AS
BEGIN
SELECT COALESCE(CM.CategoryID, CM2.CategoryID) AS CategoryID,
COALESCE(CM.ClassID, CM2.ClassID) AS ClassID,
COALESCE(CM.Code, CM2.Code) AS Code
--Matched classIDs:
FROM dbo.udfSplitCommaSeparatedIntList(#ClassID) CLAS
JOIN dbo.ClassToCategoryMapping CM
ON CM.ClassId = CLAS.Value
--Unmatched CategoryIDs:
FULL
OUTER
JOIN dbo.udfSplitCommaSeparatedIntList(#CategoryID) CAT
ON CM.CategoryID = CAT.Value
LEFT
JOIN dbo.ClassToCategoryMapping CM2
ON CM.CategoryID IS NULL
AND CM2.CategoryID = CAT.Value
END
I have included Category, Class and Code in the result since its easier to see what's going on, however I guess you only really need code
This makes use of the following function to split the supplied comma separated strings:
CREATE FUNCTION [dbo].[udfSplitCommaSeparatedIntList]
(
#Values varchar(50)
)
RETURNS #Result TABLE
(
Value int
)
AS
BEGIN
DECLARE #LengthValues int
SELECT #LengthValues = COALESCE(LEN(#Values), 0)
IF (#LengthValues = 0)
RETURN
DECLARE #StartIndex int
SELECT #StartIndex = 1
DECLARE #CommaIndex int
SELECT #CommaIndex = CHARINDEX(',', #Values, #StartIndex)
DECLARE #Value varchar(50);
WHILE (#CommaIndex > 0)
BEGIN
SELECT #Value = SUBSTRING(#Values, #StartIndex, #CommaIndex - #StartIndex)
INSERT #Result VALUES (#Value)
SELECT #StartIndex = #CommaIndex + 1
SELECT #CommaIndex = CHARINDEX(',', #Values, #StartIndex)
END
SELECT #Value = SUBSTRING(#Values, #StartIndex, LEN(#Values) - #StartIndex + 1)
INSERT #Result VALUES (#Value)
RETURN
END
this is the sample query that can achieve your goal, is this what you want?
DECLARE #SAMPLE TABLE
(
ID INT IDENTITY(1,1),
CategoryId INT,
ClassID INT
)
INSERT INTO #sample
VALUES(2,10)
INSERT INTO #sample
VALUES(2,11)
INSERT INTO #sample
VALUES(2,12)
INSERT INTO #sample
VALUES(3,13)
DECLARE #CategoryID INT
DECLARE #ClassID Int
--Play around your parameter(s) here
SET #CategoryID = 2
SET #ClassID = 13
--Snenario 1
--#CategoryId=2, #ClassID=10 (Here 10 is the child while 2 is the parent, CategoryID 2 corresponds to ClassID's 10, 11, 12)
--Expected Result: 10
IF EXISTS(SELECT * FROM #SAMPLE WHERE CategoryId = #CategoryID AND ClassID = #ClassID)
SELECT ClassID FROM #SAMPLE WHERE CategoryId = #CategoryID AND ClassID = #ClassID
--Scenario 2
--#CategoryID=2, #ClassID=13 (13 is the child of a different parent, CategoryID 2 corresponds to ClassID's 10, 11 ,12)
--Expected Result: 10, 11, 12, 13
ELSE
SELECT ClassID FROM #SAMPLE WHERE ClassID = #ClassID OR CategoryId = #CategoryID
Try this
select * from yourtable
where CategoryId = #CategoryID and ClassID = #ClassID
union
select * from
(
select * from yourtable where ClassID = #ClassID
union
select * from yourtable where CategoryId = #CategoryID
) v
where not exists (select * from yourtable where CategoryId = #CategoryID and ClassID = #ClassID)
UPDATE FOR DELIMITED STRING
If you have a comma delimited string then it is best to use a CLR function to create the table, but you could use a SQL function. Examples of how to do this are easy to find with a Google search... but for reference here is one good article on the subject -> http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings I expect at some point there will be native support on most platforms.
Given that you have a function that returns a table of one column (named ID) of type int, or an empty table on a null input. Note: You may have to have the null return a table with one row containing an invalid value (a value that will never join), say -1.
The code is as simple as this:
SELECT Code
FROM ClassToCategoryMapping
LEFT JOIN MakeTableFromCVS(#CategoryID) AS CatTable
ON CatTable.ID = CategoryID
LEFT JOIN MakeTableFromCVS(#ClassID) AS ClassTable
ON ClassTable.ID = ClassID
WHERE
CASE
WHEN #CatgoryID IS NULL THEN -1 ELSE CatTable.ID
END = ISNULL(CatTable.ID,-1)
AND
CASE
WHEN #ClassID IS NULL THEN -1 ELSE ClassTable.ID
END = ISNULL(ClassTable.ID,-1)
AND
COALESCE(CatTable.ID,ClassTable.ID,-1) != -1
The logic is the same as below. Because the join will vary the values if it is not null we have to use a different trick. Here we use a marker value (in this case -1) to signal the null value. Any value that won't appear in the comma separated list will work as this marker value, remember it must be of the same type.
You don't need dynamic SQL here and you will find SQL server is better at optimizing if you don't use dynamic SQL. In fact, you don't even need an if statement If you can be sure the input is always null you can do this:
SELECT Code
FROM ClassToCategoryMapping
WHERE
How this works
This query checks for both CategoryID and ClassID columns match the incoming parameters but "ignores" the input when they are null by checking the column against itself. This is an handy SQL trick.
Note if you do need to check for empty strings then this will be almost as fast
DECLARE #myCatID varchar(max)
DECLARE #myClassID varchar(max)
SET #myCatID = #CategoryID
SET #myClassID = #ClassID
IF LTRIM(RTRIM(#CategoryID) = '' THEN SET #myCatID = NULL
IF LTRIM(RTRIM(#ClassID) = '' THEN SET #myClassID = NULL
SELECT Code
FROM ClassToCategoryMapping
WHERE CatgoryID = ISNULL(#myCatID,CategoryID)
AND ClassID = ISNULL(#myClassID,ClassID)
You can replace ISNULL() with COALESCE() if you want... they do the same thing in this case.

Sequential numbers randomly selected and added to table

The SO Question has lead me to the following question.
If a table has 16 rows I'd like to add a field to the table with the numbers 1,2,3,4,5,...,16 arranged randomly i.e in the 'RndVal' field for row 1 this could be 2, then for row 2 it could be 5 i.e each of the 16 integers needs to appear once without repetition.
Why doesn't the following work? Ideally I'd like to see this working then to see alternative solutions.
This creates the table ok:
IF OBJECT_ID('tempdb..#A') IS NOT NULL BEGIN DROP TABLE #A END
IF OBJECT_ID('tempdb..#B') IS NOT NULL BEGIN DROP TABLE #B END
IF OBJECT_ID('tempdb..#C') IS NOT NULL BEGIN DROP TABLE #C END
IF OBJECT_ID('tempdb..#myTable') IS NOT NULL BEGIN DROP TABLE #myTable END
CREATE TABLE #B (B_ID INT)
CREATE TABLE #C (C_ID INT)
INSERT INTO #B(B_ID) VALUES
(10),
(20),
(30),
(40)
INSERT INTO #C(C_ID)VALUES
(1),
(2),
(3),
(4)
CREATE TABLE #A
(
B_ID INT
, C_ID INT
, RndVal INT
)
INSERT INTO #A(B_ID, C_ID, RndVal)
SELECT
#B.B_ID
, #C.C_ID
, 0
FROM #B CROSS JOIN #C;
Then I'm attempting to add the random column using the following. The logic is to add random numbers between 1 and 16 > then to effectively overwrite any that are duplicated with other numbers > in a loop ...
SELECT
ROW_NUMBER() OVER(ORDER BY B_ID) AS Row
, B_ID
, C_ID
, RndVal
INTO #myTable
FROM #A
DECLARE #rowsRequired INT = (SELECT COUNT(*) CNT FROM #myTable)
DECLARE #i INT = (SELECT #rowsRequired - SUM(CASE WHEN RndVal > 0 THEN 1 ELSE 0 END) FROM #myTable)--0
DECLARE #end INT = 1
WHILE #end > 0
BEGIN
SELECT #i = #rowsRequired - SUM(CASE WHEN RndVal > 0 THEN 1 ELSE 0 END) FROM #myTable
WHILE #i>0
BEGIN
UPDATE x
SET x.RndVal = FLOOR(RAND()*#rowsRequired)
FROM #myTable x
WHERE x.RndVal = 0
SET #i = #i-1
END
--this is to remove possible duplicates
UPDATE c
SET c.RndVal = 0
FROM
#myTable c
INNER JOIN
(
SELECT RndVal
FROM #myTable
GROUP BY RndVal
HAVING COUNT(RndVal)>1
) t
ON
c.RndVal = t.RndVal
SET #end = ##ROWCOUNT
END
TRUNCATE TABLE #A
INSERT INTO #A
SELECT
B_ID
, C_ID
, RndVal
FROM #myTable
If the original table has 6 rows then the result should end up something like this
B_ID|C_ID|RndVal
----------------
| | 5
| | 4
| | 1
| | 6
| | 3
| | 2
I don't understand your code, frankly
This will update each row with a random number, non-repeated number between 1 and the number of rows in the table
UPDATE T
SET SomeCol = T2.X
FROM
MyTable T
JOIN
(
SELECT
KeyCol, ROW_NUMBER() OVER (ORDER BY NEWID()) AS X
FROM
MyTable
) T2 ON T.KeyCol = T2.KeyCol
This is more concise but can't test to see if it works as expected
UPDATE T
SET SomeCol = X
FROM
(
SELECT
SomeCol, ROW_NUMBER() OVER (ORDER BY NEWID()) AS X
FROM
MyTable
) T
When you add TOP (1) (because you need to update first RndVal=0 record) and +1 (because otherwise your zero mark means nothing) to your update, things will start to move. But extremely slowly (around 40 seconds on my rather outdated laptop). This is because, as #myTable gets filled with generated random numbers, it becomes less and less probable to get missing numbers - you usually get duplicate, and have to start again.
UPDATE top (1) x
SET x.RndVal = FLOOR(RAND()*#rowsRequired) + 1
FROM #myTable x
WHERE x.RndVal = 0
Of course, #gbn has perfectly valid solution.
This is basically the same as the previous answer, but specific to your code:
;WITH CTE As
(
SELECT B_ID, C_ID, RndVal,
ROW_NUMBER() OVER(ORDER BY NewID()) As NewOrder
FROM #A
)
UPDATE CTE
SET RndVal = NewOrder
SELECT * FROM #A ORDER BY RndVal