How to do calculations and then compare them in SQL Server - sql

I have a table with columns Recordnumber, Test, Value, Date and Complement. Recordnumber and test are the primary key.
I need compare values from TW01SS and TW01D1+TW01D2 with the same Recordnumber and depending on which value is bigger add it to Complement column. Any ideas?
Thank you

You can create two CTE's, one to get the SS count and the other the D1+D2 count then UPDATE the Complement column by the Recordnumber.
See WITH common_table_expression (Transact-SQL)
Specifies a temporary named result set, known as a common table
expression (CTE). This is derived from a simple query and defined
within the execution scope of a single SELECT, INSERT, UPDATE, DELETE
or MERGE statement. This clause can also be used in a CREATE VIEW
statement as part of its defining SELECT statement. A common table
expression can include references to itself. This is referred to as a
recursive common table expression.
;WITH
D1D2_Count
AS
-- Define D1+D2 Count CTE Query.
(
SELECT RecordNumber AS D1D2_ID,
SUM(Value) OVER(PARTITION BY RecordNumber) AS D1D2_Total
FROM TestA
WHERE Test LIKE '%D1' OR Test LIKE '%D2'
),
SS_Count
AS
-- Define SS Count CTE Query.
(
SELECT RecordNumber AS SS_ID,
SUM(Value) OVER(PARTITION BY RecordNumber) AS SS_Total
FROM TestA
WHERE Test LIKE '%SS'
)
UPDATE A
SET Complement =
CASE WHEN D1D2_Total > SS_Total
THEN D1D2_Total
ELSE SS_Total
END
FROM TestA AS A
LEFT JOIN D1D2_Count ON RecordNumber = D1D2_ID
LEFT JOIN SS_Count ON RecordNumber = SS_ID
See Fiddle.
Optionally, you can add a WHERE clause to the end of the UPDATE statement so that it'll only update the Complement that has the greater total
If D1+D2 > SS, UPDATE Only the D1+D2 rows
Otherwise, UPDATE Only the SS rows.
Like this:
WHERE Test LIKE
CASE WHEN D1D2_Total > SS_Total
THEN '%D1'
ELSE '%SS'
END
OR Test LIKE
CASE WHEN D1D2_Total > SS_Total
THEN '%D2'
ELSE '%SS'
END

