Sql Server: Comma separated column values exist in other comma separated column - sql

Let's say I have a table
OrgData
OrgDataid columvalues
1 1,2,3,4,5
2 6,7,8,9
3 16,17,18,19
Another table holds selected values
selectedData
rowid sid orgid values
1 1 1 1,2
2 1 2 6,7,8,9
3 2 1 1,2,3,4,5
Where sid is the id of OrgData
I want output like
for sid 1;
outputData
OrgData columvalues
1 partial selected
2 Full selected
3 Nothing selected
I want some straightway query, while I am trying to do it with splitting every comma separated to rows and looping for each one.
Thanks

You might try this:
--mock-up-tables with your data
DECLARE #OrgData TABLE(OrgDataid INT,columvalues VARCHAR(100));
INSERT INTO #OrgData VALUES
(1,'1,2,3,4,5')
,(2,'6,7,8,9')
,(3,'16,17,18,19');
DECLARE #selectedData TABLE(rowid INT,[sid] INT,orgid INT,[values] VARCHAR(100));
INSERT INTO #selectedData VALUES
(1,1,1,'1,2')
,(2,1,2,'6,7,8,9')
,(3,2,1,'1,2,3,4,5');
--First CTE to split OrgData
WITH OrgDataSplitted AS
(
SELECT od.*
,LEN(od.columvalues)-LEN(REPLACE(od.columvalues,',',''))+1 AS CountParts
,B.Part.value('.','int') AS PartInt
FROM #OrgData AS od
CROSS APPLY(SELECT CAST('<x>' + REPLACE(columvalues,',','</x><x>') + '</x>' AS XML)) AS A(Casted)
CROSS APPLY A.Casted.nodes('/x') AS B(Part)
)
--Second CTE to split SelectedData
,SelectedSplitted AS
(
SELECT sd.*
,B.Part.value('.','int') AS PartInt
FROM #selectedData AS sd
CROSS APPLY(SELECT CAST('<x>' + REPLACE([values],',','</x><x>') + '</x>' AS XML)) AS A(Casted)
CROSS APPLY A.Casted.nodes('/x') AS B(Part)
)
--The query to join them
SELECT o.OrgDataid,o.CountParts,s.rowid,COUNT(rowid) AS CountIdent
FROM Orgdatasplitted AS o
FULL OUTER JOIN SelectedSplitted AS s ON o.OrgDataID=s.orgid and o.PartInt=s.PartInt
GROUP BY o.OrgDataid,o.CountParts,s.rowid
ORDER BY o.OrgDataid
The result. If CountParts and CountIdent is the same it's full, >0 is partial and 0 is none
+-----------+------------+-------+------------+
| OrgDataid | CountParts | rowid | CountIdent |
+-----------+------------+-------+------------+
| 1 | 5 | 1 | 2 |
+-----------+------------+-------+------------+
| 1 | 5 | 3 | 5 |
+-----------+------------+-------+------------+
| 2 | 4 | 2 | 4 |
+-----------+------------+-------+------------+
| 3 | 4 | NULL | 0 |
+-----------+------------+-------+------------+

Try the below script, may be help you. But I agree with #Gordon Linoff,
Fix you data structure. Storing values in comma-delimited strings is the wrong way.
SELECT OrgDataid
,(CASE WHEN OD.columvalues = SD.[values] THEN 'Full selected'
WHEN SD.rowid IS NULL THEN 'Nothing selected'
ELSE 'partial selected' END) AS columvalues
FROM OrgData AS OD
LEFT OUTER JOIN selectedData AS SD ON OD.columvalues LIKE '%'+SD.[values]+'%'

Related

SQL Server recursive query to show path of parents

