SQL Update with Min and Sum and Group By - sql

I'm looking to write an update that will correct a flag that's gone astray.
I'd like to set the PRIMARY_FLAG = 1 for the min(TABLE_ID) grouped by ACCOUNT_NUMBER where the sum(PRIMARY_FLAG) <> 1 grouped by ACCOUNT_NUMBER.
Here is what the table looks like now:
TABLE_ID ACCOUNT_NUMBER PRIMARY_FLAG
-------- -------------- ------------
1 ABC123 0
2 ABC123 1
3 ABC123 0
4 987XYZ 0
5 987XYZ 0
6 987XYZ 0
7 5A5B5C 1
8 5A5B5C 1
9 5A5B5C 0
10 5A5B5C 0
Here's what I want it to look like after the update:
TABLE_ID ACCOUNT_NUMBER PRIMARY_FLAG
-------- -------------- ------------
1 ABC123 0
2 ABC123 1
3 ABC123 0
4 987XYZ 1
5 987XYZ 0
6 987XYZ 0
7 5A5B5C 1
8 5A5B5C 0
9 5A5B5C 0
10 5A5B5C 0
Scenario 1 - TABLE_ID 1, 2, 3 with ACCOUNT_NUMBER = ABC123 is already correct and I do not want the update to touch it.
Scenario 2 - ACCOUNT_NUMBER = 987XYZ, none of the TABLE_ID have a PRIMARY_FLAG = 1, so update will set PRIMARY_FLAG = 1 where TABLE_ID = 4
Scenario 3 - ACCOUNT_NUMBER = 5A5B5C has multiple TABLE_ID = 1, so update would leave TABLE_ID 7 but SET PRIMARY_FLAG = 0 where TABLE_ID = 8
Help is appreciate!

Here is a generic method:
update t
set primary_flag = (case when t.id = (select min(t2.id)
from t t2
where t2.account_number = t.account_number
)
then 1 else 0
end)
where t.account_number in (select t2.acount_number
from t t2
group by t2.acount_number
having sum(t2.primary_flag) <> 1
);
This is standard SQL and will work in most databases. However, specific databases might have more concise or efficient methods of doing the same thing.

Join the table to the query that returns the minimum table_id for each account_number that needs to be updated:
update t
set t.primary_flag = case when t.table_id = g.minid then 1 else 0 end
from tablename t inner join (
select account_number, min(table_id) minid
from tablename
group by account_number
having sum(primary_flag) <> 1
) g on g.account_number = t.account_number;
See the demo.
Or with a CTE:
with cte as (
select *,
row_number() over (partition by account_number order by table_id) rn,
sum(primary_flag) over (partition by account_number) total
from tablename
)
update cte
set primary_flag = case when rn = 1 then 1 else 0 end
where total <> 1
See the demo.
To avoid unnecessary updates for the rows that do not need to be updated:
with cte as (
select *,
row_number() over (partition by account_number order by table_id) rn,
sum(primary_flag) over (partition by account_number) total
from tablename
)
update cte
set primary_flag = abs(primary_flag - 1)
where (total <> 1) and ((rn = 1 and primary_flag = 0) or (rn > 1 and primary_flag = 1))
See the demo.
Results:
> TABLE_ID | ACCOUNT_NUMBER | PRIMARY_FLAG
> -------: | :------------- | -----------:
> 1 | ABC123 | 0
> 2 | ABC123 | 1
> 3 | ABC123 | 0
> 4 | 987XYZ | 1
> 5 | 987XYZ | 0
> 6 | 987XYZ | 0
> 7 | 5A5B5C | 1
> 8 | 5A5B5C | 0
> 9 | 5A5B5C | 0
> 10 | 5A5B5C | 0

Related

How to query all months from January to December

