updating records by putting sum(column) value - sql

I have table1 like this:
+-----+-----+------+
| cat | val | type |
+-----+-----+------+
| A | 100 | c1 |
| H | 25 | c2 |
| H | 50 | c3 |
| H | 30 | c2 |
| A | 15 | c3 |
| H | 10 | c1 |
| H | 15 | c1 |
| B | 10 | c4 |
| H | 20 | c4 |
| H | 15 | c3 |
+-----+-----+------+
I need to add the sum(val) group by type to only one H belonging to each type
So I have after grouping by type we have say table2 :
+-----+-----+
| cat | val |
+-----+-----+
| c1 | 125 |
| c2 | 55 |
| c3 | 80 |
| c4 | 30 |
+-----+-----+
I need 125 added to any one H values with type c1, 55 added to any one H values with c2, and so on..If there is no H with c1,then it should create that record.
So finally we get:
+-----+-----+------+
| cat | val | type |
+-----+-----+------+
| A | 100 | c1 |
| H | 25 | c2 |
| H | 130 | c3 |
| H | 85 | c2 |
| A | 15 | c3 |
| H | 135 | c1 |
| H | 15 | c1 |
| B | 10 | c4 |
| H | 50 | c4 |
| H | 15 | c3 |
+-----+-----+------+
How do I do it without doing table1 union table2 (with 'H' as cat) group by type? Also I don't have update privileges and cannot use stored procedures. I also have to keep in mind that table1 is a result of a query involving multiple inner joins that I don't want to use over and over again for select statements.

See whether this makes sense. I've added the ID column just to display the final result in the same order as the input (for easier reading).
SQL> -- T1 is what you currently have; it can/could be your current query
SQL> with t1 (id, cat, val, type) as
2 (select 1, 'A', 100, 'C1' from dual union all
3 select 2, 'H', 25 , 'C2' from dual union all
4 select 3, 'H', 50 , 'C3' from dual union all
5 select 4, 'H', 30 , 'C2' from dual union all
6 select 5, 'A', 15 , 'C3' from dual union all
7 select 6, 'H', 10 , 'C1' from dual union all
8 select 7, 'H', 15 , 'C1' from dual union all
9 select 8, 'B', 10 , 'C4' from dual union all
10 select 9, 'H', 20 , 'C4' from dual union all
11 select 10,'H', 15 , 'C3' from dual
12 ),
13 -- sum VAL per type
14 t1_sum as
15 (select type, sum(val) sum_val
16 from t1
17 group by type
18 ),
19 -- find row number; let any H be number 1
20 t1_rn as
21 (select id, cat, val, type,
22 row_number() over (partition by type
23 order by case when cat = 'H' then 1 end) rn
24 from t1
25 )
26 -- the final result; add SUM_VAL to the first H row per type
27 select r.cat, r.val + case when r.rn = 1 then s.sum_val else 0 end val,
28 r.type
29 From t1_rn r join t1_sum s on s.type = r.type
30 order by r.id;
CAT VAL TYPE
--- ---------- ----
A 100 C1
H 80 C2
H 130 C3
H 30 C2
A 15 C3
H 135 C1
H 15 C1
B 10 C4
H 50 C4
H 15 C3
10 rows selected.
SQL>
[EDIT: trying to clarify how to use your large query]
Suppose that this is that very large and complex query of yours:
select a.cat,
case when a.cat = 'A' then b.val
when a.cat = 'Z' then c.val
else 'H'
end val,
c.type
from a join b on a.id = b.id and a.x = b.y
join c on c.id = b.idx
where a.date_column < sysdate
and c.type = 'X';
As I've said, create a view based on it as
create or replace view v_view as
select a.cat,
case when a.cat = 'A' then b.val
when a.cat = 'Z' then c.val
else 'H'
end val,
c.type
from a join b on a.id = b.id and a.x = b.y
join c on c.id = b.idx
where a.date_column < sysdate
and c.type = 'X';
and use it as a source for "my" query (from line 14 onwards):
with t1_sum as
(select type, sum(val) sum_val
from v_view --> here's the view
group by type
), etc.
Or, use the "huge" query itself as the initial CTE:
with t1 as
-- this is your "huge" query
(select a.cat,
case when a.cat = 'A' then b.val
when a.cat = 'Z' then c.val
else 'H'
end val,
c.type
from a join b on a.id = b.id and a.x = b.y
join c on c.id = b.idx
where a.date_column < sysdate
and c.type = 'X'
),
-- sum VAL per type
t1_sum as
(select type, sum(val) sum_val
from t1
group by type
), etc.