I am working with SQL Server statements and have one table like:
| item | value | parentItem |
+------+-------+------------+
| 1 | 2test | 2 |
| 2 | 3test | 3 |
| 3 | 4test | 4 |
| 5 | 1test | 1 |
| 6 | 3test | 3 |
| 7 | 2test | 2 |
And I would like to get the below result using a SQL Server statement:
| item1 | value1 |
+-------+--------------------------+
| 1 | /4test/3test/2test |
| 2 | /4test/3test |
| 3 | /4test |
| 5 | /4test/3test/2test/1test |
| 6 | /4test/3test |
| 7 | /4test/3test/2test |
I didn't figure out the correct SQL to get all the values for all the ids according to parentItem.
I have tried this SQL :
with all_path as
(
select item, value, parentItem
from table
union all
select a.item, a.value, a.parentItem
from table a, all_path b
where a.item = b.parentItem
)
select
item as item1,
stuff(select '/' + value
from all_path
order by item asc
for xml path ('')), 1, 0, '') as value1
from
all_path
But got the "value1" column in result like
/4test/4test/4test/3test/3test/3test/3test/2test/2test/2test/2test
Could you please help me with that? Thanks a lot.
based on the expected output you gave, use the recursive part to concatenate the value
;with yourTable as (
select item, value, parentItem
from (values
(1,'2test',2)
,(2,'3test',3)
,(3,'4test',4)
,(5,'1test',1)
,(6,'3test',3)
,(7,'2test',2)
)x (item,value,parentItem)
)
, DoRecursivePart as (
select 1 as Pos, item, convert(varchar(max),value) value, parentItem
from yourTable
union all
select drp.pos +1, drp.item, convert(varchar(max), yt.value + '/' + drp.value), yt.parentItem
from yourTable yt
inner join DoRecursivePart drp on drp.parentItem = yt.item
)
select drp.item, '/' + drp.value
from DoRecursivePart drp
inner join (select item, max(pos) mpos
from DoRecursivePart
group by item) [filter] on [filter].item = drp.item and [filter].mpos = drp.Pos
order by item
gives
item value
----------- ------------------
1 /4test/3test/2test
2 /4test/3test
3 /4test
5 /4test/3test/2test/1test
6 /4test/3test
7 /4test/3test/2test
Here's the sample data
drop table if exists dbo.test_table;
go
create table dbo.test_table(
item int not null,
[value] varchar(100) not null,
parentItem int not null);
insert dbo.test_table values
(1,'test1',2),
(2,'test2',3),
(3,'test3',4),
(5,'test4',1),
(6,'test5',3),
(7,'test6',2);
Here's the query
;with recur_cte(item, [value], parentItem, h_level) as (
select item, [value], parentItem, 1
from dbo.test_table tt
union all
select rc.item, tt.[value], tt.parentItem, rc.h_level+1
from dbo.test_table tt join recur_cte rc on tt.item=rc.parentItem)
select rc.item,
stuff((select '/' + cast(parentItem as varchar)
from recur_cte c2
where rc.item = c2.item
order by h_level desc FOR XML PATH('')), 1, 1, '') [value1]
from recur_cte rc
group by item;
Here's the results
item value1
1 4/3/2
2 4/3
3 4
5 4/3/2/1
6 4/3
7 4/3/2

Convert one row into multiple rows with fewer columns

I'd like to convert single rows into multiple rows in PostgreSQL, where some of the columns are removed. Here's an example of the current output:
name | st | ot | dt |
-----|----|----|----|
Fred | 8 | 2 | 3 |
Jane | 8 | 1 | 0 |
Samm | 8 | 0 | 6 |
Alex | 8 | 0 | 0 |
Using the following query:
SELECT
name, st, ot, dt
FROM
times;
And here's what I want:
name | t | val |
-----|----|-----|
Fred | st | 8 |
Fred | ot | 2 |
Fred | dt | 3 |
Jane | st | 8 |
Jane | ot | 1 |
Samm | st | 8 |
Samm | dt | 6 |
Alex | st | 8 |
How can I modify the query to get the above desired output?
SELECT
times.name, x.t, x.val
FROM
times cross join lateral (values('st',st),('ot',ot),('dt',dt)) as x(t,val)
WHERE
x.val <> 0;
The core problem is the reverse of a pivot / crosstab operation. Sometimes called "unpivot".
Basically, Abelisto's query is the way to go in Postgres 9.3 or later. Related:
SELECT DISTINCT on multiple columns
You may want to use LEFT JOIN LATERAL ... ON u.val <> 0 to include names without valid values in the result (and shorten the syntax a bit).
What is the difference between LATERAL JOIN and a subquery in PostgreSQL?
If you have more than a few value columns (or varying lists of columns) you may want to use a function to build and execute the query automatically:
CREATE OR REPLACE FUNCTION f_unpivot_columns(VARIADIC _cols text[])
RETURNS TABLE(name text, t text, val int)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT
'SELECT t.name, u.t, u.val
FROM times t
LEFT JOIN LATERAL (VALUES '
|| string_agg(format('(%L, t.%I)', c, c), ', ')
|| ') u(t, val) ON (u.val <> 0)'
FROM unnest(_cols) c
);
END
$func$;
Call:
SELECT * FROM f_unpivot_times_columns(VARIADIC '{st, ot, dt}');
Or:
SELECT * FROM f_unpivot_columns('ot', 'dt');
Columns names are provided as string literals and must be in correct (case-sensitive!) spelling with no extra double-quotes. See:
Are PostgreSQL column names case-sensitive?
db<>fiddle here
Related with more examples and explanation:
How to unpivot a table in PostgreSQL
One way:
with times(name , st , ot , dt) as(
select 'Fred',8 , 2 , 3 union all
select 'Jane',8 , 1 , 0 union all
select 'Samm',8 , 0 , 6 union all
select 'Alex',8 , 0 , 0
)
select name, key as t, value::int from
(
select name, json_build_object('st' ,st , 'ot',ot, 'dt',dt) as j
from times
) t
join lateral json_each_text(j)
on true
where value <> '0'
-- order by name, case when key = 'st' then 0 when key = 'ot' then 1 when key = 'dt' then 2 end

