SQL Postgres union data with missed values - sql

I have two results of queries:
id | col1 | col2 | col3
1 1 null 3j
2 2 12 35
3 null 32 31
4 null 43 33
5 null 44 4
id | col1 | col2 | col3
6 1 null 3j
7 2 null 35
8 3 null 31
9 4 null 33
10 5 null null
I need to do union:
id | col1 | col2 | col3
6 1 null 3j
7 2 12 35
8 3 32 31
9 4 43 33
10 5 null null
5 null 44 4
The problem is some values are missing
I wrote this big sql query to solve this problem:
select *
from (
select max(id) as id,
max(col1) as col1,
max(col2) as col2,
max(col3) as col3
from (
select max(id) as id,
max(col1) as col1,
max(col2) as col2,
max(col3) as col3
from (
select max(id) as id,
max(col1) as col1,
max(col2) as col2,
max(col3) as col3
from (
select *
from t1
where id = 1
union
select *
from t2
where id = 2
) t
group by case
when col1 is null
or
length(col1) =
0 then id
else col1 end
) t1
group by case
when col2 is null
or length(col2) = 0
then id
else col2 end
) t2
group by case
when col3 is null
or length(col3) = 0 then id
else col3 end
) t3
may be are there some ideas to simplify it? Or are there other approaches to enrich data efficiently, because I also need to do intersection, right, left, inner union and I don't want to build so monsters queries

well you cat try something like this:
union
select max(col1),
max(col2),
max(col3)
from t1
where id = 1
or id = 2
group by coalesce(nullif(col1, ''),
nullif(col2, ''),
nullif(col3, ''));
upd:
outer union
select max(col1),
max(col2),
max(col3)
from t1
where id = 1
or id = 2
group by coalesce(nullif(col1, ''),
nullif(col2, ''),
nullif(col3, ''))
having count = 1;
inner union
select max(col1),
max(col2),
max(col3)
from t1
where id = 1
or id = 2
group by coalesce(nullif(col1, ''),
nullif(col2, ''),
nullif(col3, ''))
having count > 1;
left and right are outer intersect with common query with 'where'

Related

How to create a crosstab / coefficient table where columns and rows are the same in SQL / Snowflake?

