SQL: a precise flip of row data into column - sql

I am wondering if there is an elegant refactoring for the bottom 3 'create table' segments below, which retrieves the same result.
I am hoping to generalise for a far bigger dataset.
Many thanks in advance.
// These top two lines for viewing in SQLite:
.mode column
.headers on
Create table Data_Table (
Other varchar(255),
Id varchar(255),
Value int );
INSERT INTO Data_Table (other, id, value )
VALUES ('x','a', 1);
INSERT INTO Data_Table (other,id, value )
VALUES ('y','a', 2);
INSERT INTO Data_Table (other,id, value )
VALUES ('x','b', 2);
INSERT INTO Data_Table (other,id, value )
VALUES ('y','b', 3);
Create table SubTable_A as
select t1.other as other, t1.value as a
from Data_table as t1
where t1.id = 'a';
Create table SubTable_B as
select t2.value as b
from Data_table as t2
where t2.id = 'b';
Create table Soln_Table as
select t1.*, t2.*
from SubTable_A as t1, SubTable_B as t2
where t1.rowid = t2.rowid;
Bottom line, we now have this data set
other a b
---------- ---------- ----------
x 1 2
y 2 3

You can use conditional aggregation:
select t.other,
max(case when t.id = 'a' then value end) as a,
max(case when t.id = 'b' then value end) as b
from Data_Table t
group by t.other;

Related

INSERT with VALUES and subquery, postgres

I need to insert several rows at once, and values for some columns should be taken from ‘VALUES’, and for some columns, I would need to use a subquery.
Assuming that subquery will return exactly the same amount of rows as I’m trying to insert. Something like this:
WITH subq AS (
--returns 3 rows
SELECT param FROM tbl2 WHERE status = 1
)
INSERT INTO tbl1 (col1, col2, col3)
VALUES
('col1_val1', 'col2_val1', subq.row1.param),
('col1_val2', 'col2_val2', subq.row2.param),
('col1_val3', 'col2_val3', subq.row3.param)
You can add the text to the CTE and make a INSERT INTO SELECT
WITH CTE as
(
(SELECT 'col1_val1', 'col2_val1', param FROM tbl2 WHERE status = 1 ORDER By params LIMIT 1 OFFSET 0)
UNION ALL
(SELECT 'col1_val2', 'col2_val2', param FROM tbl2 WHERE status = 1 ORDER By params LIMIT 1 OFFSET 1)
UNION ALL
(SELECT 'col1_val3', 'col2_val3', param FROM tbl2 WHERE status = 1 ORDER By params LIMIT 1 OFFSET 2))
INSERT INTO tbl1 (col1, col2, col3)
SELECT * FROM CTE

Way to randomly update a set of values using another table as a collection of random values without using a loop?

I have a table with a column that contains some values. I want to replace all the existing values with random values from another table (but only the existing ones - so WHERE COL1 IS NOT NULL). There is no way to correlate the two tables. Each of the random values needs to be different (well... unless they are randomly the same, in which case it's fine).
For example:
CREATE TABLE T1 (ID NUMBER(11,0), COL1 VARCHAR2(20));
CREATE TABLE T2 (COL2 VARCHAR2(20));
INSERT INTO T1 VALUES (1, NULL);
INSERT INTO T1 VALUES (54, NULL);
INSERT INTO T1 VALUES (941, 'Some text');
INSERT INTO T1 VALUES (251, NULL);
INSERT INTO T1 VALUES (352, 'Some other text');
INSERT INTO T1 VALUES (354, NULL);
INSERT INTO T2 VALUES ('Val1');
INSERT INTO T2 VALUES ('Val2');
INSERT INTO T2 VALUES ('Val3');
INSERT INTO T2 VALUES ('Val4');
INSERT INTO T2 VALUES ('Val5');
INSERT INTO T2 VALUES ('Val6');
INSERT INTO T2 VALUES ('Val7');
I have tried a few things and have searched this site for answers. The answers I've found seem to require that there is some correlation between the two tables. A lot of the examples are for SQL Server. I've tried a few out anyway, but I can't seem to get a MERGE or CROSS APPLY approach to work (I appreciate this is most likely my failure...).
The only solution I have that actually works at the moment is the following:
BEGIN
FOR X IN (
SELECT ID FROM T1 WHERE COL1 IS NOT NULL
)
LOOP
UPDATE T1 SET COL1 = (
SELECT COL2 FROM (
SELECT COL2 FROM T2 ORDER BY SYS_GUID()
) WHERE ROWNUM = 1
)
WHERE T1.ID = X.ID;
END LOOP;
END;
/
This produces a (desired) result of:
SELECT * FROM T1;
ID COL1
---------------------------
1
54
941 Val3
251
352 Val7
354
(COL1 values are both random each time I run the loop).
... But I know there must be a set-based way to achieve this... right?
You could do it with a MERGE statement.
merge into t1
using ( select a1.id
, a2.col2
from ( select id
, row_number() over (order by dbms_random.value) rn
from t1
where col1 is not null ) a1
join ( select col2
, row_number() over (order by dbms_random.value) rn
from t2) a2
on a1.rn = a2.rn
) q
on ( q.id = t1.id)
when matched then
update set t1.col1 = q.col2
/
The USING query is a little unorthodox. There are two subqueries, one for each table, which generate analytic row_number() in a random order (this is tidier than using rownum. The two subqueries are joined on the random row numbers which gives a random combination of T1.ID and T2.COL_2. After that, it's a straightforward MERGE.
You can create a FUNCTION named GET_RANDOM_VALUE
create or replace FUNCTION GET_RANDOM_VALUE
RETURN VARCHAR2 AS
sValue VARCHAR2(100) := '?';
BEGIN
select col2 INTO sValue
from t2
order by dbms_random.value
fetch first 1 rows only;
RETURN sValue;
END GET_RANDOM_VALUE;
and the UPDATE command can be
UPDATE t1
SET col1 = GET_RANDOM_VALUE()
WHERE col1 IS NOT NULL;
Normally, I avoid using SELECT in function, but in this case, I found this solution more readable and more easy to understand.

Overwrite ID values using UPDATE statement from SELECT in another table SQL Server 2008

I have two tables that have exactly 20 rows in them. I want to overwrite Table2's ID values with the ID values from Table1 so that I can do tests with JOIN queries. How would I go about overwriting the values?
I have found posts instructing how to do an UPDATE using a SELECT statement, however it requires the data to be joined on a column, in my case no columns match.
UPDATE Table2
SET Table2.ID = Table1.ID
FROM Table1
The query above overwrites all ID columns in Table2 with the value of the first ID column in Table1.
For fun (yes I have a warped sense of humour for a Friday night!), here is a query that does it! - I've declare the tables and the top and only used 6 rows in each table, but you'll get the idea:
--Setup test data
declare #table1 table (ID int, Name varchar(10))
declare #table2 table (ID int, Name varchar(10))
insert #table1
select ID = 1, Name = 'Item1'
union select ID = 2, Name = 'Item2'
union select ID = 3, Name = 'Item3'
union select ID = 4, Name = 'Item4'
union select ID = 5, Name = 'Item5'
union select ID = 6, Name = 'Item6'
insert #table2
select ID = 11, Name = 'Item11'
union select ID = 12, Name = 'Item12'
union select ID = 13, Name = 'Item13'
union select ID = 14, Name = 'Item14'
union select ID = 15, Name = 'Item15'
union select ID = 16, Name = 'Item16'
--Do the update
update t1
set
ID = t2.ID
from
#table1 t1 --Assign a row number to each row of table 1
cross apply (select rownum = COUNT(1) from #table1 sub where sub.ID <= t1.ID) x1,
#table2 t2 --Assign a row number to each row of table 2
cross apply (select rownum = COUNT(1) from #table2 sub where sub.ID <= t2.ID) x2
where x1.rownum = x2.rownum --Match the row numbers
Update:
Alternative Update command based on suggestion by #sllev:
update t1
set
ID = t2.ID
from
(select id, rownum = ROW_NUMBER() OVER(order by ID) from #table1) t1
join (select id, rownum = ROW_NUMBER() over (order by id) from #table2) t2
on t1.rownum = t2.rownum
And another solution, this time using CTE's. Table #First gets the id's from table #Second:
CREATE TABLE #First ( ID INT NOT NULL )
INSERT INTO #First VALUES (1), (2), (3), (4), (5)
CREATE TABLE #Second ( ID INT NOT NULL )
INSERT INTO #Second VALUES (6), (7), (8), (9), (10)
GO
WITH first AS (SELECT ID, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum FROM #First)
, second AS (SELECT ID, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum FROM #Second)
UPDATE #First
SET ID = s.ID
FROM second s
JOIN first f ON s.RowNum = f.RowNum
WHERE #First.ID = f.ID
SELECT * FROM #First
DROP TABLE #First;
DROP TABLE #Second;
Since you only have 20 rows in them, I would manually perform the update by typing in your changes. Especially since the records do not correlate to each other as they typically should for an update with a select.
As stated in the comments, if this is a one-off for testing the easiest option for just 20 rows would be to open Table2 in SSMS and type the ID values in.

How to create a view which merges two tables?

I have two tables which have the exact same structure. Both tables can store the same data with different primary keys (autoincremented integers). Therefore, there is a third table which lists which two primary keys list the same data. However, there also exist rows which don't exist in the other. Therefore, a simple join won't work since you will have two rows with the same primary key but different data. Therefore, is there a way of reassigning primary keys to unused values in the view?
Table1
ID name
1 Adam
2 Mark
3 David
4 Jeremy
Table2
ID name
1 Jessica
2 Jeremy
3 David
4 Mark
Table3
T1ID T2ID
2 4
3 3
4 2
I am looking for a result table like the following:
Result
ID name
1 Adam
2 Mark
3 David
4 Jeremy
5 Jessica
The real heart of the question is how i can assign the temporary fake id of 5 to Jessica and not just some random number. The rule I want for the ids is that if the row exists in the first table, then use its own id. Otherwise, use the next id that an insert statement would have generated (the column is on autoincrement).
Answer to edited question
select id, name from table1
union all
select X.offset + row_number() over (order by id), name
from (select MAX(id) offset from table1) X
cross join table2
where not exists (select * from table3 where t2id = table2.id)
The MAX(id) is used to "predict" the next identity that would occur if you merged the data from the 2nd table into the first. If Table3.T2ID exists at all, it means that it is already included in table1.
Using the test data below
create table table1 (id int identity, name varchar(10))
insert table1 select 'Adam' union all
select 'Mark' union all
select 'David' union all
select 'Jeremy'
create table table2 (id int identity, name varchar(10))
insert table2 select 'Jessica' union all
select 'Jeremy' union all
select 'David' union all
select 'Mark'
create table table3 (t1id int, t2id int)
insert table3 select 2,4 union all
select 3,3 union all
select 4,2
Answer to original question below
So the 3rd table is the one you want to build (a view instead of a table)?
select newid=row_number() over (order by pk_id), *
from
(
select a.*
from tblfirst a
UNION ALL
select b.*
from tblsecond b
) X
The data will contain a unique newid value for each record, whether from first or second table. Change pk_id to your primary key column name.
Assuming you have below data, as I understand reading your question:
Table: T1
ID name
--------
1 a
2 b
3 c
Table: T2
ID name
--------
2 b
3 c
4 d
Table: Rel
ID1 ID2
--------
2 2
3 3
T1 has some data which is not in T2 and vice versa.
Following query will give all data unioned
SELECT ROW_NUMBER() OVER (order by name) ID, Col
from
(
SELECT ISNULL(T1.name,'') name
FROM T1 t1 LEFT JOIN Rel TR ON TR.ID1 = T1.ID
union
SELECT ISNULL(T2.name,'') name
FROM T2 t2 LEFT JOIN Rel TR ON TR.ID2 = T2.ID
) T
If I understand you correct, following might work
Select everything from your first table
Select everything from your second table that is not linked to your third table
Combine the results
Test data
DECLARE #Table1 TABLE (ID INTEGER IDENTITY(1, 1), Value VARCHAR(32))
DECLARE #Table2 TABLE (ID INTEGER IDENTITY(1, 1), Value VARCHAR(32))
DECLARE #Table3 TABLE (T1ID INTEGER, T2ID INTEGER)
INSERT INTO #Table1 VALUES ('Adam')
INSERT INTO #Table1 VALUES ('Mark')
INSERT INTO #Table1 VALUES ('David')
INSERT INTO #Table1 VALUES ('Jeremy')
INSERT INTO #Table2 VALUES ('Jessica')
INSERT INTO #Table2 VALUES ('Jeremy')
INSERT INTO #Table2 VALUES ('David')
INSERT INTO #Table2 VALUES ('Mark')
INSERT INTO #Table3 VALUES (2, 4)
INSERT INTO #Table3 VALUES (3, 3)
INSERT INTO #Table3 VALUES (4, 2)
SQL Statement
SELECT ROW_NUMBER() OVER (ORDER BY ID), t1.Value
FROM #Table1 t1
UNION ALL
SELECT ROW_NUMBER() OVER (ORDER BY ID) + offset, t2.Value
FROM #Table2 t2
LEFT OUTER JOIN #Table3 t3 ON t3.T2ID = t2.ID
CROSS APPLY (
SELECT Offset = COUNT(*)
FROM #Table1
) offset
WHERE t3.T2ID IS NULL

Interesting SQL issue

I have a SQL problem I am trying to digest. I am using SQL Server 2005.
In a table I have data as such:
ID Type
1 A
2 A
3 A
3 B
4 B
I need to find all of the IDs that have a Type of both A and B.
Use the INTERSECT operator:
SELECT DISTINCT ID FROM [Table] WHERE Type = 'A'
INTERSECT
SELECT DISTINCT ID FROM [Table] WHERE Type = 'B'
select distinct a.id
from table a
join table b on a.id=b.id
where a.type='A'
and b.type='B';
With a semi-join (no sorting, only index seek on B):
select a.id from table a
where a.type = 'A'
and exists (select * from table b where a.id = b.id and b.type = 'B')
If you want to abstract the problem a little bit and find cases where rows with the same id contain different values in the type column, you can check for <> like this:
DECLARE #TestTable TABLE (thisid int, thisval varchar(1))
INSERT INTO #TestTable VALUES (1, 'A')
INSERT INTO #TestTable VALUES (2, 'A')
INSERT INTO #TestTable VALUES (3, 'A')
INSERT INTO #TestTable VALUES (3, 'B')
INSERT INTO #TestTable VALUES (4, 'B')
SELECT DISTINCT thisid
FROM #TestTable a
WHERE EXISTS
( SELECT *
FROM #TestTable b
WHERE a.thisid=b.thisid AND a.thisval<>b.thisval)
-- www.caliberwebgroup.com
This returns:
3
select id, count(type = 'A') as a_count, count(type = 'B') as b_count
from your_table
group by 1
having a_count > 0 and b_count > 0;
At least, this works in sane SQL environments. Dunno if it works in yours.
I was not looking at other answers, but still posting. lol
SELECT distinct t1.ID
FROM table1 AS t1
WHERE exists
(select t2.ID from table1 t2 where t2.type="A" and t2.ID=t1.ID)
and exists
(select t3.ID from table1 t3 where t3.type="B" and t3.ID=t1.ID);
SELECT Id FROM tableX AS x, tableX AS y
WHERE x.id = y.id AND x.type = 'A' AND y.type = 'B'
This is very simple
Declare #t table([ID] INT, [Type] VARCHAR(2))
INSERT INTO #t SELECT 1, 'A' UNION ALL SELECT 2,'A' UNION ALL SELECT 3,'A'
UNION ALL SELECT 3,'B' UNION ALL SELECT 4,'B' UNION ALL SELECT 5,'A' UNION ALL SELECT 5,'A'
;WITH CTE AS
(
SELECT Rn = Row_NUMBER() OVER(PARTITION BY [ID],[TYPE] ORDER BY [ID])
,*
FROM #t
)
SELECT ID
FROM CTE
WHERE Rn =1 AND ([Type]='A' or [Type]='B')
GROUP BY [ID]
HAVING (COUNT([ID])>1)
Output:
id
3
this would help if there are "unknown" amounts of types and you want to find all IDs which have all of types
select id from yourtable group by id having count(*)=(select count(distinct type) from yourtable)
select id
from idtypedata
group by id
having
sum(
case type
when 'A' then 1
when 'B' then 2
-- when 'C' then 4
-- when 'D' then 8
end
) & 1 = 1
And
sum(
case type
when 'A' then 1
when 'B' then 2
-- when 'C' then 4
-- when 'D' then 8
end
) & 2 = 2