How to query all months from January to December for all values. The values of 0 should also be presented in query result.
My query:
SELECT *
FROM
(SELECT
MONTH(begin_ts) AS [Month],
SUM(CASE
WHEN bmktonr = '7'
THEN dauer
ELSE reserve1
END) + SUM(CASE
WHEN bmktonr = '11'
THEN dauer
ELSE reserve1
END) AS Prozess_Verfügbarkeit,
SUM(CASE
WHEN bmktonr = '1'
THEN dauer
ELSE reserve1
END) + SUM(CASE
WHEN bmktonr = '2'
THEN dauer
ELSE reserve1
END) AS Verfügbarkeit
FROM
[hydra1].[hydadm].[v_ereignis]
WHERE
masch_nr = 'FIMI1'
AND YEAR(begin_ts) = YEAR(CURRENT_TIMESTAMP)
GROUP BY
MONTH(begin_ts)) T
INNER JOIN
(SELECT
p.masch_nr,
SUM(b.ruest_zeit) AS SOLLRüsten,
SUM(b.bearb_zeit) AS SOLLProduktion,
SUM(b.ruest_zeit_zuschl) AS SOLLZuschlag,
SUM(p.bmk_07) AS ISTRüsten,
SUM(p.bmk_11) AS ISTProduktion,
MONTH(prot_dat) AS Month
FROM
[hydra1].[hydadm].[v_auftrag_status] p
JOIN
[hydra1].[hydadm].[v_auftrags_bestand] b ON b.auftrag_nr = p.auftrag_nr
WHERE
p.masch_nr = 'GEORG'
AND a_status = 'E'
AND YEAR(prot_dat) = YEAR(CURRENT_TIMESTAMP)
GROUP BY
p.masch_nr, MONTH(prot_dat)) T1 ON T.Month = T1.Month
ORDER BY
T.Month
This is how it should look then:
Month | Prozess_Verfügbarkeit | Verfügbarkeit
------+-----------------------+--------------
1 | 344 | 4556
2 | 0 | 0
3 | 0 | 0
4 | 0 | 0
5 | 0 | 0
6 | 0 | 0
7 | 0 | 0
8 | 0 | 0
9 | 0 | 0
10 | 0 | 0
11 | 0 | 0
12 | 0 | 0
Thanks a lot.
Something like this ought to do it:
WITH months AS (
SELECT 1 AS month_number
UNION ALL SELECT 2
UNION ALL SELECT 3
...
UNION ALL SELECT 12
)
SELECT months.month_number
, your_query.thing_a
, your_query.thing_b
FROM months
LEFT
JOIN your_query
ON your_query.Month = months.month_number
;

Distinct Conditional Counting to Avoid Overlap

Consider this table:
[Table1]
------------------------
| Person_ID | Yes | No |
|-----------|-----|----|
| 1 | 1 | 0 |
|-----------|-----|----|
| 1 | 1 | 0 |
|-----------|-----|----|
| 2 | 0 | 1 |
|-----------|-----|----|
| 2 | 0 | 1 |
|-----------|-----|----|
| 3 | 1 | 0 |
|-----------|-----|----|
| 3 | 1 | 0 |
|-----------|-----|----|
| 3 | 0 | 1 |
|-----------|-----|----|
| 3 | 1 | 0 |
------------------------
I need a distinct count on Person_ID to get the number of people that are marked Yes and No. However, if someone has a single instance of No, they should be counted as a No and not be included in the Yes count no matter how many Yes they have.
My first thought was to try something similar to:
select count(distinct (case when Yes = 1 then Person_ID else null end)) Yes_People
, count(distinct (case when No = 1 then Person_ID else null end)) No_People
from Table1
but this will result in 3 being counted in both the Yes and No counts.
My desired output would be:
--------------------------
| Yes_People | No_People |
|------------|-----------|
| 1 | 2 |
--------------------------
I'm hoping to avoid the performance hit from having to evaluate a subquery against each row but if it has to be the way to go I will accept that.
Aggregate first at the person level and then overall:
select sum(yes_only) as yes_only,
sum(1 - yes_only) as no
from (select person_id,
(case when max(yes) = min(yes) and max(yes) = 1
then 1
end) as yes_only
from t
group by person_id
) t
You can first group them by the person.
Then the CASE for the Yes people can have a not No condition.
SELECT
COUNT(CASE WHEN No = 0 AND Yes = 1 THEN Person_ID END) AS Yes_People,
COUNT(CASE WHEN No = 1 THEN Person_ID END) AS No_People
FROM
(
select Person_ID
, MAX(Yes) as Yes
, MAX(No) as No
FROM Table1
GROUP BY Person_ID
) q
You could use a window function to rank the rows for a single person_id to prioritize a 'No' over a 'Yes', but that will require a subquery
select count(case when yes=1 then 1 end) as yes_count,
count(case when no=1 then no_count) as no_count
from (
select person_id, yes, no, row_number() over (order by no desc, yes desc) as rn
from table1
)
where rn = 1
The inner subquery plus the where filter will get you a single row per person_id, giving priority to the 'no' records.
This of course assumes yes/no are mutually exclusive, and if that's true, you should probably change the model to a single field.
Think you need to precheck every person with a window function
with t as (select 1 p_id, 1 yes, 0 no from dual
union all select 1 p_id, 1 yes, 0 no from dual
union all select 2 p_id, 0 yes, 1 no from dual
union all select 2 p_id, 0 yes, 1 no from dual
union all select 3 p_id, 1 yes, 0 no from dual
union all select 3 p_id, 0 yes, 1 no from dual
union all select 3 p_id, 1 yes, 0 no from dual)
, chk as (
select max(no) over (partition by p_id) n
, max(yes) over (partition by p_id) y
, p_id
from t)
-- select * from chk;
select count(distinct decode(y-n,1,p_id,null )) yes_people
, count(distinct decode(n,1,p_id,null )) no_people
from chk
group by 1;
You can use Conditional aggregation as following:
SQL> with table1 as (select 1 PERSON_ID, 1 yes, 0 no from dual
2 union all select 1 PERSON_ID, 1 yes, 0 no from dual
3 union all select 2 PERSON_ID, 0 yes, 1 no from dual
4 union all select 2 PERSON_ID, 0 yes, 1 no from dual
5 union all select 3 PERSON_ID, 1 yes, 0 no from dual
6 union all select 3 PERSON_ID, 0 yes, 1 no from dual
7 union all select 3 PERSON_ID, 1 yes, 0 no from dual)
8 SELECT
9 SUM(CASE WHEN NOS = 0 AND YES > 0 THEN 1 END) YES_PEOPLE,
10 SUM(CASE WHEN NOS > 0 THEN 1 END) NO_PEOPLE
11 FROM
12 (
13 SELECT
14 SUM(NO) NOS,
15 PERSON_ID,
16 SUM(YES) YES
17 FROM TABLE1
18 GROUP BY PERSON_ID
19 );
YES_PEOPLE NO_PEOPLE
---------- ----------
1 2
SQL>
Cheers!!

