How to display Text Unit only one time if it repeated for same Feature when do stuff? - tsql

I work with SQL Server 2012 and face an issue: I can't display Text Unit only one time where it repeated for feature using Stuff.
What I need is when Text Unit is repeated for same feature, then no need to repeat it - only display it once.
In my case, I face issue that I can't prevent repeat Text Unit when It be same Text Unit for same Feature.
Voltage | Voltage | Voltage ONLY one Voltage display .
CREATE TABLE #FinalTable
(
PartID INT,
DKFeatureName NVARCHAR(100),
TextUnit NVARCHAR(100),
StatusId INT
)
INSERT INTO #FinalTable (PartID, DKFeatureName, TextUnit, StatusId)
VALUES
(1211, 'PowerSupply', 'Voltage', 3),
(1211, 'PowerSupply', 'Voltage', 3),
(1211, 'PowerSupply', 'Voltage', 3)
SELECT
PartID, DKFeatureName,
COUNT(PartID) AS CountParts,
TextUnit = STUFF ((SELECT ' | ' + TextUnit
FROM #FinalTable b
WHERE b.PartID = a.PartID
AND a.DKFeatureName = b.DKFeatureName
AND StatusId = 3
FOR XML PATH('')), 1, 2, ' ')
INTO
#getUnitsSticky
FROM
#FinalTable a
GROUP BY
PartID, DKFeatureName
HAVING
(COUNT(PartID) > 1)
SELECT *
FROM #getUnitsSticky
Expected result is :
Voltage
Incorrect result or result I don't need is as below :
Voltage|Voltage|Voltage

TomC's answer is basically correct. However, when using this method with SQL Server, it is usually more efficient to get the rows in a subquery and then use stuff() in the outer query. That way, the values in each row are processed only once.
So:
SELECT PartID, DKFeatureName, CountParts,
STUFF( (SELECT ' | ' + TextUnit
FROM #FinalTable b
WHERE b.PartID = a.PartID AND
b.DKFeatureName = a.DKFeatureName AND
StatusId = 3
FOR XML PATH('')
), 1, 3, ' ') as TextUnit
INTO #getUnitsSticky
FROM (SELECT PartID, DKFeatureName, COUNT(*) as CountParts
FROM #FinalTable a
GROUP BY PartID, DKFeatureName
HAVING COUNT(*) > 1
) a;
This also removes the leading space from the concatenated result.

To put this into a complete answer - this should be your SQL (shortened slightly and removed the last temp table):
SELECT
PartID, DKFeatureName,
COUNT(PartID) AS CountParts,
TextUnit = STUFF ((SELECT distinct ' | ' + TextUnit
FROM #FinalTable b
WHERE b.PartID = a.PartID
AND a.DKFeatureName = b.DKFeatureName
AND StatusId = 3
FOR XML PATH('')), 1, 2, ' ')
FROM #FinalTable a
GROUP BY PartID, DKFeatureName
HAVING (COUNT(PartID) > 1)

Related

Convert multiple rows to one row