Related

Postgres: Find missing items in a version table

I have a Table in Postgres 12 which tracks what Items i are use in which Versions v:
CREATE TABLE compare_test(v BIGINT, i BIGINT);
With example data:
INSERT INTO compare_test VALUES
(1,21),
(1,22),
(1,23),
(2,21),
(2,22),
(2,23),
(3,21),
(3,22);
I'm trying to create a View that returns:
source_v
target_v
source_i
target_i
1
3
23
null
2
3
23
null
Queries used to compare missing values in two Tables like:
SELECT l.v as source_v, l.i as source_i,
r.v as target_v, r.i as target_i
FROM compare_test l
LEFT JOIN
compare_test r ON r.i = l.i
WHERE r.i IS NULL;
and
SELECT l.v as source_v, l.i as source_i
FROM compare_test l
WHERE NOT EXISTS
(
SELECT i as target_i
FROM compare_test r
WHERE r.i = l.i
)
do not seem to work if the joined Table is the same Table or if more than 2 Versions are in the Table.
I don't have the option to change the Database Structure but I can use plugins.
The solution below gives those results.
It makes re-use of a CTE.
(but somehow I got a feeling that there should exist a more efficient way)
with cte1 as (
SELECT i
, count(*) cnt
, min(v) min_v
, max(v) max_v
FROM compare_test
GROUP BY i
)
, cte2 as
(
select *
from cte1 as c1
where not exists (
select 1
from cte1 c2
where c2.min_v = c1.min_v
and c2.max_v < c1.max_v
)
)
select distinct
t.v as source_v
, c1.max_v as target_v
, c2.i as source_i
, null as target_i
from cte2 c2
left join compare_test t
on t.i = c2.i
left join cte1 c1
on t.v between c1.min_v and c1.max_v
and c1.i != t.i
order by source_v
But if it's not really required to follow the relations, then it becomes really simple.
Then it's just a left join of the existing to all possible combinations.
select distinct
src.v as source_v
, missing.v as target_v
, src.i as source_i
, missing.i as target_i
from
(
select ver.v, itm.i
from (select distinct v from compare_test) as ver
cross join (select distinct i from compare_test) as itm
left join compare_test t
on t.v = ver.v and t.i = itm.i
where t.v is null
) as missing
left join compare_test as src
on src.i = missing.i and src.v != missing.v
order by target_i, target_v, source_v
source_v | target_v | source_i | target_i
-------: | -------: | -------: | -------:
1 | 5 | 21 | 21
2 | 5 | 21 | 21
3 | 5 | 21 | 21
1 | 5 | 22 | 22
2 | 5 | 22 | 22
3 | 5 | 22 | 22
1 | 3 | 23 | 23
2 | 3 | 23 | 23
1 | 5 | 23 | 23
2 | 5 | 23 | 23
5 | 1 | 44 | 44
5 | 2 | 44 | 44
5 | 3 | 44 | 44
db<>fiddle here

Oracle Group and Pivot - Dynamic Pivot in Oracle