SQL Query for below scenario for multiple rows

Need response as per expected result column in attached image.
The row filtration is required in multiple rows
The rule is (x.attr2 = '1' AND x.attr3 = '1') AND (x.attr2='' AND x.attr3='2') then its expected column value is true but all other conditions its false
Its MS SQL
Key Atr2 Atr3 expected result
111 1 1 TRUE
111 2 2
112 1 4 FALSE
113 1 4 FALSE
113 2 2
114 1 1 FALSE
Check the below script-
IF OBJECT_ID('[Sample]') IS NOT NULL
DROP TABLE [Sample]
CREATE TABLE [Sample]
(
[Key] INT NOT NULL,
Attr1 INT NOT NULL,
Attr2 INT NOT NULL,
Attr3 INT NOT NULL
)
GO
INSERT INTO [Sample] ([Key],Attr1,Attr2,Attr3)
VALUES (111,62,1,1),
(111,62,2,2),
(112,62,1,4),
(113,62,1,4),
(113,62,2,2),
(114,62,1,1)
--EXPECTED_RESULT:
SELECT S.*,CASE WHEN T.[KEY] IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END AS Expected_Result
FROM [Sample] S LEFT JOIN
(
SELECT T.[KEY] FROM
(
SELECT x.*,
ROW_NUMBER() OVER( PARTITION BY x.[KEY],x.attr1 ORDER BY x.attr2,x.attr3) AS r_no
--,CASE WHEN (x.attr2 = 1 AND x.attr3 = 1) OR (x.attr2 = 2 AND x.attr3 = 2)
--then 'TRUE' else 'FALSE' end as expected_result
FROM [Sample] x WHERE x.attr2=x.attr3
) T WHERE T.r_no>1
) T ON S.[KEY]=T.[KEY]
This query:
select key from tablename
group by key
having sum(case when atr2 = '1' and atr3 = '1' then 1 else 0 end) > 0
and sum(case when atr2 = '2' and atr3 = '2' then 1 else 0 end) > 0
and count(*) = 2
uses conditional aggregation to find the keys for which the result should be true.
So join it to the table like this:
select t.*,
case when g.[key] is null then 'FALSE' else 'TRUE' end result
from tablename t left join (
select [key] from tablename
group by [key]
having sum(case when atr2 = '1' and atr3 = '1' then 1 else 0 end) > 0
and sum(case when atr2 = '2' and atr3 = '2' then 1 else 0 end) > 0
and count(*) = 2
) g on g.[key] = t.[key]
See the demo.
Results:
> Key | Atr2 | Atr3 | result
> --: | ---: | ---: | :-----
> 111 | 1 | 1 | TRUE
> 111 | 2 | 2 | TRUE
> 112 | 1 | 4 | FALSE
> 113 | 1 | 4 | FALSE
> 113 | 2 | 2 | FALSE
> 114 | 1 | 1 | FALSE

