Recursive SQL query - using results from query within query - sql

I'm running SQL Server 2012, and here's what I need:
Row Field1 Field2
1 0 1
2 ? 2
3 ? -5
I need a query that will go throw row by row.
It should take row2,field1 and set it equal to row1,field1+row2,field2
It then would take row3,field1 and set it equal to row2,field1+row3,field2
Initially the table has values in Field1 that are all equal to 0, and so when I run my query it just always uses 0 for the field1 values.
Any help would be appreciated. I was thinking a CTE would be the way to go, but I just don't know where to go with that.
Edit:
Just to clear up some things, in my example. The initial input would be
Row Field1 Field2
1 0 1
2 0 2
3 0 -5
The desired output would be:
Row Field1 Field2
1 1 1
2 3 2
3 -2 -5
My actual table is a bit complicated, but I know I can apply it specifically if I could understand how to pull it off with this example.

Is this what you need? (Unclear if when you refer to row2,field1 for example you mean the before or after update value)
CREATE TABLE YourTable
(
Row INT,
Field1 INT NULL,
Field2 INT
)
INSERT INTO YourTable
VALUES (1,0,1),
(2,0,2),
(3,0,-5);
WITH CTE AS
(
SELECT *,
SUM(Field2) OVER (ORDER BY Row ROWS UNBOUNDED PRECEDING) AS RunningTotal
FROM YourTable
)
UPDATE CTE
SET Field1 = RunningTotal
SELECT *
FROM YourTable
Final Result
Row Field1 Field2
----------- ----------- -----------
1 1 1
2 3 2
3 -2 -5
Or another (more literal) interpretation of your word problem might be
WITH CTE AS
(
SELECT *,
LAG(Field2) OVER (ORDER BY Row) AS PrevRowField2
FROM YourTable
)
UPDATE CTE
SET Field1 = PrevRowField2 + Field1
WHERE PrevRowField2 IS NOT NULL

Something like this adapted from TSQL A recursive update?
With cte As (
Select
Row,
Field1,
Field2
From
t
Where
Row = 1
Union All
Select
t.Row,
t.Field2 + c.Field1,
t.Field2
From
t
Inner Join
cte c
On t.Row = c.Row + 1
)
Update
t
Set
Field1 = c.Field1
From
t
inner join
cte c
On t.Row = c.Row
http://sqlfiddle.com/#!6/cf843/1

Related

SQL aggregate and filter functions

Consider following table:
Number | Value
1 a
1 b
1 a
2 a
2 a
3 c
4 a
5 d
5 a
I want to choose every row, where the value for one number is the same, so my result should be:
Number | Value
2 a
3 c
4 a
I manage to get the right numbers by using nested
SQL-Statements like below. I am wondering if there is a simpler solution for my problem.
SELECT
a.n,
COUNT(n)
FROM
(
SELECT number n , value k
FROM testtable
GROUP BY number, value
) a
GROUP BY n
HAVING COUNT(n) = 1
You can try this
SELECT NUMBER,MAX(VALUE) AS VALUE FROM TESTTABLE
GROUP BY NUMBER
HAVING MAX(VALUE)=MIN(VALUE)
You can try also this:
SELECT DISTINCT t.number, t.value
FROM testtable t
LEFT JOIN testtable t_other
ON t.number = t_other.number AND t.value <> t_other.value
WHERE t_other.number IS NULL
Another alternative using exists.
select distinct num, val from testtable a
where not exists (
select 1 from testtable b
where a.num = b.num
and a.val <> b.val
)
http://sqlfiddle.com/#!9/dd080dd/5

Count value across multiple columns

