Addition with NULL values across multiple columns - sql

Col1 Col2 Col3 SumCol
4 9 NULL 13
NULL 8 2 10
8 3 NULL 11
NULL 5 5 10
I have a table populated with columns Col1, Col2, and Col3, and I am trying to create a new column, SumCol. I know addition with NULL values is annoying so I appreciate any help

you can use below queries in sql-server
select id, col1, col2, col3, (coalesce(col1, 0) + coalesce(col2, 0) + coalesce(col3, 0)) total
from #tbl
OR
select id, col1, col2, col3, (ISNULL(col1, 0) + ISNULL(col2, 0) + ISNULL(col3, 0)) total
from #tbl

It is very simple by using XQuery or COALESCE().
SQL #1
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Col1 INT, Col2 INT, Col3 INT);
INSERT INTO #tbl (Col1, Col2, Col3) VALUES
( 4 , 9, NULL),
(NULL, 8, 2 ),
( 8 , 3, NULL),
(NULL, 5, 5 );
-- DDL and sample data population, end
SELECT ID, Col1, Col2, Col3
, x.value('sum(/root/*/text())', 'INT') AS Summary
FROM #tbl
CROSS APPLY (SELECT Col1, Col2, Col3 FOR XML PATH(''), TYPE, ROOT('root')) AS t(x);
SQL #2
Based on the #DaleK's advice, a most common solution is below.
SELECT *
, Summary = COALESCE(Col1,0) + COALESCE(Col2,0) + COALESCE(Col3,0)
FROM #tbl;
SQL #3
A generic way tailored towards Col1, Col2, ..., ColN scenario.
SELECT ID, Col1, Col2, Col3
, x.value('sum(/root/*[not(local-name()="ID")]/text())', 'INT') AS Summary
FROM #tbl AS p
CROSS APPLY (SELECT * FROM #tbl AS c
WHERE c.ID = p.ID
FOR XML PATH(''), TYPE, ROOT('root')) AS t(x);
Output
+----+------+------+------+---------+
| ID | Col1 | Col2 | Col3 | Summary |
+----+------+------+------+---------+
| 1 | 4 | 9 | NULL | 13 |
| 2 | NULL | 8 | 2 | 10 |
| 3 | 8 | 3 | NULL | 11 |
| 4 | NULL | 5 | 5 | 10 |
+----+------+------+------+---------+

A computed column is often easier, if you want that calculation to always be available to anyone who queries the table:
ALTER TABLE YourTable
ADD COLUMN SumCol AS ISNULL(Col1, 0) + ISNULL(Col2, 0) + ISNULL(Col3, 0);

Related

Row count discrepancy between Intersect and Except queries

I'm getting some strange behaviour using intersect and except. Tb1 has the least rows out of the two tables, and the difference in row count between tb1 and the intersect query results is 143 (intersect = 9782, tb1 = 9925).
But when I run the same query with except, it returns 24 lines. My understanding is that it should have returned 143 rows, being the rows that didn't match in the intersect query. Could someone help me understand why this might be?
There is a possibility that both datasets have multiple duplicate rows (being subset data). Could this be the cause of the difference?
SELECT
amount
,date
FROM tb1
INTERSECT
SELECT
amount
,date
FROM tb2
As you're probably already aware, the difference between UNION and UNION ALL is that the former returns a unique result, while the latter doesn't.
The same can be said about INTERSECT versus INTERSECT ALL.
And also about EXCEPT versus EXCEPT ALL.
So when there are dups, then the totals can be different from what you expect.
Here's a simplified demo to illustrate.
create table TableA (
col1 int not null,
col2 varchar(8)
);
create table TableB (
col1 int not null,
col2 varchar(8)
);
insert into TableA (Col1, Col2) values
(1,'A') -- only A
, (3,'AB') -- 1 in both
, (4,'AAB'), (4,'AAB') -- 2 in A, 1 in B
, (5,'ABB') -- 1 in A, 2 in B
, (6,'AABB'), (6,'AABB') -- 2 in both
, (7, NULL); -- 1 NULL in both
8 rows affected
insert into TableB (Col1, Col2) values
(2,'B') -- only B
, (3,'AB') -- 1 in both
, (4,'AAB') -- 2 in A, 1 in B
, (5,'ABB'), (5,'ABB') -- 1 in A, 2 in B
, (6,'AABB'), (6,'AABB') -- 2 in both
, (7, null); -- 1 NULL in both
8 rows affected
select Col1, Col2 from TableA
intersect
select Col1, Col2 from TableB
order by Col1, Col2
col1 | col2
---: | :---
3 | AB
4 | AAB
5 | ABB
6 | AABB
7 | null
select Col1, Col2 from TableA
intersect all
select Col1, Col2 from TableB
order by Col1, Col2
col1 | col2
---: | :---
3 | AB
4 | AAB
5 | ABB
6 | AABB
6 | AABB
7 | null
select Col1, Col2 from TableA
except
select Col1, Col2 from TableB
order by Col1, Col2
col1 | col2
---: | :---
1 | A
select Col1, Col2 from TableA
except all
select Col1, Col2 from TableB
order by Col1, Col2
col1 | col2
---: | :---
1 | A
4 | AAB
Demo on db<>fiddle here

