I have values within a Notes field consisting of values such as this where abc represents different words:
Request Notes: VAR - abc abc abc abc abc
Unfortunately the design of how the data is stored is very poor, and I need to pull out the Note Type for each "Required Notes:" record. It also needs ordering from most recent to oldest (last part of the string to first part of the string).
CREATE TABLE #TestData
(
ClientID int,
Notes varchar(8000)
)
insert into #TestData
(
ClientID,
Notes
)
select
1,
'Request Notes: VAR - abc abc abc abc abc'
union all
select
2,
'Request Notes: OZR - abc abc abc abc abc Request Notes: ACC - abc abc abc abc abc Request Notes: TYU - abc abc abc abc abc'
union all
select
3,
'Request Notes: TYU - abc abc abc abc abc Request Notes: VAR - abc abc abc abc abc'
This is how I expect the output to be for the above examples:
--Expected Output
Client ID Type Order
1 VAR 1
2 TYU 1
2 ACC 2
2 OZR 3
3 VAR 1
3 TYU 2
I have this together so far which extracts OZR, but I'm stumped on how to get the others and order the list into the expected output above.
DECLARE #Text varchar(500) = 'Request Notes: OZR - abc abc abc abc abc Request Notes: ACC - abc abc abc abc abc Request Notes: TYU - abc abc abc abc abc'
SELECT TRIM(REPLACE(REPLACE(SUBSTRING(#Text, CHARINDEX(':', #Text), CHARINDEX('-',#text) - CHARINDEX(':', #Text) + Len('-')),':',''),'-',''))
You can use openjson to extract your data as an array and filter:
select d.ClientId, n.*
from #testdata d
cross apply (
select
Left(j.[value],3) [Type],
Row_Number() over(order by Convert(int,j.[key]) desc) [Order]
from OpenJson(Concat('["',replace(notes,'Notes: ', '","'),'"]')) j
where j.[value] != 'Request'
)n;
Example Fiddle
Here's a recursive CTE version. It splits the string based on finding "Request Notes:" then does a left/right combo to extract the 3 letter code. It iterates the order as it builds. Then you select from the CTE and only take rows where there's some NotesRemainder:
;
WITH CTESplit
AS (
SELECT ClientID,
RIGHT(LEFT(Notes, CHARINDEX('Request Notes:', Notes) + 17), 3) AS NotesPart,
RIGHT(Notes, LEN(Notes) - CHARINDEX('Request Notes:', Notes) - 17) AS NotesRemainder,
-1 AS [Order]
FROM #TestData
WHERE Notes IS NOT NULL AND CHARINDEX('Request Notes:', Notes) > 0
UNION ALL
SELECT CTESplit.ClientID,
RIGHT(LEFT(CTESplit.NotesRemainder, CHARINDEX('Request Notes:', CTESplit.NotesRemainder) + 17), 3),
RIGHT(CTESplit.NotesRemainder, LEN(CTESplit.NotesRemainder) - CHARINDEX('Request Notes:', CTESplit.NotesRemainder)),
CTESplit.[Order] - 1
FROM CTESplit
WHERE CTESplit.NotesRemainder IS NOT NULL AND CHARINDEX('Request Notes:', CTESplit.NotesRemainder) > 0
UNION ALL
SELECT CTESplit.ClientID,
RIGHT(LEFT(CTESplit.NotesRemainder, CHARINDEX('Request Notes:', CTESplit.NotesRemainder) + 17), 3),
NULL,
CTESplit.[Order] - 1
FROM CTESplit
WHERE CTESplit.NotesRemainder IS NOT NULL AND CHARINDEX('Request Notes:', CTESplit.NotesRemainder) = 0
)
SELECT CS.ClientID,
CS.NotesPart AS Type,
CS.[Order] +(SELECT MIN([Order])*-1 FROM CTESplit WHERE ClientID = CS.ClientID) AS [Order]
FROM CTESplit AS CS
WHERE CS.NotesRemainder IS NOT NULL
ORDER BY CS.ClientID,
CS.[Order] ASC;
Related
Data Available in Table1
ID Name1 Address
1 nm1 abc
1 nm2 def
1 nm3 ghi
0 nm4 jkl
0 nm5 mno
0 nm6 pqr
1 nm7 stu
1 nm8 vwx
1 nm9 yza
0 nm10 bcd
Expected Output from Table1
ID Name1 Address Counter
1 nm1 abc 1
1 nm2 def 1
1 nm3 ghi 1
0 nm4 jkl 2
0 nm5 mno 2
0 nm6 pqr 2
1 nm7 stu 3
1 nm8 vwx 3
1 nm9 yza 3
0 nm10 bcd 4
Order must be sort using the key fields and need to generate the auto increment no in the expected output.
Thanks in advance.
You need to know how the rows are order. I suppose this is sample data and in your real scenario you can ordered the data in unique way. With your sample data I am using the following statement to order the rows:
CAST(REPLACE([Name], 'nm', '') AS INT)
There are different solutions of this (some depending on your SQL version, too). If you are using SQL Server 2012+ you can use the LAG function to find if ID value is changed from the previous and current row and then to sum this changes using OVER clause:
DECLARE #DataSource TABLE
(
[ID] INT
,[Name] VARCHAR(12)
,[Address] VARCHAR(12)
);
INSERT INTO #DataSource ([ID], [Name], [Address])
VALUES (1, 'nm1', 'abc')
,(1, 'nm2', 'def')
,(1, 'nm3', 'ghi')
,(0, 'nm4', 'jkl')
,(0, 'nm5', 'mno')
,(0, 'nm6', 'pqr')
,(1, 'nm7', 'stu')
,(1, 'nm8', 'vwx')
,(1, 'nm9', 'yza')
,(0, 'nm10', 'bcd');
SELECT *
,SUM([change_made]) OVER (ORDER BY CAST(REPLACE([Name], 'nm', '') AS INT))
FROM
(
SELECT *
,IIF([ID] <> LAG([ID], 1, -1) OVER (ORDER BY CAST(REPLACE([Name], 'nm', '') AS INT)), 1, 0) AS [change_made]
FROM #DataSource
) DS
ORDER BY CAST(REPLACE([Name], 'nm', '') AS INT);
I have this table
customer | product | quantity
-------------------------------
CLI01 | A | 10
CLI01 | B | 20
CLI02 | A | 31
CLI03 | A | 10
CLI03 | C | 12
and I want to create in SQL Server this output:
customer | crossProduct | quantity
-----------------------------------
CLI01 | A+B | 30
CLI02 | Only A | 31
CLI03 | B+C | 22
Thanks in advance
Niko
If you only care about two products, then this is simple aggregation:
select customer,
(case when count(distinct product) > 2 then 'Lots of Products'
when min(product) = max(product) then 'Only ' + min(product)
else min(product) + '+' + max(product)
end) as crossproduct,
sum(quantity)
from t
group by customer;
If you care about more than two products, then you'll need to do aggregation string concatenation. That is a bit painful in SQL Server. Start by Googling "sql server aggregate string concatenation".
This is s sample:
----- Test Data ----------
DECLARE #TestData TABLE (customer VARCHAR(10),product VARCHAR(10),quantity INT)
INSERT INTO #TestData
SELECT 'CLI01','A',10 UNION ALL
SELECT 'CLI01','B',20 UNION ALL
SELECT 'CLI02','A',31 UNION ALL
SELECT 'CLI03','A',10 UNION ALL
SELECT 'CLI03 ','C',12
----- Query -------------
SELECT customer,CASE WHEN COUNT( DISTINCT t.product)=1 THEN 'Only ' ELSE '' END + LEFT(c.product,LEN(c.product)-1) AS Product,SUM(quantity) AS quantity
FROM #TestData AS t
CROSS APPLY(SELECT a.product+'+' FROM #TestData AS a WHERE a.customer=t.customer FOR XML PATH('')) c(product)
GROUP BY customer,c.product
ORDER BY t.customer
customer Product quantity
CLI01 A+B 30
CLI02 Only A 31
CLI03 A+C 22
Have you tried using stuff? This will give you what you need. Works with as many products as necessary, from sql 2008 onwards.
CREATE TABLE x (customer VARCHAR (20), product CHAR(1), quantity INT )
INSERT INTO x
VALUES( 'CLI01', 'A', 10),
( 'CLI01', 'B', 20),
( 'CLI02', 'A', 31),
( 'CLI03', 'A', 10),
( 'CLI03', 'C', 12)
SELECT x1.customer, x3.Products, SUM(x1.quantity)
FROM x x1
CROSS APPLY ( SELECT Products = STUFF( (select '+' + product AS [text()]
FROM x x2
WHERE x2.customer = x1.customer
FOR XML PATH ('') ), 1, 1,'') ) x3
GROUP BY x1.customer, x3.Products
I have the following scenario:
Using SQLDbx - database: DB2
The following SQL
WITH priceanddates AS
(
SELECT a.*
FROM FPRICE a
INNER JOIN
(
SELECT edate, MAX(idx) maxIdx
FROM FPRICE
WHERE price>0
GROUP BY edate
) b ON a.edate = b.edate AND a.idx = b.maxIdx
)
SELECT *
FROM priceanddates
WHERE fcode='XYZ'
ORDER BY edate
yields:
fcode edate idx price
XYZ 2010-09-17 2 34,3
XYZ 2010-09-20 2 34,3
XYZ 2010-09-21 2 34,3
XYZ 2010-09-22 2 34,3
XYZ 2010-09-23 2 35,7
XYZ 2010-09-24 2 34,5
XYZ 2010-09-27 2 35,5
BUT - I need something more...I would like to add a column in the final select which says something about the date's validity, like this:
fcode edate_from edate_to idx price
XYZ 2010-09-17 2010-09-20 2 34,3
XYZ 2010-09-20 2010-09-21 2 34,3
XYZ 2010-09-21 2010-09-22 2 34,3
XYZ 2010-09-22 2010-09-23 2 34,3
XYZ 2010-09-23 2010-09-24 2 35,7
XYZ 2010-09-24 2010-09-27 2 34,5
XYZ 2010-09-27 2010-09-30 2 35,5
So, edate_to is based on the "next" edate_from (as the view is currently ordered by date), or (as I hopefully deduced correctly) edate_to is simply the MIN(edate larger than "current" edate)
The final cutoff date for all dates in the DB is 2010-09-30, alas the last edate_to must always be 2010-09-30.
Obviously, I am not very skilled when it comes to writing SQL, hopefully someone can point me in the right direction - or even better - provide a solution :)
I did search around SO for a while, but I couldn't really find what I was looking for...
Cheers, Dritt.
Maybe:
WITH priceanddates AS
(
SELECT a.*,
(SELECT COALESCE(MIN(aa.edate), '2010-09-30')
FROM FPRICE aa
WHERE aa.edate > a.edate) AS edate_to
FROM FPRICE a
INNER JOIN
(
SELECT edate, MAX(idx) maxIdx
FROM FPRICE
WHERE price>0
GROUP BY edate
) b ON a.edate = b.edate AND a.idx = b.maxIdx
)
SELECT *
FROM priceanddates
WHERE fcode='XYZ'
ORDER BY edate
Use the new LEAD function.
More on it here.
WITH priceanddates AS
(
SELECT a.*
FROM FPRICE a
INNER JOIN
(
SELECT edate, MAX(idx) maxIdx
FROM FPRICE
WHERE price>0
GROUP BY edate
) b ON a.edate = b.edate AND a.idx = b.maxIdx
)
SELECT fcode
, edate AS edate_from
, LEAD (edate) OVER (ORDER BY edate) AS edate_to
, idx
, price
FROM priceanddates
WHERE fcode='XYZ'
ORDER BY edate
I am having a result as:
Code Declaration
123 a1 - 2nos
123 a2 - 230nos
123 a3 - 5nos
123 a1 - 100nos
123 a3 - 6nos
Is it possible to sum the duplicates and i need the output to be displayed like this:
Code Declaration
123 a1 - 102nos
123 a2 - 230nos
123 a3 - 11nos
I am basing this answer off the previous question that you posted. Here is a CTE version that will split the data and them total the declaration:
;with cte (code, DeclarationItem, Declaration) as
(
select Code,
cast(left(Declaration, charindex(',',Declaration+',')-1) as varchar(50)) DeclarationItem,
stuff(Declaration, 1, charindex(',',Declaration+','), '') Declaration
from yourtable
union all
select code,
cast(left(Declaration, charindex(',',Declaration+',')-1) as varchar(50)) DeclarationItem,
stuff(Declaration, 1, charindex(',',Declaration+','), '') Declaration
from cte
where Declaration > ''
),
s2 as
(
select code,
ltrim(rtrim(left(declarationitem, charindex('-', declarationitem)-1))) col1
, reverse(left(reverse(declarationitem), charindex('-', reverse(declarationitem))-1)) col2
from cte
),
fnl as
(
select code, col1,
left(col2, patindex('%[Aa-Zz]%', col2)-1) value,
substring(col2, patindex('%[Aa-Zz]%', col2), len(col2)) str
from s2
)
select code, col1 +' - '+ cast(sum(cast(value as int)) as varchar(50)) + str as declarationitem
from fnl
group by code, col1, str
See SQL Fiddle with Demo
The result is:
| CODE | DECLARATIONITEM |
--------------------------
| 123 | 123 a1 - 102nos |
| 123 | 123 a2 - 230nos |
| 123 | 123 a3 - 11nos |
Declare #t Table([Code] int, [Declaration] varchar(max))
Insert Into #t VALUES (123,'a1 - 2nos'),(123,'a2 - 230nos'),(123,'a3 - 5nos'),(123,'a1 - 100nos'),(123,'a3 - 6nos')
;With Cte As(
Select
Code
,Substring([Declaration],0,PatIndex('%-%',[Declaration])) Part1
,Cast(Substring(LTRIM(RTRIM(Substring([Declaration],PatIndex('%-%',[Declaration])+1,Len([Declaration])))),0,PatIndex('%nos%',LTRIM(RTRIM(Substring([Declaration],PatIndex('%-%',[Declaration])+1,Len([Declaration])))))) As Int) Part2
From #t)
Select Code,Part1 + '-' + Cast(Sum(Part2) AS Varchar(10)) + 'nos' As Declaration
From Cte
Group By Code,Part1
SELECT Code, SUBSTRING(Declaration, 0, CHARINDEX('-', Declaration) + 2) +
CAST(SUM(SUBSTRING(Declaration,
CHARINDEX('-', Declaration) + 2,
PATINDEX('%[Aa-Zz]%', SUBSTRING(Declaration,
CHARINDEX('-', Declaration) + 2,
LEN(Declaration)
)
) - 1
) + 0) AS varchar(max)) +
REVERSE(SUBSTRING(REVERSE(Declaration), 0, PATINDEX('%[0-9]%', REVERSE(Declaration)))) AS Decalration
FROM your_table
GROUP BY Code,
SUBSTRING(Declaration, 0, CHARINDEX('-', Declaration) + 2),
REVERSE(SUBSTRING(REVERSE(Declaration), 0, PATINDEX('%[0-9]%', REVERSE(Declaration))))
Demo on SQLFiddle
I have a tableA
ID col1 col2 status
1 ABC 123 NULL
2 ABC 214 NULL
3 BCA 001 NULL
4 ABC 123 NULL
5 BWE 765 NULL
6 ABC 123 NULL
7 BCA 001 NULL
I want to flag the duplicate data (col1, col2) & populate the column=status with a message referring to the ID of which is duplicate.
For example, ID=4 is duplicate of ID = 1 , ID=6 is duplicate of ID = 1 and ID 7 is duplicate of ID = 3.
status = "Duplicate of ID = (ID here) "
Expected result:
ID col1 col2 status
1 ABC 123 NULL
2 ABC 214 NULL
3 BCA 001 NULL
4 ABC 123 Duplicate of ID = 1
5 BWE 765 NULL
6 ABC 123 Duplicate of ID = 1
7 BCA 001 Duplicate of ID = 3
I can able to flag the duplicates but cant able to point then to the ID numbers. The script I used is :
WITH CTE_Duplicates1 AS
(SELECT ROW_NUMBER() OVER (PARTITION BY col1,col2
ORDER BY (SELECT 0)) RN,Status
FROM tableA
)
UPDATE CTE_Duplicates1
SET qnxtStatus = 'Duplicate of ID ='
WHERE RN<>1
Please correct. Thanks
;WITH CTE_Duplicates1 AS
(
SELECT MIN(ID) OVER (PARTITION BY col1, col2) Mn,
ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY ID) Rn,
*
FROM tableA
)
UPDATE CTE_Duplicates1
SET qnxtStatus = 'Duplicate of ID =' + CAST(Mn AS VARCHAR(11))
WHERE Rn > 1