Aggregating several columns to single column - sql

I have a table that looks like
id cat data
--------------------
1 1 foo
2 1 bar
3 1 baz
4 2 some
5 2 random
6 3 Data 1
7 2 data
8 3 Data 2
9 3 Data 3
And I want the last 3 ids and data of each category in a single row like
cat id1 data1 id2 data2 id3 data3
-----------------------------------------------------
1 1 foo 2 bar 3 baz
2 4 some 5 random 7 data
3 6 Data 1 8 Data 2 9 Data 3
I already tried the following:
Get the data with the highest id for each cat:
SELECT id, data FROM tbl t1 WHERE EXISTS (
SELECT 1 FROM tbl t2 WHERE t1.cat = t2.cat
GROUP BY t2.cat HAVING MAX(t2.id) = t1.id
)
Get the data with the 2nd highest ids for each cat:
SELECT id, data FROM tbl t1 WHERE EXISTS (
SELECT 1 FROM tbl t2 WHERE t1.cat = t2.cat AND NOT EXISTS (
-- Not the highest value
SELECT 1 FROM tbl t3 WHERE t1.cat = t3.cat GROUP BY t3.cat
HAVING MAX(t3.id) = t2.id
) GROUP BY t2.cat HAVING MAX(t2.id) = t1.id
)
Get the data with the 3rd highest id for each cat:
SELECT id, data FROM tbl t1 WHERE EXISTS (
SELECT 1 FROM tbl t2 WHERE t1.cat = t2.cat AND NOT EXISTS (
-- id is not 2nd highest
SELECT 1 FROM tbl t3 WHERE t1.cat = t3.cat AND NOT EXISTS (
-- id is not the highest
SELECT 1 FROM tbl t4 WHERE t1.cat = t4.cat GROUP BY t4.cat
HAVING MAX(t4.id) = t3.id
) GROUP BY t3.cat HAVING MAX(t3.id) = t2.id
) AND NOT EXIST (
-- not the highest id
SELECT 1 FROM tbl t5 WHERE t1.cat = t5.cat GROUP BY t5.cat
HAVING MAX(t5.id) = t2.id
) GROUP BY t2.cat HAVING MAX(t2.id) = t1.id
)
And now, joining the entire thing. But I believe that there exists a better solution. What is it?
PS: I have to do it with Informix

Not my answer, a coworker of mine came up with this:
create temp table t(
id smallint,
cat smallint,
data char(10)
) with no log;
insert into t values (1, 1, "foo");
insert into t values (2, 1, "bar");
insert into t values (3, 1, "baz");
insert into t values (4, 2, "some");
insert into t values (5, 2, "random");
insert into t values (6, 3, "Data 1");
insert into t values (7, 2, "data");
insert into t values (8, 3, "Data 2");
insert into t values (9, 3, "Data 3");
insert into t values (10, 4, "some");
insert into t values (11, 4, "more");
insert into t values (12, 4, "random");
insert into t values (13, 4, "data");
insert into t values (14, 4, "for");
insert into t values (15, 4, "testing");
insert into t values (16, 5, "one");
select
cat,
max(case when cnt = 3 then id end) as id1,
max(case when cnt = 2 then id end) as id2,
max(case when cnt = 1 then id end) as id3,
max(case when cnt = 3 then data end) as data1,
max(case when cnt = 2 then data end) as data2,
max(case when cnt = 1 then data end) as data3
from
(
select
a.cat,
a.id,
a.data,
count(*) as cnt
from
t a,
t b
where
a.cat = b.cat and
a.id <= b.id
group by
a.id,
a.cat,
a.data
having
count(*) <= 3
)
group by
1
order by
1;
cat id1 id2 id3 data1 data2 data3
1 1 2 3 foo bar baz
2 4 5 7 some random data
3 6 8 9 Data 1 Data 2 Data 3
4 13 14 15 data for testing
5 16 one