I am looking to count the number of times set of values occurred in a table. These values could occur in up to 10 different columns. I need to increment the count regardless of which column it is in. I know how I could count if they were all in the same column but not spanning multiple columns.
Values can be added in any order. I have about a thousand
Cpt1 Cpt2 Cpt3 Cpt4 Cpt5
63047 63048 63048 NULL NULL
I would want to for this row I'd expect this as the result
63047 1
63048 2
You could use a union all call to treat them as one column:
SELECT col, COUNT(*)
FROM (SELECT col1 FROM mytable
UNION ALL
SELECT col2 FROM mytable
UNION ALL
SELECT col3 FROM mytable
-- etc...
) t
GROUP BY col
It's not entirely clear what your table exactly looks like, but I'm guessing that what you're looking for is:
SELECT row_count = COUNT(*),
row_count_with_given_value = SUM ( CASE WHEN field1 = 'myValue' THEN 1
WHEN field2 = 'myValue' THEN 1
WHEN field3 = 'myValue' THEN 1
WHEN field4 = 'myValue' THEN 1 ELSE 0 END)
FROM myTable
Assuming the fieldx columns are not NULL-able, you could write it like this too:
SELECT row_count = COUNT(*),
row_count_with_given_value = SUM ( CASE WHEN 'myValue' IN (field1, field2, field3, field4) THEN 1 ELSE 0 END)
FROM myTable
Something like this might work (after adapting to your value domain and data types):
create table t1
(i1 int,
i2 int,
i3 int);
insert into t1 values (1,0,0);
insert into t1 values (1,1,1);
insert into t1 values (1,0,0);
declare #i int = 0;
select #i = #i + i1 + i2 + i3 from t1;
print #i;
drop table t1;
Output is: 5
Many databases support lateral joins, of one type of another. These can be used to simplify this operation. Using the SQL Server/Oracle 12C syntax:
select v.cpt, count(*)
from t cross apply
(values (cpt1), (cpt2), . . .
) v(cpt)
where cpt is not null
group by v.cpt;

insert values into a table's column and update that column

