How to select distinct value from two columns into one column - sql

There are two location related columns in my table like below
| Service_No | A_LOC | Z_LOC |
|------------|-------|-------|
| 001 | A | B |
| 002 | A | C |
| 003 | Null | C |
| 004 | F | B |
How do I select the distinct values of columns A_LOC and Z_LOC combined into a single list? The result of this query would be:
A, B, C, F

here is one way:
select string_Agg(Location,',') as distinct_location_list
from (
select A_LOC Location FROM tablename
union
select Z_LOC Location FROM tablename
) tt

I think, I have a quick solution for you. However, you can optimize it or you can do it in different ways.
DECLARE #YourTable TABLE
(
Service_No VARCHAR(30),
A_LOC VARCHAR(30),
Z_LOC VARCHAR(30)
);
INSERT INTO #YourTable Values('001','A','B');
INSERT INTO #YourTable Values('002','A','C');
INSERT INTO #YourTable Values('003',NULL,'C');
INSERT INTO #YourTable Values('004','F','B');
DECLARE #TempTable TABLE(FINALDATA VARCHAR(30));
INSERT INTO #TempTable
SELECT A_LOC FROM #YourTable WHERE A_LOC IS NOT NULL;
INSERT INTO #TempTable
SELECT Z_LOC FROM #YourTable WHERE Z_LOC IS NOT NULL;
SELECT DISTINCT FINALDATA FROM #TempTable;
Note: This code is written in SQL SERVER. Please check the code and let me know.

Alternatively try this method if the string_agg function is not available.
select
STUFF(
(SELECT ', ' + Loc FROM
(
select distinct A_Loc as Loc from #t
union
select distinct Z_Loc as Loc from #t
) t2
FOR XML PATH (''))
, 1, 1, '')

I would recommend unpivoting using apply and then filtering and distincting:
select string_agg(loc, ',')
from (select distinct loc
from t cross apply
(values (a_loc), (z_loc)) v(loc)
where loc is not null
) v;

Related

Sort an array of strings in SQL

I have a column of strings in SQL Server 2019 that I want to sort
Select * from ID
[7235, 6784]
[3235, 2334]
[9245, 2784]
[6235, 1284]
Trying to get the result below:
[6784, 7235]
[2334, 3235]
[2784, 9245]
[1284, 6235]
Given this sample data:
CREATE TABLE dbo.ID(ID int IDENTITY(1,1), SomeCol varchar(64));
INSERT dbo.ID(SomeCol) VALUES
('[7235, 6784]'),
('[3235, 2334]'),
('[9245, 2784]'),
('[6235, 1284]');
You can run this query:
;WITH cte AS
(
SELECT ID, SomeCol,
i = TRY_CONVERT(int, value),
s = LTRIM(value)
FROM dbo.ID CROSS APPLY
STRING_SPLIT(PARSENAME(SomeCol, 1), ',') AS s
)
SELECT ID, SomeCol,
Result = QUOTENAME(STRING_AGG(s, ', ')
WITHIN GROUP (ORDER BY i))
FROM cte
GROUP BY ID, SomeCol
ORDER BY ID;
Output:
ID
SomeCol
Result
1
[7235, 6784]
[6784, 7235]
2
[3235, 2334]
[2334, 3235]
3
[9245, 2784]
[2784, 9245]
4
[6235, 1284]
[1284, 6235]
Example db<>fiddle
The source table has a column with a JSON array.
That's why it is a perfect case to handle it via SQL Server JSON API.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID int IDENTITY PRIMARY KEY, jArray NVARCHAR(100));
INSERT #tbl (jArray) VALUES
('[7235, 6784]'),
('[3235, 2334]'),
('[9245, 2784]'),
('[6235, 1284]');
-- DDL and sample data population, end
SELECT t.*
, Result = QUOTENAME(STRING_AGG(j.value, ', ')
WITHIN GROUP (ORDER BY j.value ASC))
FROM #tbl AS t
CROSS APPLY OPENJSON(t.jArray) AS j
GROUP BY t.ID, t.jArray
ORDER BY t.ID;
Output
+----+--------------+--------------+
| ID | jArray | Result |
+----+--------------+--------------+
| 1 | [7235, 6784] | [6784, 7235] |
| 2 | [3235, 2334] | [2334, 3235] |
| 3 | [9245, 2784] | [2784, 9245] |
| 4 | [6235, 1284] | [1284, 6235] |
+----+--------------+--------------+

