Combine multiple rows into one in SQL - sql

In sql server 2008, I have a table that has the columns [a],[b],[c],[sort] and it has 4 records:
1, NULL, NULL 0
NULL, 2, NULL 1
NULL, NULL, 3 2
10, NULL, NULL 3
I need to combine all the rows in a way that i get one row as a result, and for each column I get the first (ordered by sort column) non-null value. So my result should be:
1, 2, 3
Can anyone suggest how to do this?
Thanks

One way
SELECT (SELECT TOP 1 [a]
FROM #T
WHERE [a] IS NOT NULL
ORDER BY [sort]) AS [a],
(SELECT TOP 1 [b]
FROM #T
WHERE [b] IS NOT NULL
ORDER BY [sort]) AS [b],
(SELECT TOP 1 [c]
FROM #T
WHERE [c] IS NOT NULL
ORDER BY [sort]) AS [c]
Or another
;WITH R
AS (SELECT [a],
[b],
[c],
[sort]
FROM #T
WHERE [sort] = 0
UNION ALL
SELECT Isnull(R.[a], T.[a]),
Isnull(R.[b], T.[b]),
Isnull(R.[c], T.[c]),
T.[sort]
FROM #T T
JOIN R
ON T.sort = R.sort + 1
AND ( R.[a] IS NULL
OR R.[b] IS NULL
OR R.[c] IS NULL ))
SELECT TOP 1 [a],
[b],
[c]
FROM R
ORDER BY [sort] DESC

Related

How to Use Exists in self join