sql selecting unique rows based on a specific column

I have an table like this :
Col1 Col2 Col3 Col4
asasa 1 d 44
asasa 2 sd 34
asasa 3 f 3
dssd 4 d 2
sdsdsd 5 sd 11
dssd 1 dd 34
xxxsdsds2 d 3
erewer 3 sd 3
I am trying to filter out something like this based on Col1
Col1 Col2 Col3 Col4
asasa 1 d 44
dssd 4 d 2
sdsdsd 5 sd 11
xxxsdsds2 d 3
erewer 3 sd 3
I am trying to get the all unique rows based on the values in Col1. If I have duplicates in Col1, the first row should be taken.
I tried SELECT Col1 FROM tblname GROUP BY Col1 and got unique Col1 but extending it using * is giving me error.
You should be able to achieve your goal using something like the following:
WITH CTE AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Col1 ORDER BY Col2) AS rn FROM MyTable
)
SELECT * FROM CTE WHERE rn = 1
What it does is it creates a CTE (Common Table Expression) that adds a ROW_NUMBER on Col1, ordered by the data in row2.
In the outer select, we then only grab the rows from the CTE where the row number generated is 1.
Try this
;WITH CTE(
SELECT *,
ROW_NUMBER() OVER(PARTITIAN BY Col1 ORDER BY(SELECT NULL))RN
FROM tblname
)
SELECT Col1, Col2, Col3, Col4 FROM CTE;
Depending on the flavor of SQL that you have are using, what may help you are window functions.
In SQL Server, this can be accomplished with the FIRST_VALUE window function like so:
DROP TABLE IF EXISTS #vals;
CREATE TABLE #vals (COL1 VARCHAR(10), COL2 INT, COL3 VARCHAR(5), COL4 INT);
INSERT INTO #vals (COL1, COL2, COL3, COL4)
VALUES ('asasa', 1, 'd', 44),
('asasa', 2, 'sd', 34),
('asasa', 3, 'f', 3),
('dssd' , 4, 'd', 2),
('sdsdsd', 5, 'sd', 11),
('dssd', 1, 'dd', 34),
('xxxsdsds', 2, 'd', 3),
('erewer', 3, 'sd', 3);
SELECT *
FROM #vals
SELECT DISTINCT COL1,
FIRST_VALUE(COL2) OVER (PARTITION BY COL1 ORDER BY Col1) AS Col2,
FIRST_VALUE(COL3) OVER (PARTITION BY COL1 ORDER BY Col1) AS Col3,
FIRST_VALUE(COL4) OVER (PARTITION BY COL1 ORDER BY Col1) AS Col4
FROM #vals AS v1
This returns:
|COL1 | Col2 | Col3 | Col4|
|-----------|-----------|-----------|-------|
|asasa | 1 | d | 44 |
|dssd | 4 | d | 2 |
|erewer | 3 | sd | 3 |
|sdsdsd | 5 | sd | 11 |
|xxxsdsds | 2 | d | 3 |
which may then be ORDERed in whatever way is needed.
Select DISTINCT , should do the trick. Here is a good reference https://www.w3schools.com/sql/sql_distinct.asp

T-SQL sequential updating with two columns