SQL query to get list of all records that are placed higher in hierarchy

Table:
+-----+------------+-------------+
| Id | DocumentNo | ParentCCID |
+-----+------------+-------------+
| 10 | CC001 | NULL |
| 20 | CC002 | CC001 |
| 33 | CC003 | CC002 |
+-----+-------------+-------------+
Value passed to the query: CC003
Expected Output:
CC003
CC002
CC001
Failed Attempt:
select b2.documentno,b2.ParentCCID from basicdetails b1
inner join basicdetails b2 on b1.documentno = b2.ParentCCID
where b2.documentno='CC003'
Note: DocumentNo is unique primary key. ParentCCID could have null values if there is no parent record.
EDIT:
create table basicdetails2
(
id int identity,
documentno varchar(30),
parentccid varchar(30)
)
insert into basicdetails2 values('CC001', null)
insert into basicdetails2 values('CC002', 'CC001')
insert into basicdetails2 values('CC003', 'CC002')
insert into basicdetails2 values('CC004', 'CC003')
You want a recursive cte:
with cte as (
select bd.documentno, bd.ParentCCID
from basicdetails bd
where bd.documentno = 'CC003'
union all
select cte.documentno, cte.ParentCCID
from cte join
basicdetails bd
on bd.documentno = cte.ParentCCID
)
select bd.documentno
from cte;
Just a minor twist on Gordon's answer (already +1).
I like to track the level and see the parents for each record
Example
Declare #Fetch varchar(25) = 'CC003'
;with cte as (
Select DocumentNo
,ParentCCDocumentNo
,Lvl=1
From YourTable
Where DocumentNo=#Fetch
Union All
Select R.DocumentNo
,R.ParentCCDocumentNo
,P.Lvl+1
From YourTable R
Join cte P on P.ParentCCDocumentNo = R.DocumentNo)
Select Lvl = Row_Number() over (Order By Lvl Desc)
,DocumentNo
,ParentCCDocumentNo
From cte
Order By 1 desc
Returns
Lvl DocumentNo ParentCCDocumentNo
3 CC003 CC002
2 CC002 CC001
1 CC001 NULL

How to find duplicate row when any one word of desc column matching within group

