UPDATE with SUM() in MySQL - sql

My table:
ID NAME COST PAR P_val S_val
1 X 5 0 1 0
1 y 5 0 2 0
1 z 5 0 0 5
2 XY 4 0 4 4
I need to update the PAR field with the SUM(S_val), grouped by ID:
For ID 1 PAR should be SUM(SVAL) WHERE ID=1
For ID 2 PAR should be SUM(SVAL) WHERE ID=2
Expected ouput:
ID NAME COST PAR P_val S_val
1 X 5 5 1 0
1 y 5 5 2 0
1 z 5 5 0 5
2 XY 4 4 4 4
How can I UPDATE the PAR value?
My code:
UPDATE Table_Name SET PAR = (SELECT SUM(S_val) FROM Table_Name WHERE ID=1)
FROM Table_Name
This does not work.

Unfortunately, you cannot update a table joined with itself in MySQL.
You'll need to create a function as a workaround:
DELIMITER $$
CREATE FUNCTION `fn_get_sum`(_id INT) RETURNS int(11)
READS SQL DATA
BEGIN
DECLARE r INT;
SELECT SUM(s_val)
INTO r
FROM table_name
WHERE id = _id;
RETURN r;
END $$
DELIMITER ;
UPDATE table_name
SET par = fn_get_sum(id)

Try:
UPDATE Table_NAme SET PAR= summedValue
FROM TAble_NAME t
JOIN (
SELECT ID, SUM(S_val) as summedvalue
FROM TABLE_NAME GROUP BY ID
) s on t.ID = s.ID

UPDATE Table_Name SET PAR = (SELECT SUM(S_val) FROM Table_Name WHERE ID=1)
FROM Table_Name
Check writing. delete "FROM Table_Name" row.
TRUE command is:
UPDATE Table_Name SET PAR = (SELECT SUM(S_val) FROM Table_Name WHERE ID=1)

Related

Counting nulls for each column in table