I have a table like
col1 | col2 | col3 | col4 | col5
id1 | 1 0 0 1 0
id2 | 1 1 0 0 0
id3 | 0 1 0 1 0
id4 | 0 0 1 0 1
id5 | 1 0 1 0 0
id6 | 0 0 0 1 0
.
.
.
idN
How would I create a query such that I get a table like
col1 | col2 | col3 | col4 | col5
col1 | 3 1 1 1 0
col2 | 1 2 0 1 0
col3 | 1 1 2 0 1
col4 | 1 1 1 2 0
col5 | 0 0 1 0 1
where each entry in the result is the number of times that some value of 1 in one column occurred with another column that had a value of 1.
I can get the diagonal values by doing the following:
SELECT
sum(col1), sum(col2), sum(col3), sum(col4), sum(col5)
FROM (
SELECT
col1, col2, col3, col4, col5, col1 + col2 + col3 + col4 + col5 ) AS total
FROM (
SELECT
ROW_NUMBER()OVER(PARTITION BY id ORDER BY date) row_num, *
FROM (
SELECT DISTINCT(id), date, col1, col2, col3, col4, col5
FROM db.schema.table)
)
WHERE row_num = 1 AND total <= 1
ORDER BY total DESC);
I assume that I have to do some kind of pivot or various union all's but I can't seem to figure it out.
You can solve it with a union of 5 selects and 25 case statements - 5 case statements in each select. It's quite an ugly solution I have to admit and will work only if you have a constant number of columns, but it will definitely do the job.
As you do not know the exact number of columns the idea ios to unpivot firs, manipulate and them pivot back. This should work:
-- identify table columns
with table_columns_list as (
select column_name, ordinal_position
from information_schema.columns
where table_schema like 'schema' and table_name like 'table'
),
-- unpivot the table and add row id
flat_table as (
select * from ( select * , row_number() as row_id from my_table)
unpivot(value for column_name in (select column_name from table_columns_list)
),
-- calculate all matrix values
full_flat_table as (
select a.row_id as row_id , a.column_name as a_column_name, b.column_name as
b_column_name, min(a.value,b.value) as value
from flat_table as a inner join flat_table as b on a.row_id=b.row_id
)
select *
from full_flat_table
pivot(sum(value) for a_column_name in (select column_name from
table_columns_list))
as p
order by b_column_name;

Can I change column order in SQL table based on a value that appears in different columns?

I have a table that looks like this:
Column1 | Column2 | Column3| Column4
4 | 3 | 2 | 1
2 | 1
3 | 2 | 1
I want to flip the columns so that 1 always start in column 1 and then the rest of the values follow to the right. Like this:
Column1 | Column2 | Column3 | Column4
1 | 2 | 3 | 4
1 | 2
1 | 2 | 3
This is an example table. The real table is a hierarchy of a company so 1 = CEO and 2 = SVP for example. 1 is always the same name but as the number gets higher (lower in chain of command) the more names that are in that level. I'm hoping for an automated solution that looks for 1, makes that the first column and then populates the columns. I am struggling because the value that 1 represents is in different columns so I can't just change the order of the columns.
I was able to accomplish this using VBA but I would prefer to keep it in SQL.
I don't have any useful code that I have tried so far.
You can use Case expression:
WITH CTE1 AS
(SELECT 4 AS COL1, 3 AS COL2 , 2 AS COL3, 1 AS COL4 FROM DUAL
UNION ALL
SELECT 2, 1, NULL, NULL FROM DUAL
UNION ALL
SELECT 3, 2, 1, NULL FROM DUAL
)
SELECT CASE WHEN COL1 <> 1 THEN 1 ELSE COL1 END AS COL1,
CASE WHEN COL2 <> 2 THEN 2 ELSE COL2 END AS COL2,
CASE WHEN COL3 <> 3 THEN 3 ELSE COL3 END AS COL3,
CASE WHEN COL4 <> 4 THEN 4 ELSE COL4 END AS COL4
FROM CTE1;
You can apply some CASEes checking all possibilities, this is assuming NULLs for missing data:
COALESCE(col4,col3,col2,col1) AS c1,
CASE
WHEN col4 IS NOT NULL THEN col3
WHEN col3 IS NOT NULL THEN col2
WHEN col2 IS NOT NULL THEN col1
END AS c2,
CASE
WHEN col4 IS NOT NULL THEN col2
WHEN col3 IS NOT NULL THEN col1
END AS c3,
CASE
WHEN col4 IS NOT NULL THEN col1
END AS c4
You want to sort the values. A generic SQL solution would use:
select max(case when seqnum = 1 then col end) as col1,
max(case when seqnum = 2 then col end) as col2,
max(case when seqnum = 3 then col end) as col3,
max(case when seqnum = 4 then col end) as col4
from (select col1, col2, col3, col4, col,
row_number() over (order by col) as seqnum
from ((select col1 as col, 1 as which, col1, col2, col3, col4 from t) union all
(select col2 as col, 2 as which, col1, col2, col3, col4 from t) union all
(select col3 as col, 3 as which, col1, col2, col3, col4 from t) union all
(select col4 as col, 4 as which, col1, col2, col3, col4 from t)
) t
where col is not null
) t
group by col1, col2, col3, col4;
This would be simpler in a database that supports lateral joins. And a unique id on each row would also help.

Count per category

have a table as below -
COL1 | COL2 | COL3
1 1 1
1 1 2
1 2 0
1 2 1
2 3 1
2 3 2
2 4 0
2 4 1
3 1 0
3 2 0
.
.
.
I want to select COL1 where all COL2 have sum(COL3) is > 0. If I am sure there are 20 distinct values in COL2, Then how can i pull all COL1 values that have all 20 COL2 filled with COL3 > 0. So the end result should be
COL1 | COL2 | COL3
1 1 3
1 2 1
2 3 3
2 4 1
I have tried a lot of ways to do this but no success.
Just use group by and having.
select col1,col2,sum(col3)
from tbl
group by col1,col2
having sum(col3)>0
select t1.*
from yourTable t1
inner join
(
select t.col1
from
(
select col1, col2, sum(col3) as col_sum
from yourTable
group by col1, col2
) t
group by t.col1
having sum(case when t.col_sum = 0 then 1 else 0 end) = 0
) t2
on t1.col1 = t2.col1
I use a CTE and a Group by with a where condition
;WITH CTE as (
select COL1,COL2,SUM(COL3) as COL3 FROM table1
Group By
COL1,COL2
)
select * from CTE
where COL3>0
Just group col2 and check if it's bigger then 0
select col1,col2,sum(col3)
from tbl
group by col2
having sum(col3)>0
http://sqlfiddle.com/#!9/537f8c/1
See if the below gives you the result that you are after. It is selecting the col1, col2 and a sum of col3 from a derived(?) table that is excluding the col3's that are 0:
select col1, col2, sum(col3)
from
(
select col1, col2, col3 from tbl where col3 <> 0
) as ds
group by col3

divide one record into multiple ones

I need a query to change this:
col1 col2 col3
1 2 abc
3 4 cd
4 5 null
To this
col1 col2 col3
1 2 a
1 2 b
1 2 c
3 4 c
3 4 d
4 5 NULL
Thank you.
Try it like this
EDIT shorter syntax for the running numbers
EDIT2 JamieD77's comment to include the TOP into the creation of the numbers
CREATE FUNCTION dbo.SingleChars(#SomeText NVARCHAR(MAX))
RETURNS TABLE
AS
RETURN
WITH nr10 AS
(
SELECT * FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS tbl(A)
)
,RunningNumbers AS
(
SELECT TOP (ISNULL(LEN(#SomeText),0)) ROW_NUMBER() OVER(ORDER BY (SELECT(NULL))) AS Nmbr FROM nr10,nr10 AS a,nr10 AS b,nr10 AS c,nr10 AS d,nr10 AS e,nr10 AS f,nr10 AS g
)
SELECT SUBSTRING(#SomeText,Nmbr,1) AS TheCharacter
,ASCII(SUBSTRING(#SomeText,Nmbr,1)) AS ASCII_Code
FROM RunningNumbers;
A test
SELECT * FROM dbo.SingleChars('This is a test');
The result
T 84
h 104
i 105
s 115
32
i 105
s 115
32
a 97
32
t 116
e 101
s 115
t 116
Now your problem
DECLARE #tbl TABLE(col1 INT, col2 INT, col3 VARCHAR(100));
INSERT INTO #tbl VALUES(1,2,'abc'),(3,4,'cd'),(4,5,NULL);
SELECT col1,col2,sc.TheCharacter
FROM #tbl
OUTER APPLY dbo.SingleChars(col3) AS sc;
The result
1 2 a
1 2 b
1 2 c
3 4 c
3 4 d
4 5 NULL
In MSSQL you can use recursive CTE. Just take one left letter on each step:
WITH T1 as
(
SELECT col1,col2,LEFT(col3,1) as col4, SUBSTRING(col3,2,10000) as col3
FROM Table1
UNION ALL
SELECT col1,col2,LEFT(col3,1) as col4, SUBSTRING(col3,2,10000) as col3
FROM T1 WHERE Col3 <> ''
)
SELECT col1,col2,col4 FROM T1 ORDER BY col1,col2;
one more query:
WITH T1 as
(
SELECT col1,col2,LEFT(col3,1) as col4, col3, 2 as nextPoint
FROM Table1
UNION ALL
SELECT col1,col2,SUBSTRING(col3,nextPoint,1) as col4, col3,
nextPoint+1 as nextPoint
FROM T1 WHERE LEN(col3)>=nextPoint
)
SELECT col1,col2,col4 FROM T1 ORDER BY col1,col2;

SQL Query Select first rank 1 row From Multiple ranks/Group

I have following data
Table1
id col1 col2 col3
----------------------------------
1 abc 01/01/2012 -
1 abc 01/01/2012 A
2 abc 01/01/2012 -
2 abc 01/02/2012 -
3 abc 01/02/2012 -
3 xyz 01/01/2012 -
4 abc 01/02/2012 -
4 xyz 01/01/2012 -
4 xyz 01/02/2012 -
following is order to evaluate -
if(col1 is false) then evaluate col2 if(col2 is false) then col3:
Col1 - xyz has first preference from all values in this column
col2 - min date
col3 - not '-' or min(col3)
I want to return only one row for each id, if col1 fails go to col2, if this fails then go to col3 condition.
From above table result should be
id col1 col2 col3
----------------------------------
1 abc 01/01/2012 A
2 abc 01/01/2012 -
3 xyz 01/01/2012 -
4 xyz 01/01/2012 -
I tried using dense rank but it didn't help. I'm not sure how to perform this logic using any available function or sql logic.
for col1 - if more than one row for same code or xyz code then fail
for col2 - if more than one row with same min date then fail
[use this only if col1 condition fails]
You can specify many conditions to order by in your analytic function
SELECT *
FROM (SELECT id,
col1,
col2,
col3,
dense_rank() over (partition by id
order by (case when col1 = 'xyz'
then 1
else 0
end) desc,
col2 asc,
col3 asc) rnk
FROM your_table)
WHERE rnk = 1
I'm assuming that you want dense_rank given that you used the dense_rank tag. You don't talk about how you want to handle ties or whether ties are even possible, so it's not clear from the question itself whether you want to use the rank, dense_rank, or row_number analytic functions. If you are only ever fetching the highest ranking row per id, rank and dense_rank will behave identically and will return multiple rows if there are ties for first place. row_number will always return a single row by arbitrarily breaking the tie. If you want to fetch rows other than the first row per id, then you'll need to think about ties and you'll get different behavior from rank and dense_rank. If two rows are tied for first, dense_rank will assign the third row a rnk of 2 while rank will assign it a rnk of 3.
This seems to work for the sample data you posted
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 1 id, 'abc' col1, to_date('01/01/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
3 select 1 id, 'abc' col1, to_date('01/01/2012', 'MM/DD/YYYY') col2, 'A' col3 from dual union all
4 select 2 id, 'abc' col1, to_date('01/01/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
5 select 2 id, 'abc' col1, to_date('01/02/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
6 select 3 id, 'abc' col1, to_date('01/02/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
7 select 3 id, 'xyz' col1, to_date('01/01/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
8 select 4 id, 'abc' col1, to_date('01/02/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
9 select 4 id, 'xyz' col1, to_date('01/01/2012', 'MM/DD/YYYY') col2, null col3 from dual union all
10 select 4 id, 'xyz' col1, to_date('01/02/2012', 'MM/DD/YYYY') col2, null col3 from dual
11 )
12 SELECT *
13 FROM (SELECT id,
14 col1,
15 col2,
16 col3,
17 dense_rank() over (partition by id
18 order by (case when col1 = 'xyz'
19 then 1
20 else 0
21 end) desc,
22 col2 asc,
23 col3 asc) rnk
24 FROM x)
25* WHERE rnk = 1
SQL> /
ID COL COL2 C RNK
---------- --- --------- - ----------
1 abc 01-JAN-12 A 1
2 abc 01-JAN-12 1
3 xyz 01-JAN-12 1
4 xyz 01-JAN-12 1
with tmp(id, col1, col2, col3, col1b, col3b) as
(select distinct id, col1, col2, col3,
case when col1 = 'xyz' then '0' else '1' || col1 end,
case when col3 = '-' then '1' else '0' || col3 end
from Table1)
select t1.id, t1.col1, t1.col2, t1.col3
from tmp t1
left join tmp t2 on t1.id = t2.id
and t1.col1b > t2.col1b
left join tmp t3 on t1.id = t3.id
and t1.col1b = t3.col1b
and t1.col2 > t3.col2
left join tmp t4 on t1.id = t4.id
and t1.col1b = t4.col1b
and t1.col2 = t4.col2
and t1.col3b > t4.col3b
where t2.id is null
and t3.id is null
and t4.id is null