I have a result like this:
I need to update "flag" column as duplicate when any one word from the row matches with second row within group of "mfgid" column.
--test dataset
declare #table as table
(id int,
mfgid int,
[desc] varchar(100))
insert into #table
values (1,111,'abc xyz pqr'),
(2,111,'abc tyu fgh'),
(3,222,'abc pqr'),
(4,222,'lmn stu'),
(5,333,'pqr spd hki abc'),
(6,333,'lmn jsk pqr klo')
How can I do this?
Here is a possible solution
WITH K AS
(
SELECT mfgid,
value,
count(*) over ( partition by mfgid, value order by mfgid) Dups
FROM #Table cross apply STRING_SPLIT([desc], ' ')
)
SELECT T.*,
IIF(
EXISTS(SELECT 1 FROM K WHERE K.mfgid = T.mfgid AND K.Dups > 1),
'Duplicte',
''
) Flag
FROM #Table T;
Results:
+----+-------+-----------------+----------+
| id | mfgid | desc | Flag |
+----+-------+-----------------+----------+
| 1 | 111 | abc xyz pqr | Duplicte |
| 2 | 111 | abc tyu fgh | Duplicte |
| 3 | 222 | abc pqr | |
| 4 | 222 | lmn stu | |
| 5 | 333 | pqr spd hki abc | Duplicte |
| 6 | 333 | lmn jsk pqr klo | Duplicte |
+----+-------+-----------------+----------+
Demo
two possible solutions below:
--test dataset
declare #table as table
(id int,
mfgid int,
[desc] varchar(100))
insert into #table
values (1,111,'abc xyz pqr'),
(2,111,'abc tyu fgh'),
(3,222,'abc pqr'),
(4,222,'lmn stu'),
(5,333,'pqr spd hki abc'),
(6,333,'lmn jsk pqr klo')
Solution 1:
If you have only 4 words in string (based on your screenshot)
;with cte2 as
(select *
from (select id,
mfgid,
parsename(replace(s.[desc],' ','.'),1) as [a1],
parsename(replace(s.[desc],' ','.'),2) as [a2],
parsename(replace(s.[desc],' ','.'),3) as [a3],
parsename(replace(s.[desc],' ','.'),4) as [a4]
from #table as s) as a
unpivot (testval FOR val IN (a1, a2, a3, a4)) unpvt
)
select m.id, m.mfgid, m.[desc], t.flag
from #table as m
outer apply
(select top (1) 'duplicate' as flag
from cte2 as a
join cte2 as b
on a.mfgid = b.mfgid
and a.id != b.id
and a.testval = b.testval
and m.mfgid = a.mfgid) as t
test is here
Solution 2:
If you have more that 4 words in string
;with cte as
( select t.*, s.[value]
from #table as t
cross apply
(select ltrim(rtrim(split.a.value('.','varchar(100)'))) as [value]
from (select cast('<M>'+replace([desc],' ','</M><M>')+'</M>' as xml) as data) as a
cross apply data.nodes ('/M') as split(a)
) as s
)
select m.id, m.mfgid, m.[desc], t.flag
from #table as m
outer apply
(select top (1) 'duplicate' as flag
from cte as a
join cte as b
on a.mfgid = b.mfgid
and a.id != b.id
and a.Value = b.Value
and m.mfgid = a.mfgid) as t
test is here
This assumes the OP is using SQL Server 2016+, as they haven't let us know the version:
WITH Split AS(
SELECT T.id,
T.mfgid,
T.[desc],
SS.[value]
FROM #table T
CROSS APPLY STRING_SPLIT([desc],' ') SS)
SELECT S.id,
S.mfgid,
S.[desc],
CASE MAX(Dups) WHEN 0 THEN NULL ELSE 'Duplicate' END AS Flag
FROM Split S
CROSS APPLY (SELECT COUNT(*) AS [Dups]
FROM Split ca
WHERE ca.mfgid = S.mfgid
AND ca.[value] = S.[value]
AND ca.id != S.id) C
GROUP BY S.id,
S.mfgid,
S.[desc];

SQL SELECT Convert Min/Max into Separate Rows

I have a table that has a min and max value that I'd like create a row for each valid number in a SELECT statement.
Original table:
| Foobar_ID | Min_Period | Max_Period |
---------------------------------------
| 1 | 0 | 2 |
| 2 | 1 | 4 |
I'd like to turn that into:
| Foobar_ID | Period_Num |
--------------------------
| 1 | 0 |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 2 | 4 |
The SELECT results need to come out as one result-set, so I'm not sure if a WHILE loop would work in my case.
If you expect just a handful of rows per foobar, then this is a good opportunity to learn about recursive CTEs:
with cte as (
select foobar_id, min_period as period_num, max_period
from original t
union all
select foobar_id, min_period + 1 as period_num, max_period
from cte
where period_num < max_period
)
select foobar_id, period_num
from cte
order by foobar_id, period_num;
You can extend this to any number of periods by setting the MAXRECURSION option to 0.
One method would be to use a Tally table, ther's plenty of examples out there, but I'm going to create a very small one in this example. Then you can JOIN onto that and return your result set.
--Create the Tally Table
CREATE TABLE #Tally (I int);
WITH ints AS(
SELECT 0 AS i
UNION ALL
SELECT i + 1
FROM ints
WHERE i + 1 <= 10)
--And in the numbers go!
INSERT INTO #Tally
SELECT i
FROM ints;
GO
--Create the sample table
CREATE TABLE #Sample (ID int IDENTITY(1,1),
MinP int,
MaxP int);
--Sample data
INSERT INTO #Sample (Minp, MaxP)
VALUES (0,2),
(1,4);
GO
--And the solution
SELECT S.ID,
T.I AS P
FROM #Sample S
JOIN #Tally T ON T.I BETWEEN S.MinP AND S.MaxP
ORDER BY S.ID, T.I;
GO
--Clean up
DROP TABLE #Sample;
DROP TABLE #Tally;
Depending on the size of the data and the range of the period, the easiest way to do this is to use a dynamic number fact table, as follows:
WITH rn AS (SELECT ROW_NUMBER() OVER (ORDER BY object_id) -1 as period_num FROM sys.objects)
SELECT f.foobar_id, rn.period_num
FROM foobar f
INNER JOIN rn ON rn.period_num BETWEEN f.min_period AND f.max_period
However, if you're working with a larger volume of data, it will be worth creating a number fact table with an index. You can even use a TVV for this:
-- Declare the number fact table
DECLARE #rn TABLE (period_num INT IDENTITY(0, 1) primary key, dummy int)
-- Populate the fact table so that all periods are covered
WHILE (SELECT COUNT(1) FROM #rn) < (SELECT MAX(max_period) FROM foobar)
INSERT #rn select 1 from sys.objects
-- Select using a join to the fact table
SELECT f.foo_id, rn.period_num
FROM foobar f
inner join #rn rn on rn.period_num between f.min_period and f.max_period
Just Create a function sample date and use it
CREATE FUNCTION [dbo].[Ufn_GetMInToMaxVal] (#Min_Period INT,#Max_Period INT )
RETURNS #OutTable TABLE
(
DATA INT
)
AS
BEGIN
;WIth cte
AS
(
SELECT #Min_Period As Min_Period
UNION ALL
SELECT Min_Period+1 FRom
cte
WHERE Min_Period < #Max_Period
)
INSERT INTO #OutTable
SELECT * FROM cte
RETURN
END
Get the result by executing sql statement
DECLARE #Temp AS TABLE(
Foobar_ID INT,
Min_Period INT,
Max_Period INT
)
INSERT INTO #Temp
SELECT 1, 0,2 UNION ALL
SELECT 2, 1,4
SELECT Foobar_ID ,
DATA
FROM #Temp
CROSS APPLY
[dbo].[Ufn_GetMInToMaxVal] (Min_Period,Max_Period)
Result
Foobar_ID DATA
----------------
1 0
1 1
1 2
2 1
2 2
2 3
2 4

Use Dyamic Pivot query for this?

i have the below table. (no primary key in this table)
ID | IC | Name | UGCOS | MCOS
---------------------------------------------------------
1AA | A123456B | Edmund | Australia | Denmark
1AA | A123456B | Edmund | Australia | France
2CS | C435664C | Grace | Norway | NULL
3TG | G885595H | Rae | NULL | Japan
I need to get the result like this.
ID | IC | Name | UGCOS | MCOS | MCOS1
--------------------------------------------------------------------
1AA | A123456B | Edmund | Australia | Denmark | France
2CS | C435664C | Grace | Norway | NULL | NULL
3TG | G885595H | Rae | NULL | Japan | NULL
Did googled around and seems like PIVOT is what i need to do that. However i am not sure how can that be implemented to my tables. It would be great help if somebody can help me with it. Thanks!
I'll create a second answer, as this approach is something completely different from my first:
This dynamic query will first find the max count of a distinct ID and then build a dynamic pivot
CREATE TABLE #tmpTbl (ID VARCHAR(100),IC VARCHAR(100),Name VARCHAR(100),UGCOS VARCHAR(100),MCOS VARCHAR(100))
INSERT INTO #tmpTbl VALUES
('1AA','A123456B','Edmund','Australia','Denmark')
,('1AA','A123456B','Edmund','Australia','France')
,('1AA','A123456B','Edmund','Australia','OneMore')
,('2CS','C435664C','Grace','Norway',NULL)
,('3TG','G885595H','Rae',NULL,'Japan');
GO
DECLARE #maxCount INT=(SELECT TOP 1 COUNT(*) FROM #tmpTbl GROUP BY ID ORDER BY COUNT(ID) DESC);
DECLARE #colNames VARCHAR(MAX)=
(
STUFF
(
(
SELECT TOP(#maxCount)
',MCOS' + CAST(ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS VARCHAR(10))
FROM sys.objects --take any large table or - better! - an numbers table or a tally CTE
FOR XML PATH('')
),1,1,''
)
);
DECLARE #cmd VARCHAR(MAX)=
'SELECT p.*
FROM
(
SELECT *
,''MCOS'' + CAST(ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT NULL)) AS VARCHAR(10)) AS colName
FROM #tmpTbl
) AS tbl
PIVOT
(
MIN(MCOS) FOR colName IN(' + #colNames + ')
) AS p';
EXEC(#cmd);
GO
DROP TABLE #tmpTbl;
The result
1AA A123456B Edmund Australia Denmark France OneMore
2CS C435664C Grace Norway NULL NULL NULL
3TG G885595H Rae NULL Japan NULL NULL
This is a suggestion with a concatenated result:
CREATE TABLE #tmpTbl (ID VARCHAR(100),IC VARCHAR(100),Name VARCHAR(100),UGCOS VARCHAR(100),MCOS VARCHAR(100))
INSERT INTO #tmpTbl VALUES
('1AA','A123456B','Edmund','Australia','Denmark')
,('1AA','A123456B','Edmund','Australia','France')
,('2CS','C435664C','Grace','Norway',NULL)
,('3TG','G885595H','Rae',NULL,'Japan');
SELECT ID,IC,Name,UGCOS,
(
STUFF(
(
SELECT ' ,' + x.MCOS
FROM #tmpTbl AS x
WHERE x.ID=outerTbl.ID
FOR XML PATH('')
),1,2,''
)
) AS MCOS
FROM #tmpTbl AS outerTbl
GROUP BY ID,IC,Name,UGCOS;
GO
DROP TABLE #tmpTbl;
The result
1AA A123456B Edmund Australia Denmark ,France
2CS C435664C Grace Norway NULL
3TG G885595H Rae NULL Japan
Using Cross Apply and Pivot we can achieve this
DECLARE #Table1 TABLE
( ID varchar(3), IC varchar(8), Name varchar(6), UGCOS varchar(9), MCOS varchar(7))
;
INSERT INTO #Table1
( ID , IC , Name , UGCOS , MCOS )
VALUES
('1AA', 'A123456B', 'Edmund', 'Australia', 'Denmark'),
('1AA', 'A123456B', 'Edmund', 'Australia', 'France'),
('2CS', 'C435664C', 'Grace', 'Norway', NULL),
('3TG', 'G885595H', 'Rae', NULL, 'Japan')
;
Select ID , IC , Name , UGCOS,MAX([MCOS1])[MCOS1],MAX([MCOS2])[MCOS2] from (
select ID , IC , Name , UGCOS , MCOS,col,val,col +''+CAST(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY col) AS VARCHAR)RN from #Table1
CROSS APPLY (values('MCOS',MCOS))CS(col,val))T
PIVOT (MAX(val) FOR RN IN ([MCOS1],[MCOS2]))PVT
GROUP BY ID , IC , Name , UGCOS
Do you always have a maximum of 2 rows of data that you'll want to turn into columns? If so, this would do you;
CREATE TABLE #TableName (ID varchar(3), IC varchar(8), Name varchar(6), UCGOS varchar(9), MCOS varchar(7))
INSERT INTO #TableName
VALUES
('1AA','A123456B','Edmund','Australia','Denmark')
,('1AA','A123456B','Edmund','Australia','France')
,('2CS','C435664C','Grace','Norway',NULL)
,('3TG','G885595H','Rae',NULL,'Japan')
SELECT DISTINCT a.ID
,a.IC
,a.NAME
,a.UCGOS
,b.Mcos1 MCOS
,c.Mcos2 MCOS1
FROM #TableName a
LEFT JOIN (
SELECT ID
,MAX(MCOS) Mcos1
FROM #TableName
GROUP BY ID
) b ON a.ID = b.ID
LEFT JOIN (
SELECT ID
,MIN(MCOS) Mcos2
FROM #TableName
GROUP BY ID
) c ON a.ID = c.ID
AND (
b.ID = c.ID
AND b.Mcos1 <> c.Mcos2
)
DROP TABLE #TableName
Gives you the result you're after.