Select As Status If Subtable Lines Include Value

I have 2 Table named Order and OrderDetails
Order Table
orderID orderDate OrderUserId
1 10 01.01.2010 .....
2 20 05.05.2011 .....
OrderDetails Table
DetailID OrderIdOfDetail DetailProductID DetailStatusID
1 25 10 xxx 1
2 26 10 xxx 2
3 27 10 xxx 2
4 28 10 xxx 0
5 29 10 xxx 1
6 30 20 xxx 1
7 31 20 xxx 2
8 32 20 xxx 0
DetailStatusId for closed , pending, cancelled bla...
For example I want to select orderID : 10.
If detail lines include DetailsStatusID 1 select order Status as "Pending".
If detail lines include DetailsStatusID 2 select order Status as "Open".
If all DetailsStatusID is equal and 0 select order status as "Closed"
I tried "if exist" but couldn't success.
How can I do it ?
You can use a query like the following:
SELECT o.orderID,
CASE
WHEN MAX(CASE WHEN DetailStatusID = 1 THEN 1 ELSE 0 END) +
MAX(CASE WHEN DetailStatusID = 2 THEN 1 ELSE 0 END) +
MAX(CASE WHEN DetailStatusID = 0 THEN 1 ELSE 0 END) >= 2
THEN 'MultiStatus'
WHEN COUNT(CASE WHEN DetailStatusID = 1 THEN 1 END) > 0 THEN 'Pending'
WHEN COUNT(CASE WHEN DetailStatusID = 2 THEN 1 END) > 0 THEN 'Open'
WHEN MAX(DetailStatusID) = 0 THEN 'Closed'
END AS Status
FROM Order AS o
JOIN OrderDetails AS od ON o.orderID = od.OrderIdOfDetail
GROUP BY orderID
The query uses a CASE expression in order to calculate the Status field:
It first checks whether the order contains at least one record with DetailStatusID = 1. If it does 'Pending' is returned.
It then checks whether the order contains at least one record with DetailStatusID = 2. If it does 'Open' is returned.
It then checks whether all records of the group have DetailStatusID = 0. In this case 'Closed' is returned. I assume that DetailStatusID cannot take negative vaues.
This uses a left join to an derived table (subquery) that uses conditional aggregation to count the different DetailStatusId. This will also return a result of Other for Status if, for some reason, the OrderId has no corresponding details record.
select o.*
, [Status]=case
when od.Status0 > 0 and od.Status0 = od.DetailCount
then 'Closed'
when od.Status1 > 0
then 'Pending'
when od.Status2 > 0
then 'Open'
else 'Other'
end
from orders as o
left join (
select
OrderIdOfDetail
, DetailCount = count(*)
, Status0 = sum(case when DetailStatusId = 0 then 1 else 0 end)
, Status1 = sum(case when DetailStatusId = 1 then 1 else 0 end)
, Status2 = sum(case when DetailStatusId = 2 then 1 else 0 end)
from OrderDetails
group by OrderIdOfDetail
) as od
on o.OrderId = od.OrderIdOfDetail
rextester demo: http://rextester.com/YPA79670
query results:
+---------+---------------------+-------------+---------+
| orderID | orderDate | OrderUserId | Status |
+---------+---------------------+-------------+---------+
| 10 | 01.01.2010 00:00:00 | 1 | Pending |
| 20 | 05.05.2011 00:00:00 | 2 | Pending |
+---------+---------------------+-------------+---------+
subquery only results:
+-----------------+-------------+---------+---------+---------+
| OrderIdOfDetail | DetailCount | Status0 | Status1 | Status2 |
+-----------------+-------------+---------+---------+---------+
| 10 | 5 | 1 | 2 | 2 |
| 20 | 3 | 1 | 1 | 1 |
+-----------------+-------------+---------+---------+---------+
select *, case when DetailStatusID = '1' then 'Pending'
when DetailStatusID = '2' then 'Open'
when DetailStatusID = '0' then 'Closed'
end as OrderStatusDescription
From OrderDetails
Where OrderIdOfDetail = '10'

SQL Server 2008 Cumulative Sum that resets value

