Compare comma separated rows - sql

I have a requirement where in I should compare top row with previous rows of database records which they are comma separated.
example :
My table data looks something like this:
ID Phase Updated By
1 Test1,Test2,Test3 sxmalla
2 Test1,Test2 rkgauta
3 Test1,Test3 sxmalla
I have to display somthing the most recent changes in Phase and who has updated.The comma seperated data is submitted with multiple chechboxes in front end. User can update them through multiple checkboxes in same page.
Here Test3 is most recent change,then below that Test2 is the most recent change and on third row the actual first entry.
I need to show the result as
Phase Updated By
Test3 sxmalla
Test2 rkgauta
Test1,Test3 sxmalla
Here is the code that I currently using to compare 2 comma seperated rows
ALTER Proc [dbo].[PMT_GetPhasesEditHistory]
#Project_ID int
As Begin
SET NOCOUNT ON
Declare #added varchar(1000), #removed varchar(1000),#strN varchar(1000),#strO varchar(1000)
DECLARE test_cursor CURSOR FOR
SET #strO=(SELECT TOP 1 P1.Phase from phase P1,Phase P2 where P1.ID = P2.ID-1 and p1.project_id=#Project_ID order by p1.ID Desc)
SET #strN =(SELECT TOP 1 P2.Phase from phase P1,Phase P2
where P1.ID = P2.ID-1 and p1.project_id=#Project_ID order by p1.ID Desc)
If object_id('dbo.#tN') is not null Begin ; drop table dbo.#tN ;End
CREATE TABLE dbo.#tN( var varchar(100)); insert into #tN select * from fnSplitStringAsTable(#strN,',')
If object_id('dbo.#tO') is not null Begin; drop table dbo.#tO ;End
CREATE TABLE dbo.#tO (var varchar(100)); insert into #tO select * from fnSplitStringAsTable(#strO,',')
Declare #i int
Set #i=1
Set #added = ''
While #i != (select COUNT(*)+1 from #tN)
Begin
if not exists(select VAR from #tO where var = (select Top 1 var from #tN where var in ( Select Top (#i) VAR from #tN where Var in (Select Top (#i) var From #tN order by VAR desc) order by var asc)))
Begin
Set #added = #added + (select Top 1 var from #tN where var in ( Select Top (#i) VAR from #tN where Var in (Select Top (#i) var From #tN order by VAR desc) order by var asc)) + ','
End
set #i=#i+1
End
If(len(#added) > 1) Begin; set #added = RTRIM(LEFT(#added,Len(#added) - 1)); End
Select #added as Added
drop table #tN;Drop table #tO
End

Here is a possible solution:
WITH CTE_Prep AS
(
SELECT *, DENSE_RANK() OVER (ORDER BY ID DESC) RN
FROM phase
CROSS APPLY dbo.DelimitedSplit8K(Phase,',')
)
,CTE_Result AS
(
SELECT ID, STUFF ((SELECT ', ' +Item
FROM CTE_Prep c WHERE Item NOT IN (SELECT Item FROM CTE_Prep c2 WHERE c2.RN + 1 = c.RN)
AND c.Id = c3.ID
FOR XML PATH('')),1,2,'') AS Phase
FROM CTE_Prep c3
GROUP BY ID
)
SELECT r.Phase, p.[Updated By] FROM CTE_Result r
LEFT JOIN phase p ON r.id = p.id
This is using DelimitedSplit8K function from SQLServerCentral for splitting, but I'm sure you can do with your existing.
SQLFiddle DEMO

I am going to try and answer the normalization question.
Table PhaseTest
PhaseID int
TestID int
PK PhaseID, TestID
(this PK will enforce no duplicate TestID in a PhaseID
Table PhaseUser
PhaseID int PK
UserID int
Table Test
TestID int PK
TestName varchar
Table User
UserID int PK
UserName varchar
this shows the results on separate lines (no comma)
select PT1.PhaseID, Test.TestName, User.UserName
from PhaseTest PT1
left outer join PhaseTest PT2
on PT2.PhaseID = PT1.PhaseID + 1
and PT2.TestID = PT1.TestID
join PhaseUser
on PhaseUser.UserID = PT1.UserID
join Test
on Test.TestID = PT1.UserID
where PT2.PhaseID is null
order by PT1.TestID
union
select PT1.PhaseID, Test.TestName, User.UserName
from PhaseTest PT1
join PhaseUser
on PhaseUser.UserID = PT1.UserID
join Test
on Test.TestID = PT1.UserID
where PT1.PhaseID = (select max(PhaseID) from PhaseTest)

Related

How to find Missing number in Sequence field using SQL?

I have a table called Student and field name called StudentNumber
Student Table
StudentNumber
-------------
1
2
3
4
5
8
10
Expecting output
6
7
9
I tried like below
Declare #trans int;
set #trans = 1;
while(#trans <=50000)
BEGIN
if((select StudentNumber from [Student] where StudentNumber = #trans) != #trans)
BEGIN
print #trans;
END
END
set #trans = #trans + 1;
You could use (SQL Server 2016 and above):
SELECT Number
FROM (select cast([key] as int) +
(SELECT MIN(StudentNumber) FROM Students) as number
from OPENJSON( '[1'
+ replicate(',1',(SELECT MAX(StudentNumber) FROM Students)-
(SELECT MIN(StudentNumber) FROM Students))+']')) n
LEFT JOIN Students s
ON n.number = s.StudentNumber
WHERE s.StudentNumber IS NULL;
DBFiddle Demo
Note: You could exchange first subquery with any other tally number generator.
More info: SQL, Auxiliary table of numbers
it should be like below
Declare #trans int;
set #trans = 1;
while(#trans <=50000)
BEGIN
if NOT EXISTS (select StudentNumber from [Student] where StudentNumber = #trans)
BEGIN
print #trans;
END
END
set #trans = #trans + 1;
You can do the following
;with report as(
select 1 as missing
union all
select missing + 1
from report
where missing < #max
)
select *
from report m
where not exists ( select 1 from student s where s.id = m.missing)
option (maxrecursion 0);
Here a working demo
Result
missing
6
7
9
Hope that this will help you
I know you try to solve it iterative, just for sports
an idea how to achieve the same using recursive CTE
with missingstudents (StudentNumber)
as (
select StudentNumber-1 from Students s
where not exists (
select StudentNumber from Students s2
where s.StudentNumber-1 = s2.StudentNumber)
UNION ALL
select StudentNumber-1 from missingstudents s
where not exists (
select StudentNumber from Students s2
where s.StudentNumber-1 = s2.StudentNumber)
)
select * from missingstudents
You can try this:
select m.number from
(select min(StudentNumber) a,max(StudentNumber) b from Students) c ,master..spt_values M
where c.a <= m.number
and c.b > = m.number
and type ='P'
and m.number not in (select StudentNumber from Students)
Try like this;
declare #id int
declare #maxStudentNumber int
set #id = 1
select #maxStudentNumber = max(StudentNumber) from Student
create table #MissingIds
(
id int
)
while #id < #maxStudentNumber
begin
insert into #MissingIds values(#id)
set #id = #id + 1
end
select m.id
from #MissingIds m
left join Student s
on m.id = s.StudentNumber
where s.StudentNumber is null
drop table #MissingIds

How to copy records from table and insert into same table using stored procedure SQL Server

I have following table:
I want to copy only those records which are from version 0 and their student_id is never repeated in version 1, that means unchanged records. and I want to insert all copied records to same table with version 1. What will be stored procedure for this.
using group by and having max(version) = 0:
insert into student_name (student_id, student_name, version)
select student_id, max(student_name), 1
from student_name
group by student_id
having max(version) = 0
As a stored procedure, taking a parameter for version, that inserts records for students who do not have a record for that version: and outputs the rows that were inserted:
create procedure dbo.insert_new_version (#version int) as
begin;
set nocount, xact_abort on;
insert into student_name (student_id, student_name, version)
output inserted.*
select
student_id
, student_name = max(student_name)
, version = #version
from student_name
group by student_id
having max(version) < #version
end;
rextester demo: http://rextester.com/JSTNI40605
returns:
+-----------+------------+--------------+---------+
| record_id | student_id | student_name | version |
+-----------+------------+--------------+---------+
| 11 | 3 | ccc | 1 |
+-----------+------------+--------------+---------+
You can select the records by doing:
select t.*
from t
where t.version = 0 and
not exists (select 1
from t t2
where t2.student_id = t.student_id and t2.version = 1
);
The rest is just an insert.
I'd suggest using a while loop to go through the table and identify the items that you need to copy, then these values will be
evaluated and if they meet the criterion for re-inserting and then insert them.
Your code should look like the following. Add CREATE PROC part and Edit table names where applicable
(Caveat: I have written this on notepad so if you get a few errors, just try to fix them)
DECLARE #counter int = 0, #row_Count int = 0, #currentId int,
#currentName nvarchar(100), #version int, #current_Student_id int
SET #row_Count = (SELECT COUNT(record_Id) from yourTable)
WHILE #counter <= #row_Count
BEGIN
SET #currentId = (SELECT record_Id FROM (SELECT row_number() over (order by id)
AS RowNum, record_Id FROM yourTable) sub WHERE RowNum=#counter)
SET #currentName = (SELECT student_name FROM yourTable WHERE record_Id = #currentId)
SET #current_Student_id = (SELECT student_id FROM yourTable WHERE record_Id = #currentId)
SET #version = (SELECT version FROM yourTable WHERE record_Id = #currentId)
--USE IF to check if the current version is 0 and the student ID has not been inserted already
IF (SELECT COUNT(record_Id) FROM yourTable WHERE student_id = #current_Student_id AND version = 1) < 1
AND #version = 0
BEGIN
INSERT INTO yourTable (student_id, student_name, version)
VALUES
(
#current_Student_id,
#currentName,
1
)
END
SET #counter = #counter + 1;
END
You can select and insert like this
Insert INTO tableName select t1.student_Id, t1.student_name,1 from tablename t1
where t1.version = 0 and not exists
(select 1 from tablename t2 where t2.student_id = t.student_id and t2.version = 1);
You can try this by LEFT Join:
INSERT INTO tbl
SELECT T1.record_id,T1.student_Id,T1.student_name, 1
FROM tbl T1 LEFT JOIN tbl T2
ON T1.student_Id = T2.student_Id AND T2.version = 1
WHERE T1.version = 0 AND T2.record_id IS NULL

How to tune the following query?

This query gives me the desired result but i can't run this query every time.The 2 loops is costing me.So i need to implement something like view.But the logic has temp tables involved which isn't allowed in views as well.so, is there any other way to store this result or change the query so that it will cost me less.
DECLARE #Temp TABLE (
[SiteID] VARCHAR(100)
,[StructureID] INT
,[row] DECIMAL(4, 2)
,[col] DECIMAL(4, 2)
)
DECLARE #siteID VARCHAR(100)
,#structureID INT
,#struct_row INT
,#struct_col INT
,#rows_count INT
,#cols_count INT
,#row INT
,#col INT
DECLARE structure_cursor CURSOR
FOR
SELECT StructureID
,SiteID
,Cols / 8.5 AS Cols
,Rows / 11 AS Rows
FROM Structure
WHERE SellerID = 658 --AND StructureID = 55
OPEN structure_cursor
FETCH NEXT
FROM structure_cursor
INTO #structureID
,#siteID
,#struct_col
,#struct_row
SELECT #rows_count = 1
,#cols_count = 1
,#row = 1
,#col = 1
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #row <= #struct_row
BEGIN
WHILE #col <= #struct_col
BEGIN
--PRINT 'MEssage';
INSERT INTO #Temp (
SiteID
,StructureID
,row
,col
)
VALUES (
#siteID
,#structureID
,#rows_count
,#cols_count
)
SET #cols_count = #cols_count + 1;
SET #col = #col + 1;
END
SET #cols_count = 1;
SET #col = 1;
SET #rows_count = #rows_count + 1;
SET #row = #row + 1;
END
SET #row = 1;
SET #col = 1;
SET #rows_count = 1;
FETCH NEXT
FROM structure_cursor
INTO #structureID
,#siteID
,#struct_col
,#struct_row
END
CLOSE structure_cursor;
DEALLOCATE structure_cursor;
SELECT * FROM #Temp
Do this with a set-based operation. I think you just want insert . . . select:
INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT StructureID, SiteID, Cols / 8.5 AS Cols, Rows / 11 AS Rows
FROM Structure
WHERE SellerID = 658;
You should avoid cursors, unless you really need them for some reason (such as calling a stored procedure or using dynamic SQL on each row).
EDIT:
Reading the logic, it looks like you want to insert rows for based on the limits in each row. You still don't want to use a cursor. For that, you need a number generator and master..spt_values is a convenient one, if it has enough rows. So:
with n as (
select row_number() over (order by (select null)) as n
from master..spt_values
)
INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT StructureID, SiteID, ncol.n / 8.5 AS Cols, nrow.n / 11 AS Rows
FROM Structure s JOIN
n ncol
ON ncol.n <= s.struct_col CROSS JOIN
n nrow
ON nrow <= s.struct_row
WHERE SellerID = 658;
You can generate the number of rows and columns and then CROSS APPLY with those, like below. I've left out your SellerID condition.
;WITH Cols
AS
(
SELECT StructureID, SiteID, CAST(Cols / 8.5 AS INT) AS Col
FROM Structure
UNION ALL
SELECT s.StructureID, s.SiteID, Col - 1
FROM Structure s
INNER JOIN Cols c ON s.StructureID = c.StructureID AND s.SiteID = c.SiteID
WHERE Col > 1
)
, Rows
AS
(
SELECT StructureID, SiteID, CAST(Rows / 11 AS INT) AS Row
FROM Structure
UNION ALL
SELECT s.StructureID, s.SiteID, Row - 1
FROM Structure s
INNER JOIN Rows r ON s.StructureID = r.StructureID AND s.SiteID = r.SiteID
WHERE Row > 1
)
--INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT s.SiteID, s.StructureID, r.Row, c.Col
FROM Structure s
CROSS APPLY Cols c
CROSS APPLY Rows r
WHERE s.StructureID = c.StructureID AND s.SiteID = c.SiteID
AND s.StructureID = r.StructureID AND s.SiteID = r.SiteID
We can do this by using CROSS APPLY and CTE.
CREATE TABLE Structure(SiteID varchar(20), StructureID int,
Cols decimal(18,2), [Rows] decimal(18,2))
INSERT INTO Structure (SiteID, StructureID, Cols, [Rows])
VALUES
('MN353970', 51,17,22),
('MN272252', 52,17,11)
;WITH RowCTE([Rows]) AS
(
SELECT 1
UNION ALL
SELECT 2
),
ColCTE(Cols) AS
(
SELECT 1
UNION ALL
SELECT 2
)
SELECT SiteID, StructureID, R.Rows, C.Cols
FROM Structure s
CROSS APPLY
(
SELECT Cols FROM ColCTE
) C
CROSS APPLY
(
SELECT [Rows] FROM RowCTE
) R
Sql Fiddle Demo

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 Server 2000 : generating and incrementing data from column conditionally without using CURSOR

:)
Is there any way to create an index, and incrementing with a given condition, but without CURSOR handling/usage
For example:
The condition in my case is that: "if the current color (this is the item to be checked) is the same as the last one: not increment, otherwise increment in one unit"
This must be in a SQL query with no CURSOR USAGE and of course a good time (work with ... 10000 rows at least)
Thanks in advance.
EDIT: I forgot to mention that NEW_INDEX Column doesn't exist. It must be generated with the with the query.
EDIT2: Is there a way that only make use of SELECT/INSERT/UPDATE statements? (not set, declare...)
Assume a table called Colors with fields ID, Color, and ColorIndex of types int, varchar, and int respectively. I also assume the OP means prev / after based on an ordering of the ID field in asc order.
You could do this without a cursor, and use a while loop...but it definately isn't set based:
DECLARE #MyID int
DECLARE #CurrentIndex int
DECLARE #CurrentColor varchar(50)
DECLARE #PreviousColor varchar(50)
SET #CurrentIndex = (SELECT 0)
SET #MyID = (SELECT TOP 1 ID FROM Colors ORDER BY ID ASC)
SET #CurrentColor = (SELECT '')
SET #PreviousColor = (SELECT Color FROM Colors WHERE ID = #MyID)
WHILE (#MyID IS NOT NULL)
BEGIN
IF (#CurrentColor <> #PreviousColor)
BEGIN
SET #PreviousColor = (SELECT Color FROM Colors WHERE ID = #MyID)
SET #CurrentIndex = (SELECT #CurrentIndex + 1)
UPDATE Colors SET ColorIndex = #CurrentIndex WHERE ID = #MyID
END
ELSE
BEGIN
UPDATE Colors SET ColorIndex = #CurrentIndex WHERE ID = #MyID
SET #PreviousColor = (SELECT Color FROM Colors WHERE ID = #MyID)
END
SET #MyID = (SELECT TOP 1 ID FROM Colors WHERE ID > #MyID ORDER BY ID ASC)
SET #CurrentColor = (SELECT Color FROM Colors WHERE ID = #MyID)
END
The result after execution:
Performance wasn't too shabby as long as ID and color are indexed. The plus side is it is a bit faster then using a regular old CURSOR and it's not as evil. Solution supports SQL 2000, 2005, and 2008 (being that you are using SQL 2000 which did not support CTEs).
declare #ID int,
#MaxID int,
#NewIndex int,
#PrevCol varchar(50)
select #ID = min(ID),
#MaxID = max(ID),
#PrevCol = '',
#NewIndex = 0
from YourTable
while #ID <= #MaxID
begin
select #NewIndex = case when Colour = #PrevCol
then #NewIndex
else #NewIndex + 1
end,
#PrevCol = Colour
from YourTable
where ID = #ID
update YourTable
set NewIndex = #NewIndex
where ID = #ID
set #ID = #ID + 1
end
https://data.stackexchange.com/stackoverflow/q/122958/
select
IDENTITY(int,1,1) as COUNTER
,c1.ID
into
#temp
from
CUSTOMERS c1
left outer join (
select
c1.ID, max(p.ID) as PRV_ID
from
CUSTOMERS c1,
(
select
ID
from
CUSTOMERS
) p
where
c1.ID > p.ID
group by
c1.ID
) k on k.ID = c1.ID
left outer join CUSTOMERS p on p.ID = k.PRV_ID
where
((c1.FAVOURITE_COLOUR < p.FAVOURITE_COLOUR)
or
(c1.FAVOURITE_COLOUR > p.FAVOURITE_COLOUR)
or
p.FAVOURITE_COLOUR is null)
update
CUSTOMERS
set
NEW_INDEX = i.COUNTER
--select *
from
CUSTOMERS
inner join (
select
c1.ID, max(t.COUNTER) as COUNTER
from
CUSTOMERS c1,
(
select
ID
,COUNTER
from
#temp
) t
where
c1.ID >= t.ID
group by
c1.ID
) i on i.ID = CUSTOMERS.ID
drop table #temp
select * from CUSTOMERS