I want to have the column "CurrentCapacity" to be the SUM of all references specific column.
Lets say there are three rows in SecTable which all have FirstTableID = 1. Size values are 1, 1 and 3.
The row in FirstTable which have ID = 1 should now have a value of 5 in the CurrentCapacity column.
How can I make this and how to do automatically on insert, update and delete?
Thanks!
FirstTable
+----+-------------+-------------------------+
| ID | MaxCapacity | CurrentCapacity |
+----+-------------+-------------------------+
| 1 | 5 | 0 (desired result = 5) |
+----+-------------+-------------------------+
| 2 | 5 | 0 |
+----+-------------+-------------------------+
| 3 | 5 | 0 |
+----+-------------+-------------------------+
SecTable
+----+-------------------+------+
| ID | FirstTableID (FK) | Size |
+----+-------------------+------+
| 1 | 1 | 2 |
+----+-------------------+------+
| 2 | 1 | 3 |
+----+-------------------+------+
In general, a view is a better solution than trying to keep a calculated column up-to-date. For your example, you could use this:
CREATE VIEW capacity AS
SELECT f.ID, f.MaxCapacity, COALESCE(SUM(s.Size), 0) AS CurrentCapacity
FROM FirstTable f
LEFT JOIN SecTable s ON s.FirstTableID = f.ID
GROUP BY f.ID, f.MaxCapacity
Then you can simply
SELECT *
FROM capacity
to get the results you desire. For your sample data:
ID MaxCapacity CurrentCapacity
1 5 5
2 5 0
3 5 0
Demo on SQLFiddle
Got this question to work with this trigger:
CREATE TRIGGER UpdateCurrentCapacity
ON SecTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE #Iteration INT
SET #Iteration = 1
WHILE #Iteration <= 100
BEGIN
UPDATE FirstTable SET FirstTable.CurrentCapacity = (SELECT COALESCE(SUM(SecTable.Size),0) FROM SecTable WHERE FirstTableID = #Iteration) WHERE ID = #Iteration;
SET #Iteration = #Iteration + 1
END
END
GO
Personally, I would not use a trigger either or store CurrentCapacity as a value since it breaks Normalization rules for database design. You have a relation and can already get the results by creating a view or setting CurrentCapacity to a calculated column.
Your view can look like this:
SELECT Id, MaxCapacity, ISNULL(O.SumSize,0) AS CurrentCapacity
FROM dbo.FirstTable FT
OUTER APPLY
(
SELECT ST.FirstTableId, SUM(ST.Size) as SumSize FROM SecTable ST
WHERE ST.FirstTableId = FT.Id
GROUP BY ST.FirstTableId
) O
Sure, you could fire a proc every time a row is updated/inserted or deleted in the second table and recalculate the column, but you might as well calculate it on the fly. If it's not required to have the column accurate, you can have a job update the values every X hours. You could combine this with your view to have both a "live" and "cached" version of the capacity data.
I need to store data with varchar name and Integer intValue. All integer values are unique and I need to keep up that contract
I need to write query to add the element using the following rule: if after insertion there is an intValue duplication - we need to increase intValue of existed element to resolve conflict. Repeat that operation until no conflict left.
Example:
B | 2 | | B | 2 |
C | 3 | | E | 3 |
D | 4 | => insert (E 3) => | C | 4 |
A | 1 | | D | 5 |
Z | 7 | | A | 1 |
| Z | 7 |
The only idea is to run update query in a loop but that looks too unefficient.
I need to write this query in Spring JPA, so the only requirement that the query should not be database specific
Business case:
Let's say there is a people in the queue. And intValue is position in the queue. So, "Add" means that some person come, pay money and say: I dont wanna be the last in the queue. I want to be, for example, the 3rd. So you take the money and put that person in a queue so other people after him - increments their position.
The only difference from the queue - that in my case there are gaps allowed
Aha, we might say that the gaps are occasioned by people leaving the queue.
Lets try this. Loops are inevitable--either server does them, or we can do as SQL.
-- prepare test data
declare #PeopleQueue table (pqname varchar(100), intValue int);
insert into #PeopleQueue
SELECT 'B' AS pqname, 2 as intValue UNION ALL
SELECT 'C' AS pqname, 3 as intValue UNION ALL
SELECT 'D' AS pqname, 4 as intValue UNION ALL
SELECT 'A' AS pqname, 1 as intValue UNION ALL
SELECT 'Z' AS pqname, 7 as intValue
;
--SELECT '' AS pqname, 0 as intValue UNION ALL
Select * from #PeopleQueue; - verify good test data
-- Solve the problem
Declare #pqnameNEW varchar(100) = 'E';
Declare #intNEW int = 3; -- 3 for conflict, or for no conflict, use 13
Declare #intHIGH int;
IF EXISTS ( SELECT 1 FROM #PeopleQueue WHERE intValue = #intNEW )
BEGIN
-- find the end of the sequence, before the gap
SET #intHIGH = (
SELECT TOP 1
intValue
FROM #PeopleQueue pq
WHERE NOT EXISTS
(
SELECT NULL
FROM #PeopleQueue pn
WHERE pn.intValue = pq.intValue + 1
)
AND pq.intValue >= #intNEW
)
;
-- now Update all from intNEW thru intHIGH
UPDATE #PeopleQueue
SET intValue = intValue + 1
WHERE intValue >= #intNEW
AND intValue <= #intHIGH
End;
-- finally insert the new item
INSERT into #PeopleQueue Values (#pqnameNEW, #intNEW);
Select * from #PeopleQueue; -- verify correct solution
Edited--11/28 17:00
Or, estimate the number of Bump-the-Line-Inserts (vs append to the end inserts), and design the intValues to be originally in multiples of ten (10) so that long sequences of updates are minimized.
update queue
SET intValue = intValue + 1
WHERE intValue >= 3
AND intValue <= (
SELECT q1.intValue
FROM queue as q1 LEFT JOIN queue AS q2 ON q1.intValue + 1 = q2.intValue
WHERE q2.name is NULL AND q1.intValue > 3
ORDER BY q1.intValue
LIMIT 1
)
I am using Microsoft SQL Server 2014 and have a table with three columns and the field data type is Decimal(38,0).
I want to update each row of my table to insert a decimal point after the first two digits. For example, I want 123456 to become 12.3456. The numbers are different lengths; some are five digits, some are seven digits, etc.
My table is:
+-------------+-------+-------+
| ID | X | Y |
+-------------+-------+-------+
| 1200 | 321121| 345000|
| 1201 | 564777| 4145 |
| 1202 | 4567 | 121444|
| 1203 | 12747 | 789887|
| 1204 | 489899| 124778|
+-------------+-------+-------+
And I want to change this to:
+-------------+--------+--------+
| ID | X | Y |
+-------------+--------+--------+
| 1200 | 32.1121| 34.5000|
| 1201 | 56.4777| 41.45 |
| 1202 | 45.67 | 12.1444|
| 1203 | 12.747 | 78.9887|
| 1204 | 48.9899| 12.4778|
+-------------+--------+--------+
My code is:
Update [dbo].[UTM]
SET [X] = STUFF([X],3,0,'.')
[Y] = STUFF([X],3,0,'.')
And I tried this:
BEGIN
DECLARE #COUNT1 int;
DECLARE #COUNT2 int;
DECLARE #TEMP_X VARCHAR(255);
DECLARE #TEMP_Y VARCHAR(255);
DECLARE #TEMP_main VARCHAR(255);
SELECT #COUNT1 = COUNT(*) FROM [UTM];
SET #COUNT2 = 0;
WHILE(#COUNT2<#COUNT1)
BEGIN
SET #TEMP_main = (SELECT [id] from [UTM] order by [id] desc offset #COUNT2 rows fetch next 1 rows only);
SET #TEMP_X = (SELECT [X] from [UTM] order by [id] desc offset #COUNT2 rows fetch next 1 rows only);
SET #TEMP_Y = (SELECT [Y] from [UTM] order by [id] desc offset #COUNT2 rows fetch next 1 rows only);
UPDATE [dbo].[UTM]
SET [X] = CONVERT(decimal(38,0),STUFF(#TEMP_X,3,0,'.'))
,[Y] = CONVERT(decimal(38,0),STUFF(#TEMP_Y,3,0,'.'))
WHERE [id] = #TEMP_main;
SET #COUNT2 = #COUNT2 + 1
END
END
This runs on an assumption from a previously deleted post (that you have negative number as well).
Firstly, as you're using a decimal(38,0) you can't store values with any kind of precision, thus you need to change the data type as well. This provides the results you appear to be looking for:
USE Sandbox;
GO
CREATE TABLE dbo.SampleTable (ID int,
X decimal(38,0),
Y decimal(38,0));
INSERT INTO dbo.SampleTable (ID,
X,
Y)
VALUES (1200,321121,345000),
(1201,564777,4145 ),
(1202,4567 ,121444),
(1203,12747 ,789887),
(1204,489899,124778),
(1205,-32472,-27921);
GO
--Fix the datatype
ALTER TABLE dbo.SampleTable ALTER COLUMN X decimal(10,4); --Based on data provided, may need larger scale
ALTER TABLE dbo.SampleTable ALTER COLUMN Y decimal(10,4); --Based on data provided, may need larger scale
GO
--update the data
UPDATE dbo.SampleTable
SET X = STUFF(ABS(CONVERT(int,X)),3,0,'.') * CONVERT(decimal(10,4),CASE WHEN X < 0 THEN -1.0 ELSE 1.0 END),
Y = STUFF(ABS(CONVERT(int,Y)),3,0,'.') * CONVERT(decimal(10,4),CASE WHEN Y < 0 THEN -1.0 ELSE 1.0 END);
SELECT *
FROM dbo.SampleTable;
GO
DROP TABLE dbo.SampleTable;
Note that you won't get a value like 41.45, but instead 41.4500. If you don't want to display trailing 0's you need to do the formatting in your presentation layer (otherwise you'd have to store the values as a varchar, and that's a very bad idea).
Try the following update:
UPDATE UTM
SET
X = CAST(X AS DECIMAL(10,2)) / POWER(10, LEN(CAST(ABS(X) AS VARCHAR(10)))-2),
Y = CAST(Y AS DECIMAL(10,2)) / POWER(10, LEN(CAST(ABS(Y) AS VARCHAR(10)))-2);
The logic here is to divide each number by 10 to the power of the number's length minus 2. This works for both positive and negative numbers, because we use the number's absolute value for normalizing. Follow the link below for a running demo.
Demo
Just do this in the update:
Update [dbo].[UTM]
SET X = STUFF(CONVERT(VARCHAR(255), X), 3, 0, '.'),
Y = STUFF(CONVERT(VARCHAR(255), X), 3, 0, '.');
The values are converted to strings, but the strings will be implicitly converted back to whatever type X and Y are. You may get an error if the types are not compatible.
If you have negative values, then you should include them in the same data. This is handled using case:
Update [dbo].[UTM]
SET X = STUFF(CONVERT(VARCHAR(255), X), (CASE WHEN X < 0 THEN 4 ELSE 3 END), 0, '.'),
Y = STUFF(CONVERT(VARCHAR(255), X), (CASE WHEN X < 0 THEN 4 ELSE 3 END), 0, '.');
You can get the number of digits in a number using FLOOR(LOG10(num) + 1) and POWER(10, num_digits) to determine the number to divide with. No string operations at all:
DECLARE #t TABLE (ID INT, X DECIMAL(38, 0), Y DECIMAL(38, 0));
INSERT INTO #t VALUES
(1200, 321121, 345000),
(1201, 564777, 4145),
(1202, 4567, 121444),
(1203, 12747, 789887),
(1204, 489899, 124778);
SELECT ID
, X, X / POWER(10, FLOOR(LOG10(ABS(X))) + 1 - 2) AS X2
, Y, Y / POWER(10, FLOOR(LOG10(ABS(Y))) + 1 - 2) As Y2
FROM #t
You can easily extend it to handle values that have decimal portion:
DECLARE #t TABLE (X DECIMAL(38, 8));
INSERT INTO #t VALUES
( 12345.00000),
( 12345.67890),
(-12345.00000),
(-12345.67890);
SELECT X, CASE
WHEN XS >= 0 THEN X / POWER(10, XS)
ELSE X * POWER(10, -XS)
END X2
FROM #t
CROSS APPLY (SELECT FLOOR(LOG10(ABS(X))) + 1 - 2 AS XS) AS CA
Output:
| X | X2 |
|-----------------|------------|
| 12345.00000000 | 12.345000 |
| 12345.67890000 | 12.345679 |
| -12345.00000000 | -12.345000 |
| -12345.67890000 | -12.345679 |
Demo on db<>fiddle
I am currently using guid NEWID() but I know it is not cryptographically secure.
Is there any better way of generating a cryptographically secure number in SQL Server?
CRYPT_GEN_RANDOM is documented to return a "cryptographic random number".
It takes a length parameter between 1 and 8000 which is the length of the number to return in bytes.
For lengths <= 8 bytes. This can be cast to one of the SQL Server integer types straightforwardly.
+-----------+------------------+---------+
| Data type | Range | Storage |
+-----------+------------------+---------+
| bigint | -2^63 to 2^63-1 | 8 Bytes |
| int | -2^31 to 2^31-1 | 4 Bytes |
| smallint | -2^15 to 2^15-1 | 2 Bytes |
| tinyint | 0 to 255 | 1 Byte |
+-----------+------------------+---------+
Three of them are signed integers and one unsigned. The following will each use the full range of their respective datatypes.
SELECT
CAST(CRYPT_GEN_RANDOM(1) AS TINYINT),
CAST(CRYPT_GEN_RANDOM(2) AS SMALLINT),
CAST(CRYPT_GEN_RANDOM(4) AS INT),
CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)
It is also possible to supply a shorter value than the datatype storage.
SELECT CAST(CRYPT_GEN_RANDOM(3) AS INT)
In this case only positive numbers can be returned. The sign bit will always be 0 as the last byte is treated as 0x00. The range of possible numbers that can be returned by the above is between 0 and POWER(2, 24) - 1 inclusive.
Suppose the requirement is to generate some random number between 1 and 250.
One possible way of doing it would be
SELECT ( 1 + CAST(CRYPT_GEN_RANDOM(1) AS TINYINT) % 250) AS X
INTO #T
FROM master..spt_values V1, master..spt_values
However this method has a problem.
SELECT COUNT(*),X
FROM #T
GROUP BY X
ORDER BY X
The first ten rows of results are
+-------+----+
| Count | X |
+-------+----+
| 49437 | 1 |
| 49488 | 2 |
| 49659 | 3 |
| 49381 | 4 |
| 49430 | 5 |
| 49356 | 6 |
| 24914 | 7 |
| 24765 | 8 |
| 24513 | 9 |
| 24732 | 10 |
+-------+----+
Lower numbers (in this case 1 -6) are generated twice as regularly as the others because there are two possible inputs to the modulus function that can generate each of those results.
One possible solution would be to discard all numbers >= 250
UPDATE #T
SET X = CASE
WHEN Random >= 250 THEN NULL
ELSE ( 1 + Random % 250 )
END
FROM #T
CROSS APPLY (SELECT CAST(CRYPT_GEN_RANDOM(1) AS TINYINT)) CA (Random)
This appears to work on my machine but it is probably not guaranteed that SQL Server will only evaluate the function once across both references to Random in the CASE expression. Additionally it still leaves the problem of needing second and subsequent passes to fix up the NULL rows where the random value was discarded.
Declaring a scalar UDF can solve both those issues.
/*Work around as can't call CRYPT_GEN_RANDOM from a UDF directly*/
CREATE VIEW dbo.CRYPT_GEN_RANDOM1
AS
SELECT CAST(CRYPT_GEN_RANDOM(1) AS TINYINT) AS Random
go
CREATE FUNCTION GET_CRYPT_GEN_RANDOM1()
RETURNS TINYINT
AS
BEGIN
DECLARE #Result TINYINT
WHILE (#Result IS NULL OR #Result >= 250)
/*Not initialised or result to be discarded*/
SELECT #Result = Random FROM dbo.CRYPT_GEN_RANDOM1
RETURN #Result
END
And then
UPDATE #T
SET X = dbo.GET_CRYPT_GEN_RANDOM1()
Alternatively and more straight forwardly one could simply use
CAST(CRYPT_GEN_RANDOM(8) AS BIGINT) % 250
On the grounds that the range of bigint is so huge that any bias will likely be insignificant. There are 73,786,976,294,838,208 ways that 1 can be generated and 73,786,976,294,838,206 that 249 can be from the query above.
If even that small possible bias is not permitted you could discard any values NOT BETWEEN -9223372036854775750 AND 9223372036854775749 as shown earlier.
Interesting question :)
I think this will work: CRYPT_GEN_RANDOM
I have two tables - let's call them dbo.ValuesToReduce and dbo.Reserve
The data in the first table (dbo.ValuesToReduce) is:
ValuesToReduceId | PartnerId | Value
-------------------------------------
1 | 1 | 53.15
2 | 2 | 601.98
3 | 1 | 91.05
4 | 2 | 44.56
5 | 3 | 19.11
The second table (dbo.Reserve) looks like this
ReserveId | PartnerId | Value
-------------------------------
1 | 1 | -101.55
2 | 2 | -425.19
3 | 3 | -28.17
What I need to do is: update the Values in ValuesToReduce table using the latter table of Reserves, reducing the numbers until the reserve supply is exhausted. Here's what I should get after running the script:
ValuesToReduceId | PartnerId | Value
-------------------------------------
1 | 1 | 0.00
2 | 2 | 176.79
3 | 1 | 42.65
4 | 2 | 44.56
5 | 3 | 0.00
ReserveId | PartnerId | Value
-------------------------------
1 | 1 | 0.00
2 | 2 | 0.00
3 | 3 | -9.06
So basically, every partner has a "reserve" which he can deplete, and values in the value table should be reduced by partner accordingly if there is still something in the reserves. Reserves should be collocated in the order provided by ValuesToReduceId.
For partner with PartnerId of 1, you can see that he had enough reserve to update his first value to 0 and still had some left to reduce the second value by that amount.
Partner with ID of 2 had a reserve of 425.19, and there were two entries in the values table for that partner, 601.98 and 44.56, in that order (by ValuesToReduceId), so we only updated the first value since the reserve is not big enough for both. The wrong way would have been to update the second value to 0.00 and the first to 221.35.
Partner with ID of 3 has more than enough reserve, so after updating his value to 0, he's left with -9.06
I tried something with recursive cte, but I can't seem to get my head around it.
Hope I described the problem clearly enough..
You cannot, as far as I know, update two tables in a single select statement.
But you could do this in SQL using a WHILE loop. Search for the first transaction, then carry it out, until there are no possible transactions left.
declare #valid int
declare #resid int
declare #val float
while 1 = 1
begin
select top 1
#resid = r.ReserveId
, #valid = v.ValuesToReduceId
, #val = CASE WHEN -r.Value > v.Value THEN v.Value ELSE -r.Value END
from ValuesToReduce v
inner join Reserves r on r.PartnerId = v.PartnerId
where r.Value < 0 and v.Value > 0
order by r.ReserveId
if ##rowcount = 0
break
update ValuesToReduce
set Value = Value - #val
where ValuesToReduceId = #valid
update Reserves
set Value = Value + #val
where ReserveId = #resid
end
Here's code to create test tables:
create table ValuesToReduce (
ValuesToReduceId int,
PartnerId int,
Value float
)
insert into ValuesToReduce values (1,1,53.15)
insert into ValuesToReduce values (2,2,601.98)
insert into ValuesToReduce values (3,1,91.05)
insert into ValuesToReduce values (4,2,44.56)
insert into ValuesToReduce values (5,3,19.11)
create table Reserves (
ReserveId int,
PartnerId int,
Value float
)
insert into Reserves values (1,1,-101.55)
insert into Reserves values (2,2,-425.19)
insert into Reserves values (3,3,-28.17)