TSQL while loop - sql-server

UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT ref, SUM(code) AS codesum
FROM Users
GROUP BY ref) AS U ON U.ref = H.ref
The above code gets all users for every house (Houses table). SUMs the code column (Users table) for all users. And finally updates the result in lstatus column of the houses table.
My question is:
I need to rewrite the query which is NOT to sum the code column. Rather I want to create case statements. for example:
tempvar = 0 //local variable might be required
For each user {
If code == 1 then tempvar += 5
else if code == 2 then tempvar += 10
etc
tempvar = 0;
}
Once we have looped through all the users for each house we can now set lStatus = tempvar.
The tempvar should then be reset to 0 for the next house.

You should try to avoid loops and other procedural constructs when coding SQL. A relational database can't easily optimize such things and they often perform orders of magnitude slower than their declarative counterparts. In this case, it seems simple enough to replace your SUM(code) with the CASE statement that you describe:
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT ref, SUM(CASE code WHEN 1 THEN 5 WHEN 2 THEN 10 ELSE 0 END) AS codesum
FROM Users
GROUP BY ref) AS U ON U.ref = H.ref
In this way, SUM can still handle the duty that you imagine your temp variable would be doing.
Also, if you have many cases, you might think about putting those in a table and simply joining on that to get your sum. This might be better to maintain. I'm using a table variable here, but it could look like the following:
DECLARE #codes TABLE (
code INT NOT NULL PRIMARY KEY,
value INT NOT NULL
)
INSERT INTO #codes SELECT 1, 5
INSERT INTO #codes SELECT 2, 10
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT a.ref, SUM(b.value) AS codesum
FROM Users a
JOIN #codes b on a.code = b.code -- Here we get the values dynamically
GROUP BY a.ref) AS U ON U.ref = H.ref

Try this:
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (
SELECT ref, SUM(
CASE
WHEN Code = 1
THEN 5
WHEN Code = 2
THEN 10
END
) AS codesum
FROM Users
GROUP BY ref
) AS U ON U.ref = H.ref

Try this :
UPDATE Houses
SET lStatus = U.code
FROM Houses H
JOIN (
SELECT ref, SUM(
CASE
WHEN Code = 1
THEN 5
WHEN Code = 2
THEN 10
ELSE 0
END
) AS code
FROM Users
GROUP BY ref
) AS U ON U.ref = H.ref

Related

Sql query with fetch by condition

Good day to you all. Faced with a problem and I am in a stupor, please help. You need to write an sql query according to the condition: where code = 2 and num = 2 then change the value, if code = 3 then take the record whose value code = 1
, a as (
select numberfn, street
from tbl
where code = 2
and nume = 2)
, b as (
select N from tbl where code = '3'
)
, actual as (
select b.value, a.N
from b
join a
on b.N = a.N
where b.code = '1'
union all
select st.value, st.N
from rreg st
join tbl b
on st.N = b.N
)
I tried to simplify it using case as well, but the second condition leads me to a dead end.
, actual as (
select N
, case when (code= 2 and num= 2)
then value
else .... (condition: select an entry for N where code = 1)
end value
from tbl
)
You have 2 requirements here, one is to update, the other is to select a value.
Separately they are:
UPDATE table
SET value = ??
WHERE code = 2 AND num = 2
And
SELECT * FROM table
WHERE code = 3 AND value = 1
(Where ?? = value you want to change the column value to.)
If you want both in one function, then they can be moved into a stored procedure.

Check if table has specific row value

I have two tables called game and gameprogress. Gameprogress has a column called state that describes progress. Game is related to gameprogress trough an id.
Possible states is: 1,3,4 in gameprogress.
I want to find games without a state = 1 and without a state = 4. I've tried something like this:
select top 10 gp.gameid, count(gp.state) as dup
from gameprogress gp
join game g on g.id= gp.gameid
where g.gamestate != 2 and gp.state != 1
group by gp.gameid
having count(gp.state)>1
You can use CASE EXPRESSION in the HAVING clause :
SELECT TOP 10 g.gameid
FROM game g
LEFT JOIN gameprogress gp
ON g.id= gp.gameid
GROUP BY g.gameid
HAVING COUNT(CASE WHEN gp.state = 3 THEN 1 END) = COUNT(*)