SQL: Pick highest and lowest value (int) from one row

I am looking for a way to pick the highest and lowest value (integer) from a single row in table. There are 4 columns that i need to compare together and get highest and lowest number there is.
The table looks something like this...
id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4
1 | John | 5 | 5 | 2 | 1
2 | Peter | 3 | 2 | 4 | 1
3 | Josh | 3 | 5 | 1 | 3
Can you help me, please? Thanks!
You can do this using CROSS APPLY and the VALUES clause. Use VALUES to group all your compared columns and then select the max.
SELECT
MAX(d.data1) as MaxOfColumns
,MIN(d.data1) as MinOfColumns
,a.id
,a.name
FROM YOURTABLE as a
CROSS APPLY (
VALUES(a.col_to_compare1)
,(a.col_to_compare2)
,(a. col_to_compare3)
,(a.col_to_compare4)
,(a. col_to_compare5)
) as d(data1) --Name the Column
GROUP BY a.id
,a.name
Assuming you are looking for min/max per row
Declare #YourTable table (id int,name varchar(50),col_to_compare1 int,col_to_compare2 int,col_to_compare3 int,col_to_compare4 int)
Insert Into #YourTable values
(1,'John',5,5,2,1),
(2,'Peter',3,2,4,1),
(3,'Josh',3,5,1,3)
Select A.ID
,A.Name
,MinVal = min(B.N)
,MaxVal = max(B.N)
From #YourTable A
Cross Apply (Select N From (values(a.col_to_compare1),(a.col_to_compare2),(a.col_to_compare3),(a.col_to_compare4)) N(N) ) B
Group By A.ID,A.Name
Returns
ID Name MinVal MaxVal
1 John 1 5
3 Josh 1 5
2 Peter 1 4
These solutions keep the current rows and add additional columns of min/max.
select *
from t cross apply
(select min(col) as min_col
,max(col) as max_col
from (
values
(t.col_to_compare1)
,(t.col_to_compare2)
,(t.col_to_compare3)
,(t.col_to_compare4)
) c(col)
) c
OR
select *
,cast ('' as xml).value ('min ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as min_col
,cast ('' as xml).value ('max ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as max_col
from t
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4 | min_col | max_col |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 1 | John | 5 | 5 | 2 | 1 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 2 | Peter | 3 | 2 | 4 | 1 | 1 | 4 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 3 | Josh | 3 | 5 | 1 | 3 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
A way to do this is to "break" apart the data
declare #table table (id int, name varchar(10), col1 int, col2 int, col3 int, col4 int)
insert into #table values (1 , 'John' , 5 , 5 , 2 , 1)
insert into #table values (2 , 'Peter' , 3 , 2 , 4 , 1)
insert into #table values (3 , 'Josh' , 3 , 5 , 1 , 3)
;with stretch as
(
select id, col1 as col from #table
union all
select id, col2 as col from #table
union all
select id, col3 as col from #table
union all
select id, col4 as col from #table
)
select
t.id,
t.name,
agg.MinCol,
agg.MaxCol
from #table t
inner join
(
select
id, min(col) as MinCol, max(col) as MaxCol
from stretch
group by id
) agg
on t.id = agg.id
Seems simple enough
SELECT min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table
Gives you the Min and Max for each column.
Following OP's comment, I believe he may be looking for a min/max grouped by the person being queried against.
So that would be:
SELECT name, min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table GROUP BY name

SQL SELECT: concatenated column with line breaks and heading per group

I have the following SQL result from a SELECT query:
ID | category| value | desc
1 | A | 10 | text1
2 | A | 11 | text11
3 | B | 20 | text20
4 | B | 21 | text21
5 | C | 30 | text30
This result is stored in a temporary table named #temptab. This temporary table is then used in another SELECT to build up a new colum via string concatenation (don't ask me about the detailed rationale behind this. This is code I took from a colleague). Via FOR XML PATH() the output of this column is a list of the results and is then used to send mails to customers.
The second SELECT looks as follows:
SELECT t1.column,
t2.column,
(SELECT t.category + ' | ' + t.value + ' | ' + t.desc + CHAR(9) + CHAR(13) + CHAR(10)
FROM #temptab t
WHERE t.ID = ttab.ID
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)') AS colname
FROM table1 t1
...
INNER JOIN #temptab ttab on ttab.ID = someOtherTable.ID
...
Without wanting to go into too much detail, the column colname becomes populated with several entries (due to multiple matches) and hence, a longer string is stored in this column (CHAR(9) + CHAR(13) + CHAR(10) is essentially a line break). The result/content of colname looks like this (it is used to send mails to customers):
A | 10 | text1
A | 11 | text11
B | 20 | text20
B | 21 | text21
C | 30 | text30
Now I would like to know, if there is a way to more nicely format this output string. The best case would be to group the same categories together and add a heading and empty line between different categories:
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
My question is: How do I have to modify the above query (especially the string-concatenation-part) to achieve above formatting? I was thinking about using a GROUP BY statement, but this obviously does not yield the desired result.
Edit: I use Microsoft SQL Server 2008 R2 (SP2) - 10.50.4270.0 (X64)
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30')
Declare #String varchar(max) = ''
Select #String = #String + Case when RowNr=1 Then Replicate(char(13)+char(10),2) +'*'+Category+'*' Else '' end
+ char(13)+char(10) + category + ' | ' + cast(value as varchar(25)) + ' | ' + [desc]
From (
Select *
,RowNr=Row_Number() over (Partition By Category Order By Value)
From #YourTable
) A Order By Category, Value
Select Substring(#String,5,Len(#String))
Returns
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
This should return what you want
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30');
WITH Categories AS
(
SELECT category
,'**' + category + '**' AS CatCaption
,ROW_NUMBER() OVER(ORDER BY category) AS CatRank
FROM #YourTable
GROUP BY category
)
,Grouped AS
(
SELECT c.CatRank
,0 AS ValRank
,c.CatCaption AS category
,-1 AS ID
,'' AS Value
,'' AS [desc]
FROM Categories AS c
UNION ALL
SELECT c.CatRank
,ROW_NUMBER() OVER(PARTITION BY t.category ORDER BY t.Value)
,t.category
,t.ID
,CAST(t.value AS VARCHAR(100))
,t.[desc]
FROM #YourTable AS t
INNER JOIN Categories AS c ON t.category=c.category
)
SELECT category,Value,[desc]
FROM Grouped
ORDER BY CatRank,ValRank
The result
category Value desc
**A**
A 10 text1
A 11 text11
**B**
B 20 text20
B 21 text21
**C**
C 30 text30

SQL group by and cross apply

id | systemName | Systemid
-------------------------------
100 | A100 | 1
100 | B100 | 2
100 | C100 | 3
100 | D100 | 4
200 | A100 | 1
200 | B200 | 2
What is the best way to achieve the below result? Column System name should have the comma separated values of counted systems
id Systemidcount SystemName
---------------------------------------------
100 | 4 | A100,B100,C100,D100
200 | 2 | A100,B200
I am not able to format it correctly for some reason, apologies
You may notice the sub-query alias A. This is to prevent redundant calls in the CROSS APPLY
Declare #YourTable table (id int,systemname varchar(25),systemid int)
Insert Into #YourTable values
(100,'A100',1),
(100,'B100',2),
(100,'C100',3),
(100,'D100',4),
(200,'A100',1),
(200,'B200',2)
Select A.*
,SystemName = B.Value
From (Select ID,Systemidcount = count(*) From #YourTable Group By ID) A
Cross Apply (
Select Value=Stuff((Select Distinct ',' + systemname
From #YourTable
Where ID=A.ID
For XML Path ('')),1,1,'')
) B
Order By ID
Returns
ID Systemidcount SystemName
100 4 A100,B100,C100,D100
200 2 A100,B200
This is string aggregation:
select id, count(*) as systemidcount,
stuff(v.systemnames, 1, 1, '') as systemnames
from t cross apply
(select ',' + systemName
from t t2
where t2.id = t.id
for xml path ('')
) v(systemnames);
SELECTid,COUNT(Systemid) as SystemidCount,
SystemName=Stuff((SELECT ',' + SystemName FROM t t1 WHERE t1.id=t.id
FOR XML PATH (''))
, 1, 1, '' )
FROM t
GROUP BY id