If you using Informix 11.50 or above , there is an option where isn't perfect, but maybe can help. Check the select at end bellow.
They will return a multiset datatype with char() data type... where probably will create a difficult to read it, depending of the program language are you using.
Thanks to Fernando Nunes who suggest this SQL into IIUG forum
At this moment I don't see other alternative besides complex SQLs.
drop table teste;
create temp table teste ( id smallint, cat smallint, data char(10));
insert into teste values ( 1, 1, 'foo ' );
insert into teste values ( 2, 1, 'bar ' );
insert into teste values ( 3, 1, 'baz ' );
insert into teste values ( 4, 2, 'some ' );
insert into teste values ( 5, 2, 'random ' );
insert into teste values ( 6, 3, 'Data 1 ' );
insert into teste values ( 7, 2, 'data ' );
insert into teste values ( 8, 3, 'Data 2 ' );
insert into teste values ( 9, 3, 'Data 3 ' );
insert into teste values ( 10, 3, 'Data 4 ' );
select * from teste;
select ms.*
from
(
SELECT MULTISET( SELECT ITEM t.id || ',' || t.cat || ',' || t.data m1 FROM
teste t WHERE t.cat = tout.cat) FROM (SELECT unique cat from teste) tout
) msdrop table teste;
will return :
expression MULTISET{'1,1,foo ','2,1,bar ','3,1,baz '}
expression MULTISET{'4,2,some ','5,2,random ','7,2,data '}
expression MULTISET{'6,3,Data 1 ','8,3,Data 2 ','9,3,Data 3 '}

Related

I would like to update table using merge and my table source will use cross join