select *, case when SS >= DX then SS else DX end
from T cross apply (values (
sum(case when Test = 'TW01SS1' then Value end)
over (partition by RecordNumber),
sum(case when Test like 'TW01D[12]' then Value end)
over (partition by RecordNumber))
) s(SS, DX);
You can use else coalesce(DX, SS) if null/missing tests are at play.
As an update:
update T
set Complement = (
select case when SS >= DX then SS else DX end
from T t2 cross apply (values (
sum(case when Test = 'TW01SS1' then Value end),
sum(case when Test like 'TW01D[12]' then Value end)
) s(SS, DX)
where t2.RecordNumber = T.RecordNumber
);

Related

Only one expression can be specified in the select list w

I am having problem in part of my code anyway to do this
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS. The update part is working but how to use insert into to calculate if a condition is not meant it will insert.
IF
/* CHECKLIST TO UPDATE*/
(NOT EXISTS
(SELECT *
FROM ENERGY.D_ENERGY_REFERENCE D_ENERGY_REFERENCE
,ENERGY.D_CHECK_LIST D_CHECK_LIST
WHERE D_ENERGY_REFERENCE.ID = D_CHECK_LIST.ID
AND D_ENERGY_REFERENCE.REFERENCE = 19051
)
)
BEGIN
INSERT INTO DB.D_ENERGY_REFERENCE(ID, REFERENCE_NO, REFERENCE,VALUE_INTEGER)
(SELECT ID,
(SELECT ISNULL(MAX(REFERENCE_NO), 0) + 1 FROM DB.D_ENERGY_REFERENCE),
19051, (SELECT D_CHECK_LIST.ID,
CASE
WHEN CAST(COUNT(CASE WHEN D_CHECK_LIST.EVALUATION NOT IN (0,1) THEN EVALUATION ELSE NULL END) AS FLOAT) = 0 THEN NULL
ELSE
(
CAST(COUNT(CASE WHEN D_CHECK_LIST.EVALUATION IN (2, 3, 50001, 50003, 50004, 50005, 50006, 50020, 50027, 50028) THEN EVALUATION ELSE NULL END) AS FLOAT)
/
CAST(COUNT(CASE WHEN D_CHECK_LIST.EVALUATION NOT IN (0,1) THEN EVALUATION ELSE NULL END) AS FLOAT)
) * 100
END FROM DB.D_CHECK_LIST
GROUP BY D_CHECK_LIST.ID)
FROM DB.D_ENERGY_REFERENCE D_ENERGY_REFERENCE
WHERE D_ENERGY_REFERENCE.ID = ID AND D_ENERGY_REFERENCE.REFERENCE = 19051
GROUP BY D_ENERGY_REFERENCE.ID
)
END
Can you please check this following part in the sub query of your script-
.......
19051,
(
SELECT
D_CHECK_LIST.ID, -- This is the column 1
CASE
WHEN -- Here you are generating column 2 in the sub query
......
)
Here you are selecting 2 column - one is "D_CHECK_LIST.ID" and other one is generation through CASE WHEN statement. I think you should SELECT any 1 column from those 2 column. If both are required, you can use separate Sub query for that.
The ERROR code "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS" is self explanatory that you can not implement a Sub Query with more than 1 column selected unless the Sub Query is using inside EXISTS method.

LEAD in SQL Server 2008

I have done a stored procedure with contains LEAD, unfortunately the script must run on 2008, so anyone know how to achieve this in 2008?
INSERT INTO #ARTICLES(EAN, ID_ART, QTE, PV_NET_HT)
SELECT EAN, ID_ART, QTE_CDE, PA_NET
FROM (
SELECT TEXTE_LIG,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN ID_ART ELSE LEAD(ID_ART) OVER (ORDER BY (SELECT NULL)) END AS ID_ART,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN CHAR01 ELSE LEAD(CHAR01) OVER (ORDER BY (SELECT NULL)) END AS EAN,
LEAD(QTE_CDE, CASE WHEN TEXTE_LIG LIKE '%++%' THEN 1 ELSE 2 END) OVER (ORDER BY (SELECT NULL)) AS QTE_CDE,
LEAD(PX_BASE, CASE WHEN TEXTE_LIG LIKE '%++%' THEN 2 ELSE 3 END, NULL) OVER (ORDER BY (SELECT NULL)) AS PX_BASE,
LEAD(PX_NET, CASE WHEN TEXTE_LIG LIKE '%++%' THEN 2 ELSE 3 END, NULL) OVER (ORDER BY (SELECT NULL)) AS PA_NET
FROM AMAZON_ACHATS_LIG
WHERE ID_ACHATS_ENT = #IDENTITY
) AS LIGNE
WHERE TEXTE_LIG LIKE 'LIN%';
To replace LEAD in 2008, you need to do a self join to the same table, to the next row. To do this, the easiest way is to have a contiguous ID of some sort, and you can join to the record with ID + one. If no suitable contiguous ID exists, then select your data in a CTE, and add a row_number to it. Then use that row_number in the outer query for the self join.
Incidentally, be aware there is no such thing as "keeping the database order". If you don't have an order specified for a given query, then SQL will decide the output order, which may be the same order as entry, or may be something totally different, or may be mostly the same order except for a few records. It may return totally different orders for the same query on different occasions, depending on which query plan it decides to use this time. If you want data to keep the same order it was entered in, you need to have an auto-incrementing identity column to ensure that is possible, or someday you will not get what you expect.
In your query, your various leads have offsets of none (same as 1), 1, 2 and 3, so you will need to self join three times to cover all of those options. In your query, you then replace each of the LEAD with the data from the correct self-joined table. You want something like this:
WITH BASEDATA AS (
--THIS IS YOUR BASIC DATA, WITH A ROW NUMBER ADDED
-- DO THIS AS A CTE, SO YOU CAN JOIN TO IT MULTIPLE TIMES
SELECT TEXTE_LIG, ID_ART, CHAR01, QTE_CDE, PX_BASE, PX_NET, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RowNo
FROM AMAZON_ACHATS_LIG
WHERE ID_ACHATS_ENT = #IDENTITY
)
INSERT INTO #ARTICLES(EAN, ID_ART, QTE, PV_NET_HT)
SELECT EAN, ID_ART, QTE_CDE, PA_NET
FROM (
SELECT TEXTE_LIG,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN ID_ART ELSE B1.ID_ART END AS ID_ART,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN CHAR01 ELSE B1.CHAR01 END AS EAN,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN B1.QTE_CDE ELSE B2.QTE_CDE END AS QTE_CDE,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN B2.PX_BASE ELSE B3.PX_BASE END AS PX_BASE,
CASE WHEN TEXTE_LIG LIKE '%++%' THEN B2.PX_NET ELSE B3.PX_NET END AS PA_NET
FROM BASEDATA B0 --THE BASIC DATA
LEFT OUTER JOIN BASEDATA B1 ON B1.RowNo = B0.RowNo + 1 --This is your LEAD (1) table
LEFT OUTER JOIN BASEDATA B2 ON B2.RowNo = B0.RowNo + 2 --This is your LEAD (2) table
LEFT OUTER JOIN BASEDATA B3 ON B3.RowNo = B0.RowNo + 3 --This is your LEAD (3) table
--WHERE ID_ACHATS_ENT = #IDENTITY --DON'T NEED THIS AMY MORE, DONE IN CTE
) AS LIGNE
WHERE TEXTE_LIG LIKE 'LIN%';

Filter if values provided otherwise return everything

Say I have a table t with 2 columns:
a int
b int
I can do a query such as:
select b
from t
where b > a
and a in(1,2,3)
order by b
where 1,2,3 is provided from the outside.
Obviously, the query can return no rows. In that case, I'd like to select everything as if the query did not have the and a in(1,2,3) part. That is, I'd like:
if exists (
select b
from t
where b > a
and a in(1,2,3)
)
select b
from t
where b > a
and a in(1,2,3)
order by b
else
select b
from t
where b > a
order by b
Is there a way to do this:
Without running two queries (one for exists, the other one the actual query)
That is less verbose than repeating queries (real queries are quite long, so DRY and all that stuff)
Using NOT EXISTS with a Sub Query to Determine if condition exists
SELECT b
FROM
t
WHERE
b > a
AND (
NOT EXISTS (SELECT 1 FROM #Table WHERE a IN (1,2,3))
OR a IN (1,2,3)
)
ORDER BY
b
The reason this works is because if the condition exists then the OR statement will include the rows and if the condition does not exist then the NOT EXISTS will include ALL rows.
Or With Common Table Expression and window Function with Conditional Aggregation.
WITH cte AS (
SELECT
b
,CASE WHEN a IN (1,2,3) THEN 1 ELSE 0 END as MeetsCondition
,COUNT(CASE WHEN a IN (1,2,3) THEN a END) OVER () as ConditionCount
FROM
t
)
SELECT
b
FROM
cte
WHERE
(ConditionCount > 0 AND MeetsCondition = 1)
OR (ConditionCount = 0)
ORDER BY
b
I find it a bit "ugly". Maybe it would be better to materialize output from your query within a temp table and then based on count from temp table perform first or second query (this limits accessing the original table from 3 times to 2 and you will be able to add some flag for qualifying rows for your condition not to repeat it). Other than that, read below . . .
Though, bear in mind that EXISTS query should execute pretty fast. It stops whether it finds any row that satisfies the condition.
You could achieve this using UNION ALL to combine resultset from constrained query and full query without constraint on a column and then decide what to show depending on output from first query using CASE statement.
How CASE statement works: when any row from constrained part of your query is found, return resultset from constrainted query else return everything omitting the constraint.
If your database supports using CTE use this solution:
with tmp_data as (
select *
from (
select 'constraint' as type, b
from t
where b > a
and a in (1,2,3) -- here goes your constraint
union all
select 'full query' as type, b
from t
where b > a
) foo
)
SELECT b
FROM tmp_data
WHERE
CASE WHEN (select count(*) from tmp_data where type = 'constraint') > 0
THEN type = 'constraint'
ELSE type = 'full query'
END
;

How to set value to variable in select query in SQL Server 2012?

I want to generate a sequence for a complex query. For that purpose I have used a variable #rowNo.
My logic is :-
If a field - isremoved = 0 then increase row number by 1.
If isremoved = 1 then increase row number by 1 only once till you find next 0.
This is so far I have done, but it gives me syntax error.
DECLARE #rowNo INT;
SET #rowNo = -1;
SELECT
case when sampleTable.isremoved = 0 then #rowNo + 1
else #rowNo end
as rowNumber,
X,
Y,
Z
.
.
FROM tbl_sample sampleTable
INNER JOIN tbl_sample_2 sample2 ON sampleTable.id = sample2.id
.
.
.
This is my desire output :-
So what is the right way to achieve this kind of functionality in sql server 2012 ?
EDIT :-
I do have one solution to use sub query to retrieve row number. But that will hit performance as it is very complex query (more than 20 joins) with huge amount of data.
So please suggest me an alternative.
In SQL Server 2012+, you can do what you want with a cumulative sum:
SELECT sum(case when sampleTable.isremoved = 0 then 1 else 0 end) over
(order by . . .)
. . .
The order by should repeat the order by in the outer query. You can also try using order by (select null)). In my experience, this uses the ordering of the data in the outer query, but this is not documented to always work.
SQL Server does not allow you to set variables in a SELECT and to return values in a result set. One or the other, but not both.
one way to do this is with subquery:
Select (Select count(*) from tbl_sample
where id <= a.id
and a.isRemoved =1) rowNumber,
X, Y, Z
From tbl_sample a
Join tbl_sample_2 b
On b.id = a.id
I assume your rowNumber logic is equivalent to: if previous isremoved = 0 then increment rowNumber by 1, otherwise keep it as it is.
Using a combination of LAG and SUM ... OVER you can easily implement this logic:
SELECT id, isremoved,
SUM(IIF(prevFlag = 0, 1, 0)) OVER (ORDER BY id) AS rowNumber
FROM (
SELECT a.id, isremoved,
COALESCE(LAG(isremoved) OVER (ORDER BY a.id), isremoved) AS prevFlag
FROM tbl_sample AS a
INNER JOIN tbl_sample_2 AS b ON b.id = a.id) AS t
Demo here

Assign null if subquery retrieves multiple records. How can it be done?

I have the following query. I simplified it for demo purpose. I am using SQL Server - t-sql
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID )
from tblMain tm
if the subquery returns multiple records, I like to assign tm.LocID to null else if there is only 1 record returned then assign it to tm.LocID. I am looking for a simple way to do this. Any help would be appreciated.
One way I can see is to have a CASE statement and check if (Count * > 1 ) then assign null else return the value but that would require a select statement within a select statement.
You have the right idea about using a case expression for count(*), but it will not require another subquery:
SELECT tm.LocID = (SELECT CASE COUNT(*) WHEN 1 THEN MAX(LocID) END
FROM tblLoc tl
WHERE tl.LocID = tm.LodID )
FROM tblMain tm
or just use a HAVING clause, like
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID
group by locID
having count(*) = 1)
)
from tblMain tm
Your query above (and many of the other answers here) is a correlated subquery which will be very slow since it performs a separate aggregation query on each record. This following will address both your problem and potentially perform a bit better since the count happens in a single pass.
SELECT
CASE
WHEN x.locid IS NOT NULL THEN x.locid
ELSE NULL
END
FROM tblMain m
LEFT JOIN (
SELECT
locid
FROM tblLoc
GROUP BY locid
HAVING COUNT(1) = 1
) x
ON x.locid = m.locid
;
The above is in Postgres syntax (what I'm familiar with) so you would have to make it TSQL compatible.