I have a table like this:
+-----+------+------------+
| SN | CASE | CASE_VALUE |
+-----+------+------------+
| A | AA | 1 |
| A | AB | 5 |
| A | AC | 3 |
| A | AD | 4 |
| B | BA | 5 |
| B | BB | 7 |
| B | BC | 5 |
| B | BD | 1 |
+-----+------+------------+
Not sure if there is any way to get
+-----+--------+--------------+--------+--------------+--------+--------------+--------+--------------+
| SN | CASE_1 | CASE_1_VALUE | CASE_2 | CASE_2_VALUE | CASE_3 | CASE_3_VALUE | CASE_4 | CASE_4_VALUE |
+-----+--------+--------------+--------+--------------+--------+--------------+--------+--------------+
| A | AA | 1 | AB | 5 | AC | 3 | AD | 4 |
| B | BA | 5 | BB | 7 | BC | 5 | BD | 1 |
+-----+--------+--------------+--------+--------------+--------+--------------+--------+--------------+
There is no order required for those four cases
28/02/2021 Edit
If there's no order between case name?
Like blow
+-----+------+------------+
| SN | CASE | CASE_VALUE |
+-----+------+------------+
| A | AB | 1 |
| A | CD | 5 |
| A | IJ | 3 |
| A | GH | 4 |
| B | OP | 5 |
| B | EF | 7 |
| B | MN | 5 |
| B | KJ | 1 |
+-----+------+------------+
One option is using conditional aggregation in order to pivot as desired such as
SELECT sn,
MAX(CASE WHEN SUBSTR(case,2,1) = 'A' THEN case END) AS case_1,
MAX(CASE WHEN SUBSTR(case,2,1) = 'A' THEN case_value END) AS case_1_value,
MAX(CASE WHEN SUBSTR(case,2,1) = 'B' THEN case END) AS case_2,
MAX(CASE WHEN SUBSTR(case,2,1) = 'B' THEN case_value END) AS case_2_value,
MAX(CASE WHEN SUBSTR(case,2,1) = 'C' THEN case END) AS case_3,
MAX(CASE WHEN SUBSTR(case,2,1) = 'C' THEN case_value END) AS case_3_value,
MAX(CASE WHEN SUBSTR(case,2,1) = 'D' THEN case END) AS case_4,
MAX(CASE WHEN SUBSTR(case,2,1) = 'D' THEN case_value END) AS case_4_value
FROM t
GROUP BY sn
Demo
The pivot is static in the above case. You can create a function returning of SYS_REFCURSOR type as below one in order to have a dynamic pivot
CREATE OR REPLACE FUNCTION Get_Pivoted_Cols RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( 'MAX(CASE WHEN SUBSTR(case,2,1) = '''||cs||''' THEN case END ) AS "case_'||rn||'",
MAX(CASE WHEN SUBSTR(case,2,1) = '''||cs||''' THEN case_value END ) AS "case_'||rn||'_value"', ',')
WITHIN GROUP ( ORDER BY rn )
INTO v_cols
FROM ( SELECT DISTINCT SUBSTR(case,2,1) AS cs, ROW_NUMBER() OVER (PARTITION BY sn ORDER BY case) AS rn
FROM t );
v_sql :='SELECT sn,'|| v_cols ||' FROM t GROUP BY sn';
OPEN v_recordset FOR v_sql;
RETURN v_recordset;
END;
/
and call from SQL Developer's Command Line in order to see the result set
VAR rc REFCURSOR
EXEC :rc := Get_Pivoted_Cols;
PRINT rc
You need to pivot rows, then to use decode function to map your output like below. (I hope CASE is not the real name of your column).
with your_data (SN, "CASE", CASE_VALUE ) as (
select 'A', 'AA', 1 from dual union all
select 'A', 'AB', 5 from dual union all
select 'A', 'AC', 3 from dual union all
select 'A', 'AD', 4 from dual union all
select 'B', 'BA', 5 from dual union all
select 'B', 'BB', 7 from dual union all
select 'B', 'BC', 5 from dual union all
select 'B', 'BD', 1 from dual
)
select SN
, decode(SN, 'A', CASE_1, CASE_5)CASE_1
, decode(SN, 'A', CASE_1_VALUE, CASE_5_VALUE)CASE_1_VALUE
, decode(SN, 'A', CASE_2, CASE_6)CASE_2
, decode(SN, 'A', CASE_2_VALUE, CASE_6_VALUE)CASE_2_VALUE
, decode(SN, 'A', CASE_3, CASE_7)CASE_3
, decode(SN, 'A', CASE_3_VALUE, CASE_7_VALUE)CASE_3_VALUE
, decode(SN, 'A', CASE_4, CASE_8)CASE_4
, decode(SN, 'A', CASE_4_VALUE, CASE_8_VALUE)CASE_4_VALUE
from your_data t
pivot (
max(case_value) as value, max("CASE") FOR "CASE" in (
'AA' CASE_1
,'AB' CASE_2
,'AC' CASE_3
,'AD' CASE_4
,'BA' CASE_5
,'BB' CASE_6
,'BC' CASE_7
,'BD' CASE_8
)
)
;
Closest I could get, assuming (based on your data) that the first character in case will always match with sn we can pivot on that second character
select *
from (
select
data.*
, substr(case, 2, 1) case_t
from data
)
pivot (
max(case) type
, max(case_value) value
for case_t in (
'A' case_1
, 'B' case_2
, 'C' case_3
, 'D' case_4
)
)

unduplicating similar data based on multiple fields

I'd like to create a table of possible duplicate records from an original table, however the data is based on two different attibutes and they must only join in the same grouping id. Also in some cases the data doesn't look exactly the same (but the similarities are there). Here is what the original table would look like:
group_id| House_num | Apt | code
----------------------------------
45 | 1000 | 1 | M
45 | 1 | | D
45 | 1000 | 2 | M
45 | 2 | | D
87 | 2300 | 310 | M
87 | 2310 | | D
87 | 2400 | 470 | M
87 | 2470 | | D
What I'd like to be returned is where these like numbers are all on the same row. So something like:
new_id |group_id|a.house_num|a.apt|a.code|b.house_num|b.apt| b.code
-------------------------------------------------------------------------
1 | 45 | 1000 | 1 | M | 1 | | D
2 | 45 | 1000 | 2 | M | 2 | | D
3 | 87 | 2300 | 310 | M | 2310 | | D
4 | 87 | 2400 | 470 | M | 2470 | | D
I'm not sure what kind of join to use here; also, im not sure how to get around the ones where a.house_num is the base number, a.apt is the suffixed number and b.housenumber is the combination of the two. Any help would be greatly appreciated, thank you.
create table t (group_id, House_num , Apt , code) as
select 45 , 1000 , 1 , 'M' from dual union all
select 45 , 1 , null , 'D' from dual union all
select 45 , 1000 , 2 , 'M' from dual union all
select 45 , 2 , null , 'D' from dual union all
select 87 , 2300 , 310 , 'M' from dual union all
select 87 , 2310 , null , 'D' from dual union all
select 87 , 2400 , 470 , 'M' from dual union all
select 87 , 2470 , null , 'D' from dual;
select rownum new_id,
a.GROUP_ID, a.HOUSE_NUM ahn, a.APT aapt, a.CODE acode,
b.HOUSE_NUM bhn, b.APT bapt, b.CODE bcode
from t a
join t b
on a.group_id = b.group_id
and a.code = 'M'
and b.code = 'D'
and (
b.house_num = a.apt
or b.house_num like '%'||a.apt
);
NEW_ID GROUP_ID AHN AAPT ACODE BHN BAPT BCODE
1 45 1000 1 M 1 D
2 45 1000 2 M 2 D
3 87 2300 310 M 2310 D
4 87 2400 470 M 2470 D
You can use the below to achieve your answer.The DB fiddle here
WITH data
AS (SELECT * FROM YOUR_TABLE),
data2
AS (SELECT row_number()over
(order by thing1,thing2) rw,
d1.group_id,
d1.thing1 a_thing1,
d1.thing2 a_thing2,
d1.thing3 a_thing3
FROM data d1
WHERE d1.thing3 = 'M'),
data3
AS (SELECT row_number()over
(order by thing1,thing2) rw,
d1.group_id,
d1.thing1 b_thing1,
d1.thing2 b_thing2,
d1.thing3 b_thing3
FROM data d1
WHERE d1.thing3 = 'D')
SELECT d2.rw New_id,
d2.group_id,
d2.a_thing1,
d2.a_thing2,
d2.a_thing3,
d3.b_thing1,
d3.b_thing2,
d3.b_thing3
FROM data2 d2,
data3 d3
WHERE d2.group_id = d3.group_id
AND d2.rw = d3.rw

TSQL query based on IF?

This the sample of my data:
+----+---+----+----+----+----+----+-------------+------------+-------------+
| ID | C | C1 | C2 | C3 | C4 | C5 | EndingPoint | TransferID | Transferred |
+----+---+----+----+----+----+----+-------------+------------+-------------+
| 1 | A | A | | | | | B | 1 | 80 |
+----+---+----+----+----+----+----+-------------+------------+-------------+
| 2 | A | A | B | | | | C | 2 | 40 |
+----+---+----+----+----+----+----+-------------+------------+-------------+
| 3 | A | A | B | C | | | A | 3 | 10 |
+----+---+----+----+----+----+----+-------------+------------+-------------+
| 4 | B | B | | | | | C | 1 | 25 |
+----+---+----+----+----+----+----+-------------+------------+-------------+
| 5 | B | B | C | | | | A | 2 | 30 |
+----+---+----+----+----+----+----+-------------+------------+-------------+
| 6 | C | C | | | | | A | 1 | 70 |
+----+---+----+----+----+----+----+-------------+------------+-------------+
I need to generate Temporary Table? or WITH clause, that will look like:
In case that TransferID = 1 THEN it will take the string from C1 and EndPoint and value from Transferred:
+------+----+------------+
| From | To |Transferred |
+------+----+------------+
| A | B | 80 |
+------+----+------------+
+------+----+------------+
| From | To |Transferred |
+------+----+------------+
| B | C | 25 |
+------+----+------------+
+------+----+------------+
| From | To |Transferred |
+------+----+------------+
| C | A | 70 |
+------+----+------------+
In case than TransferID=2 THEN: It will take the value from C1 and C2 and value from Transferred.
The next row would be then the value from C2 and EndPoint and value from Transferred:
+------+----+------------+
| From | To |Transferred |
+------+----+------------+
| A | B | 40 |
+------+----+------------+
| B | C | 40 |
+------+----+------------+
+------+----+------------+
| From | To |Transferred |
+------+----+------------+
| B | C | 30 |
+------+----+------------+
| C | A | 30 |
+------+----+------------+
In Case that TransferID=3 THEN: It will take the value from C1 and C2 and value from Transferred.
The next row would be then the value from C2 and C3 and value from Transferred.
The next row would be then the value from C3 and EndPoint and value from Transferred
+------+----+------------+
| From | To |Transferred |
+------+----+------------+
| A | B | 10 |
+------+----+------------+
| B | C | 10 |
+------+----+------------+
| C | A | 10 |
+------+----+------------+
And so on up to TransferID=5
And then from Temporary Table or With Selection (not sure what will work better), I will select the SUM of Transferred, GROUP BY From, To.
I am using MS SQL 2008 and SQL Fiddle is here
Fiddle Code :
Create TABLE T (
ID int NOT NULL,
C varchar(5) NOT NULL,
C1 varchar(5),
C2 varchar(5),
C3 varchar(5),
C4 varchar(5),
C5 varchar(5),
EndingPoint varchar(5) NOT NULL,
TransferID int NOT NULL,
Transferred int);
INSERT INTO T VALUES (1,'A','A','','','','','B',1,80);
INSERT INTO T VALUES (2,'A','A','B','','','','C',2,40);
INSERT INTO T VALUES (3,'A','A','B','C','','','A',3,10);
INSERT INTO T VALUES (4,'B','B','','','','','C',1,25);
INSERT INTO T VALUES (5,'B','B','C','','','','A',2,30);
INSERT INTO T VALUES (6,'C','C','','','','','A',1,70);
Many thanks in advance!
You can implement the logic using a bunch of union all statements:
select c1 as frompt, endingpoint as topt, transferred
from t
where transferred = 1
union all
select c1 as frompt, c2 as topt, transferred
from t
where transferred = 2
union all
select c2 as frompt, endingpoint as topt, transferred
from t
where transferred = 2
union all
select c1 as frompt, c2 as topt, transferred
from t
where transferred = 3
union all
select c2 as frompt, c3 as topt, transferred
from t
where transferred = 3
union all
select c3 as frompt, endingpoint as topt, transferred
from t
where transferred = 3;
This can actually be simplified to:
select (case when transferred = 1 then c1
when transferred = 2 then c2
when transferred = 3 then c3
end) as frompt, endingpoint as topt, transferred
from t
union all
select c1, c2, transferred
from t
where transferred >= 2
union all
select c2, c3, transferred
from t
where transferred >= 3;
Note: the resulting format is more normalized and it is a better structure for the data and the version you are storing.
You can unpivot those C1-EndingPoint columns and then use the Lead function and order by the C1 to EndingPoint column names (which happen to be in correct order)...
http://sqlfiddle.com/#!3/61e519/2
select * from (
select
ID,
[From] = pt,
[To] = Lead(pt, 1) over(partition by Id order by col),
[Transferred]
from
T
unpivot(
pt for col in (C1, C2, C3, C4, C5, EndingPoint)
) unp
where
pt <> ''
) t
where
[To] is not null
SQL 2008 version without using Lead function...
http://sqlfiddle.com/#!3/61e519/5
with cte as (
select
ID,
[From] = pt,
rn = row_number() over(partition by Id order by col),
[Transferred]
from
T
unpivot(
pt for col in (C1, C2, C3, C4, C5, EndingPoint)
) unp
where
pt <> ''
)
select
c.[ID],
c.[From],
[To] = n.[From],
c.[Transferred]
from
cte c
inner join cte n on n.ID = c.ID and n.rn = c.rn + 1
Here is the SQL2008 version of the #dotjo's solution:
;with r as (
select *
from T
unpivot(pt for col in (C1, C2, C3, C4, C5, EndingPoint)) unp
where pt!='')
select r.pt FromPt, r2.pt ToPt, sum(r.Transferred) Transferred
from r
cross apply (select top 1 *
from r r2
where r.ID=r2.ID
and r.col<r2.col
order by r2.col) r2
group by r.pt, r2.pt
When I change my #TransferID to each of your specified numbers(1 through 3), I get the same results you do. Hope this helps!
SQL Server 2005 and Above Solution
DECLARE #TransferID INT = 1;
WITH CTE_Unpivot
AS
(
SELECT EndingPoint,TransferID,val,col,ID,Transferred
FROM T
UNPIVOT
(
val FOR col IN (C1,C2,C3,C4,C5)
) unpvt
WHERE col <= 'C' + CAST(#TransferID AS VARCHAR(5))
AND TransferID <= #TransferID
)
SELECT DISTINCT VAL,CA2.EndingPoint,CA.Transferred
FROM CTE_Unpivot A
CROSS APPLY
(
SELECT TOP 1 Transferred
FROM CTE_Unpivot
WHERE val = A.val
AND col = A.col
AND TransferID = #TransferID
ORDER BY ID
) CA
CROSS APPLY
(
SELECT TOP 1 EndingPoint
FROM CTE_Unpivot
WHERE val = A.val
AND col = A.col
ORDER BY ID
) CA2
WHERE val != ''

What does select 2 mean in a union sql statement?

I have the following code which unions two select statements but the second select starts off as select 2. What does this do?
select tax_type, sum(amount)
from bill_tax_tbl a (index multi), bill_summ_tbl b
where a.customer_no = #customer_no
and a.invoice_month = convert(tinyint,datepart(mm,dateadd(mm,-1,convert(datetime,#date))))
and a.job = b.job and a.billing_sub_job = b.billing_sub_job
and b.job_group is null
group by tax_type
union
select 2, sum(amount)
from bill_tax_tbl a (index multi), bill_summ_tbl b
where a.customer_no = #customer_no
and tax_type = 1
and a.invoice_month = convert(tinyint,datepart(mm,dateadd(mm,-1,convert(datetime,#date))))
and a.job = b.job and a.billing_sub_job = b.billing_sub_job
and b.job_group is null
2 is a value constant that will end up in the tax_type field.
Example
Given the table:
+-----+-----+
| a | b |
+===========+
| 'a' | 'b' |
| 'c' | 'd' |
+------------
Queried By:
SELECT a, b from table
UNION
SELECT 'y','z'
Would return:
+-----+-----+
| a | b |
+===========+
| 'a' | 'b' |
| 'c' | 'd' |
| 'y' | 'z' |
+------------