I have a query output like that. I want to have N3 column is the distinct merge of N1 and N2.
Query output example
My current query is this:
SELECT rd.cId
, rd.dId
,ARRAY_AGG(ei.IncentiveName IGNORE NULLS) AS N1
,ARRAY_AGG(ai.IncentiveName IGNORE NULLS) AS N2
FROM rd
join ei on...
join ai on ...
GROUP BY rd.cId, rd.dId
We found the problem, in many of the records, there was null value in 2nd column which was returning overall outcome as null.
Fixed that by checking with help of IFNull(N1, []) which solved the problem.
SELECT rd.cId
, rd.dId
ARRAY_CONCAT(
IFNULL(ARRAY_AGG(DISTINCT ei.IncentiveName IGNORE NULLS),[]),
IFNULL(ARRAY_AGG(DISTINCT ai.IncentiveName IGNORE NULLS),[])
)
FROM rd
join ei on...
join ai on ...
GROUP BY rd.cId, rd.dId
You can merge both arrays with array_concat. Then unnest the array and regroup to an arrray, but keep only distinct elements.
with tbl as
(select 1 id, [1,2,2,3] N1, [1,5,7,8] N2
union all select 2 ,[],[5,5,5,6])
select *,
array_concat(N1,N2),
(select array_agg(distinct x) from unnest(array_concat(N1,N2))x)
from tbl
In case you want to group by id column:
with tbl as
(select 1 id, [1,2,2,3] N1, [1,5,7,8] N2
union all select 2 ,[],[5,5,5,6]
union all select 2 ,[7,8],[9]),
helper as (
select id, array_agg(N1_) N1, array_agg(N2_) N2
from tbl,unnest(N1) N1_,unnest(N2) N2_
group by 1
)
select *,
#array_concat(N1,N2),
(select array_agg(distinct x) from unnest(array_concat(N1,N2))x)
from helper
Consider below
with your_current_query as (
SELECT rd.cId
, rd.dId
,ARRAY_AGG(ei.IncentiveName IGNORE NULLS) AS N1
,ARRAY_AGG(ai.IncentiveName IGNORE NULLS) AS N2
FROM rd
join ei on...
join ai on ...
GROUP BY rd.cId, rd.dId
)
select *, array(
select * from t.N1 union distinct
select * from t.N2
) as N3
from your_current_query t
Related
CREATE TABLE #A (UpperLimit NUMERIC(4))
CREATE TABLE #B (Id NUMERIC(4), Amount NUMERIC(4))
INSERT INTO #A VALUES
(1000), (2000), (3000)
INSERT INTO #B VALUES
(1, 3100),
(2, 1900),
(3, 1800),
(4, 1700),
(5, 900),
(6, 800)
Given these 2 tables, I want to join Table A to B ON B.Amount < A.UpperLimit but each record from Table B can only be used once, so the desired output would be:
I could easily do this by plopping Table B's records into a temp table, cursor over table A taking top record < UpperLimit and Deleting that record from the temp table or some other programmatic solution, but I'd like to avoid that and I'm pretty sure this could be done with a "normal" (recursive CTE? Partition?) query.
You could achieve your desired output using below recursive CTE
WITH
DATA AS
(
SELECT * FROM #A A1 INNER JOIN #B B1 ON A1.UpperLimit >= B1.Amount
),
MA AS
(
SELECT MIN(UpperLimit) AS MinLimit, MAX(UpperLimit) AS MaxLimit FROM #A
),
RESULT AS
(
-- Get the first record corresponding with maximum upper limit
SELECT *
FROM DATA D1
WHERE NOT EXISTS
(SELECT 1
FROM DATA D2
WHERE D2.UpperLimit = D1.UpperLimit AND D2.Amount > D1.Amount)
AND D1.UpperLimit = (SELECT MaxLimit FROM MA)
-- Recursive get remain record corresponding with other upper limit
UNION ALL
SELECT D1.*
FROM RESULT R1 INNER JOIN DATA D1
ON (R1.UpperLimit > D1.UpperLimit AND R1.Id != D1.Id)
WHERE D1.UpperLimit >= (SELECT MinLimit FROM MA)
AND NOT EXISTS
(SELECT 1
FROM DATA D2
WHERE D2.UpperLimit = D1.UpperLimit AND D2.Amount > D1.Amount AND D2.Id != R1.Id)
)
SELECT DISTINCT * FROM RESULT ORDER BY UpperLimit DESC;
Demo: https://dbfiddle.uk/Y-m0K6Mk
Might be a bit lengthy but hopefully clear enough.
with a as
(select -- order and number rows in table A in some way
row_number() over (order by UpperLimit) as RnA,
*
from #a),
b as
(select -- order and number rows in table B in the same way
row_number() over (order by Amount) as RnB,
*
from #b),
m as
(select -- get and number all possible pairs of values from both tables considering the restriction
row_number() over (order by a.UpperLimit desc, b.Amount desc) as RnM,
*
from a
join b on
b.Amount < a.UpperLimit),
r as
(select -- use recursion to get all possible combinations of the value pairs with metrics of interest for comparison
convert(varchar(max), RnA) as ListA,
convert(varchar(max), RnB) as ListB,
RnA,
RnB,
1 as CountB,
convert(int, Amount) as SumB
from m
where RnM = 1
union all
select
r.ListA + ' ' + convert(varchar(max), m.RnA),
r.ListB + ' ' + convert(varchar(max), m.RnB),
m.RnA,
m.RnB,
r.CountB + 1,
r.SumB + convert(int, m.Amount)
from m
join r on
m.RnA < r.RnA and
m.RnB < r.RnB),
e as
(select top(1) -- select combinations of interest using metrics
ListA,
ListB
from r
order by CountB desc, SumB desc),
ea as
(select -- turn id list into table for table A
ea.Rn,
ea.Value
from e
cross apply(select row_number() over (order by (select null)) as Rn, Value from string_split(e.ListA, ' ')) as ea),
eb as
(select -- turn id list into table for table B
eb.Rn,
eb.Value
from e
cross apply(select row_number() over (order by (select null)) as Rn, Value from string_split(e.ListB, ' ')) as eb)
select -- get output table with actual values from the original tables
a.UpperLimit,
b.Amount,
b.Id
from ea
join eb on
ea.Rn = eb.Rn
join a on
ea.Value = a.RnA
join b on
eb.Value = b.RnB;
You can use an APPLY with a TOP 1 for this. Each row in the outer table gets only one row from the APPLY.
SELECT
*
FROM #A a
OUTER APPLY (
SELECT TOP (1) *
FROM #B b
WHERE b.Amount < a.UpperLimit
) b;
To simulate an inner-join (rather than a left-join) use CROSS APPLY.
This query returns very close to desired outcome.
WITH CTE AS (SELECT B.*,
ROW_NUMBER() OVER (PARTITION BY B.Value ORDER BY B.Value DESC) AS RowNum
FROM #B B),
cc as (SELECT A.Limit, CTE.*
FROM #A A
LEFT JOIN CTE ON CTE.Value < A.Limit AND CTE.RowNum = 1),
cc2 as (select *, MAX(Value) OVER ( PARTITION BY cc.Limit) as l1 from cc)
select Limit, ID, Value
from cc2
where Value = l1
This query use 3 Common Table Expressions. First sort Table B with ROW_NUMBER() function and PARTITION BY clause, second one JOIN Table A with Table B with the condition given and the third one filters the record that is in Limit on Table A and use the Limit only once.
I am stuck with this. I have a simple set-up with two tables. One table is holding emailaddresses one table is holding vouchercodes. I want to join them in a third table, so that each emailaddress has one random vouchercode.
Unfortunatly I am stuck with this as there are no identic Ids to match both values. What I have so far brings no result:
Select
A.Email
B.CouponCode
FROM Emailaddresses as A
JOIN CouponCodes as B
on A.Email = B.CouponCode
A hint would be great as search did not bring me any further yet.
Edit -
Table A (Addresses)
-------------------
Column A | Column B
-------------------------
email1#gmail.com True
email2#gmail.com
email3#gmail.com True
email4#gmail.com
Table B (Voucher)
-------------------
ABCD1234
ABCD5678
ABCD9876
ABCD5432
Table C
-------------------------
column A | column B
-------------------------
email1#gmail.com ABCD1234
email2#gmail.com ABCD5678
email3#gmail.com ABCD9876
email4#gmail.com ABCD5432
Sample Data:
While joining without proper keys is not a good solution, for your case you can try this. (note: not tested, just a quick suggestion)
;with cte_email as (
select row_number() over (order by Email) as rownum, Email
from Emailaddresses
)
;with cte_coupon as (
select row_number() over (order by CouponCode) as rownum, CouponCode
from CouponCodes
)
select a.Email,b.CouponCode
from cte_email a
join cte_coupon b
on a.rownum = b.rownum
You want to randomly join records, one email with one coupon each. So create random row numbers and join on these:
select
e.email,
c.couponcode
from (select t.*, row_number() over (order by newid()) as rn from emailaddresses t) e
join (select t.*, row_number() over (order by newid()) as rn from CouponCodes t) c
on c.rn = e.rn;
Give a row number for both the tables and join it with row number.
Query
;with cte as(
select [rn] = row_number() over(
order by [Column_A]
), *
from [Table_A]
),
cte2 as(
select [rn] = row_number() over(
order by [Column_A]
), *
from [Table_B]
)
select t1.[Column_A] as [Email_Id], t2.[Column_A] as [Coupon]
from cte t1
join cte2 t2
on t1.rn = t2.rn;
Find a demo here
I need to generate combinations from the string of numbers
3,4,5,6,7 digit combinations
for example from this string
01;05;06;03;02;10;11;
here 7 numbers are there. for 3 digit 35 combinations will be there and it should be in order of order numbers in the string.
like
01;05;06;|
01;05;03;|
01;05;02;|
01;05;10;|
01;05;11;|
01;06;03;|
01;06;02;|
01;06;10;|
01;06;11;|
01;03;02;|
01;03;10;|
01;03;11;|
01;02;10;|
01;02;11;|
01;10;11;|
05;06;03;|
05;06;02;|
05;06;10;|
05;06;11;|
05;03;02;|
05;03;10;|
05;03;11;|
05;02;10;|
05;02;11;|
05;10;11;|
06;03;02;|
06;03;10;|
06;03;11;|
06;02;10;|
06;02;11;|
06;10;11;|
03;02;10;|
03;02;11;|
03;10;11;|
02;10;11;|
You can do this with two inner joins after splitting the string.
rextester: http://rextester.com/JJGKI77804
String Splitter for the test:
/* Jeff Moden's http://www.sqlservercentral.com/articles/Tally+Table/72993/ */
create function dbo.DelimitedSplitN4K (#pString nvarchar(4000), #pDelimiter nchar(1))
returns table with schemabinding as
return
with e1(n) as (
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all select 1
)
, e2(n) as (select 1 from e1 a, e1 b)
, e4(n) as (select 1 from e2 a, e2 b)
, cteTally(n) as (select top (isnull(datalength(#pString)/2,0))
row_number() over (order by (select null)) from e4)
, cteStart(n1) as (select 1 union all
select t.n+1 from cteTally t where substring(#pString,t.n,1) = #pDelimiter)
, ctelen(n1,l1) as(select s.n1
, isnull(nullif(charindex(#pDelimiter,#pString,s.n1),0)-s.n1,4000)
from cteStart s
)
select Itemnumber = row_number() over(order by l.n1)
, Item = substring(#pString, l.n1, l.l1)
from ctelen l;
go
the query
declare #str nvarchar(4000)= '01;05;06;03;02;10;11;';
with cte as (
select ItemNumber, Item
from dbo.DelimitedSplitN4K(#str,';')
where Item != ''
)
select combo=a.Item+';'+b.Item+';'+c.Item
from cte as a
inner join cte as b on a.ItemNumber<b.ItemNumber
inner join cte as c on b.ItemNumber<c.ItemNumber;
order by a.ItemNumber, b.ItemNumber, c.ItemNumber
ordered by ItemNumber results:
01;05;06
01;05;03
01;05;02
01;05;10
01;05;11
01;06;03
01;06;02
01;06;10
01;06;11
01;03;02
01;03;10
01;03;11
01;02;10
01;02;11
01;10;11
05;06;03
05;06;02
05;06;10
05;06;11
05;03;02
05;03;10
05;03;11
05;02;10
05;02;11
05;10;11
06;03;02
06;03;10
06;03;11
06;02;10
06;02;11
06;10;11
03;02;10
03;02;11
03;10;11
02;10;11
If you want to return a single string, pipe delimited then:
with cte as (
select ItemNumber, Item
from dbo.DelimitedSplitN4K(#str,';')
where Item != ''
)
select combo=stuff(
(select '|'+a.Item+';'+b.Item+';'+c.Item
from cte as a
inner join cte as b on a.ItemNumber<b.ItemNumber
inner join cte as c on b.ItemNumber<c.ItemNumber
order by a.ItemNumber, b.ItemNumber, c.ItemNumber
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
results:
01;05;06|01;05;03|01;05;02|01;05;10|01;05;11|01;06;03|01;06;02|01;06;10|01;06;11|01;03;02|01;03;10|01;03;11|01;02;10|01;02;11|01;10;11|05;06;03|05;06;02|05;06;10|05;06;11|05;03;02|05;03;10|05;03;11|05;02;10|05;02;11|05;10;11|06;03;02|06;03;10|06;03;11|06;02;10|06;02;11|06;10;11|03;02;10|03;02;11|03;10;11|02;10;11
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way
I had nearly the same query but resulted somehow different
Please check
/*
create table Combination (id char(2))
insert into Combination values ('01'),('05'),('06'),('03'),('02'),('10'),('11')
*/
select c1.id, c2.id, c3.id, c1.id + ';' + c2.id + ';' + c3.id Combination
from Combination c1, Combination c2, Combination c3
where
c2.id between c1.id and c3.id
and c1.id <> c2.id
and c2.id <> c3.id
order by c1.id, c2.id, c3.id
The output is
I have a table that stores html templates which contain markup with placeholders in key locations, something like this ...
<div>
<div>{FirstName}</div>
<div>{LastName}</div>
</div>
I want to write a query that returns from the table all of the placeholders used from all rows.
SELECT Template
FROM MyTable
WHERE ????
So for the above example the result I want is ...
{FirstName}
{LastName}
I have seen people using regex in SQL but can't figure out how to only return the matches and not the whole column value.
It's also worth noting that I want a result per match ideally but if I got a comma separated list per row that matched or something that would do.
I would approach this using a numbers table, which are very useful anyway, so if you don't have one, I would consider creating one, but for the sake of a complete answer I will assume you don't have one and can't create one. In such scenarios you can generate a list of numbers on the fly quite easily using:
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
--N4 (N) AS (SELECT 1 FROM N3 AS N1 CROSS JOIN N3 AS N2)
Numbers (Number) AS (SELECT ROW_NUMBER() OVER(ORDER BY N) FROM N3)
SELECT Number
FROM Numbers;
This starts with a table of 10 rows created with a table value constructor (N1), it then joins this table with itself to get a table of 100 rows (N2), then joins N2 to itself to get 10,000 rows (N3), this can be repeated as required, before finally using ROW_NUMBER() to get a sequential number in each row. Aaron Bertrand has done a pretty comprehensive series on generating a set or sequence without loops, and this method comes out on top (as a method of creating the table on the fly).
Once you have this numbers table you can join it to your template to find the position of each "{" using SUBSTRING:
SELECT t.Template,
StartPosition = n.Number
FROM dbo.T
INNER JOIN Numbers n
ON SUBSTRING(t.Template, n.Number, 1) = '{';
With your example this will return 16, and 43. Then you can use CHARINDEX to find the "}" that follows each "{":
SELECT t.Template,
StartPosition = n.Number,
EndPosition = CHARINDEX('}', t.template, n.Number) + 1
FROM dbo.T
INNER JOIN Numbers n
ON SUBSTRING(t.Template, n.Number, 1) = '{';
Then you can use SUBSTRING again to extract the term between each start and end position. So a full working example would be:
DECLARE #T TABLE (Template NVARCHAR(MAX));
INSERT #T (Template)
VALUES ('<div>
<div>{FirstName}</div>
<div>{LastName}</div>
</div>');
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
--N4 (N) AS (SELECT 1 FROM N3 AS N1 CROSS JOIN N3 AS N2)
Numbers (Number) AS (SELECT ROW_NUMBER() OVER(ORDER BY N) FROM N3)
SELECT t.Template,
StartPosition = n.Number,
EndPosition = CHARINDEX('}', t.template, n.Number) + 1,
Term = SUBSTRING(t.template, n.Number, CHARINDEX('}', t.template, n.Number) + 1 - n.Number)
FROM #T t
INNER JOIN Numbers n
ON SUBSTRING(t.Template, n.Number, 1) = '{';
See this:
CREATE TABLE #temp(id int identity(1,1), template nvarchar(max))
INSERT INTO #temp(template)
SELECT REPLICATE(N'<div>
<div>{FirstName}</div>
<div>{LastName}</div>
</div>',1000)
;WITH cte AS(
SELECT id,
SUBSTRING(template,CHARINDEX(N'{',template),CHARINDEX(N'}',template)-CHARINDEX(N'{',template)+1) as match,
SUBSTRING(template,CHARINDEX(N'}',template)+1,LEN(template)) as templateRest
FROM #temp
UNION ALL
SELECT id,
SUBSTRING(templateRest,CHARINDEX(N'{',templateRest),CHARINDEX(N'}',templateRest)-CHARINDEX(N'{',templateRest)+1) as match,
SUBSTRING(templateRest,CHARINDEX(N'}',templateRest)+1,LEN(templateRest)) as templateRest
FROM cte
WHERE templateRest LIKE N'%}%'
)
SELECT t.id, t.template, c.match
-- Only distinctive:
-- SELECT DISTINCT t.id, t.template c.match
FROM cte AS c
INNER JOIN #temp AS t
ON c.id = t.id
OPTION(MAXRECURSION 1000) -- if needed, this value could still be raised
DROP TABLE #temp
GO
You can filter it for the template and retrieve all matches.
I needed help with using the function MAX() properly as I seem to be getting more than one row when I have clearly stated that I want the MAX(Monthid), which should return the last monthyear row for the customer.
What I need is the last monthyear row for either customer_segment or agreement. When I finally put the customer_segment and agreement columns to the original, I get upto 6 different monthyear rows wiht different customer_segment names when I only want 1 row.
How do fix this?
--Finding customer segment
SELECT
a.[cust_no]
,Customer_Segment
,max(monthid) AS monthyear
INTO #Segment
FROM Original_table a
INNER JOIN Customer_Segment ku
on ku.Cust_no=a.cust_no
GROUP BY a.cust_no,Customer_Segment
--------------------------------------------------------------------------
--Finding agreement(yes/no)
SELECT DISTINCT
a.cust_no,
Agreement,
max(monthid) as Monthyear
into #Agreement
FROM Original_table a
INNER JOIN Cust_Details zx
ON zx.cust_no=a.cust_no
GROUP BY a.cust_no,
zx.Agreement
------------------------------------------------
-- Attaching columns to original file on cust_no
select DISTINCT
A.cust_no,
B.Customer_Segment,
d.Agreement
from Original_table A
LEFT JOIN ( SELECT DISTINCT * FROM #Segment ) b
on b.cust_no=A.cust_no
LEFT JOIN( SELECT distinct * FROM #Agreement ) d
ON d.cust_no=a.cust_no
Aren't you missing some info on the joins?
(...)
LEFT JOIN ( SELECT DISTINCT * FROM #Segment ) b
on b.cust_no=A.cust_no and
b.Customer_Segment = A.Customer_Segment
LEFT JOIN( SELECT distinct * FROM #Agreement ) d
ON d.cust_no=a.cust_no and
d.Agreement = A.Agreement
try this:
select
A.cust_no, b.monthyear,
e.Customer_Segment,
d.Agreement
from Original_table A
JOIN (SELECT a.[cust_no] cust_no ,max(monthid) AS monthyear
FROM Original_table a) b on b.cust_no=A.cust_no
OUTER APPLY
( SELECT TOP 1 Agreement FROM Cust_Details d
WHERE d.cust_no=a.cust_no
ORDER BY Agreement
) d
OUTER APPLY
( SELECT TOP 1 Customer_Segment FROM Customer_Segment e
WHERE e.cust_no=a.cust_no
ORDER BY Customer_Segment
) e