I have a Table1:
IdT1 : 11, 12, 13
IdT1Group : 30,30,30
Table2:
IdT2 : 1, 2, 3, 4
IdT1 : 11, 11, 12, 12
Detail : A, B, A, B
AND User Defined Table Type #T2:
IdT2 : 1, 2, 3
IdT1 : 11,11,11
Detail: A,B,C
I would like to Update Table2 to be:
IdT2 : 1,2,5, 3,4,6, 7,8,9
IdT1 : 11,11,11, 12,12,12, 13,13,13
Detail : A,B,C, A,B,C, A,B,C
So, I was Use Merge in StoredProcedure:
;WITH Table2
AS (SELECT Table2.* FROM Table2 INNER JOIN Table1 ON Table2.IdT1 = Table1.IdT1 AND IdT1Group = 30)
MERGE INTO Table2 AS tblTarget
USING (SELECT #T2.*, T1Item.IdT1 AS T1Id FROM #T2 CROSS JOIN Table1 where IdT1Group = 30)
AS tblSource
ON tblTarget.IdT1 = tblSource.T1Id And tblTarget.IdT2 = tblSource.IdT2
WHEN MATCHED THEN
UPDATE
SET Detail = tblSource.Detail
WHEN NOT MATCHED BY SOURCE THEN
DELETE
WHEN NOT MATCHED BY TARGET THEN
INSERT (IdItem, IdT2, Detail)
VALUES (tblSource.IdT1,
tblSource.IdT2, tblSource.Detail);
But after i tried my code, my table2 become:
IdT2 : 1,2,5, 6,7,8, 9,10,11
IdT1 : 11,11,11, 12,12,12, 13,13,13
Detail: A,B,C, A,B,C, A,B,C
This is what should it be:
IdT2 : 1,2,5, 3,4,6, 7,8,9
IdT1 : 11,11,11, 12,12,12, 13,13,13
Detail : A,B,C, A,B,C, A,B,C
I think the output should look like follows because one of the values get deleted. further i think you have a identity column or a incremental value in the Table2 for IdT2.
when you delete an item the increased value will not get deducted.
output
IdT2 IdT1 Detail
----------- ----------- -------
1 11 A
2 11 B
5 11 C
3 12 C
6 12 A
7 12 B
8 13 A
9 13 B
10 13 C
SQL statement (NOTE: Created # tables instead of physical tables to get this going)
create table #Table1
(
IdT1 int , IdT1Group int
)
insert into #Table1
select 11 IdT1,30 IdT1Group union all
select 12 IdT1,30 IdT1Group union all
select 13 IdT1,30 IdT1Group
create table #Table2
(
IdT2 int identity(1,1), IdT1 int , Detail varchar(100)
)
insert into #Table2
SELECT 11 IdT1 ,'A' Detail union all
SELECT 11 ,'B' union all
SELECT 12 ,'A' union all
SELECT 12 ,'B'
declare #T2 table
(
IdT2 int , IdT1 int , Detail varchar(100)
)
insert into #T2
SELECT 1 IdT2 , 11 IdT1 ,'A' Detail union all
SELECT 2 , 11 ,'B' union all
SELECT 3 , 12 ,'C'
MERGE INTO #Table2 AS tblTarget
USING (SELECT T2.*, T1Item.IdT1 AS T1Id FROM #T2 T2 CROSS JOIN #Table1 T1Item where IdT1Group = 30)
AS tblSource
ON tblTarget.IdT1 = tblSource.T1Id And tblTarget.IdT2 = tblSource.IdT2--tblTarget.IdT1 = tblSource.T1Id And tblTarget.IdT2 = tblSource.IdT2
WHEN MATCHED THEN
UPDATE
SET Detail = tblSource.Detail
WHEN NOT MATCHED BY SOURCE THEN
DELETE
WHEN NOT MATCHED BY TARGET THEN
INSERT (IdT1, Detail)
VALUES (tblSource.T1Id,
tblSource.Detail);
select * from #Table2
order by 2,1
drop table #Table1
drop table #Table2

Update table in Sql

Table tblProductStock:
ID Name Qyt
1 X 50
2 Y 40
3 Z 30
Table tblStockMinus:
Id Name Qty
1 X 10
1 X 20
2 Y 30
3 Z 20
I want to update tblProductStock so that it will have:
ID Name Qyt
1 X 20
2 Y 10
3 Z 10
I have tried to use the following SQL request, but it didn't work correctly:
UPDATE ProductStock_Show
SET Quantity = ProductStock_Show.Quantity - ProductStock_Show_Minus.Quantity
FROM ProductStock_Show JOIN ProductStock_Show_Minus
ON ProductStock_Show_Minus.ProductId=ProductStock_Show.ProductId
This will give you required result.
select t1.id, t1.qty-t2.qty as qty from tblProductStock as t1 inner join
(
select id, sum(qty) as qty from tblStockMinus group by id
) as t2 on t1.id=t2.id
You need to update this (but it depends in the RDBMS. This is for MS SQL Server)
Update t
set
t.qty=t3.qty from tblProductStock as t inner join
(
select t1.id, t1.qty-t2.qty as qty from tblProductStock as t1 inner join
(
select id, sum(qty) as qty from tblStockMinus group by id
) as t2 on t1.id=t2.id
) as t3 on t.id=t3.id
DECLARE #Table1 TABLE
( ID int, Name varchar(1), Qyt int)
;
INSERT INTO #Table1
( ID , Name , Qyt )
VALUES
(1, 'X', 50),
(2, 'Y', 40),
(3, 'Z', 30)
;
DECLARE #Table2 TABLE
( Id int, Name varchar(1), Qty int)
;
INSERT INTO #Table2
( Id , Name , Qty )
VALUES
(1, 'X', 10),
(1, 'X', 20),
(2, 'Y', 30),
(3, 'Z', 20)
;
UPDATE #Table1 SET QYT =
t.Qyt - s from (
select
t.id,
t.Name,
t.Qyt ,
SUM(tt.Qty)S
FROM #Table1 t
INNER JOIN #Table2 tt
ON t.Name = tt.Name
GROUP BY t.id,t.Name,t.Qyt)T
SELECT * from #Table1

Replace grouped Id from second table

I have two tables.
Table1
Id, Column1, Column2, Column3
1 1 2 3
2 1 2 3
3 2 1 3
4 2 1 3
5 2 2 2
Table2
Id, Table1_Id
1 1
2 1
3 2
4 2
5 2
6 3
7 4
8 4
9 5
From first table I need remove duplicate rows with same Column1, Column2 and Column3 values and to Table 2 I need replace FKs Table1_Id column with Id after removes duplicate rows from Table1.
I need this
Table1
Id, Column1, Column2, Column3
1 1 2 3
3 2 1 3
5 2 2 2
Table2
Id, Table1_Id
1 1
2 1
3 1
4 1
5 1
6 3
7 3
8 3
9 5
-- Build test data
create table #table1(
id int,
column1 int,
column2 int,
column3 int
)
create table #table2(
id int,
table1_id int
)
insert into #table1
select 1, 1, 2, 3 union all
select 2, 1, 2, 3 union all
select 3, 2, 1, 3 union all
select 4, 2, 1, 3 union all
select 5, 2, 2, 2
insert into #table2
select 1, 1 union all
select 2, 1 union all
select 3, 2 union all
select 4, 2 union all
select 5, 2 union all
select 6, 3 union all
select 7, 4 union all
select 8, 4 union all
select 9, 5
-- update rows of table2
;with cte as(
select
*,
rn = row_number() over(partition by column1, column2, column3 order by id)
from #table1
)
update t2
set t2.table1_id = c.id
from #table2 t2
inner join #table1 t1
on t1.id = t2.table1_id
inner join cte c on
c.column1 = t1.column1
and c.column2 = t1.column2
and c.column3 = t1.column3
and c.rn = 1
-- delete duplicate rows of table1
;with cte as(
select
*,
rn = row_number() over(partition by column1, column2, column3 order by id)
from #table1
)
delete from cte where rn > 1
select * from #table1
select * from #table2
--drop test data
drop table #table1
drop table #table2
-- test tables
declare #t1 table
(Id int, Column1 int, Column2 int, Column3 int)
declare #t2 table
(Id int, Column1 int)
-- data
insert #t1 values
(1,1,2,3),(2,1,2,3),(3,2,1,3),(4,2,1,3),(5,2,2,2)
insert #t2 values (1,1)
,(2,1),(3,2),(4,2),(5,2)
,(6,3),(7,4),(8,4),(9,5)
-- update t2
begin transaction
;with cte as(
select
id,
lowrow_id = min(id) over(partition by column1, column2, column3)
from #t1
)
update t2
set
Column1 = cte.lowrow_id
from
#t2 t2
join cte
on t2.Column1 = cte.id
where cte.lowrow_id < cte.id
-- delete t1
;with cte as(
select
id,
lowrow_id = min(id) over(partition by column1, column2, column3)
from #t1
)
delete cte
where
lowrow_id < id
commit transaction
-- show result
select * from #t1
select * from #t2
Output
Id Column1 Column2 Column3
1 1 2 3
3 2 1 3
5 2 2 2
Id Column1
1 1
2 1
3 1
4 1
5 1
6 3
7 3
8 3
9 5
with tbl1 as (
select ROW_NUMBER() over (partition by column1,column2,column3 order by id) as rownb,min(id) over (partition by column1,column2,column3) as pid,id,column1,column2,column3 from Table1
)
update table2 set Table1_id = tbl1.pid from table2 left join tbl1 on table2.Table1_id = tbl1.id
with tbl1 as (
select ROW_NUMBER() over (partition by column1,column2,column3 order by id) as rownb,min(id) over (partition by column1,column2,column3) as pid,id,column1,column2,column3 from Table1
)
delete from tbl1 where rownb > 1

How to select row based on existance of value in other column

I realise the title to this question may be vague but I am not sure how to phrase it. I have the following table:
i_id option p_id
---- ------ ----
1 A 4
1 B 8
1 C 6
2 B 3
2 C 5
3 A 7
3 B 3
4 E 11
How do I select a row based on the value of the option column for each unique i_id: if 'C' exists, select the row, else select row with 'B' else with 'A' so that result set is:
i_id option p_id
---- ------ ----
1 C 6
2 C 5
3 B 3
select i_id, option, p_id
from (
select
i_id,
option,
p_id,
row_number() over (partition by i_id order by case option when 'C' then 0 when 'B' then 1 when 'A' then 2 end) takeme
from thetable
where option in ('A', 'B', 'C')
) foo
where takeme = 1
This will give you the values ordered by C, B, A, while removing any i_id record that does not have one of these values.
WITH ranked AS
(
SELECT i_id, [option], p_id
, ROW_NUMBER() OVER (PARTITION BY i_id ORDER BY CASE [option]
WHEN 'C' THEN 1
WHEN 'B' THEN 2
WHEN 'A' THEN 3
ELSE 4
END) AS rowNumber
FROM yourTable
WHERE [option] IN ('A', 'B', 'C')
)
SELECT r.i_id, r.[option], r.p_id
FROM ranked AS r
WHERE r.rowNumber = 1
create table t2 (
id int,
options varchar(1),
pid int
)
insert into t2 values(1, 'A', 4)
insert into t2 values(1, 'B', 8)
insert into t2 values(1, 'C', 6)
insert into t2 values(1, 'E', 7)
select t2.* from t2,
(select id, MAX(options) as op from t2
where options <> 'E'
group by id) t
where t2.id = t.id and t2.options = t.op
Well, I would suggest that this problem can be made easier if you can assign a numeric "score" to each letter, such that "better" letters have higher scores. Then you can use MAX to find, for each group, the row with the highest "score" for the option. Since 'A' < 'B' < 'C', we could cheat here and use option as the score, and thus:
SELECT t1.i_id, t1.option, t1.p_id
FROM thetable t1
INNER JOIN (SELECT t2.i_id, MAX(option)
FROM thetable t2
GROUP BY t2.i_id) AS maximums
ON t1.i_id = maximums.i_id
WHERE option != 'D'
This assumes that {i_id, option} is a natural key of the table (i.e., that no two rows will have the same combination of values for those two columns; or, alternatively, that you have an uniqueness constraint on that pair of columns).

Consolidating subsets in a table

I have a table in SqlServer 2008 with data of the form
UserID StartWeek EndWeek Type
1 1 3 A
1 4 5 A
1 6 10 A
1 11 13 B
1 14 16 A
2 1 5 A
2 6 9 A
2 10 16 B
I'd like to consolidate/condense the adjacent types so that the resulting table looks like this.
UserID StartWeek EndWeek Type
1 1 10 A
1 11 13 B
1 14 16 A
2 1 9 A
2 10 16 B
Does anyone have any suggestions as to the best way to accomplish this? I've been looking at using Row_number and Partition, but I can't get it to behave exactly as I'd like.
There's probably a neater way to do it, but this produces the correct result
DECLARE #t TABLE
(UserId TINYINT
,StartWeek TINYINT
,EndWeek TINYINT
,TYPE CHAR(1)
)
INSERT #t
SELECT 1,1,3,'A'
UNION SELECT 1,4,5,'A'
UNION SELECT 1,6,10,'A'
UNION SELECT 1,11,13,'B'
UNION SELECT 1,14,16,'A'
UNION SELECT 2,1,5,'A'
UNION SELECT 2,6,9,'A'
UNION SELECT 2,10,16,'B'
;WITH srcCTE
AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY t1.UserID, t1.Type
ORDER BY t1.EndWeek
) AS rn
FROM #t AS t1
)
,recCTE
AS
(
SELECT *
,0 AS grp
FROM srcCTE
WHERE rn = 1
UNION ALL
SELECT s.UserId
,s.StartWeek
,s.EndWeek
,s.TYPE
,s.rn
,CASE WHEN s.StartWeek - 1 = r.EndWeek
THEN r.grp
ELSE r.grp+ 1
END AS GRP
FROM srcCTE AS s
JOIN recCTE AS r
ON r.UserId = s.UserId
AND r.TYPE = s.TYPE
AND r.rn = s.rn - 1
)
SELECT UserId
,MIN(StartWeek) AS StartWeek
,MAX(EndWeek) AS EndWeek
,TYPE
FROM recCTE AS s1
GROUP BY UserId
,TYPE
,grp
Also using a CTE, but in a slightly different way
DECLARE #Consolidate TABLE (
UserID INTEGER, StartWeek INTEGER,
EndWeek INTEGER, Type CHAR(1))
INSERT INTO #Consolidate VALUES (1, 1, 3, 'A')
INSERT INTO #Consolidate VALUES (1, 4, 5, 'A')
INSERT INTO #Consolidate VALUES (1, 6, 10, 'A')
INSERT INTO #Consolidate VALUES (1, 14, 16, 'A')
INSERT INTO #Consolidate VALUES (1, 11, 13, 'B')
INSERT INTO #Consolidate VALUES (2, 1, 5, 'A')
INSERT INTO #Consolidate VALUES (2, 6, 9, 'A')
INSERT INTO #Consolidate VALUES (2, 10, 16, 'B')
;WITH ConsolidateCTE AS
(
SELECT UserID, StartWeek, EndWeek, Type
FROM #Consolidate
UNION ALL
SELECT cte.UserID, cte.StartWeek, c.EndWeek, c.Type
FROM ConsolidateCTE cte
INNER JOIN #Consolidate c ON
c.UserID = cte.UserID
AND c.StartWeek = cte.EndWeek + 1
AND c.Type = cte.Type
)
SELECT UserID, [StartWeek] = MIN(Startweek), EndWeek, Type
FROM (
SELECT UserID, Startweek, [EndWeek] = MAX(EndWeek), Type
FROM ConsolidateCTE
GROUP BY UserID, StartWeek, Type
) c
GROUP BY UserID, EndWeek, Type
ORDER BY 1, 2, 3