We want to count how many nulls each column in a table has. There are too many columns to do this one by one, so the following PLSQL procedure was created.
In the first part of the procedure, all column names are obtained. This works, as the dbms_output correctly lists them all.
Secondly, a query inserts the count of null values in the variable 'nullscount'. This part does not work, as the output printed for this variable is always 0, even for columns where we know there are nulls.
Does anyone know how to handle the second part correctly?
Many thanks.
CREATE OR REPLACE PROCEDURE COUNTNULLS AS
nullscount int;
BEGIN
for c in (select column_name from all_tab_columns where table_name = upper('gp'))
loop
select count(*) into nullscount from gp where c.column_name is null;
dbms_output.put_line(c.column_name||' '||nullscount);
end loop;
END COUNTNULLS;
You can get it with just one query like this: this query scans table just once:
DBFiddle: https://dbfiddle.uk/asgrCezT
select *
from xmltable(
'/ROWSET/ROW/*'
passing
dbms_xmlgen.getxmltype(
(
select
'select '
||listagg('count(*)-count("'||column_name||'") as "'||column_name||'"',',')
||' from '||upper('gp')
from user_tab_columns
where table_name = upper('gp')
)
)
columns
column_name varchar2(30) path './name()',
cnt_nulls int path '.'
);
Results:
COLUMN_NAME CNT_NULLS
------------------------------ ----------
A 5
B 4
C 3
Dynamic sql in this query uses (24 chars + column name length) so it should work fine for example for 117 columns with average column name length = 10. If you need more, you can rewrite it a bit, for example:
select *
from xmltable(
'let $cnt := /ROWSET/ROW/CNT
for $r in /ROWSET/ROW/*[name() != "CNT"]
return <R name="{$r/name()}"> {$cnt - $r} </R>'
passing
dbms_xmlgen.getxmltype(
(
select
'select count(*) CNT,'
||listagg('count("'||column_name||'") as "'||column_name||'"',',')
||' from '||upper('gp')
from user_tab_columns
where table_name = upper('gp')
)
)
columns
column_name varchar2(30) path '#name',
cnt_nulls int path '.'
);
create table gp (
id number generated by default on null as identity
constraint gp_pk primary key,
c1 number,
c2 number,
c3 number,
c4 number,
c5 number
)
;
-- add some data with NULLS and numbers
DECLARE
BEGIN
FOR r IN 1 .. 20 LOOP
INSERT INTO gp (c1,c2,c3,c4,c5) VALUES
(CASE WHEN mod(r,2) = 0 THEN NULL ELSE mod(r,2) END
,CASE WHEN mod(r,3) = 0 THEN NULL ELSE mod(r,3) END
,CASE WHEN mod(r,4) = 0 THEN NULL ELSE mod(r,4) END
,CASE WHEN mod(r,5) = 0 THEN NULL ELSE mod(r,5) END
,5);
END LOOP;
END;
/
-- check what is in the table
SELECT * FROM gp;
-- do count of each column
DECLARE
l_colcount NUMBER;
l_statement VARCHAR2(100) := 'SELECT COUNT(*) FROM $TABLE_NAME$ WHERE $COLUMN_NAME$ IS NULL';
BEGIN
FOR r IN (SELECT column_name,table_name FROM user_tab_columns WHERE table_name = 'GP') LOOP
EXECUTE IMMEDIATE REPLACE(REPLACE(l_statement,'$TABLE_NAME$',r.table_name),'$COLUMN_NAME$',r.column_name) INTO l_colcount;
dbms_output.put_line('Table: '||r.table_name||', column'||r.column_name||', COUNT: '||l_colcount);
END LOOP;
END;
/
Table created.
Statement processed.
Result Set 4
ID C1 C2 C3 C4 C5
1 1 1 1 1 5
2 - 2 2 2 5
3 1 - 3 3 5
4 - 1 - 4 5
5 1 2 1 - 5
6 - - 2 1 5
7 1 1 3 2 5
8 - 2 - 3 5
9 1 - 1 4 5
10 - 1 2 - 5
11 1 2 3 1 5
12 - - - 2 5
13 1 1 1 3 5
14 - 2 2 4 5
15 1 - 3 - 5
16 - 1 - 1 5
17 1 2 1 2 5
18 - - 2 3 5
19 1 1 3 4 5
20 - 2 - - 5
20 rows selected.
Statement processed.
Table: GP, columnID, COUNT: 0
Table: GP, columnC1, COUNT: 10
Table: GP, columnC2, COUNT: 6
Table: GP, columnC3, COUNT: 5
Table: GP, columnC4, COUNT: 4
Table: GP, columnC5, COUNT: 0
c.column_name is never null because it's the content of the column "column_name" of the table "all_tab_columns"
not the column of which name is the value of c.column_name, in table gp.
You have to use dynamic query and EXECUTE IMMEDIATE to achieve what you want.

SQL query to calculate totals from table that has overlapping/duplicate data

I need to calculate the total number of items from a table that has smaller building units. For example, let's say I have
Unit_Table
UnitId StartUnit EndUnit QtyUnit
---------------------------------------
1 0 2 5
1 0 15 20
1 2 15 15
1 15 30 8
2 0 2 2
2 0 15 12
And let's say I have
Final_Table
UnitId StartFinal EndFinal QtyFinal
----------------------------------------
1 0 30
1 0 15
1 2 30
2 0 15
How do I populate the QtyFinal column with the correct numbers (28, 20, 23 and 12 in this example)? Let's assume that all building blocks necessary for Final_Table are in Unit_Table
The following query does not work:
;With Aggr As (
Select F.UnitId
, F.StartFinal
, F.EndFinal
, Sum(U.QtyUnit) As QtySum
From Final_Table As F
Inner Join Unit_Table As U
On F.UnitId = U.UnitId
Where F.StartFinal <= U.StartUnit
And F.EndFinal > U.StartUnit
Group By F.UnitId
, F.StartFinal
, F.EndFinal
)
Update Final_Table
Set QtyFinal = QtySum
From Final_Table As F
Inner Join Aggr
On F.UnitId = Aggr.UnitId
And F.StartFinal = Aggr.StartFinal
And F.EndFinal = Aggr.EndFinal
The problem is that the Unit_Table has overlaps ((0-2) (2-15) and (0-15) for UnitId 1, for example), how do I eliminate rows 0-2 and 2-15 from the aggregate function for calculating 0-30?
I was thinking about processing Final_Table first. Then, for each row, we can look "the path" from Start to the End in the Unit_Table.
DECLARE
VarStart NUMBER;
VarEnd NUMBER;
VarQty NUMBER;
TotalQty NUMBER;
BEGIN
FOR i IN (SELECT * FROM Final_Table ORDER BY 1, 2) LOOP
VarStart := i.StartFinal;
VarQty := 0;
TotalQty := 0;
LOOP
SELECT EndUnit, QtyUnit
INTO VarEnd, VarQty
FROM Unit_Table
WHERE UnitID = i.Unitid
AND StartUnit = VarStart
AND ROWNUM = 1;
TotalQty := TotalQty + VarQty;
VarStart := VarEnd;
EXIT WHEN VarEnd = i.Endfinal;
END LOOP;
UPDATE Final_Table
SET QtyFinal = TotalQty
WHERE UnitId = i.Unitid
AND StartFinal = i.Startfinal
AND EndFinal = i.EndFinal;
END LOOP;
END;
/
I tried it and the result is:
UNITID STARTFINAL ENDFINAL QTYFINAL
1 0 15 20
1 0 30 28
1 2 30 23
2 0 15 12
Of course, there's a problem when "the path" its not completed in Unit_Table, like this:
UnitId StartUnit EndUnit QtyUnit
---------------------------------------
3 0 2 7
3 15 30 11
... But I don't know if it is possible in your application. Maybe this can't happend because is one of your requirement before inserting in Unit_Table.
I think you need a correlated subquery or cross apply:
Update F
Set QtyFinal = u.QtyUnit
From Final_Table F cross apply
(select sum(u.QtyUnit)
from Unit_Table u
where F.UnitId = u.UnitId and
F.StartFinal <= u.StartUnit And F.EndFinal > u.StartUnit
) u;
Notes:
The update needs the table alias defined in the from clause. Otherwise, it is a different reference to the table.
If you are not familiar with cross apply, it is a lot like a correlated subquery in the from clause.
A correlated subquery would be similar, but it would update all rows instead of only those that match.

SQL query to get multihierarchy items

in my SQL Table i have following data
ID Level Description Code MasterID
1 1 Cars AD0 NULL
2 1 Trucks JF1 NULL
3 1 Items YU2 NULL
4 2 New Cars AS3 1
5 2 Used Cars TG4 1
6 2 Car parts UJ5 1
7 2 New trucks OL6 2
8 2 Used trucks PL7 2
9 2 Truck parts KJL8 2
10 2 Factory stuff UY9 3
11 2 Custom stuff RT10 3
12 3 Toyota 6YH11 4
13 3 BMW 9OKH12 4
14 3 VW 13 5
15 3 Tiers Type I J14 6
16 3 Tiers Type II J15 6
17 3 Tiers Type III ADS16 9
18 3 Seats SA17 6
19 3 Doors UU18 6
20 3 Lights 9OL19 6
21 4 Left light GH20 20
22 4 Right light H21 20
23 4 Left door JHJ22 19
24 4 Michelin UY23 16
25 4 Vredestein GTF24 17
26 4 Dunlop 25 15
My achievement is to get all hierarchy data for each single item. For Exmaple, the outpu should look like as following
ID Level Description Code MasterId1 Description1 MasterId2 Description2 MasterId3 Description3
24 4 Michelin UY23 16 Tiers Type II 6 Car Parts 1 Cars
.
.
19 3 Doors UU18 6 Car Parts 1 Cars NULL NULL
.
.
10 2 Factory Stuff UY9 3 Items NULL NULL NULL NULL
.
.
3 1 Items NULL NULL NULL NULL NULL NULL NULL
.
.
If somebody can help or give an advise how to achieve this?
This is not dynamic but it could be pretty easily.
Using a recursive cte you can get the hierarchy for the entire table and self join a few times to get the table structure you want.
;WITH cte AS
(
SELECT *, ID AS [RootID], 1 AS [MasterLevel] FROM Table1
UNION ALL
SELECT t1.*, cte.[RootID], cte.[MasterLevel] + 1 FROM Table1 t1
JOIN cte ON t1.ID = cte.MasterID
)
SELECT r.ID, r.[Level], r.[Description], r.[Code],
m1.ID AS MasterId1, m1.[Description] AS Description1,
m2.ID AS MasterId2, m1.[Description] AS Description2,
m3.ID AS MasterId3, m1.[Description] AS Description3
FROM cte r
LEFT JOIN cte m1 ON m1.[RootID] = r.[RootID] AND m1.MasterLevel = 2
LEFT JOIN cte m2 ON m2.[RootID] = r.[RootID] AND m2.MasterLevel = 3
LEFT JOIN cte m3 ON m3.[RootID] = r.[RootID] AND m3.MasterLevel = 4
WHERE r.MasterLevel = 1
ORDER BY r.RootID DESC, r.MasterLevel
This would build a dynamic sql to get master and desciption fields based on the maximum Level value. or you could define how many levels you want to see by changing the #MaxLevel
DECLARE #Sql VARCHAR(MAX) = '',
#SelectSql VARCHAR(MAX) = '',
#JoinSql VARCHAR(MAX) = '',
#MaxLevel INT,
#idx INT = 1
SET #MaxLevel = (SELECT MAX([Level]) FROM Table1)
WHILE #idx < #MaxLevel
BEGIN
SET #SelectSql = #SelectSql + REPLACE(', m<index>.ID AS MasterId<index>, m<index>.[Description] AS Description<index> ', '<index>', #idx)
SET #JoinSql = #JoinSql + REPLACE(' LEFT JOIN cte m<index> ON m<index>.[RootID] = r.[RootID] AND m<index>.MasterLevel = <index> ', '<index>', #idx)
SET #idx = #idx + 1
END
SET #Sql = '
;WITH cte AS
(
SELECT *, ID AS [RootID], 0 AS [MasterLevel] FROM Table1
UNION ALL
SELECT t1.*, cte.[RootID], cte.[MasterLevel] + 1 FROM Table1 t1
JOIN cte ON t1.ID = cte.MasterID
)
SELECT r.ID, r.[Level], r.[Description], r.[Code]' + #SelectSql
+ 'FROM cte r ' + #JoinSql
+ 'WHERE r.MasterLevel = 0
ORDER BY r.RootID DESC, r.MasterLevel'
EXEC(#Sql)

sqlquery to get missing

I have a table student with following columns
StudentId SemesterId ExamYearId
1 1 1
1 2 2
1 3 3
3 1 1
3 2 2
3 3 4
7 1 1
7 3 4
8 1 1
8 2 2
I want a query to get all such student for whom no data exists for semesterid=3 and examyearid=3, however same student should have data for semesterid=2.
In this case it should return me studentid=8 as studentid=8 has data for semesterid=2 and examyearid=2 however no data for examyearid=3 and semesterid=3.
Basically I want to be able to input semesterid and examyearid and find which studentids have not filled data for that semesterid and examyearid.
DECLARE #SemesterId INT
,#ExamYearId INT
SET #SemesterId = 3;
SET #ExamYearId = 3;
SELECT *
FROM TableName t
WHERE EXISTS (SELECT 1
FROM TableName
WHERE SemesterId = #SemesterId - 1
AND ExamYearId = #ExamYearId - 1
AND t.StudentId = StudentId )
AND NOT EXISTS (SELECT 1
FROM TableName
WHERE SemesterId = #SemesterId
AND ExamYearId = #ExamYearId
AND t.StudentId = StudentId )
SELECT st1.*
FROM Student st1
left join Student st2
on st1.id = st2.id
and st1.Semesterid=2
and st2.Semesterid=3
and st2.Examyearid=3
where st2.id is null

Calculation of 4 fields with another row

Table1 has 6 columns that code, code1, %ofcode1, calc, code2, %ofcode2.
code code1 %ofcode1 calc code2 %ofcode2
1 a 20 + b 10
2 1 - c
3 2 10 * d 10
Table2 has 2 columns that field, value.
field value
a 50
b 20
c 10
d 20
I need final calculation value using function
calculation might be like tis using table1 format and getting values from table2.
50*20/100 + 20*10/100
12 - 10
2*10/100 * 20*10/100 = 0.4
I need value that 0.4
You can try implementing something along these lines.
DECLARE
var_a VARCHAR2(50);
int_a NUMBER;
BEGIN
var_a := 'a'; -- SELECT code1 FROM Table1 WHERE code="input";
IF REGEXP_LIKE(var_a, '^\d+(\.\d+)?$')
THEN int_a := to_number(var_a, '9999.99');
ELSE int_a := (-16);-- SELECT value FROM Table2 WHERE field=var_a
END IF;
dbms_output.put_line('first int is: ' || int_a);
END;
/