select
datum, naam,
to_char(verkocht::numeric/(verkocht+beschikbaar)*100, 'fm00D00%') rangen
from
(select
sr.namenl rang,
count((case when t.currentstatus is null then '1' end)) as beschikbaar,
count((case when t.currentstatus = 101 then '2' end)) as verkocht,
coalesce(e.namenl) || ' ' || coalesce(e.subtitlenl,'') naam,
e.startts datum,
sr.id seatrank
from
tm.ticket t
inner join
tm.tickettype tt on tt.id = t.tickettypeid
inner join
tm.event e on e.id = tt.eventid
inner join
tm.seatrank sr on sr.id = tt.seatrankid
where
e.id = 28744
group by
1, 4, 5, 6
order by
1 desc) sq1
group by
1, 2, 3
Current result (https://i.stack.imgur.com/1KPOi.png)
What I would like is to convert the 4 row result into 1 row. The 4 different outcomes in row "rangen" next to each other so that I have 1 row with 6 columns. Don't know if this is possible and I am fairly new to this as you can see :)
You can use string_agg function. For example, like as this query:
select
datum, naam,
string_agg(rangen, ', ') as rangen
from table
group by
datum, naam

(Postgres) Query in a tree table in ascending and descending mode

I'm having some issues with two queries to search in a "tree" table.
So, my table is represented by the following code, and it has one only direction. However, I need to get data in both directions, ascending and descending mode.
create table graph_examle (input int null, output int );
insert into graph_examle (input, output) values
(null, 1),
(1, 2),
(2, 3 ),
(3, 4 ),
(null, 7 ),
(7,8),
(8, 4 ),
(null, 10 ),
(10, 11 ),
(11, 4),
(3, 15),
(25, 15),
(26, 15),
(15, 4 );
The ascending query has some issues. If I search by id 1, I'm expecting to see the relations:
1, 1->2, 1->2->3, 1->2->3->4, but the results are:
WITH recursive cte (initial_id, level, path, loop, input, output) AS
(
SELECT input, 1, ':' ||input || ':' , 0, input, output
FROM graph_examle WHERE input = 1
UNION ALL
SELECT
c.initial_id,
c.level + 1,
c.path ||ur.input|| ':' ,
CASE WHEN c.path LIKE '%:' ||ur.input || ':%' THEN 1 ELSE 0 END,
ur.*
FROM graph_examle ur
INNER JOIN cte c ON c.output = ur.input AND c.loop = 0
)
SELECT *
FROM cte
ORDER BY initial_id, level;
The descending query does not work as expected. If I search by id 4, I'm expecting to see the relations:
4, 4->3, 4->3->2, 4->3->2->1
4->8, 4->8->7
4->11,4->11>10
4->15, (...)
But I'm only getting:
WITH RECURSIVE cte (input, output, level, real_parent_id, path) AS
(
SELECT
ur.input, ur.input, 1, output, ( ur.input|| ' -> ' || ur.output)
FROM graph_examle ur
WHERE ur.output = 4
UNION ALL
SELECT
ur_cte.input, ur.input, level + 1, ur.output, (ur_cte.path || '->' || ur.output)
FROM cte ur_cte
INNER JOIN graph_examle ur on ur.input = ur_cte.real_parent_id
)
SELECT *
FROM cte
ORDER BY path
Note that in my queries I'm trying to solve circular dependencies
The ascending query sounds good ... maybe you can concatenate the path and output columns.
For the descending query, you can try this :
WITH RECURSIVE cte (input, output, level, path, loop) AS
(
SELECT
ur.input, ur.output, 1, ( ur.output|| ' -> ' || ur.input), 0
FROM graph_examle ur
WHERE ur.output = 4
UNION ALL
SELECT
ur.input, ur_cte.output, level + 1, (ur_cte.path || '->' || ur.input),
CASE WHEN ur_cte.path LIKE '%->' || ur.input THEN 1 ELSE 0 END
FROM cte ur_cte
INNER JOIN graph_examle ur on ur.output = ur_cte.input
WHERE ur_cte.loop = 0
AND ur.input IS NOT NULL
)
SELECT *
FROM cte
ORDER BY path
see dbfiddle

TSQL - in a string, replace a character with a fixed one every 2 characters

I can't replace every 2 characters of a string with a '.'
select STUFF('abcdefghi', 3, 1, '.') c3,STUFF('abcdefghi', 5, 1,
'.') c5,STUFF('abcdefghi', 7, 1, '.') c7,STUFF('abcdefghi', 9, 1, '.')
c9
if I use STUFF I should subsequently overlap the strings c3, c5, c7 and c9. but I can't find a method
can you help me?
initial string:
abcdefghi
the result I would like is
ab.de.gh.
the string can be up to 50 characters
Create a numbers / tally / digits table, if you don't have one already, then you can use this to target each character position:
with digits as ( /* This would be a real table, here it's just to test */
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(n)
), t as (
select 'abcdefghi' as s
)
select String_Agg( case when d.n%3 = 0 then '.' else Substring(t.s, d.n, 1) end, '')
from t
cross apply digits d
where d.n <Len(t.s)
Using for xml with existing table
with digits as (
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(n)
),
r as (
select t.id, case when d.n%3=0 then '.' else Substring(t.s, d.n, 1) end ch
from t
cross apply digits d
where d.n <Len(t.s)
)
select result=(select '' + ch
from r r2
where r2.id=r.id
for xml path('')
)
from r
group by r.id
You can try it like this:
Easiest might be a quirky update ike here:
DECLARE #string VARCHAR(100)='abcdefghijklmnopqrstuvwxyz';
SELECT #string = STUFF(#string,3*A.pos,1,'.')
FROM (SELECT TOP(LEN(#string)/3) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) A(pos);
SELECT #string;
Better/Cleaner/Prettier was a recursive CTE:
We use a declared table to have some tabular sample data
DECLARE #tbl TABLE(ID INT IDENTITY, SomeString VARCHAR(200));
INSERT INTO #tbl VALUES('')
,('a')
,('ab')
,('abc')
,('abcd')
,('abcde')
,('abcdefghijklmnopqrstuvwxyz');
--the query
WITH recCTE AS
(
SELECT ID
,SomeString
,(LEN(SomeString)+1)/3 AS CountDots
,1 AS OccuranceOfDot
,SUBSTRING(SomeString,4,LEN(SomeString)) AS RestString
,CAST(LEFT(SomeString,2) AS VARCHAR(MAX)) AS Growing
FROM #tbl
UNION ALL
SELECT t.ID
,r.SomeString
,r.CountDots
,r.OccuranceOfDot+2
,SUBSTRING(RestString,4,LEN(RestString))
,CONCAT(Growing,'.',LEFT(r.RestString,2))
FROM #tbl t
INNER JOIN recCTE r ON t.ID=r.ID
WHERE r.OccuranceOfDot/2<r.CountDots-1
)
SELECT TOP 1 WITH TIES ID,Growing
FROM recCTE
ORDER BY ROW_NUMBER() OVER(PARTITION BY ID ORDER BY OccuranceOfDot DESC);
--the result
1
2 a
3 ab
4 ab
5 ab
6 ab.de
7 ab.de.gh.jk.mn.pq.st.vw.yz
The idea in short
We use a recursive CTE to walk along the string
we add the needed portion together with a dot
We stop, when the remaining length is to short to continue
a little magic is the ORDER BY ROW_NUMBER() OVER() together with TOP 1 WITH TIES. This will allow all first rows (frist per ID) to appear.

Remove characters from concatenation

I have a table in with the following layout:
CREATE TABLE dbo.tbl (
Ten_Ref VARCHAR(20) NOT NULL,
Benefit VARCHAR(20) NOT NULL
);
INSERT INTO dbo.tbl (Ten_Ref, Benefit)
VALUES ('1', 'HB'),
('1', 'WTC'),
('1', 'CB'),
('2', 'CB'),
('2', 'HB'),
('3', 'WTC');
I then run this code to perform a transform and concatenation (I need all the benefit information in one field'
with [pivot] as
(
SELECT Ten_Ref
,[HB] = (Select Benefit FROM tbl WHERE t.Ten_Ref = Ten_Ref and Benefit = 'HB')
,[CB] = (Select Benefit FROM tbl WHERE t.Ten_Ref = Ten_Ref and Benefit = 'CB')
,[WTC] = (Select Benefit FROM tbl WHERE t.Ten_Ref = Ten_Ref and Benefit = 'WTC')
/*Plus 7 more of these*/
FROM tbl as t
GROUP BY Ten_Ref
)
select p.ten_Ref
/*A concatenation to put them all in one field, only problem is you end up with loads of spare commas*/
,[String] = isnull (p.HB,0) + ',' + isnull (p.cb,'') + ',' + isnull (p.wtc,'')
from [pivot] as p
My problem is not every ten_ref has all of the Benefits attached.
Using this code, where there is a gap or NULL then I end up with loads of double commas e.g 'HB,,WTC'
How can I get it so it is only one comma, regardless of the amount of benefits each tenancy has?
Are you looking for something like this?
SELECT A.Ten_Ref,
STUFF(CA.list,1,1,'') list
FROM tbl A
CROSS APPLY(
SELECT ',' + Benefit
FROM tbl B
WHERE A.Ten_Ref = B.Ten_Ref
ORDER BY Benefit
FOR XML PATH('')
) CA(list)
GROUP BY A.ten_ref,CA.list
Results:
Ten_Ref list
-------------------- ------------------
1 CB,HB,WTC
2 CB,HB
3 WTC
Or if you really want to use pivot and manually concatenate, you could do this:
SELECT Ten_Ref,
--pvt.*,
ISNULL(HB + ',','') + ISNULL(CB + ',','') + ISNULL(WTC + ',','') AS list
FROM tbl
PIVOT
(
MAX(Benefit) FOR Benefit IN([HB],[CB],[WTC])
) pvt

use cursor to display rows and group them by one column using variable

I have two tables:
Sales.SalesOrderHeader(SalesOrderID(PK), SalesOrderNumber, Status,..)
Sales.SalesOrderDetail(SalesOrderID(PK,FK), ..)
I've declared a variable #numdetail and a cursor SaleReportCursor.
How can I Print something like:
SalesOrderNumber1 (3 items) was shipped.
SalesOrderNumber2 (4 items) was shipped.
SalesOrderNumber3 (2 items) was shipped.
So that products with the same SalesOrderID can be grouped and counted?
This is what I got for the Cursor:
DECLARE
#salesOrderID INT,
#salesOrderNum NVARCHAR(25),
#dueDate DATETIME,
#status tinyint,
#message varchar(80),
#numDetail INT,
#count INT = 0,
#astatus varchar(10);
DECLARE salesReportCursor CURSOR
FOR
select s.SalesOrderID, SalesOrderNumber,DueDate, Status
FROM [Sales].[SalesOrderDetail] s
join Sales.SalesOrderHeader h
on s.SalesOrderID=h.SalesOrderID
where
h.DueDate between '2008-08-01' and '2008-08-31'
group by SalesOrderNumber,s.SalesOrderID,DueDate, Status
Order by SalesOrderNumber desc
FOR READ ONLY
OPEN salesReportCursor
FETCH NEXT from salesReportCursor
INTO #salesOrderID, #salesOrderNum , #dueDate , #status;
WHILE ##FETCH_STATUS = 0
BEGIN
set #numDetail= 1
if #salesOrderNum =#salesOrderNum
set #numDetail=#numDetail+1
Set #astatus=
case when #status=1 then 'In process'
when #status=2 then 'Approved'
when #status=3 then 'Backordered'
when #status=4 then 'Rejected'
when #status=5 then 'Shipped'
when #status=6 then 'Cancelled'
end
Select #message=cast(#salesOrderNum as varchar)
+' ('+ cast(#numDetail as varchar)+' items) due '+
cast(#dueDate as varchar) +' is '+ #astatus
set #count=#count+1
Print #message
FETCH NEXT from salesReportCursor
INTO #salesOrderID, #salesOrderNum , #status;
END
CLOSE salesReportCursor
DEALLOCATE salesReportCursor
What my outcome looks like:
SalesOrderNumber1 (2 items) was shipped.
SalesOrderNumber2 (2 items) was shipped.
SalesOrderNumber3 (2 items) was shipped.
I think this was because of after the BEGIN where I set #numdetail initially as 1 for each row then add it by 1. I wonder how to group and count products with the same SalesOrderID?
You shouldn't be needing a cursor at all for that...
Try this instead (use COUNT to get number of items per order):
select
SalesOrderNumber + ' (' + CONVERT(varchar, COUNT(*)) + ' items) due ' + CONVERT(varchar, h.DueDate, 20) + ' is ' + Statuses.Name
FROM
[Sales].[SalesOrderDetail] s
join Sales.SalesOrderHeader h
on s.SalesOrderID=h.SalesOrderID
join (
select 1 as Status, 'In process' as Name
union all select 2, 'Approved'
union all select 3, 'Backordered'
union all select 4, 'Rejected'
union all select 5, 'Shipped'
union all select 6, 'Cancelled'
) Statuses on
Statuses.Status = h.Status
where
h.DueDate between '2008-08-01' and '2008-08-31'
group by
h.SalesOrderNumber,
s.SalesOrderID,
h.DueDate,
h.Status
Order by
h.SalesOrderNumber desc
(I haven't tried the code myself, so there could be some syntax errors, but I hope you get the point)
You should also put the statuses in its own table... Preferably a table without an identity column, and use it as a lookup table.