I inherited a table with the following structure:
rowID rn userID Data1 Data2 Data3
----- -- ------ ----- ---- ----
1 1 1 A null 123
2 2 1 B 111 null
3 1 2 C 222 333
4 2 2 D null null
5 3 2 E 111 null
6 1 3 F 333 222
The first recs (rn=1) need to be inserted, while the rest (rn <>1) need to update the insertions (sequentially). I can insert easily, using where rn = 1 and checking for absence of the userID.
My problem is that I need to now update all recs sequentially using rn <>1 so that the user table reflects the latest state. That is, the user table after UPDATEs should look like this:
rowID userID Data1 Data2 Data3
----- ------ ----- ----- -----
1 1 B 111 123
2 2 E 111 333
3 3 F 333 222
My thought was to write a CTE, where each "pass" would grab all the recs where rn=2, then rn=3, then rn=4.... until I have no more rn to process. This way, I can update in sets.
Is this possible (or should I use do-while)? if so, do I need a recursive one or a "regular" CTE?
Here is what I tried:
;with my_cte (rowID, rn, userID, Data1, Data2, Data3, val) As
(
SELECT rowID, rn, userID, Data1, Data2, Data3, val
from #MyTempTable x
where rn =1
UNION ALL
SELECT rowID, rn, userID, Data1, Data2, Data3, b.val +1
from #MyTempTable y
INNER JOIN
my_cte b
ON y.userID = b.userID
WHERE y.rn = b.val +1
)
UPDATE userTable
SET
[Data1] = COALESCE(c.Data1, [Data1])
,[Data2]= COALESCE(c.Data2, [Data2])
,[Data3]= COALESCE(c.Data3, [Data3])
From #MyTempTable c
JOIN
( SELECT user_id
FROM my_cte
WHERE rn<>1
) b
ON b.user_id = c.user_id
WHERE
EXISTS
( Select userID
from userTable q
Where q.userId = b.userId
)
I could not get this to work, and it looks like only the first row is updating. Any thoughts? I'm a noob with CTEs. More than anything I'd like to know what exactly the CTE is doing... is it even possible for the update to run in "passes"?
Following CTE returns your given output for your given inputs. You can use these results as a starting point for inserting the records into another table.
The gist of it is
Use a recursive CTE, starting with all rows where rn=1.
In the recursive part, pick Data1-3 from the recursive part if available, otherwise retain the exisiting value (COALESCE). The result of the CTE now is your final values + the initial values where rn=1
Add a ROW_NUMBER for each userID but ORDER DESC on the existing rn. This makes sure that the latest values get rownumber 1.
Finally select all with rownumber 1 and add another rownumber as per your final results.
SQL Statement
;WITH q AS (
SELECT rn
, UserID
, Data1
, Data2
, Data3
FROM Inherited
WHERE rn = 1
UNION ALL
SELECT i.rn
, i.UserID
, COALESCE(i.Data1, q.Data1)
, COALESCE(i.Data2, q.Data2)
, COALESCE(i.Data3, q.Data3)
FROM q
INNER JOIN Inherited i ON i.rn = q.rn+1 AND i.userID = q.userID
)
SELECT rn = ROW_NUMBER() OVER (ORDER BY userID)
, *
FROM (
SELECT UserID
, Data1
, Data2
, Data3
, rn = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY rn DESC)
FROM q
) t
WHERE rn = 1
Test Script
;WITH Inherited (rowID, rn, userID, Data1, Data2, Data3) AS (
SELECT * FROM (VALUES
(1, 1, 1, 'A', null, '123')
, (2, 2, 1, 'B', '111', null)
, (3, 1, 2, 'C', '222', '333')
, (4, 2, 2, 'D', null, null)
, (5, 3, 2, 'E', '111', null)
, (6, 1, 3, 'F', '333', '222')
) a (b, c, d, e, f, g)
)
, q AS (
SELECT rn
, UserID
, Data1
, Data2
, Data3
FROM Inherited
WHERE rn = 1
UNION ALL
SELECT i.rn
, i.UserID
, COALESCE(i.Data1, q.Data1)
, COALESCE(i.Data2, q.Data2)
, COALESCE(i.Data3, q.Data3)
FROM q
INNER JOIN Inherited i ON i.rn = q.rn+1 AND i.userID = q.userID
)
SELECT rn = ROW_NUMBER() OVER (ORDER BY userID)
, *
FROM (
SELECT UserID
, Data1
, Data2
, Data3
, rn = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY rn DESC)
FROM q
) t
WHERE rn = 1
Related
I have a table Customers like:
ID Type Date Address SSN
RT124 MASTER 12/15/2005 7 Hill st 12345
RT542 MASTER 06/14/2006 7 Hill st 12345
HT457 UNIQUE 10/27/2009 10 PARK WAY 24569
QA987 UNIQUE 08/28/2010 10 PARK WAY 24569
AH825 UNIQUE 10/12/2012 10 PARK WAY 24569
14837 SINGLE 05/05/2010 2 TED ROAD 11111
24579 MARRIED 06/24/2014 2 TED ROAD 11111
What I want is to create a new column +# for every duplicate address and SSN and always the ID #1 should be the Date most recent.
Note: this table only has duplicate rows based on the address and SSN but unique ID and it doesn't require any sum.
So the output should be like this (Click on the image to zoom):
I have done some research and tried some examples but nothing work to get this output.
I will appreciate any help !
You need to enumerate the rows and aggregate. In MySQL (pre V8), it looks like:
select address, ssn,
max(case when rn = 1 then id end) as id1,
max(case when rn = 1 then type end) as type1,
max(case when rn = 1 then date end) as date1,
max(case when rn = 2 then id end) as id2,
max(case when rn = 2 then type end) as type2,
max(case when rn = 2 then date end) as date2
. . .
from (select c.*,
(#rn := if(#as = concat_ws(':', address, ssn), #rn + 1,
if(#as := concat_ws(':', address, ssn), 1, 1)
)
) as rn
from (select c.* from customers c order by address, ssn, date desc) c cross join
(select #as := '', #rn := 0) params
) c
group by address, ssn;
Note that this doesn't repeat address and ssn. That doesn't seem useful, but you can of course repeat those columns in each group.
Is there a limit to the number of times an address can be duplicated? If there is a known limit, you could have a number of left joins for each duplicate. The following would be a solution if you knew there would only ever be 6 or fewer duplicates:
with a as (
select
ID
,type
,date
,address
,SSN
row_number() over(partition by address, SSN order by date desc) as R
from Customers
)
select
a.id ID1
,a.type TYPE1
,a.date DATE1
,a.address ADDRESS1
,a.ssn SSN1
,b.id ID2
,b.type TYPE2
,b.date DATE2
,b.address ADDRESS2
,b.ssn SSN2
,c.id ID3
,c.type TYPE3
,c.date DATE3
,c.address ADDRESS3
,c.ssn SSN3
,d.id ID4
,d.type TYPE4
,d.date DATE4
,d.address ADDRESS4
,d.ssn SSN4
,e.id ID5
,e.type TYPE5
,e.date DATE5
,e.address ADDRESS5
,e.ssn SSN5
,f.id ID6
,f.type TYPE6
,f.date DATE6
,f.address ADDRESS6
,f.ssn SSN6
from a
left join
(select * from a
where r=2
) b
on a.address=b.address and a.ssn=b.ssn
left join
(select * from a
where r=3
) c
on a.address=c.address and a.ssn=c.ssn
left join
(select * from a
where r=4
) d
on a.address=d.address and a.ssn=d.ssn
left join
(select * from a
where r=5
) e
on a.address=e.address and a.ssn=e.ssn
left join
(select * from a
where r=6
) f
on a.address=f.address and a.ssn=f.ssn
where r=1
If you have more than 6, just add another set of columns to the select statement:
,f.id ID6
,f.type TYPE6
,f.date DATE6
,f.address ADDRESS6
,f.ssn SSN6
and a new left join to the from statement:
left join
(select * from a
where r=6
) f
on a.address=f.address and a.ssn=f.ssn
I have the following [table a]
id res1 res2
1 a f
1 b f
1 b f
1 c f
2 e g
2 e g
2 e g
2 f g
I'm getting the following after doing a group_concat
select
id,
group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
group_concat(case when cnt = 1 then res2 else concat(cnt, ' ', res2) end) as r2
from
(
select id, res1,res2, count(*) as cnt
from [table a]
group by id, res1,res2
) t
group by id;
id r1 r2
1 a,2 b,c f,2 f,f
2 3 e,f 3 g,g
The res1 column is coming fine BUT res2 column is duplicating the res1 column.
Basically i want to print the value of how many times a character occurs before the character.
.I want in the following format..
id r1 r2
1 a,2 b,c 4 f
2 3 e,f 4 g
How can I achieve it ?
The way I would approach this is to do two rollups/aggregations, using two separate subqueries for the res1 and res2 columns. The first aggregation is over id and res1 (or res2), and obtains the counts for each letter or word. Then, aggregate again, this time only over the id, to obtain a comma separated string for each id. Finally, join these subqueries together to obtain the final result.
SELECT
t1.id, t1.r1, t2.r2
FROM
(
SELECT t.id, GROUP_CONCAT(res1agg ORDER BY res1) AS r1
FROM
(
SELECT
id,
res1,
CASE WHEN COUNT(*) = 1 THEN res1
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res1) END AS res1agg
FROM yourTable
GROUP BY id, res1
) t
GROUP BY t.id
) t1
INNER JOIN
(
SELECT t.id, GROUP_CONCAT(res2agg ORDER BY res2) AS r2
FROM
(
SELECT
id,
res2,
CASE WHEN COUNT(*) = 1 THEN res2
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res2) END AS res2agg
FROM yourTable
GROUP BY id, res2
) t
GROUP BY t.id
) t2
ON t1.id = t2.id;
Output:
Demo here:
Rextester
Just added 2 more conditions in your query without using more inner queries.
Try this:-
input:-
CREATE TABLE Table1 (id INT, res1 varchar(20), res2 varchar(20));
insert into Table1 values(1, 'a', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'c', 'f');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'f', 'g');
Query:-
Select t.id,group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
case when id=1 then trim(concat(sum(case when id = 1 then cnt end),' ',res2))
else trim(concat(sum(case when id = 2 then cnt end),' ',res2)) end as r2
from
(
select id, res1,res2,count(*) as cnt
from table1 a
group by id, res1,res2
) t
group by t.id
My Output:-
id r1 r2
1 a,c,2 b 4 f
2 f,3 e 4 g
Let me know if you have any questions
I have the following SQL query which is somehow broken:
SELECT *
FROM (
SELECT ID, TEST, CHR, NUMBER
FROM Test_Table
JOIN ...
WHERE ...
) TEMP_TABLE
FROM TEMP_TABLE a
LEFT
JOIN TEMP_TABLE b
ON b.test = a.test
AND b.chr = 'x'
WHERE a.number IN (5,6)
AND b.id IS NULL
GROUP
BY a.test
HAVING COUNT(*) = 2;
From the first FROM statement I get the following temporary table:
ID , TEST, CHR , NUMBER
------------------------------
( 1 , 7 , 'C' , 5),
( 2 , 7 , 'T' , 6),
( 3 , 8 , 'C' , 4),
( 4 , 8 , 'T' , 5),
( 5 , 9 , 'A' , 4),
( 6 , 9 , 'G' , 5),
( 7 , 10 , 'T' , 4),
( 8 , 10 , 'A' , 5),
( 9 , 10 , 'X' , 6),
(10 , 14 , 'T' , 4),
(11 , 14 , 'G' , 5);
From FROM TEMP_TABLE ... I try to implement the following conditions:
For example test column 7 contains two rows, if the number column contains values 5 AND 6, AND the value is NOT X in the chr column, I would like to select select the rows with 7 in the test column.
For example test column 10 contains three rows, if the number column contains values 5 AND 6, AND the value X exists in the chr column, I would like to exclude rows with 10 in the test column.
As result it should only be test column with 7, because test column 7 have 5 and 6 in the number column and not X.
Result example:
ID | TEST | CHR | NUMBER
1 | 7 | C | 5
2 | 7 | T | 6
What did go wrong with the above SQL query?
Your query has two FROM clauses remove one and try like below
SELECT *
FROM (
SELECT ID, TEST, CHR, NUMBER
FROM Test_Table
JOIN ...
WHERE ...
) a
LEFT JOIN
(
SELECT ID, TEST, CHR, NUMBER
FROM Test_Table
JOIN ...
WHERE ...
) b
ON b.test = a.test
AND b.chr = 'x'
WHERE a.number IN (5,6)
AND b.id IS NULL
GROUP
BY a.test
HAVING COUNT(*) = 2;
A easier way to do this is to create a TEMPORARY table using TEMPORARY keyword like mentioned here
CREATE TEMPORARY TABLE TEMP_TABLE (ID int, TEST varchar(100), CHR char, NUMBER int );
INSERT INTO TEMP_TABLE
(SELECT ID, TEST, CHR, NUMBER
FROM Test_Table
JOIN ...
WHERE ...);
SELECT *
FROM TEMP_TABLE a
LEFT
JOIN TEMP_TABLE b
ON b.test = a.test
AND b.chr = 'x'
WHERE a.number IN (5,6)
AND b.id IS NULL
GROUP
BY a.test
HAVING COUNT(*) = 2;
I need to be able to do something with my column (below) that can contain multiple values. The 'HearAboutEvent' column has multiple values separated by a comma. Each one of these values corresponds to an entry in another table. So the value of 11273 will equal facebook, 11274 will mean radio, and 11275 will mean commercial.
The data I am working with looks like this:
weather ID MemberID SubscriptionID DateEntered ParticipatedBefore ParticipatedBeforeCities WeatherDependent NonRefundable TShirtSize HearAboutEvent
Yes 24 18 1 2013-12-19 0 NULL 10950 10952 10957 11273, 11274, 11275
I am able to do the proper join to resolve the value of 'weather', note it is the first column and the 8th column.
This is the query I have created so far to resolve the values of WeatherDependent:
SELECT CFS1.Name as 'weather', *
FROM FSM_CustomForm_693 t
LEFT JOIN FSM_CustomFormSelectOptions CFS1 ON CFS1.ID = t.WeatherDependent
where t.ID = 24
Ultimately I need to have the data look like this:
weather ID MemberID SubscriptionID DateEntered ParticipatedBefore ParticipatedBeforeCities WeatherDependent NonRefundable TShirtSize HearAboutEvent
Yes 24 18 1 2013-12-19 0 NULL 10950 10952 10957 Facebook, radio, commercial
Things I think you could use to accomplish this are:
A Split TVF FUNCTION - http://msdn.microsoft.com/en-us/library/ms186755.aspx
CROSS APPLY - http://technet.microsoft.com/en-us/library/ms175156.aspx
STUFF & FOR XML PATH - http://msdn.microsoft.com/en-us/library/ms188043.aspx & http://msdn.microsoft.com/en-us/library/ms190922.aspx
Going one step further, you need something like this:
Excuse my profuse use of sub queries.
CREATE FUNCTION dbo.Split (#sep char(1), #s varchar(512))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT pn,
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
GO
SELECT
O.A,O.B,O.C,O.D,O.E,O.F,O.G,O.H,O.I,O.J,O.Stuffed
FROM (
SELECT
*
,STUFF((
SELECT ', ' + Name
FROM (
SELECT
V.*
,Y.Name
FROM (
SELECT
'Yes' AS A
,24 AS B
,18 AS C
,1 AS D
,'2013-12-19' AS E
,0 AS F
,NULL AS G
,10950 AS H
,10952 AS I
,10957 AS J
,'11273, 11274, 11275' AS K
)
AS V
CROSS APPLY dbo.Split(',',REPLACE(K,' ','')) AS P
JOIN (
SELECT 11273 AS Id , 'Facebook' AS Name UNION ALL
SELECT 11274 AS Id , 'radio' AS Name UNION ALL
SELECT 11275 AS Id , 'commercial' AS Name
)Y ON y.Id = p.s) ExampleTable
FOR XML PATH('')
), 1, 1, '' )
AS [Stuffed]
FROM (
SELECT
V.*
FROM (
SELECT
'Yes' AS A
,24 AS B
,18 AS C
,1 AS D
,'2013-12-19' AS E
,0 AS F
,NULL AS G
,10950 AS H
,10952 AS I
,10957 AS J
,'11273, 11274, 11275' AS K
)
AS V
CROSS APPLY dbo.Split(',',REPLACE(K,' ','')) AS P
JOIN (
SELECT 11273 AS Id , 'Facebook' AS Name UNION ALL
SELECT 11274 AS Id , 'radio' AS Name UNION ALL
SELECT 11275 AS Id , 'commercial' AS Name
)Y ON y.Id = p.s
)Z
) O
GROUP BY O.A,O.B,O.C,O.D,O.E,O.F,O.G,O.H,O.I,O.J,O.K,O.Stuffed
I have table psc_Pro_ProfessorPositions(ProfessorID,PositionID,StartDate,EndDate). It have 2 primary key is ProfessorID,PositionID.
I want to check ProfessorID,PositionID not in table to insert.I wrote like this:
insert into CoreUIs.dbo.psc_Pro_ProfessorPositions
(
ProfessorID,PositionID,StartDate,EndDate
)
select a.MaQuanLy,b.MaQuanLy,convert(smalldatetime,NgayHieuLuc),convert(smalldatetime,NgayHetHieuLuc)
from inserted
inner join GiangVien a on a.MaGiangVien = inserted.MaGiangVien
inner join ChucVu b on b.MaChucVu = inserted.MaChucVu
where a.MaQuanLy not in (select ProfessorID from CoreUIs.dbo.psc_Pro_ProfessorPositions)
and b.MaQuanLy not in (select PositionID from CoreUIs.dbo.psc_Pro_ProfessorPositions)
But it's wrong.Can help me?Thanks all.
;WITH x AS
(
SELECT TeacherID, ClassID, ClassStuID, s = [SUM],
rn = ROW_NUMBER() OVER (PARTITION BY TeacherID ORDER BY ClassID)
FROM dbo.TB1
)
SELECT TeacherID, ClassID, ClassStuID,
[SUM] = CASE rn WHEN 1 THEN s ELSE NULL END
FROM x
ORDER BY TeacherID, [SUM] DESC;
You can employ CTEs with ROW_NUMBER()OVER() to identify the first row of each TeacherID:
; with a as (
select * from TB1
union
select * from TB2
)
, b as (
select *, r=ROW_NUMBER()over(partition by a.TeacherID order by a.TeacherID,
a.ClassID, a.ClassStuID) from a
)
select b.TeacherID, b.ClassID, b.ClassStuID
, [SUM]=case b.r when 1 then b.[SUM] else null end
from b
order by b.TeacherID, b.r
go
Result: