Full Outer Join self with null values - sql-server

I want to do a full outer self-join that includes nulls. For example, if the table Data looks like:
N Name Val
--------------
1 ABC 8
1 DEF 7
2 ABC 9
2 XYZ 6
(where N is a general index column to enable a self-join on sequential groups) and I do:
SELECT COALESCE(a.n, b.n) as n, COALESCE(a.Name, b.Name) as Name, a.Val as A, b.Val as B
FROM Data a
FULL OUTER JOIN Data b on a.N = b.N - 1 and a.Name = b.Name
I want:
N Name A B
---------------
1 ABC 8 9
1 DEF 7 NULL
1 XYZ NULL 6
but what I get is more like a cross-join:
n Name A B
--------------
1 ABC 8 9
1 DEF 7 NULL
2 ABC 9 NULL
2 XYZ 6 NULL
1 ABC NULL 8
1 DEF NULL 7
2 XYZ NULL 6
How do I perform this full outer join in order to get the condensed self-join results?
(Note: In practice column N is a generalized index, so solutions that require naming the values of N aren't practical.)

So far I've been only able to see doing this as a union. and a left and right join since the criteria of what you're after changes.
SELECT COALESCE(a.Name, b.Name) as Name, a.Val as A, b.Val as B
FROM Data a
LEFT JOIN Data b on a.Name = b.Name
and B.N = 2
WHERE A.N = 1
UNION
SELECT COALESCE(a.Name, b.Name) as Name, a.Val as A, b.Val as B
FROM Data a
RIGHT JOIN Data b on a.Name = b.Name
and A.N = 1
WHERE B.N = 2
Giving us:
+------+---+----+
| NAME | A | B |
+------+---+----+
| ABC | 8 | 9 |
| DEF | 7 | |
| XYZ | | 6 |
+------+---+----+
However this relies on a hardcoded N value which I don't think is that useful... working on better.

Since we want to handle a generalized self-join index column N let's extend the sample set a little further:
create table #Data (n int, name char(3), val int)
insert into #Data values (1, 'ABC',8)
insert into #Data values (1, 'DEF',7)
insert into #Data values (2, 'ABC',9)
insert into #Data values (2, 'XYZ',6)
insert into #Data values (3, 'ABC',9)
insert into #Data values (3, 'DEF',5)
insert into #Data values (3, 'XYZ',4)
For this sample we want the SQL to produce this output:
N Name A B
---------------
1 ABC 8 9
1 DEF 7 NULL
1 XYZ NULL 6
2 ABC 9 9
2 DEF NULL 5
2 XYZ 6 4
The following code works on the general case:
SELECT COALESCE(a.n, b.n-1) as i, COALESCE(a.Name, b.Name) as Name, a.Val as A, b.Val as B
FROM #Data a
FULL OUTER JOIN #Data b ON a.N = b.N - 1 AND a.Name = b.Name
WHERE a.n < (SELECT MAX(n) FROM #Data) -- Deals with end index case
OR (a.n is null AND b.n-1 IN (SELECT DISTINCT n FROM #Data))
ORDER BY COALESCE(a.n, b.n-1), Name
To see why this works, a good intermediate step is to note that when a.N = 1 we want the rows where n = 1 from:
SELECT COALESCE(a.n, b.n - 1) as n, COALESCE(a.Name, b.Name) as Name,
a.Val as A, b.Val as B
FROM #Data a
FULL OUTER JOIN #Data b ON a.N = b.N - 1 AND a.Name = b.Name

Please see the code below:
create table Data (n int, name char(3), val int)
insert into data values (1, 'ABC',8)
insert into data values (1, 'DEF', 7)
insert into data values (2 , 'ABC' , 9)
insert into data values (2 , 'XYZ', 6)
SELECT COALESCE(a.Name, b.Name) as Name, a.Val as A, b.Val as B
FROM Data a
FULL OUTER JOIN Data b on a.N = b.N - 1 and a.Name = b.Name
The output is this:
There are nulls on both sides.

Maybe this:
SELECT [Name]
,[1]
,[2]
FROM [table]
PIVOT
(
MAX([val]) FOR [N] IN ([1], [2])
) PVT;

Related

How To Order Data From Two Columns From Two Different Tables

So I have a column in each table that has a numerical value and I need to order them.
The StepName is the only common value between the two tables
Table1 structure:
StepName | StepOrder
abc 10
ghi 48
jkl 62
Table2 Structure:
StepName | SubStepOrder
abc 1
abc 5
ghi 46
jkl 62
Desired Result:
StepName | Order
abc 10
abc 1
abc 5
ghi 48
ghi 46
jkl 62
jkl 62
I need the step numbers with the substep numbers ordered below them and once there are no more substep numbers we then go to the next step number with it's substepnumbers
If Step 5 had 6 substeps desired result set:
Step 1
Step 2...
...Step 5
SubStep 1...
...SubStep 6
Step 6
You just need to add an additional flag to indicate if each is a step or substep and use in an ordering criteria:
with t as (
select *, 0 substep
from t1
union all
select *, 1
from t2
)
select stepname, steporder
from t
order by stepname,substep,StepOrder
This works if you want to order just by the step order columns and not by the step name.
;WITH d (StepName, StepOrder, SubStepOrder) AS (
SELECT StepName, StepOrder, NULL
FROM t1
UNION ALL
SELECT t2.StepName, t1.StepOrder, t2.SubStepOrder
FROM t2
LEFT JOIN t1 ON t1.StepName = t2.StepName
)
SELECT d.StepName, ISNULL(d.SubStepOrder, d.StepOrder) StepOrder
FROM d
ORDER BY d.StepOrder, d.SubStepOrder
EDIT
Working example:
WITH t1 (StepName, StepOrder) AS (
SELECT 'abc', 10 UNION
SELECT 'ghi', 48 UNION
SELECT 'jkl', 62
), t2 (StepName, SubStepOrder) AS (
SELECT 'abc', 1 UNION
SELECT 'abc', 5 UNION
SELECT 'ghi', 46 UNION
SELECT 'jkl', 62
) , d (StepName, StepOrder, SubStepOrder) AS (
SELECT StepName, StepOrder, NULL
FROM t1
UNION ALL
SELECT t2.StepName, t1.StepOrder, t2.SubStepOrder
FROM t2
LEFT JOIN t1 ON t1.StepName = t2.StepName
)
SELECT d.StepName, ISNULL(d.SubStepOrder, d.StepOrder) StepOrder
FROM d
ORDER BY d.StepOrder, d.SubStepOrder

Get a max record for each unique column value in a table

I have a database table like this
A || B || C
------------------------------------------
1 ABC 10
1 XYZ 5
2 EFG 100
2 LMN 150
2 WER 50
3 ABC 50
3 XYZ 75
Now i want to have a result set like this,where i want to have the max value of column C for each value in column A
A || B || C
-----------------------------------------
1 ABC 10
2 LMN 150
3 XYZ 75
I have tried using distinct and max() but it did not work. like this
select distinct #table.A,#table.B,MAX(#table.C) from #table group by #table.A,#table.B
Is there a simple way to achieve this?
Using MAX() as a window function:
SELECT t.A, t.B, t.C
FROM
(
SELECT A, B, C, MAX(C) OVER (PARTITION BY A) max_C
FROM yourTable
) t
WHERE t.C = t.max_C
If you want to retrieve only a single max record for each group of A values, then you should use the method suggested by #GurV, which is the row number:
SELECT t.A, t.B, t.C
FROM
(
SELECT A, B, C, ROW_NUMBER() OVER (PARTITION BY A ORDER BY C, B DESC) row_num
FROM yourTable
) t
WHERE t.row_num = 1
Note carefully the ORDER BY C, B inside the call to ROW_NUMBER(). This will place max C records at the top of each partition, and will then also order descending by B values. Only one value will be retained though.
If you order by both C and B the combination of both may or may not give you the highest value of Column C. So I feel the below query should work for your specific requirement.
SELECT table.A, table.B, table.C
FROM
(
SELECT A, B, C, ROW_NUMBER() OVER (PARTITION BY A ORDER BY C DESC) row_num
FROM yourTable
) table
WHERE table.row_num = 1
You can use window function to do this:
select * from (select
t.*,
row_number() over (partition by A order by C desc) rn
from your_table t) t where rn = 1;
If those aren't supported, use JOIN:
select t1.*
from your_table t1
inner join (
select A, max(C) C
from your_table
group by A
) t2 on t1.A = t2.A
and t1.C = t2.C;
Just an another way with a simple Join and Group BY
Schema:
SELECT * INTO #TAB1 FROM (
SELECT 1 A, 'ABC' B , 10 C
UNION ALL
SELECT 1 , 'XYZ' , 5
UNION ALL
SELECT 2 , 'EFG' , 100
UNION ALL
SELECT 2 , 'LMN' , 150
UNION ALL
SELECT 2 , 'WER' , 50
UNION ALL
SELECT 3 , 'ABC' , 50
UNION ALL
SELECT 3 , 'XYZ' , 75
)A
Do join to sub query
SELECT C2.A,C1.B, C2.MC
FROM #TAB1 C1
INNER JOIN
(
SELECT A, MAX(C) MC
FROM #TAB1
GROUP BY A
)AS C2 ON C1.A=C2.A AND C1.C= C2.MC
And the result will be
+---+-----+-----+
| A | B | MC |
+---+-----+-----+
| 1 | ABC | 10 |
| 2 | LMN | 150 |
| 3 | XYZ | 75 |
+---+-----+-----+

join by column type

I have 2 tables like the following.
id1 | val | type
1 2 type1
1 4 type2
2 9 type2
2 7 type1
id2|type1|type2
11 2 4
33 7 9
I need result like this
id1|id2
1 11
2 33
I need to check both type1 and type2 to relate id1 and id2. I tried the following query but it does not work.
select id1,id2 from t1 inner join t2 on (type='type1' and
t1.val=t2.type1)and (type='type2' and t1.val=t2.type2)
I believe this should give you what you want:
SELECT
a.id1,
t2.id2
FROM
t2
INNER JOIN
t1 a ON a.val = t2.type1 AND a.type = 'type1'
INNER JOIN
t1 b ON b.val = t2.type2 AND b.type = 'type2'
WHERE
a.id1 = b.id1
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE t1(id1 INT, val INT, [type] VARCHAR(10))
INSERT INTO t1 VALUES
(1 , 2 ,'type1'),
(1 , 4 ,'type2'),
(2 , 9 ,'type2'),
(2 , 7 ,'type1')
CREATE TABLE t2(id2 INT, [type1] VARCHAR(10), [type2] VARCHAR(10))
INSERT INTO t2 VALUES
(11 , 2 , 4),
(33 , 7 , 9)
Query 1:
Select t1.id1 , t2.id2
from t1
inner join
(Select * from t2 t
UNPIVOT (Val for [type] IN ([type1],[type2]))up) t2
ON t1.val = t2.Val and t1.type = t2.type
GROUP BY t1.id1 , t2.id2
Results:
| id1 | id2 |
|-----|-----|
| 1 | 11 |
| 2 | 33 |

Postgresql select

My datatable:
[a] | [b]
----+----
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
3 | 1
What is the correct select for:
SELECT a FROM table WHERE b = 1 AND b = 2 AND b = 3 // Result = 1
SELECT a FROM table WHERE b = 1 AND b = 2 // Result = 2
EDIT:
Thanks this query resolve my problem:
SELECT a FROM table WHERE b IN (1,2,3) AND a IN (SELECT a FROM table GROUP BY a HAVING count(*) = 3) GROUP BY a HAVING count(*) = 3 // Result = 1
SELECT a FROM table WHERE b IN (1,2) AND a IN (SELECT a FROM table GROUP BY a HAVING count(*) = 2) GROUP BY a HAVING count(*) = 2 // Result = 2
Not exactly clear what you're asking, but I think you're looking for EXISTS: http://www.postgresql.org/docs/9.4/static/functions-subquery.html
Depending on other constraints on your data, you may be able to do:
SELECT a FROM "table" WHERE b IN(1,2,3) GROUP BY a HAVING count(*) = 3
Since OP lacks some info:
select a from (
select a,row_number() over(partition by a) rn from foo
where b in (1,2,3) )t
where rn=(select count(a) from foo where a =1) -- you can use `rn` =3 instead of `select count(a) from foo where a =1`
select a from (
select a,row_number() over(partition by a) rn from foo
where b in (1,2) )t
where rn=(select count(a) from foo where a =2)-- you can use `rn` =2 instead of `select count(a) from foo where a =2`

SQL query to find out some of three fields from three different table

I have three tables, tableA (id, A), tableB (id,B) and tableC (id,C). id is unique and primary key. Now I want to run a query on these three tables to find out sum of values of A,B and C for every id. (i.e. if id 1 is present in tableA but not in tableB then value B should be considered as 0 for id 1).example: tableA:
id A
1 5
2 6
3 2
5 7
tableB:
id B
2 5
3 8
4 1
tableC:
id C
5 2
the output should be:
id Sum
1 (5 + 0 + 0 =)5
2 (6 + 5 + 0 =)11
3 (2 + 8 + 0 =)10
4 (0 + 1 + 0 =)1
5 (7 + 0 + 2 =)9
First get a distinct list ( UNION ) of the IDs so that you include all, then LEFT JOIN to add the values together.
Something like
SELECT IDs.ID,
IFNULL(tableA.A,0) + IFNULL(tableB.B,0) + IFNULL(tableC.C,0) SumVal
FROM (
SELECT ID
FROM tableA
UNION
SELECT ID
FROM tableB
UNION
SELECT ID
FROM tableC
) IDs LEFT JOIN
tableA ON IDs.ID = tableA.ID LEFT JOIN
tableB ON IDs.ID = tableB.ID LEFT JOIN
tableC ON IDs.ID = tableC.ID
Something like this should work:
select id, sum(val) from
( select id, "A" as val from "tableA"
union all
select id, "B" as val from "tableB"
union all
select id, "C" as val from "tableC" ) as joined
group by id
order by id
I could not test it with MySql but this works my databases (HSQLDB, Oracle):
select ID, sum(X) from
(SELECT ID, A as X FROM tableA
UNION
SELECT ID, B as X FROM tableB
UNION
SELECT ID, C as X FROM tableC)
group by ID
Not sure about the exact MySQL syntax, but this works in SQL Server:
SELECT ID, SUM(ColToSum) As SumValue FROM
(
SELECT ID, A As ColToSum FROM TableA
UNION ALL
SELECT ID, B As ColToSum FROM TableB
UNION ALL
SELECT ID, C As ColToSum FROM TableC
) Combined
GROUP BY ID
Remember to use "UNION ALL", not just "UNION" which strips out duplicate rows as it combines (see http://dev.mysql.com/doc/refman/5.0/en/union.html)

Resources