I have a table created by:
CREATE TABLE table1
(
id INT,
multiplier INT,
col1 DECIMAL(10,5)
)
INSERT INTO table1
VALUES (1, 2, 1.53), (2, 3, NULL), (3, 2, NULL),
(4, 2, NULL), (5, 3, NULL), (6, 1, NULL)
Which results in:
id multiplier col1
-----------------------
1 2 1.53000
2 3 NULL
3 2 NULL
4 2 NULL
5 3 NULL
6 1 NULL
I want to add a column col2 which is defined as multiplier * col1, however the next value of col1 then updates to take the previous calculated value of col2.
The resulting table should look like:
id multiplier col1 col2
---------------------------------------
1 2 1.53000 3.06000
2 3 3.06000 9.18000
3 2 9.18000 18.36000
4 2 18.36000 36.72000
5 3 36.72000 110.16000
6 1 110.16000 110.16000
Is this possible using T-SQL? I've tried a few different things such as joining id to id - 1 and have played around with a sequential update using UPDATE and setting variables but I can't get it to work.
A recursive CTE might be the best approach. Assuming your ids have no gaps:
with cte as (
select id, multiplier, convert(float, col1) as col1, convert(float, col1 * multiplier) as col2
from table1
where id = 1
union all
select t1.id, t1.multiplier, cte.col2 as col1, cte.col2 * t1.multiplier
from cte join
table1 t1
on t1.id = cte.id + 1
)
select *
from cte;
Here is a db<>fiddle.
Note that I converted the destination type to float, which is convenient for this sort of operation. You can convert back to decimal if you prefer that.
Basically, this would require an aggregate/window function that computes the product of column values. Such set function does not exists in SQL though. We can work around this with arithmetics:
select
id,
multiplier,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2
from table1
Demo on DB Fiddle:
id | multiplier | col1 | col2
-: | ---------: | -----: | -----:
1 | 2 | 1.53 | 3.06
2 | 3 | 3.06 | 9.18
3 | 2 | 9.18 | 18.36
4 | 2 | 18.36 | 36.72
5 | 3 | 36.72 | 110.16
6 | 1 | 110.16 | 110.16
This will fail if there are negative multipliers.
If you wanted an update statement:
with cte as (
select col1, col2,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1_new,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2_new
from table1
)
update cte set col1 = col1_new, col2 = col2_new

Is there a way to perform operations on groups in SQL?