I want those Id whose Orgorder never equal to 1.
CREATE TABLE [dbo].[TEST](
[ORGORDER] [int] NULL,
[Id] [int] NOT NULL,
[ORGTYPE] [varchar](30) NULL,
ORGID INT NULL,
[LEAD] [decimal](19, 2) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[TEST] ([ORGORDER], [Id], [ORGTYPE] ,ORGID, [LEAD]) VALUES (1, 100, N'ABC',1, NULL)
GO
INSERT [dbo].[TEST] ([ORGORDER], [Id], [ORGTYPE],ORGID, [LEAD]) VALUES (0, 100, N'ABC',2, 0)
GO
INSERT [dbo].[TEST] ([ORGORDER], [Id], [ORGTYPE],ORGID, [LEAD]) VALUES (0, 100, N'ACD',1, NULL)
GO
INSERT [dbo].[TEST] ([ORGORDER], [Id], [ORGTYPE],ORGID, [LEAD]) VALUES (0, 101, N'ABC',0, 0)
GO
INSERT [dbo].[TEST] ([ORGORDER], [Id], [ORGTYPE],ORGID, [LEAD]) VALUES (2, 101, N'ABC',4, NULL)
GO
I am using exists but getting my result.
Expected result is -
ID
101
You can do this with one pass of the data, and order all ORGORDER = 1 first, then if it's the first row and it has the ORGORDER value you want to exclude, you can just ignore it.
;WITH x AS
(
SELECT Id, rn = ROW_NUMBER() OVER
(PARTITION BY Id ORDER BY CASE WHEN ORGORDER = 1 THEN 1 ELSE 2 END)
FROM dbo.TEST
)
SELECT Id FROM x WHERE rn = 1 AND ORGORDER <> 1;
Example db<>fiddle
Use a subquery in a NOT EXISTS clause, linking the subquery table to the outer query table by ID:
SELECT DISTINCT T1.ID
FROM dbo.TEST AS T1
WHERE NOT EXISTS (
SELECT *
FROM dbo.TEST AS T2
WHERE T1.ID = T2.ID
AND T2.ORGORDER = 1
)
db<>fiddle
An option would be using an aggregation with a suitable HAVING clause such as
SELECT [Id]
FROM [dbo].[TEST]
GROUP BY [Id]
HAVING SUM(CASE WHEN [ORGORDER] = 1 THEN 1 ELSE 0 END) = 0
where if there's at least one value equals to 1 for the concerned column([ORGORDER]), then that [Id] column won't be listed as result.
Demo

Assign a unique ID to groups of rows that connect over 2 columns in SQL [duplicate]

This question already has answers here:
Group All Related Records in Many to Many Relationship, SQL graph connected components
(6 answers)
Closed 1 year ago.
My situation is the following:
I've a table with 2 ID-Columns and I want to assign a unique ID to rows that link by either one.
Here is an example of 6 rows in my table. All of these rows need to get the same unique ID.
Row
ID1
ID2
1
A
1
2
A
2
3
B
2
4
B
3
5
C
3
6
C
4
Rows 1 and 2 need to get the same unique id because they have the same ID1.
Row 3 needs to get that as well because its ID2 matches the ID2 of row 2.
Row 4 needs to get that as well because its ID1 matches the ID1 of row 3.
Row 5 needs to get that as well because its ID2 matches the ID2 of row 4.
Row 6 needs to get that as well because its ID1 matches the ID1 of row 5.
Basically the two columns form a chain and I want to assign an ID to that chain.
Is there some reasonably efficient way to do this in SQL?
Okay. This is really clumsy solution (and I should probably hand in my SQL Server badge for even suggesting it), but I think it'll get you over the line. I just hope you're not running this on a very large data set.
First up, I created some dummy temp tables to mimic your data (plus a few extra rows):
DROP TABLE IF EXISTS #Tbl1 ;
CREATE TABLE #Tbl1
(
[id] TINYINT NOT NULL
, [ID1] CHAR(1) NOT NULL
, [ID2] TINYINT NOT NULL
) ;
INSERT
INTO #Tbl1 ( [id], [ID1], [ID2] )
VALUES ( 1, 'A', 1 ), ( 2, 'A', 2 ), ( 3, 'B', 2 )
, ( 4, 'B', 3 ), ( 5, 'C', 3 ), ( 6, 'C', 4 )
, ( 7, 'D', 5 ), ( 8, 'D', 5 ), ( 9, 'E', 6 ) ;
Then, using a CTE and the LAG function, I identified which rows would see an increment of a unique identifier, and dumped all this in temp table:
DROP TABLE IF EXISTS #Tbl2 ;
WITH cte_Lags AS
(
SELECT [id]
, [ID1]
, LAG ( [ID1], 1, '' )
OVER ( ORDER BY [ID1] ASC, [ID2] ASC ) AS [ID1_lag]
, [ID2]
, LAG ( [ID2], 1, 0 )
OVER ( ORDER BY [ID1] ASC, [ID2] ASC ) AS [ID2_lag]
FROM #Tbl1
)
SELECT [id] AS [row]
, [ID1]
, [ID2]
, CASE
WHEN [ID1] = [ID1_lag]
OR [ID2] = [ID2_lag]
THEN 0
ELSE 1
END AS [incr_id]
INTO #Tbl2
FROM cte_Lags ;
I then add a column for your unique ID to the temp table:
ALTER TABLE #Tbl2 ADD [unique_id] TINYINT NULL ;
Now this is where it gets real messy!
I create a iterative loop that cycles through each row of the temp table and updates the unique_id column with the appropriate number, incremented only if the row is flagged to be incremented:
DECLARE #RowNum AS TINYINT ;
DECLARE #i AS TINYINT = 0 ;
WHILE ( ( SELECT COUNT(*) FROM #Tbl2 WHERE [unique_id] IS NULL ) > 0 )
BEGIN
SELECT TOP(1) #RowNum = [row]
FROM #Tbl2
WHERE [unique_id] IS NULL
ORDER BY [ID1] ASC, [ID2] ASC, [row] ASC ;
IF ( ( SELECT [incr_id] FROM #Tbl2 WHERE [row] = #RowNum ) = 1 )
SET #i += 1 ;
UPDATE #Tbl2
SET [unique_id] = #i
WHERE [row] = #RowNum ;
END
SELECT [row]
, [ID1]
, [ID2]
, [unique_id]
FROM #Tbl2
ORDER BY [ID1] ASC, [ID2] ASC ;
Now this all assumes that data doesn't repeat further down the table -- e.g. ('A', 1) is not going to reappear at row 50. If it does, this'll all need a little rethink.
I really hope someone cleverer than I can do this for you in a simple recursive CTE or a funky grouping function. But until then, this'll keep the boss happy.
A recursive CTE is useful for something like this.
Traverse through the records, then group the results.
WITH RCTE AS (
SELECT
[Row] AS BaseRow
, ID1 AS BaseID1
, ID2 AS BaseID2
, 1 AS Lvl
, [Row], ID1, ID2
FROM YourTable
UNION ALL
SELECT
c.BaseRow
, c.BaseID1
, c.BaseID2
, c.Lvl + 1
, t.[Row], t.ID1, t.ID2
FROM RCTE c
JOIN YourTable t
ON t.[Row] < c.[Row]
AND (t.ID2 = c.ID2 OR (t.ID2 < c.ID2 AND t.ID1 = c.ID1))
)
SELECT
BaseRow AS [Row]
, BaseID1 AS ID1
, MIN(ID2) AS ID2
FROM RCTE
GROUP BY BaseRow, BaseID1
ORDER BY BaseRow, BaseID1;
db<>fiddle here

Splitting a string on comma and inserting it in a table as a row in sql

I have the following table in my database where two of the columns has comma separated string. I want to split the columns based on comma and insert the string in the database as a row. Below is my table and insert statements:
CREATE TABLE [dbo].[TABLEA](
[Id] [int] IDENTITY(1,1) NOT NULL,
[DocNumber] [varchar](50) NULL,
[InternalDocNumber] [varchar](50) NULL,
[Date] [varchar](50) NULL,
[DocType] [varchar](50) NULL,
[Description] [varchar](50) NULL,
[NameG] [varchar](max) NULL,
[NameGR] [varchar](max) NULL,
[NumberPages] [varchar](50) NULL)
Below are the insert statements in the table:
INSERT INTO [dbo].[TABLEA]
([DocNumber]
,[InternalDocNumber]
,[Date]
,[DocType]
,[Description]
,[NameG]
,[NameGR]
,[NumberPages])
VALUES
(1
,1235
,'12/23/2020'
,3
,'this is a test'
,'test1, test2, test3'
,'test6, test4'
,1),
(2
,3456
,'12/24/2020'
,3
,'this is a test1'
,'test4, test5, test6'
,'test9, test4'
,2)
,
(6
,6789
,'12/24/2020'
,3
,'this is a test3'
,'test9'
,'test100, test15, test16'
,2)
GO
From the above table. I want to create a new table that have a result like below:
ID DocNumber InternalDocnumber date DocType Description NameG NameGR NumberPage
1 1 1235 12/23/2020 3 thisisaTest test1 test6 1
1 1 1235 12/23/2020 3 thisisaTest test2 test4 1
1 1 1235 12/23/2020 3 thisisaTest test3 NULL 1
2 2 3456 12/24/2020 3 thisisaTest1 test4 test9 2
2 2 3456 12/24/2020 3 thisisaTest1 test5 test4 2
2 2 3456 12/24/2020 3 thisisaTest1 test6 NULL 2
3 6 6789 12/24/2020 3 thisisaTest3 test9 test100 2
3 6 6789 12/24/2020 3 thisisaTest3 NULL test15 2
3 6 6789 12/24/2020 3 thisisaTest3 NULL test16 2
Basically, I want the comma delimited string that is present in column NameG and NameGR to be splitted on comma based and then insert in a new table as anew row. The order is very important, if there is "Test1" in NameG column then here should be "Test6" in columnGR.
Any help with this will be highly appreciated.
This is a pain in SQL Server, because strint_split() doesn't provide a number or even a guaranteed ordering. So instead, use a recursive CTE:
with cte as (
select a.docnumber, convert(varchar(max), null) as gr, convert(varchar(max), null) as g,
convert(varchar(max), nameGR) as restGR, convert(varchar(max), nameG) as restG, 0 as lev
from tableA a
union all
select cte.docnumber,
left(restgr, charindex(',', restgr + ',') - 1) as gr,
left(restg, charindex(',', restg + ',') - 1) as g,
stuff(restgr, 1, charindex(',', restgr + ',') + 1, '') as restgr,
stuff(restg, 1, charindex(',', restg + ',') + 1, '') as restg,
lev + 1
from cte
where restgr > '' or restg > ''
)
select id, gr, g
from cte
where lev > 0;
Here is a db<>fiddle.
This only shows the one docnumber column. You can add the rest of the columns. I think they just confuse the presentation.
with gx as
(select [Id], [NameG],
row_number() over (partition by [id] order by [Id]) as rNo
from (select [Id] ,ltrim(x.Value) as [NameG]
from tablea
cross apply string_split(nameG, ',') as x) g),
grx as (
select [Id], [NameGR],
row_number() over (partition by [Id] order by [Id]) as rNo
from
(select [Id] ,ltrim(x.Value) as [NameGR]
from tablea
cross apply string_split(nameGR, ',') as x) gr),
names (Id, NameG, NameGR, r1, r2) as
( select coalesce(gx.Id, grx.Id), gx.NameG, grx.NameGR, coalesce(gx.rNo, grx.rNo), coalesce(grx.rNo, gx.rNo)
from gx
full join grx on gx.Id = grx.Id and gx.rNo = grx.rNo)
select a.Id
,[DocNumber]
,[InternalDocNumber]
,[Date]
,[DocType]
,[Description]
,n.[NameG]
,n.[NameGR]
,[NumberPages]
from tableA a
inner join names n on a.Id = n.Id
order by a.Id, r1, r2;
use the function Call STRING_SPLIT
SELECT ID,value AS NameG
FROM TABLEA
CROSS APPLY STRING_SPLIT(NameG, ',')
with this function the separated comma values ​​are separated but with the relation of the ID
then we look for the relationship with the id of tablea
WITH DataSplit as
(
SELECT ID,value AS NameG
FROM TABLEA
CROSS APPLY STRING_SPLIT(NameG, ',')
)
select TABLEA.Id,TABLEA.DocNumber,TABLEA.InternalDocNumber,TABLEA.Date,
TABLEA.DocType,TABLEA.Description, DataSplit.NameG
from TABLEA inner join DataSplit on TABLEA.id=DataSplit.id;
see the example in fiddler for more detail

Updating column values of a table with column values from a different table

I have a table (Table 1) with columns FN, LN, MN and eleven other columns. I have another table (Table 2) which has FN, LN, MN (only three columns).
I want to update my values FN, LN and MN in the Table 1 with the values FN, LN and MN from Table 2.
I do NOT need to Join on the basis of any other common column.
Please let me know if there is a way to do it.
I cannot use a SELECT * INTO statement because the structure of the two tables is not the same. Table 1 has 11 odd columns and Table 2 has 3 columns.
If Table 1 contains:
[FN] [LN] [MN] [A] [B] [C]
--------------------------------------------------------
hello world 123 something else 456
other row 45 demo data 789
something else 456 NULL NULL 999
and Table 2 contains:
[FN] [LN] [MN]
----------------------------
table two 1
just-a-demo here 2
final row 3
and the expected result is:
[FN] [LN] [MN] [A] [B] [C]
--------------------------------------------------------
table two 1 something else 456
just-a-demo here 2 demo data 789
final row 3 NULL NULL 999
you can first get the expected result like this;
select [S].[FN], [S].[LN], [S].[MN], [F].[A], [F].[B], [F].[C] from
(
select top 10
row_number() over (order by (select 1)) as [Id], [A], [B], [C]
from [Table 1]
) as [F]
left join
(
select top 10
row_number() over (order by (select 1)) as [Id], [FN], [LN], [MN]
from [Table 2]
) as [S]
on [F].[Id] = [S].[Id]
Note:
The top 10: you may need to remove this, but you must be sure both tables return the same number of rows. If they don't, there is no way to do 1:1 mapping according to the information you gave in the comments.
row_number() over (order by (select 1)) simply puts "1" for the first returned row, "2" for the next one, etc. This is a weird way to join the results from two tables: I would expect to have an actual ID in both tables, or something which enables to do an actual join.
You can then insert it into a temporary table:
what you can do is to use the insert into with a subquery select like this:
insert into [TempTable] ([FN], [LN], [MN], [A], [B], [C])
select [S].[FN], [S].[LN], [S].[MN], [F].[A], [F].[B], [F].[C] from
(
select top 10
row_number() over (order by (select 1)) as [Id], [A], [B], [C]
from [Table 1]
) as [F]
left join
(
select top 10
row_number() over (order by (select 1)) as [Id], [FN], [LN], [MN]
from [Table 2]
) as [S]
on [F].[Id] = [S].[Id]
and then replace [Table 1] by [TempTable].
Is this the way that you are looking for.
Declare #table1 Table
(id int identity(1,1),
FN varchar(15),
LN varchar(15),
MN varchar(15),
Flag1 int default(1),
Flag2 int default(1),
Flag3 int default(0))
Declare #table2 Table
(id int identity(1,1), FN varchar(15), LN varchar(15), MN varchar(15))
Insert into #table1 (Fn,LN,MN) Values
('A','B','C'),('A','B','D'),('A','X','C')
Insert into #table2 (Fn,LN,MN) Values
('A','B','C'),('A','B','D'),('A','Y','C')
Merge into #table1 A
using #table2 B On
A.FN = B.FN AND
A.LN = B.LN AND
A.MN = B.MN
When Not Matched then
Insert (FN,LN,MN)
Values (B.FN,B.LN,B.MN);
Select * from #table1
Result

Duplicate a row multiple times

Basically I want to duplicate a row a variable number of times.
I have a table with the following structure:
CREATE TABLE [dbo].[Start](
[ID] [int] NOT NULL,
[Apt] [int] NOT NULL,
[Cost] [int] NOT NULL)
I want to duplicate each row in this table (Apt-1) times so in the end there will be #Apt rows. Moreover for each new row the value of Cost is decremented until it reaches 0. ID will be the same as there are no primary keys. If I have a record like this:
1 5 3
I need 4 new rows inserted in the same table and they should look like this
1 5 2
1 5 1
1 5 0
1 5 0
I have tried so far a lot of ways but I cannot make it work. Many thanks!
try this
DECLARE #Start TABLE (
[ID] [int] NOT NULL,
[Apt] [int] NOT NULL,
[Cost] [int] NOT NULL)
INSERT #Start (ID, Apt, Cost)
VALUES (1, 5, 3)
; WITH CTE_DIGS AS (
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS rn
FROM master.sys.all_columns AS a
)
INSERT #Start (ID, Apt, Cost)
SELECT ID, Apt, CASE WHEN Cost - rn < 0 THEN 0 ELSE Cost - rn END
FROM #Start
INNER JOIN CTE_DIGS
ON Apt > rn
Try:
;with cte as
(select [ID], [Apt], [Cost], 1 counter from [Start]
union all
select [ID],
[Apt],
case sign([Cost]) when 1 then [Cost]-1 else 0 end [Cost],
counter+1 counter
from cte where counter < [Apt])
select [ID], [Apt], [Cost]
from cte