I want to have the last column cumulative based on ROW_ID that resets every time it starts again with '1'.
Initially my table doesn't have the ROW_ID, this was created using partition so at least I can segregate my records.
It should add the Amt + CumulativeSum (except for the first record) all the way down and reset every time the Row_ID = 1.
I have tried several queries but it doesn't give me the desired result. I am trying to read answers from several forums but to no avail.
Can someone advise the best approach to do this?
For the sake of representation, I made the sample table as straightforward as possible.
ID ROW-ID Amt RunningTotal(Amt)
1 1 2 2
2 2 4 6
3 3 6 12
4 1 2 2
5 2 4 6
6 3 6 12
7 4 8 20
8 5 10 30
9 1 2 2
10 2 4 6
11 3 6 12
12 4 8 20
try this
declare #tb table(ID int, [ROW-ID] int, Amt money)
insert into #tb(ID, [ROW-ID], Amt) values
(1,1,2),
(2,2,4),
(3,3,6),
(4,1,2),
(5,2,4),
(7,4,8),
(8,5,10),
(9,1,2),
(10,2,4),
(11,3,6),
(12,4,8)
select *,sum(amt) over(partition by ([id]-[row-id]) order by id,[row-id]) AS cum from #tb
other version
select *,(select sum(amt) from #tb t where
(t.id-t.[row-id])=(t1.id-t1.[ROW-ID]) and (t.id<=t1.id) ) as cum
from #tb t1 order by t1.id,t1.[row-id]
Try this
SELECT distinct (T1.ID),
T1.ROW_ID,
T1.Amt,
CumulativeSum =
CASE
WHEN T1.RoW_ID=1 THEN T1.Amt
ELSE T1.Amt+ T2.Amt
END
FROM TestSum T1, TestSum T2
WHERE T1.ID = T2.ID+1
http://sqlfiddle.com/#!6/8b2a2/2
The idea is to create partitions from R column. First leave 1 if R = 1, else put 0. Then cumulative sum on that column. When you have partitions you can finally calculate cumulative sums on S column in those partitions:
--- --- ---
| 1 | | 1 | | 1 |
| 2 | | 0 | | 1 | --prev 1 + 0
| 3 | | 0 | | 1 | --prev 1 + 0
| 1 | | 1 | | 2 | --prev 1 + 1
| 2 | => | 0 | => | 2 | --prev 2 + 0
| 3 | | 0 | | 2 | --prev 2 + 0
| 4 | | 0 | | 2 | --prev 2 + 0
| 5 | | 0 | | 2 | --prev 2 + 0
| 1 | | 1 | | 3 | --prev 2 + 1
| 2 | | 0 | | 3 | --prev 3 + 0
--- --- ---
DECLARE #t TABLE ( ID INT, R INT, S INT )
INSERT INTO #t
VALUES ( 1, 1, 2 ),
( 2, 2, 4 ),
( 3, 3, 6 ),
( 4, 1, 2 ),
( 5, 2, 4 ),
( 6, 3, 6 ),
( 7, 4, 8 ),
( 8, 5, 10 ),
( 9, 1, 2 ),
( 10, 2, 4 ),
( 11, 3, 6 ),
( 12, 4, 8 );
For MSSQL 2008:
WITH cte1
AS ( SELECT ID ,
CASE WHEN R = 1 THEN 1
ELSE 0
END AS R ,
S
FROM #t
),
cte2
AS ( SELECT ID ,
( SELECT SUM(R)
FROM cte1 ci
WHERE ci.ID <= co.ID
) AS R ,
S
FROM cte1 co
)
SELECT * ,
( SELECT SUM(S)
FROM cte2 ci
WHERE ci.R = co.R
AND ci.ID <= co.ID
)
FROM cte2 co
For MSSQL 2012:
WITH cte
AS ( SELECT ID ,
SUM(CASE WHEN R = 1 THEN 1
ELSE 0
END) OVER ( ORDER BY ID ) AS R ,
S
FROM #t
)
SELECT * ,
SUM(s) OVER ( PARTITION BY R ORDER BY ID ) AS T
FROM cte
Output:
ID R S T
1 1 2 2
2 1 4 6
3 1 6 12
4 2 2 2
5 2 4 6
6 2 6 12
7 2 8 20
8 2 10 30
9 3 2 2
10 3 4 6
11 3 6 12
12 3 8 20
EDIT:
One more way. This looks way better by execution plan then first example:
SELECT * ,
CASE WHEN R = 1 THEN S
ELSE ( SELECT SUM(S)
FROM #t it
WHERE it.ID <= ot.ID
AND it.ID >= ( SELECT MAX(ID)
FROM #t iit
WHERE iit.ID < ot.ID
AND iit.R = 1
)
)
END
FROM #t ot