So I am very new to SQL and am probably not describing what I want to do accurately. I have a table with three columns and I want to group by one column and see what percentage of each group has a certain value in the other column. For example in the table:
id col1 col2
----------------
0 A 1
1 A 2
2 B 2
3 B 2
4 A 1
I would want to group by col1 and see what percentage of each group (A or B) has value 1 in col2. The result I want from this is:
col1 percentage_col2_equals_1
------------------------------
A 66.7
B 0.0
So far I have:
SELECT col1,
((SELECT COUNT(*) FROM my_table
WHERE col2 = 1
GROUP BY col1) /
(SELECT COUNT(*) FROM my_table
GROUP BY col1) * 100)
FROM my_table
GROUP BY col1;
But this does not work. Any help would be appreciated!
use case when
SELECT col1,(coalesce(count(case when col2=1 then col2 end),0)*100.00)/count(*)
from tablename
group by col1
Same answer as everyone, just putting this here due to Postgres' expressiveness :)
Live test: https://www.db-fiddle.com/f/goL488VaPuZYii7Wik3pFk/4
select
col1,
count(*) filter(where col2 = 1) ::numeric / count(*)
from tbl
group by col1;
Output:
| col1 | ?column? |
| ---- | ---------------------- |
| A | 0.66666666666666666667 |
| B | 0.00000000000000000000 |
To present it as percentage with 1 decimal place, multiply it by 100 and round to 1:
Live test: https://www.db-fiddle.com/f/goL488VaPuZYii7Wik3pFk/5
select
col1,
round(
count(*) filter(where col2 = 1) ::numeric / count(*) * 100,
1
) as p_a
from tbl
group by col1;
select
col1,
(
count(*) filter(where col2 = 1) ::numeric / count(*) * 100
)::numeric(100,1) as p_b
from tbl
group by col1;
Output:
| col1 | p_a |
| ---- | ---- |
| A | 66.7 |
| B | 0.0 |
| col1 | p_b |
| ---- | ---- |
| A | 66.7 |
| B | 0.0 |
The following query will return your expected result:
SELECT col1,
CAST(((SUM(IIF(col2 = 1, 1, 0))) * 100.0) / COUNT(*) AS DECIMAL(5, 1)) AS percentage_col2_equals_1
FROM my_table
GROUP BY col1;
Sample execution with sample data:
DECLARE #my_table TABLE (id INT, col1 CHAR(1), col2 INT);
INSERT INTO #my_table (id, col1, col2) VALUES
(0, 'A', 1),
(1, 'A', 2),
(2, 'B', 2),
(3, 'B', 2),
(4, 'A', 1);
SELECT col1, CAST(((SUM(IIF(col2 = 1, 1, 0))) * 100.0) / COUNT(*) AS DECIMAL(5, 1)) AS percentage_col2_equals_1
FROM #my_table
GROUP BY col1;
Output:
col1 percentage_col2_equals_1
---------------------------------
A 66.7
B 0.0
CREATE TABLE #TEMP
(ID INT,
COL1 VARCHAR(10),
COL2 INT
);
INSERT INTO #TEMP
SELECT 0, 'A',1
UNION
SELECT 1, 'A',2
UNION
SELECT 2, 'B',2
UNION
SELECT 3, 'B',2
UNION
SELECT 4, 'A',1;
SELECT T.COL1,
ROUND((CAST(COUNT(CASE
WHEN T.COL2 = 1
THEN T.COL2
ELSE NULL
END) AS DECIMAL) / (S.COL2)) * 100.0, 2) AS Percentage_1
FROM #TEMP T
JOIN
(
SELECT COUNT(COL2) COL2,
COL1
FROM #TEMP
GROUP BY COL1
) S ON S.COL1 = T.COL1
GROUP BY T.COL1,
S.COL2;
this will work:
CREATE TABLE Table1
("id" int, "col1" varchar2(1), "col2" int)
;
//do inserts
select aa."col1",((select count(*) from Table1 b
where b."col1"=aa."col1" and b."col2"=1 )*100/(select count(*)
from Table1 c where c."col1"='A' )) percentge
from Table1 aa
group by aa."col1"
;
output:
A 66.66666666666666666666666666666666666667
B 0
In SQLite the expression col2 = 1 returns 1 when true and 0 when false.
So you just need the average of col2 = 1 and then round it to 1 decimal:
select
col1,
round(100.0 * avg(col2 = 1), 1) percentage_col2_equals_1
from tablename
group by col1
See the demo.
Results:
| col1 | percentage_col2_equals_1 |
| ---- | ------------------------ |
| A | 66.7 |
| B | 0 |

Query For Hierarchy Order In SQL

My Actual Table :
--------------------------------------------------
Col1 | Col2 | Col3 |
--------------------------------------------------
1 A 1
6 F 2
3 C 4
2 B 1
5 E 3
4 D 2
Expected Result :
------------------
|Columns Should Be|
------------------
A B F D C E
I need SQL query for this hierarchical logic.I have to get resultant logic using col 1 and col3 .Kindly help me out for this query.
I've just read your replies, I believe this is what you're after;
CREATE TABLE #TempData (Col1 int, Col2 varchar(1), Col3 int)
INSERT INTO #TempData (Col1, Col2, Col3)
VALUES
('1','A','1')
,('6','F','4')
,('6','C','2')
,('2','B','1')
,('5','E','3')
,('4','D','5')
SELECT
Col2
FROM #TempData
GROUP BY Col2
ORDER BY SUM(Col1)+SUM(Col3)
DROP TABLE #TempData
declare #table table (col1 int, col2 varchar(1), col3 int)
insert into #table (col1,col3)
values
(1, 1),
(6, 4),
(3, 2),
(2, 1),
(5, 3),
(4, 2)
/*
Col1 | Col2 | Col3 |
--------------------------------------------------
1 A 1
6 F 4
3 C 2
2 B 1
5 E 3
4 D 5
*/
select col1,
case
when col1 > col3 then char(col1 + 64)
else char(col3 + 64)
end as col2,
col3
from #table
in Sql server you may try this,
declare #t table (Col1 int, Col2 varchar(1), Col3 int)
INSERT INTO #t (Col1, Col2, Col3)
VALUES
('1','A','1')
,('6','F','2')
,('3','C','4')
,('2','B','1')
,('5','E','3')
,('4','D','2')
declare #str as varchar(100)=''
;with t as(select top 1 col1,col2,col3 from #t union all select t1.col1, t1.col2,t1.col3 from #t t1,t
where (t1.Col1!=t.Col1 and t1.Col3=t.Col1) )
select #str=#str+' '+col2 from t
select #str as col