SQL: extract common string from by group - sql

I have a table with a location and device name. I want to keep the part of the device name that matches within the group.
location_code | device_name | location
1 Building_1_in Building_1
1 Building_1_out Building_1
1 Building_1_gate Building_1
2 Drive 3 gate2 Drive 3
2 Drive 3 gate1 Drive 3
2 Drive 3 keypad Drive 3
I have location code and device name, but I'm trying to create the location column. I can't use a SUBSTRING function since the target strings are of different length, and I can't use a CHARINDEX function since there is no consistent delimiter. Also, there are too many location_code to write a CASE WHEN function.
Does anyone have any ideas?

If you group these records by location_code then
You can get required result by
Create a Function
Create FUNCTION dbo.getLocation
(
#location_code int
)
RETURNS varchar(max)
AS
BEGIN
declare #result varchar(max)
declare #device_name1 varchar(max)
,#device_name2 varchar(max)
,#iterator int
set #result = ''
select top 1 #device_name1 = device_name from TableName where location_code = #location_code order by device_name
select top 1 #device_name2 = device_name from TableName where location_code = #location_code order by device_name desc
set #iterator = 1
while(#iterator <= len(#device_name1) and #iterator <= len(#device_name2))
begin
if(SUBSTRING(#device_name1, #iterator, 1) = SUBSTRING(#device_name2, #iterator, 1))
begin
set #result = #result + SUBSTRING(#device_name1, #iterator, 1)
end
else
break
set #iterator = #iterator + 1
end
return #result
END
GO
and use it like
select t.location_code, t.device_name, l.location
from TableName t
inner join (select location_code, dbo.getLocation(location_code) as location from TableName group by location_code) l on l.location_code = t.location_code

The general idea:
You can sort the table ordering by location_code and device name. Then you can extract the common begining part of the first and the last for each location_code.

Related

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

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

What is best way to fetch related rows in each table of dataset in sql

Suppose I have 2 tables
1. Artical(ID,Description,PubDate)
2. ArticalMedia(ID,ArticalID,MediaURL)
Now I want to fetch 2 tables within stored procedure.
Table1: Top 5 Latest news
Table2: All Media's of Top 5 news selected in Table1
I know we can achieve this using #Temp tables. I this only & best way? Or do we have any other method to achieve same thing?
Simple 2 select statements might lead to wrong data, plesae see following example:
select top 5 * from Artical order by PubDate desc
retuns Artical's : 5,4,3,2,1
select * from ArticalMedia where ArticalID in (select top 5 ID from Artical order by PubDate desc)
can return Medias of 6,5,4,3,2. cause new Artical might be inserted in database, after first select & before second select.
Get the TOP 5 records and then join them to the ArticleMedia table:
SELECT *
FROM
(
SELECT TOP 5 ID,Description,PubDate
FROM Artical
ORDER BY PubDate DESC
) DS
INNER JOIN ArticleMedia AM
ON DS.[Id] = AM.[id]
Try this optimized query with light weight execution plan:
SELECT A.*,AM.*
FROM ArticalMedia AS AM INNER JOIN
Article AS A ON AM.ArticleID = A.ID
WHERE (AM.ArticleID IN
(SELECT TOP (5) ID
FROM Article
ORDER BY PubDate DESC))
ORDER BY A.PubDate DESC
Edit 2
Create Table valued function in SQL fn_split:
CREATE FUNCTION [dbo].[fn_Split](#sText varchar(8000), #sDelim varchar(20) = ' ')
RETURNS #retArray TABLE (idx smallint Primary Key, value varchar(8000))
AS
BEGIN
DECLARE #idx smallint,
#value varchar(8000),
#bcontinue bit,
#iStrike smallint,
#iDelimlength tinyint
IF #sDelim = 'Space'
BEGIN
SET #sDelim = ' '
END
SET #idx = 0
SET #sText = LTrim(RTrim(#sText))
SET #iDelimlength = DATALENGTH(#sDelim)
SET #bcontinue = 1
IF NOT ((#iDelimlength = 0) or (#sDelim = 'Empty'))
BEGIN
WHILE #bcontinue = 1
BEGIN
--If you can find the delimiter in the text, retrieve the first element and
--insert it with its index into the return table.
IF CHARINDEX(#sDelim, #sText)>0
BEGIN
SET #value = SUBSTRING(#sText,1, CHARINDEX(#sDelim,#sText)-1)
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
--Trim the element and its delimiter from the front of the string.
--Increment the index and loop.
SET #iStrike = DATALENGTH(#value) + #iDelimlength
SET #idx = #idx + 1
SET #sText = LTrim(Right(#sText,DATALENGTH(#sText) - #iStrike))
END
ELSE
BEGIN
--If you can’t find the delimiter in the text, #sText is the last value in
--#retArray.
SET #value = #sText
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
--Exit the WHILE loop.
SET #bcontinue = 0
END
END
END
ELSE
BEGIN
WHILE #bcontinue=1
BEGIN
--If the delimiter is an empty string, check for remaining text
--instead of a delimiter. Insert the first character into the
--retArray table. Trim the character from the front of the string.
--Increment the index and loop.
IF DATALENGTH(#sText)>1
BEGIN
SET #value = SUBSTRING(#sText,1,1)
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
SET #idx = #idx+1
SET #sText = SUBSTRING(#sText,2,DATALENGTH(#sText)-1)
END
ELSE
BEGIN
--One character remains.
--Insert the character, and exit the WHILE loop.
INSERT #retArray (idx, value)
VALUES (#idx, #sText)
SET #bcontinue = 0
END
END
END
RETURN
END
Then the query will be:
DECLARE #tmp nvarchar(max);
select #tmp = stuff((select top 5 ', ', cast(id as nvarchar(max))
FROM Article
ORDER BY PubDate DESC
for xml path ('')
), 1, 2, '');
SELECT A.*,AM.*
FROM ArticalMedia AS AM INNER JOIN
Article AS A ON AM.ArticleID = A.ID
WHERE (AM.ArticleID IN (select value from dbo.fn_split(#tmp,',')))
ORDER BY A.PubDate DESC
Use a CTE to keep it simple:
with Top5Artical as (
select top (5) Artical.ID as ArticleID
from Artical
order by Artical.PubDate desc
),
insert into #Table1
select Top5.ArticalID,
Art.Description,
Art.PubDate
from Top5Artical as Top5
inner join Artical as Art
on Top5.ArticalID = Art.ID
order by Top5.PubDate desc;
insert into #Table2
select Top5.ArticalID,
ArtMedia.ID,
ArtMedia.URL
from Top5Artical as Top5
inner join ArticalMedia as ArtMedia
on Top5.ArticalID = ArtMedia.ArticalID
order by Top5.PubDate desc;
select * from #Table1;
select * from #Table2;

sql how to get consecutive appearance of value

suppose I have a column 'value', which can appear multiple times in a table with another column 'result' which can be either 1 or 0. I would like to search for consecutive 1s (ie result = 1) until the count reaches 4, then I can select value. given the result sets below:
-result set a)
value Result
----- ------
A 1
A 1
A 1
A 0
-result set b)
value Result
----- ------
A 1
A 1
A 1
A 1
result set b meets the condition and therefore value A is selected. How do I go about this ? Thanks.
This is the query: (usually this query is to detect double record in a table, but probably meet your demand).
select value, result, count(value) as [Result Sum]
from #temp
where result = 1
group by value, result
having count(value) >3
This is the Result
value result Result Sum
----- ----------- -----------
A 1 4
UPDATED:
This is the data example in my temporary table (#temp)
value result
----- -----------
A 1
A 1
A 1
A 0
A 1
D 1
D 1
D 1
D 1
B 1
B 1
C 1
C 1
C 1
C 1
From The example data C and D are the valid values
Declare #temp2 table
(
value nvarchar(5)
)
declare #value nvarchar(5), #result int, #total int, #flag bit, #tempValue nvarchar(5)
DECLARE myCursor CURSOR FOR
SELECT value, result
FROM #temp
set #flag = 1
set #tempValue = ''
OPEN myCursor;
FETCH NEXT FROM myCursor into #value, #result;
WHILE ##FETCH_STATUS = 0
BEGIN
--logic here
if (#tempValue <> #value and #result = 1) or #flag = 1
begin
set #tempValue = #value
set #total = 1
set #flag = 0
end
else --#tempvalue = #value
begin
if #result = 1
set #total = #total + 1
else --#result = 0
set #flag = 1
if #total >3 --valid value has reached 4 consecutive result =1
begin
set #flag = 1
insert into #temp2 values (#value)
end
end
FETCH NEXT FROM myCursor into #value, #result;
END;
CLOSE myCursor;
DEALLOCATE myCursor;
select * from #temp2
This is the Result of the loop (table #temp2)
value
-----
D
C
(2 row(s) affected)
You can do this in a select statement. You can find groups of items in a row by using row_number() assuming you have an id. SQL tables are inherently unordered, so you need an id or creation date or something to specify the ordering. Here is the SQL:
select value
from (select t,
(row_number() over (partition by value order by id) -
row_number() over (partition by value, results order by id)
) as grp
from table t
) t
group by value, result, grp
having count(*) > 3 and result = 1;

SQL delete on group by conditions

A cursor is used, but it's slow and appears to be a big bottleneck in a SQL job. Basically, this is a cleanup effort to remove all but the top X accessories (ordered by sales rank) from a particular source that's previously grouped by a product id and account visibility.
The command is basically built in the each iteration of the cursor loop and exec'ed manually.
The vis column refers to multiple tenants that sort of acts like a bitmask e.g. two tenants could have the same product.
declare #prodid int
declare #cnt int
declare #vis bigint
declare #cmd varchar(600)
declare #clause varchar(600)
-- find records with more than X excess accessories
declare cur cursor for
select pa.prodid, 'cnt' = count(*), vis from [accessories] pa
group by prodid, vis
having count(*) > X -- e.g. 5
Sample output could look like
prodid cnt vis
123 6 128
234 8 260
345 10 512
In the case where X=5, the last 1 salesrank item for 123 would be removed, the last 3 for 234 and the last 5 for 345. Can this be done using a DELETE statement while including the groupings in some nested select?
open cur
fetch next from cur into #prodid, #cnt, #vis
while ##fetch_status = 0
begin
-- a clause that ends up looking like this:
-- 12345 and vis = 128 -- OR -- 23456 and vis is null
set #clause = convert(varchar(14), #prodid) + ' and vis ' + case
when #vis is null then ' is null '
else ' = ' + cast(#vis as varchar) end
-- delete all but the top X from source=2 and that match prodid and vis
set #cmd = 'delete from [accessories]
where source = 2 and prodid=' + #clause +
' and access_prodid in (select top ' + convert(varchar(5), #cnt - X) +
' access_prodid from [accessories] where prodid = '
+ #clause + ' and source = 2 order by salesrank)'
exec(#cmd)
fetch next from cur into #prodid, #cnt, #vis
end
close cur
deallocate cur
Try this:
WITH DupData AS
(
SELECT *,
ROW_NUMBER()
OVER(PARTITION BY pa.prodid, pa.vis ORDER BY salesrank) Position
FROM [accessories] pa
WHERE pa.source = 2
)
DELETE
FROM DupData
WHERE Position > 5
I would do this by using windows functions to identify the rows to be deleted:
with t as (select pa.*,
row_number() over (partition by prodid, vis order b salesrank) as sr
from [accessories] pa
)
delete from pa
from t
where pa.prodid = t.prodid and pa.vis = t.vis and pa.salesrank = t.salesrank
If there is a unique id in the pa table, then you can use that instead of the more complicated where statement. This assumes that salesrank is unique within each prodid/vis group.

SQL Query return values in a set sequence

I have been trying for a while now to return data from the database with the ID(int) values in the following order.
3, 6, 1, 9, 2, 5.
Is there anyway this can be done?
EDIT: Ok i made a bit of a stuff up in my post. the ID's above are just an example.
I am trying to do this dynamically, based around how many records from another table are linked to the record i want to pull out, e.g. i host 3 branches and each branch has a group of shops how would i determine which has the most?
I hope this helps.
Yes, something like this:
select ID from tablename
order by
CASE WHEN ID = 3 THEN 1
WHEN ID = 6 THEN 2
WHEN ID = 1 THEN 3
WHEN ID = 9 THEN 4
WHEN ID = 2 THEN 5
WHEN ID = 5 THEN 6
ELSE 7 END, ID ASC
This will put 3,6,1,9,2,5 and afterwords the other numbers in ascending order.
select cols from table where
order by
case ID when 3 then 0
when 6 then 1
when 1 then 2
when 9 then 3
...
end
You get the idea...
Create a table for the sorting.
CREATE TABLE SortPriority (
SourceID int NULL,
Priority int NULL)
Populate it with the ids and what order they should showup in. Join to the table. and use SortPriority.Priority in your sorting.
You can more easily change the sorting around this way. You would just need to modify the data. You can also later write scripts to populate the table to handle predictable needs in the changing of the sorting.
A split function like this one:
CREATE FUNCTION fnSplit(#str varchar(max), #dlm char(1))
RETURNS #result TABLE (id int, value varchar(50))
AS BEGIN
DECLARE
#id int, #value varchar(50),
#lastpos int, #pos int, #len int;
SET #id = 0;
SET #len = LEN(#str);
SET #lastpos = 1;
SET #pos = CHARINDEX(#dlm, #str + #dlm);
IF #pos <> 0
WHILE 1 = 1 BEGIN
SET #value = SUBSTRING(#str, #lastpos, #pos - #lastpos);
IF #value <> '' BEGIN
SET #id = #id + 1;
INSERT INTO #result VALUES (#id, #value);
END;
IF #pos > #len BREAK;
SET #lastpos = #pos + 1;
SET #pos = CHARINDEX(#dlm, #str + #dlm, #lastpos);
END;
RETURN;
END
would return a row set containing not only the values, but also their indexes within the list. You could then use the function in this way:
SELECT
…
FROM atable t
LEFT JOIN dbo.Split('3,6,1,9,2,5', ',') s ON t.Value = s.Value
ORDER BY
CASE WHEN s.id IS NULL THEN 2147483647 ELSE s.id END