I want to insert values into a table's column and for every value inserted I want to copy all the rows `wHERE anotherColumn ='someString'
Then I want to update the id of the rows inserted based on the name they have (rN).
Table definition:
myFirstTable
rN rID rnk dateR value1 value2 calculation1 calculation2 calculation3
abc_3m 3 abc_3mxfx 20.10.2010 1 3 4 3 0.33
abc_6m 4 abc_6mxfx 20.10.2010 2 1 3 8 4
First, let's insert new values in rN. Values taken where rN = abc_6m
insert into #myFirstTable (rN)
values
('abc_6m'), ('abc_1y'), ('abc_2y'), ('abc_3y'), ('abc_4y'), ('abc_5y'), ('abc_7y'), ('abc_10y'), ('abc_15y'), ('abc_30y')
query 1
update #myFirstTable
set rN = (select * from #myFirstTable
where rN = 'abc_6m')
where rN in (
'abc_6m', 'abc_1y', 'abc_2y', 'abc_3y', 'abc_4y', 'abc_5y', 'abc_7y', 'abc_10y', 'abc_15y', 'abc_30y'
)
The table would become:
#myFirstTable
rN rID rnk dateR value1 value2 calculation1 calculation2 calculation3
abc_3m 3 abc_3mxfx 20.10.2010 1 3 4 3 0.33
abc_6m 4 abc_6mxfx 20.10.2010 2 1 3 8 4
abc_1y 4 abc_6mxfx 20.10.2010 2 1 3 8 4
abc_2y 4 abc_6mxfx 20.10.2010 2 1 3 8 4
and so on, for all the values inserted.
then i want to update the rID to the correct value so the column rID would look like this:
rN rID rnk dateR value1 value2 calculation1 calculation2 calculation3
abc_3m 3 abc_3mxfx 20.10.2010 1 3 4 3 0.33
abc_6m 4 abc_6mxfx 20.10.2010 2 1 3 8 4
abc_1y 5 abc_6mxfx 20.10.2010 2 1 3 8 4
abc_2y 6 abc_6mxfx 20.10.2010 2 1 3 8 4
In order to do so, I was thinking of writing something like this:
query 2
update #myFirstTable
set rID = (case when rnk = 'abc_1y' then rID = '100',
case when rnk = 'abc_2y' then rID = '101'
case when rnk = 'abc_3y' then rID = '102'
case when rnk = 'abc_4y' then rID = '103'
case when rnk = 'abc_5y' then rID = '104'
case when rnk = 'abc_7y' then rID = '105'
case when rnk = 'abc_10y' then rID = '106'
case when rnk = 'abc_15y' then rID = '107'
case when rnk = 'abc_30y' then rID = '108'
) end
select * from #myFirstTable
where rN = 'abc_6M'
Questions:
1. Is my logic correct for what I'm trying to achieve? (are query1 and query2 ok syntax wise? Can I write something like this in sql server 2012?
2. Is it ok to write an update column = (case when column = then another column = 'value' like that ?
3. Could I do this in a simpler way somehow using a window function?
ex:
select * from myTable
case (
when rN not in ('abc_1m', 'abc_3m', 'abc_6m') then
select * from #myFirstTable where rN like 'abc_6m'
over (partition by (select * from #myFirstTable where rN like 'abc_6m'))) end
Sorry if I'm not phrasing the question in the most clear way, but I am not sure what I could use and how to use it, that's why I'm looking for some guidence.
Thanks
EDIT:
I'm only a beginner and this is the best I could come up with, which seems to be what you want to do. Following steps are building up on the already existing table with the two rows called MyFirstTable.
1. step (inserting additional values in the rN column):
INSERT INTO MyFirstTable (rN)
VALUES ('abc_1y'),('abc_2y'),('abc_3y'),('abc_4y'),('abc_5y'),('abc_7y'),('abc_10y'),('abc_15y'),('abc_30y');
2. step (dropping rID column and creating a new one with auto_incremeneted values)
ALTER TABLE MyFirstTable
DROP COLUMN rID;
ALTER TABLE MyFirstTable
ADD rID int IDENTITY(3,1);
More info: http://www.w3schools.com/sql/sql_autoincrement.asp
3. step (updating fields in other columns with values from the 2nd row)
Here, the update is combined with something called "self-join", because the FROM clause doesn't allow us to use the same source table as the one that is being updated and so nested query to retrieve the desired value cannot be used.
UPDATE t1
SET
t1.rnk = t2.rnk,
t1.dateR = t2.dateR,
t1.value1 = t2.value1,
t1.value2 = t2.value2,
t1.calculation1 = t2.calculation1,
t1.calculation2 = t2.calculation2,
t1.calculation3 = t2.calculation3
FROM MyFirstTable t1
LEFT JOIN MyFirstTable t2 ON t2.rN='abc_6m'
WHERE t1.rN in ('abc_1y','abc_2y','abc_3y','abc_4y','abc_5y','abc_7y','abc_10y','abc_15y','abc_30y');
More info on self-join: https://technet.microsoft.com/en-us/library/ms177490%28v=sql.105%29.aspx
I found this answer to be helpful: https://stackoverflow.com/a/5574558/4296411
Warning: I'm really only a beginner and possibly none of the above may be considered a good practice and might contain mistakes leading to unpredictable results. :) It nevertheless seems to be answering your problem. Good luck!
EDIT2:
Well, this is most probably a ridiculous overkill, but since you cannot drop the rID column, I was able to come up with this rather complex solution. First, you would perform step #1, then the step#3 and then:
DECLARE #i INT = 1;
WHILE #i <= (SELECT COUNT(rN) FROM MyFirstTable WHERE rN IN ('abc_1y','abc_2y','abc_3y','abc_4y','abc_5y','abc_7y','abc_10y','abc_15y','abc_30y')) -- getting number of desired loops based on rows to be affected
BEGIN
UPDATE MyFirstTable
SET rID = 4+#i -- using number 4 as a starting point for incrementation and #i variable to perform the actual incrementation
WHERE
rN = ( -- specifying which row should be affected in each loop
SELECT SUBQUERY.rN
FROM ( -- assigning temporary row number for each desired row
SELECT ROW_NUMBER() OVER (ORDER BY value1) as row_num, rN -- using value1 in order to keep the current order (this doesn't seem to be a good practice, but it works)
FROM MyFirstTable
WHERE rN IN ('abc_1y','abc_2y','abc_3y','abc_4y','abc_5y','abc_7y','abc_10y','abc_15y','abc_30y')
) as SUBQUERY
WHERE SUBQUERY.row_num = #i
)
SET #i = #i + 1;
END;
PS: Please don't worry about my slight obsession with your problem. :D It's just that I consider this problem fun and It helps me to educate myself further.

Subtract value to multiple rows

Well I am stuck at a point where I need to distribute a value across multiple rows. Since I do not know the specific term, I would put it in the form of example below for better understanding:
Assuming the value of x to be 20, I need to distribute/subtract it to rows in descending order.
TABLE:
ID Value1
1 6
2 5
3 4
4 3
5 9
Result should look like: (x=20)
ID Value1 Answer
1 6 14
2 5 9
3 4 5
4 3 2
5 9 0
Can anyone just give me an idea how I could go with this?
Untested for syntax, but the idea should work in SQL Server 2005 and newer.
SQL Server 2012 has SUM OVER clause which makes this even handier.
SELECT ID, Value1, CASE WHEN 20-SumA < 0 THEN 0 ELSE 20-SumA END AS Answer
FROM TABLE A
CROSS APPLY (SELECT SUM(B.Answer) SumA FROM TABLE B
WHERE B.ID <= A.ID) CA
It is perhaps easier to think of this problem in a different way. You want to calculate the cumulative sum of value1 and then subtract that value from #X. If the difference is negative, then put in 0.
If you are using SQL Server 2012, then you have cumulative sum built-in. You can do this as:
select id, value1,
(case when #X - cumvalue1 < 0 then 0 else #X - cumvalue1 end) as answer
from (select id, value1,
sum(value1) over (order by id) as cumvalue1
from table t
) t;
If you don't have cumulative sum, you can do this with a subquery instead:
select id, value1,
(case when #X - cumvalue1 < 0 then 0 else #X - cumvalue1 end) as answer
from (select id, value1,
(select sum(value1)
from table t2
where t2.id <= t.id
) as cumvalue1
from table t
) t;
I don't understand your question. I know what I think you're trying to do. But your example doesn't make sense.
You say you want to distribute 20 over the 5 rows, yet the sum of the difference between Value1 and Answer is only 3 (8+4+1+-1+-9).
And how do you want to distribute the values? Using a spread/split based on the value in Value1?
Edit: I made an example which splits 20 over the values you've specified above:
DECLARE #x FLOAT = 20.0
DECLARE #values TABLE (
ID INT,
VALUE FLOAT,
NEWVAL FLOAT)
INSERT INTO #values (ID, VALUE) VALUES (1,6), (2,5),(3,4),(4,3),(5,9)
UPDATE f
SET [NEWVAL] = [newValue]
FROM #values f
INNER JOIN (
SELECT
ID,
value + ((VALUE / [maxValue]) * #x) [newValue]
FROM
#values
CROSS APPLY (
SELECT
SUM(value) [maxValue]
FROM
#values
) m
) a ON a.ID = f.ID
SELECT * FROM #values
Unfortunately I had to change your values to floats for this to work. If you require them as integers, you'll need to use rounding and then calculate the difference of the sum of new value - #x and then spread the difference over the rows (if > 1 then add to lowest number, if < 1 subtract from largest value). Your rounding should be usually just 1 or 2.
I don't even know if I this is what you're trying to do yet.

How do I modify this query without increasing the number of rows returned?

I've got a sub-select in a query that looks something like this:
left outer join
(select distinct ID from OTHER_TABLE) as MYJOIN
on BASE_OBJECT.ID = MYJOIN.ID
It's pretty straightforward. Checks to see if a certain relation exists between the main object being queried for and the object represented by OTHER_TABLE by whether or not MYJOIN.ID is null on the row in question.
But now the requirements have changed a little. There's another row in OTHER_TABLE that can have a value of 1 or 0, and the query needs to know whether a relation exists between the primary for a 1-value, and also if it exists for a 0 value. The obvious solutions is to put:
left outer join
(select distinct ID, TYPE_VALUE from OTHER_TABLE) as MYJOIN
on BASE_OBJECT.ID = MYJOIN.ID
But that would be wrong because if 0-type and 1-type objects both exist for the same ID, it will increase the number of rows returned by the query, which isn't acceptable. So what I need is some sort of subselect that will return 1 row for each distinct ID, with a "1-type exists" column and a "0-type exists" column. And I have no idea how to code that in SQL.
For example, for the following table,
ID | TYPE_VALUE
_________________
1 | 1
3 | 0
3 | 1
4 | 0
I'd like to see a result set like this:
ID | HAS_TYPE_0 | HAS_TYPE_1
______________________________
1 | 0 | 1
3 | 1 | 1
4 | 1 | 0
Anyone know how I could set up a query to do this? Hopefully with a minimum of ugly hacks?
In the general case, you would use EXISTS:
SELECT DISTINCT ID,
CASE WHEN EXISTS (
SELECT * FROM Table1 y
WHERE y.TYPE_VALUE = 0 AND ID = x.ID)
THEN 1
ELSE 0 END AS HAS_TYPE_0,
CASE WHEN EXISTS (
SELECT * FROM Table1 y
WHERE y.TYPE_VALUE = 1 AND ID = x.ID)
THEN 1
ELSE 0 END AS HAS_TYPE_1
FROM Table1 x;
If you have a very large number of elements in the table, this won't perform so great - those nested subselects are often a kiss of death when it comes to performance.
For your specific case, you could also use GROUP BY and MAX() and MIN() to speed things up:
SELECT
ID,
CASE WHEN MIN(TYPE_VALUE) = 0 THEN '1' ELSE 0 END AS HAS_TYPE_0,
CASE WHEN MAX(TYPE_VALUE) = 1 THEN '1' ELSE 0 END AS HAS_TYPE_1
FROM Table1
GROUP BY ID;
Instead of select distinct ID, TYPE_VALUE from OTHER_TABLE
use
select ID,
MAX(CASE WHEN TYPE_VALUE =0 THEN 1 END) as has_type_0,
MAX(CASE WHEN TYPE_VALUE =1 THEN 1 END) as has_type_1
from OTHER_TABLE
GROUP BY ID;
You can do the same using PIVOT opearator...