SQL Union Count to Sum Data

I have a bit of a complex query .... I need to do an update statement on the summation of two union-ed SQL queries (problem is the data in the queries isn't numeric so i'm counting rows instead of summing values) but I then need to sum those rows.
UPDATE #LT_Actuals_TEMP
SET pCount = h.countPerfs
FROM (
select count(distinct c.perf_description) as countPerfs, b.program, b.Prog_id
from #LT_Actuals_TEMP TableP
where a.Performances = 'Y' and a.current_inactive = 0
group by b.Program, b.Prog_id
union
select distinct count(p.perf_code) as countPerfs, x.value, b.Prog_id
from T_PERF p
where x.content_type = 23
group by x.value, b.Prog_id
) h where h.Prog_id = #LT_Actuals_TEMP.program_id
the first query data comes back as such
countPerfs program Prog_id
7 Name 31
and second query comes back as
countPerfs program Prog_id
1 Name 31
what I need pCount to be set to at the end of the day is 8
Expected results
when I do select * from #LT_Actuals_TEMP
I see the value
8 for the Program Name, Id 31
You can solve it by adding another level in the from part where you sum up the data returned from the union.
Your query seems to be missing some source tables (as there are aliases used that don't point to anything) so I guess you're removed some parts, but in general it should look something like this:
UPDATE #LT_Actuals_TEMP
SET pCount = h.sum_of_countperfs
FROM (
select program, prog_id, sum(countPerfs) as sum_of_countperfs
from (
select count(distinct c.perf_description) as countPerfs, b.program, b.Prog_id
from #LT_Actuals_TEMP TableP
where a.Performances = 'Y' and a.current_inactive = 0
group by b.Program, b.Prog_id
union all
select distinct count(p.perf_code) as countPerfs, x.value, b.Prog_id
from T_PERF p
where x.content_type = 23
group by x.value, b.Prog_id
) as sub_q group by program, prog_id
) h where h.Prog_id = #LT_Actuals_TEMP.program_id
Also, you probably want to use union all so that duplicates are not removed.

How can I optimize this query? It should return the percent of values in a given constraint that are less than 40

USE SIMDB
GO
SELECT Count([Xa])*1.0/
(SELECT Count(*) FROM [dbo].[Simulations]
WHERE [dbo].[Simulations].[ExperimentID] IN (
SELECT [dbo].[Parameters].[ExperimentID] FROM [dbo].[Parameters]
WHERE [dbo].[Parameters].[SensorError] = 0 AND [dbo].[Parameters].[ExogDEXCurve] <> 4
AND [dbo].[Parameters].[ControlRange] = 0)) AS XA_PIR0
FROM [dbo].[Simulations]
WHERE [dbo].[Simulations].[ExperimentID] IN (
SELECT [dbo].[Parameters].[ExperimentID] FROM [dbo].[Parameters]
WHERE [dbo].[Parameters].[SensorError] = 0 AND [dbo].[Parameters].[ExogDEXCurve] <> 4
AND [dbo].[Parameters].[ControlRange] = 0)
AND [dbo].[Simulations].[Xa] <= 40
GO
This is a database containing simulation results from 90k simulations. The primary key in the Parameters table is [ExperimentID], and the primary key in the Simulations table is a combination of [ExperimentID] and [CycleCount], to uniquely identify rows within a given simulation. The total number of rows in the Simulations table is about 75 million. The Simulations table also has 21 columns, which it admittedly shouldn't. All indices are clustered.
This query takes ~23 minutes to run on my laptop, where the DB is stored. Checking the execution plan, there are two clustered index seeks, each of them taking 42%. Is this expected? How can I speed it up?
sqlplan here: https://www.dropbox.com/s/gq1bl1wgmesh0bl/sqlplan.sqlplan?dl=0
It looks to me that you can leave out the second select and do something like this:
select sum(1), sum(case when [Simulations].[Xa] <= 40 then 1 else 0 end)
FROM [dbo].[Simulations]
WHERE [dbo].[Simulations].[ExperimentID] IN (
SELECT [dbo].[Parameters].[ExperimentID]
FROM [dbo].[Parameters]
WHERE [dbo].[Parameters].[SensorError] = 0
AND [dbo].[Parameters].[ExogDEXCurve] <> 4
AND [dbo].[Parameters].[ControlRange] = 0)
It might also help if you create index for Simulations with ExperimentID + Xa as included field, just so that there's less data to scan, since the table seems to be quite wide.
Using EXISTS and index on dbo.Parameters table, you might reduce amount of data that needs to be read from it.
SELECT SUM(CASE WHEN S.Xa <= 40 THEN 1 ELSE 0 END) * 1.0 / SUM(1)
FROM dbo.Simulations AS S
WHERE EXISTS (
SELECT 1
FROM dbo.Parameters AS P
WHERE P.ExperimentID = S.ExperimentID
AND P.SensorError = 0
AND P.ExogDEXCurve <> 4
AND P.ControlRange = 0
);
Credits to JamesZ for providing a smart way to replace COUNT with SUM.
Also, I'd create couple of indexes on your tables using these statements. I'm sure they'll boost query execution time quite a lot:
CREATE NONCLUSTERED INDEX idx_Parameters_SensorError_ExogDEXCurve_ControlRange_ExperimentID
ON dbo.Parameters (SensorError, ExogDEXCurve, ControlRange, ExperimentID);
CREATE NONCLUSTERED INDEX idx_Simulations_ExperimentID_Xa
ON dbo.Simulations (ExperimentID, Xa);
To be precise, this needs to be optimized. I'm guessing it takes most time to execute.
Without knowing the shape of the data, optimization may be difficult. However, this query may be easier to read and faster because it leverages MS SQL's set based engine.
Replace #simulationsTable and #parametersTable with your tables. I used them to make things a easier to work with.
DECLARE #simulationsTable TABLE (
ExperimentID INT
,Xa INT
);
DECLARE #parametersTable TABLE (
ExperimentID INT
,SensorError INT
,ExogDEXCurve INT
,ControlRange INT
);
;WITH validParameters
AS (
SELECT [ExperimentID]
FROM #parametersTable
WHERE [SensorError] = 0
AND [ExogDEXCurve] <> 4
AND [ControlRange] = 0
)
SELECT COUNT([Xa]) * 1.0 / (sims.countOfSims) AS XA_PIR0
FROM #simulationsTable sim2
INNER JOIN validParameters p ON p.ExperimentID = sim2.ExperimentID
CROSS APPLY (
SELECT COUNT(1) AS countOfSims
FROM #simulationsTable sim1
INNER JOIN validParameters p ON p.ExperimentID = sim1.ExperimentID
) sims
WHERE [Xa] <= 40
GO
Additionally, if count of sims is the same for each row, you could just calculate it ahead of time and store the result.
DECLARE #countOfSims INT = (
SELECT COUNT(1) AS countOfSims
FROM #simulationsTable sim1
CROSS APPLY (
SELECT[ExperimentID]
FROM #parametersTable
WHERE [SensorError] = 0 AND [ExogDEXCurve] <> 4 AND [ControlRange] = 0
) params
WHERE params.ExperimentID = sim1.ExperimentID
);
--And then use the result in your query.
;WITH validParameters
AS (
SELECT[ExperimentID]
FROM #parametersTable
WHERE [SensorError] = 0 AND [ExogDEXCurve] <> 4 AND [ControlRange] = 0
)
SELECT COUNT([Xa])*1.0/ ( #countOfSims ) AS XA_PIR0
FROM #simulationsTable sim2
INNER JOIN validParameters p on p.ExperimentID = sim2.ExperimentID
WHERE [Xa] <= 40
GO

Recursive triggers and error : subquery returned more than 1 value

I need your help !
I am working on sql server
1-- I created this trigger but it seems to be wrong...
CREATE TRIGGER [dbo].[chargeAZero]
ON [dbo].[situations_final]
after INSERT
AS
BEGIN
SET nocount ON
UPDATE sfinal
SET charge = 00
FROM inserted i
INNER JOIN situations_final sfinal
ON i.referencepiece = sfinal.referencepiece
AND i.ancienposte = sfinal.ancienposte
AND i.numerophase = sfinal.numerophase
AND i.datestrategie = sfinal.datestrategie
/*and i.Datecadence=sfinal.Datecadence*/
WHERE (SELECT sfinal.nouveauposte
FROM situations_final sfinal
INNER JOIN inserted i
ON i.referencepiece = sfinal.referencepiece
AND i.ancienposte = sfinal.ancienposte
AND i.numerophase = sfinal.numerophase
AND i.datestrategie = sfinal.datestrategie) IS
NULL
END
The error message is always the same: the subquery returned more than one value... I think I wrote my trigger correctly as I did with others that work fine.
2-- My second question is : Is it possible to make only one trigger recursive ?
3-- As you have noticed on my database on the table "Nomenclatures" (Bill of materials in english) I have 3 elements:
*codepiecemere: The component mother
*codepiecefille: the component child
* the quantity.
I give you an example of what I need :
Mother= A Child= B Quantity= 2
Mother= B Child= C Quantity= 3
I want a trigger to give me a result like that:
A 1 B 2 C 6=2*3 (the quantity needed of C to make 1 B).
Thank you very much
Here's a recursive query that solves the material aggregation problem.
Table definition
CREATE TABLE [dbo].[Material](
[Mother] [varchar](100) NOT NULL,
[Child] [varchar](100) NOT NULL,
[Quantity] [int] NOT NULL,
)
and the query:
WITH Result(mother, child, quantity)
AS
(
select * from material
union all
select M.mother, R.Child, M.quantity * R.Quantity as Quantity
from Result R INNER JOIN Material M ON M.Child = R.Mother
)
select * from result
You can see an example here: http://sqlfiddle.com/#!6/6dc64/1
UPDATE:
Sql fiddle is not working, I don't know why
UPDATE 2
Sql Fiddle is back! :-)
The is null is not normally used with subqueries. Try this:
where not exists (select 1
from SITUATIONS_Final sfinal inner join inserted i
on i.ReferencePiece=sfinal.ReferencePiece
and i.AncienPoste=sfinal.AncienPoste
and i.numerophase=sfinal.numerophase
and i.datestrategie=sfinal.datestrategie
)
This is assuming that the is null is testing for no values being returned, as opposed to a NULL value in sfinal.nouveauposte. If the latter:
where exists (select 1
from SITUATIONS_Final sfinal inner join inserted i
on i.ReferencePiece=sfinal.ReferencePiece
and i.AncienPoste=sfinal.AncienPoste
and i.numerophase=sfinal.numerophase
and i.datestrategie=sfinal.datestrategie
where sfinal.nouveauposte is null
)
EDIT:
Do you need the subquery at all?
UPDATE sfinal
SET charge = 00
FROM inserted i
INNER JOIN situations_final sfinal
ON i.referencepiece = sfinal.referencepiece
AND i.ancienposte = sfinal.ancienposte
AND i.numerophase = sfinal.numerophase
AND i.datestrategie = sfinal.datestrategie
WHERE sfinal.nouveauposte IS NULL;
I think the problem is that you are inserting more than one row in a single command, so the inserted table contains more than one row. As a consequence the sub query
SELECT sfinal.nouveauposte
FROM situations_final sfinal
INNER JOIN inserted i
ON i.referencepiece = sfinal.referencepiece
AND i.ancienposte = sfinal.ancienposte
AND i.numerophase = sfinal.numerophase
AND i.datestrategie = sfinal.datestrategie
contains more than one row too and cannot be compared to NULL that is a scalar value.
I am ambitious :D I tried to improve the script:
WITH RESULT (MOTHER, CHILD, QUANTITY)
as
(
select Mother, Child, CONVERT(Numeric(10,0), Quantity) as Quantity from bilangammestest
union all select M.mother, R.Child, CONVERT(Numeric(10,0), M.quantity * R.Quantity) as Quantity from Result R
INNER JOIN bilangammestest M ON M.Child = R.Mother
)
select * from result
where mother not in (select child from bilangammestest )
Here are the data I have on my table "Bilangammestest":
Z A 1
Z Y 1
A B 2
Y B 2
B C 3
Here are the result I get :
Z A 1
Z Y 1
Z C 6
Z C 6
Z B 2
Z B 2
Here is the Final result I want:
Z A 1
Z Y 1
Z C 12
Z B 4
I tried to do a 'sum' but I couldn't do it correctly :(

Resources