SQL Server 2008 Combine or test rows based on value - sql

I have a table that looks something like this:
+------------+---------+--------+---------+--------+--------+
| emplname | JAN | FEB | MAR | APR | MAY |
+------------+---------+--------+---------+--------+--------+
| ALLEN | NULL | NULL | 17 | 17 | 17 |
| ALLEN | 17 | 17 | 205 | NULL | NULL |
| ADAMS | NULL | NULL | 14 | 14 | 17 |
| BROWN | NULL | 205 | 13 | 13 | 13 |
| BROWN | 12 | 12 | NULL | NULL | NULL |
+------------+---------+--------+---------+--------+--------+
I want to combine the rows where the emplname is the same and ignoring 205 values, so for ALLEN, I would get
| ALLEN | 17 | 17 | 17 | 17 | 17 |
for BROWN it would be
| BROWN | 12 | 12 | 13 | 13 | 13 |
Basically what I want to do is be able to find if, per employee, if a column has two entries for it except if it is an entry plus 205, i can ignore 205. What I want to find is something like this:
| BROWN | NULL | 13 | 13 | 13 | 13 |
| BROWN | 12 | 12 | NULL | NULL | NULL |
where that third column has 13 and 12 for that one employee. I am looking for things like that. if it exists, return the emplname. Hope that makes sense.

You could try this (I think I am understanding you correctly, this will output names of employees who need two rows)
SELECT emplname FROM yourtable
GROUP BY emplname
HAVING COUNT(CASE WHEN JAN = 205 THEN NULL ELSE JAN END) > 1 OR
COUNT(CASE WHEN FEB = 205 THEN NULL ELSE FEB END) > 1 OR
COUNT(CASE WHEN MAR = 205 THEN NULL ELSE MAR END) > 1 OR
COUNT(CASE WHEN APR = 205 THEN NULL ELSE APR END) > 1 OR
COUNT(CASE WHEN MAY = 205 THEN NULL ELSE MAY END) > 1
Let me know if that was what you have in mind; I think it answers the second part of the question.

Join the table to itself and then use the following statement
COALESCE(CASE WHEN table1.fieldname = 205 THEN NULL ELSE table1.fieldname END,
CASE WHEN table2.fieldname = 205 THEN NULL ELSE table2.fieldname END,0)
I use 0 here to mark an error but you could leave it out so you get null when you have a problem.

Try this SQL:
SELECT T.emplname
, ISNULL(T.JAN,IIF(T205.JAN=205,NULL,T205.JAN))
, ISNULL(T.FEB,IIF(T205.FEB=205,NULL,T205.FEB))
, ISNULL(T.MAR,IIF(T205.MAR=205,NULL,T205.MAR))
, ISNULL(T.APR,IIF(T205.APR=205,NULL,T205.APR))
, ISNULL(T.MAY,IIF(T205.MAY=205,NULL,T205.MAY))
FROM myTable as T left join myTable as T205 on T.emplname = T205.emplname
AND (T.JAN <> 205 AND T.FEB <> 205 AND T.MAR <> 205
AND T.APR <> 205 AND T.MAY <> 205)
AND (T205.JAN = 205 OR T205.FEB = 205 OR T205.MAR = 205
OR T205.APR = 205 OR T205.MAY = 205)

Related

How to display results for each year in a new column in SQL

How can we show result of each year between the condition dates in separate column?
Can we show '0' for "EQ_no" filed which is not in "Jobs" table.
select A.EQ_no,A.Serial_no,sum(J.total_cost)Total_Cost,YEAR(Job_Date) as Job_Year
from Equipment A
left OUTER JOIN Jobs J ON J.EQ_no=A.EQ_no
where J.Job_Date BETWEEN '01/01/2018' AND DATEADD(DAY, 1, '12/31/2020')
group by A.EQ_no,A.Serial_no,YEAR(Job_Date)
Table : Jobs
+-------+------------+------------+
| EQ_no | Job_Date | Total_Cost |
+-------+------------+------------+
| 1006 | 01/30/2017 | 250 |
| 1006 | 01/31/2018 | 350 |
| 1006 | 01/01/2019 | 150 |
| 1006 | 02/01/2019 | 322 |
| 1006 | 05/05/2019 | 450 |
| 1006 | 02/02/2020 | 500 |
| 1006 | 02/03/2021 | 1212 |
| 29198 | 02/04/2017 | 3000 |
| 29198 | 02/05/2018 | 250 |
+-------+------------+------------+
Table : Equipment
+-------+-----------+
| EQ_no | Serial no |
+-------+-----------+
| 1006 | MDRSC12 |
| 29198 | FDRSC13 |
| 6218 | REAFC14 |
+-------+-----------+
Result
+-------+-----------+------+------+------+
| EQ_no | Serial no | 2018 | 2019 | 2020 |
+-------+-----------+------+------+------+
| 1006 | MDRSC12 | 350 | 922 | 500 |
| 29198 | FDRSC13 | 250 | 0 | 0 |
| 6218 | REAFC14 | 0 | 0 | 0 |
+-------+-----------+------+------+------+
SQL Version : 2014
Add CASE statement inside the SUM aggregate function.
And use ISNULL function to replace the Null values by '0'
select A.EQ_no,A.Serial_no,ISNULL(MAX(J.EQ_no),0) AS Job_Eq_No
,ISNULL(sum(CASE WHEN YEAR(Job_Date) = 2018 THEN J.total_cost END),0)[2018]
,ISNULL(sum(CASE WHEN YEAR(Job_Date) = 2019 THEN J.total_cost END),0)[2019]
,ISNULL(sum(CASE WHEN YEAR(Job_Date) = 2012 THEN J.total_cost END),0)[2020]
from Equipment A
left OUTER JOIN Jobs J ON J.EQ_no=A.EQ_no
where J.Job_Date BETWEEN '01/01/2018' AND DATEADD(DAY, 1, '12/31/2020')
group by A.EQ_no,A.Serial_no
First, a left join is not needed for your query.
Second, you should be using proper date constants.
Third, you can do this with conditional aggregation:
select e.EQ_no, e.Serial_no,
sum(case when year(j.job_date) = 2018 then J.total_cost else 0 end) as total_cost_2018,
sum(case when year(j.job_date) = 2019 then J.total_cost else 0 end) as total_cost_2019,
sum(case when year(j.job_date) = 2020 then J.total_cost else 90 end) as total_cost_2020
from Equipment e join
Jobs j
on J.EQ_no = e.EQ_no
where j.job_date >= '2018-01-01' and
j.job_date < '2021-01-01'
group by e.EQ_no, e.Serial_no;
Also note that this fixes the where clause so the case is using the same column. Your sample data doesn't have a column named datetime_open.

SQL Grouping by year gives incorrect results

I am trying to summerize sales date, by month, sales region and type. The problem is, the results change when I try to group by year.
My simplified query is as follows:
SELECT
DAB700.DATUM,DAB000.X_REGION,DAB700.BELEG_ART, // the date, sales region, order type
// calculate the number of orders per month
COUNT (DISTINCT CASE WHEN MONTH(DAB700.DATUM) = 1 THEN DAB700.BELEG_NR END) as jan,
COUNT (DISTINCT CASE WHEN MONTH(DAB700.DATUM) = 2 THEN DAB700.BELEG_NR END) as feb,
COUNT (DISTINCT CASE WHEN MONTH(DAB700.DATUM) = 3 THEN DAB700.BELEG_NR END) as mar
FROM "DAB700.ADT" DAB700
left join "DAB050.ADT" DAB050 on DAB700.BELEG_NR = DAB050.ANUMMER // join to table 050, to pull in order info
left join "DF030000.DBF" DAB000 on DAB050.KDNR = DAB000.KDNR // join table 000 to table 050, to pull in customer info
left join "DAB055.ADT" DAB055 on DAB050.ANUMMER = left (DAB055.APNUMMER,6)// join table 055 to table 050, to pull in product info
WHERE (DAB700.BELEG_ART = 10 OR DAB700.BELEG_ART = 20) AND (DAB700.DATUM>={d '2021-01-01'}) AND (DAB700.DATUM<={d '2021-01-11'}) AND DAB055.ARTNR <> '999999' AND DAB055.ARTNR <> '999996' AND DAB055.TERMIN <> 'KW.22.22' AND DAB055.TERMIN <> 'KW.99.99' AND DAB050.AUF_ART = 0
group by DAB700.DATUM,DAB000.X_REGION,DAB700.BELEG_ART
This returns the following data, which is correct (manually checked):
| DATUM | X_REGION | BELEG_ART | jan | feb | mar |
|------------|----------|-----------|-----|-----|-----|
| 04.01.2021 | 1 | 10 | 3 | 0 | 0 |
| 04.01.2021 | 3 | 10 | 2 | 0 | 0 |
| 04.01.2021 | 4 | 10 | 1 | 0 | 0 |
| 04.01.2021 | 4 | 20 | 1 | 0 | 0 |
| 04.01.2021 | 6 | 20 | 2 | 0 | 0 |
| 05.01.2021 | 1 | 10 | 1 | 0 | 0 |
and so on....
The total number of records for Jan is 117 (correct).
Now I now want to summerize the data in one row (for example, data grouped by region and type)..
so I change my code so that I have:
SELECT
YEAR(DAB700.DATUM),
and
group by YEAR(DAB700.DATUM)
the rest of the code stays the same.
Now my results are:
| EXPR | X_REGION | BELEG_ART | jan | feb | mar |
|------|----------|-----------|-----|-----|-----|
| 2021 | 1 | 10 | 16 | 0 | 0 |
| 2021 | 1 | 20 | 16 | 0 | 0 |
| 2021 | 2 | 10 | 19 | 0 | 0 |
| 2021 | 2 | 20 | 22 | 0 | 0 |
| 2021 | 3 | 10 | 12 | 0 | 0 |
| 2021 | 3 | 20 | 6 | 0 | 0 |
Visually it is correct. But, the total count for January is now 116. A difference of 1. What am I doing wrong?
How can I keep the results from the first code - but have it presented as per the 2nd set?
You count distinct BELEG_NR. This is what makes the difference. Let's look at an example. Let's say your table contains four rows:
DATUM
X_REGION
BELEG_ART
BELEG_NR
04.01.2021
1
10
100
04.01.2021
1
10
200
05.01.2021
1
10
100
05.01.2021
1
10
300
That gives you per day, region and belegart:
DATUM
X_REGION
BELEG_ART
DISTINCT COUNT BELEG_NR
04.01.2021
1
10
2
05.01.2021
1
10
2
and per year, region and belegart
YEAR
X_REGION
BELEG_ART
DISTINCT COUNT BELEG_NR
2021
1
10
3
The BELEG_NR 100 never appears more than once per day, so every instance gets counted. But it appears twice for the year, so it gets counted once instead of twice.

SQL Server: Count with conditions

I have a table which follows the state of the item delivery:
ID | ContractID | State
----------------------------------
1 | 125 | Created
2 | 125 | Activated
3 | 125 | PickupStarted
4 | 125 | PickedUp
5 | 125 | DeliveryStarted
6 | 125 | Delivered
7 | 126 | Created
8 | 126 | Activated
9 | 126 | PickupStarted
10 | 126 | PickedUp
11 | 126 | DeliveryStarted
12 | 126 | Delivered
13 | 127 | Created
14 | 127 | Activated
15 | 127 | PickupStarted
16 | 127 | PickedUp
I would like to create SQL query which counts only those 'ContractIds' which are not delivered yet (only those whose current status has reached 'PickedUp' status). In this case that would be 'ContractId' 127.
Is there a way to do that type of COUNT()?
You can use not exists:
select count(distinct contractId)
from t
where not exists (select 1
from t t2
where t2.contractId = t.contractid and
t2.status not like 'Deliver%'
);
Or, if you specifically want to get PickedUp as the last status:
select count(*)
from t
where t.id = (select max(t2.id) from t t2 2here t2.contractid = t.contractid) and
t.status = 'PickedUp';
The two are different. The second is specifically that the last status is PickedUp. The first is anyone that has not reached a "deliver" status.

Sql Query issue and error regarding groupby cause

I am trying to calculate the total number of Projects in every year. and also how many projects are active, how many of them are canceled.
I tried to group by cause for PRojects dates so we have a total number of project in every year but I am not sure where to start and what to do
Select ts.Id as projectid ,
--a.ParentObjectId,
ts.RequestName as ProjectDates,
ts.Type,
ts.Category,
ts.SubType,
ts.status as projectstatus,
Count (ts.ReceptionDate),
cast (ts.ReceptionDate as Date) as ReceptionDate,
from [rpt].[TransmissionServicesRpt] ts
left join [dbo].[AuditHistory] a on a.ParentObjectId = ts.Id
Left join [dbo].[User] u on a.CreatedById = u.id
Group by ts.id, ts.ReceptionDate
+ -------------+--------+-----------+------------+----------+-----------------+
| New Projects | Active | Cancelled | Terminated | Inactive | Carried Forward |
+ -------------+--------+-----------+------------+----------+-----------------+
| 2013 | 32 | 45 | 4 | 11 | 30 |
| 2014 | 45 | 75 | 17 | 14 | 44 |
| 2015 | 46 | 90 | 25 | 21 | 44 |
| 2016 | 30 | 74 | 27 | 10 | 37 |
| 2017 | 82 | 119 | 11 | 26 | 82 |
| 2018 | 86 | 168 | 29 | 24 | 115 |
| 2019 | 23 | 138 | 9 | 4 | 125 |
+ -------------+--------+-----------+------------+----------+-----------------+
You want one result row per year. So group by year. You get it via YEAR or DATEPART. Then count conditionally:
select
year(receptiondate) as year,
count(*) as total,
count(case when status = 'Active' then 1 end) as active,
count(case when status = 'Cancelled' then 1 end) as cancelled,
count(case when status = 'Terminated' then 1 end) as terminated,
count(case when status = 'Inactive' then 1 end) as inactive,
count(case when status = 'Carried Forward' then 1 end) as carried_forward
from rpt.transmissionservicesrpt
group by year(receptiondate)
order by year(receptiondate);

How to subtract previous value in a column with calculation of other column on SQL server

I have a requirement for a table as shown below. As you can see mgt_year,tot_dflt_mgt and to_accum_mgt columns. In year column where its 2016 the value is 20 and accum value is 600. What I want is that when I do
(to_accum_mgt - tot_dflt_mgt)
I want this calculated result in previous row as shown in the table below. Then this calculated result i.e. 580 is used for subtracting 9 like (580 - 9) for year 2015 and so on for all trailing years. I have done this in excel and also in Oracle thanks to #mathguy, but how to achieve this result in SQL server. I have tried to use this SQL server but its not working.
Please forgive My bad English and noob formatting.
My table t:
line_seg MGT_YEAR TOT_DFLT_MGT TOT_ACCUM_MGT
--------- -------- ------------ ------------
A 2013 10
A 2014 15
A 2015 9
A 2016 20 600
B 2013 10
B 2014 15
B 2015 8
B 2016 20 500
Oracle Solution:
select mgt_year, tot_dflt_mgt,
max(tot_accum_mgt) over () -
nvl( sum(tot_dflt_mgt) over
(order by mgt_year
rows between 1 following and unbounded following)
, 0 ) as tot_accum_mgt
from t;
but I am unable use this in SQL Server.
required output
line_seg MGT_YEAR TOT_DFLT_MGT TOT_ACCUM_MGT
--------- -------- ------------ ------------
A 2013 10 556
A 2014 15 471
A 2015 9 580
A 2016 20 600
B 2013 12 457
B 2014 15 472
B 2015 8 480
B 2016 20 500
select *,
(sum(TOT_ACCUM_MGT) over()) -
(sum(TOT_DFLT_MGT ) over (order by TOT_DFLT_MGT )) as somecolname
from
table
Put Row_number() and self join it with the previous row on (a.ID = b.ID) and (a.row_num = b.row_num - 1)
OR
You can use lag() function
Please try the following query. I assumed that you are using 2012+ version of SQL Server. If not, please change the FIRST_VALUE to SUM -
SELECT t1.line_seg, t1.mgt_year, t1.[tot_dflt_mgt]
, FIRST_VALUE(t1.tot_accum_mgt) OVER(PARTITION BY t1.[line_seg] ORDER BY t1.mgt_year DESC)
- ISNULL(SUM(t2.[tot_dflt_mgt]) OVER(PARTITION BY t2.[line_seg] ORDER BY t2.mgt_year DESC), 0) AS tot_accum_mgt
FROM [dbo].[t] AS t1
LEFT JOIN [dbo].[t] AS t2 ON (t2.line_seg = t1.line_seg AND t2.mgt_year = t1.mgt_year + 1)
ORDER BY t1.line_seg, t1.mgt_year ASC;
To do this first I have to imagine the table as sorted by the descending order of date -
+------------+----------+--------------+---------------+
| line_seg | mgt_year | tot_dflt_mgt | tot_accum_mgt |
+------------+----------+--------------+---------------+
| A | 2016 | 20 | 600 |
| A | 2015 | 9 | NULL |
| A | 2014 | 15 | NULL |
| A | 2013 | 10 | NULL |
| B | 2016 | 20 | 500 |
| B | 2015 | 8 | NULL |
| B | 2014 | 15 | NULL |
| B | 2013 | 12 | NULL |
+------------+----------+--------------+---------------+
Then all I have to do is to subtract the PREVIOUS running total of tot_dflt_mgt from the latest year's tot_accum_mgt. This is equivalent to subtract the previous tot_dflt_mgt from the current computed value of tot_accum_mgt To use the previous year's fields LEFT JOIN is used to self join the table. Resulting in the following table -
+------------+----------+--------------+---------------+------------+----------+--------------+---------------+
| line_seg | mgt_year | tot_dflt_mgt | tot_accum_mgt | line_seg | mgt_year | tot_dflt_mgt | tot_accum_mgt |
+------------+----------+--------------+---------------+------------+----------+--------------+---------------+
| A | 2013 | 10 | NULL | A | 2014 | 15 | NULL |
| A | 2014 | 15 | NULL | A | 2015 | 9 | NULL |
| A | 2015 | 9 | NULL | A | 2016 | 20 | 600 |
| A | 2016 | 20 | 600 | NULL | NULL | NULL | NULL |
| B | 2013 | 12 | NULL | B | 2014 | 15 | NULL |
| B | 2014 | 15 | NULL | B | 2015 | 8 | NULL |
| B | 2015 | 8 | NULL | B | 2016 | 20 | 500 |
| B | 2016 | 20 | 500 | NULL | NULL | NULL | NULL |
+------------+----------+--------------+---------------+------------+----------+--------------+---------------+
The AND t2.mgt_year = t1.mgt_year + 1 filter in the LEFT join clause does the trick of getting previous rows value. Now all I had to do is to calculate the running total on this previous rows (t2). Also as, subtracting NULL from anything will result in NULL. So ISNULL replaces any NULL with zeros.
ISNULL(SUM(t2.[tot_dflt_mgt]) OVER(PARTITION BY t2.[line_seg] ORDER BY t2.mgt_year DESC), 0) AS tot_accum_mgt
Now, as we have the previous running total of tot_dflt_mgt, all we have to do is to delete the latest (largest mgt_year) tot_accum_mgt. We get that by using FIRST_VALUE function. SUM could also be used instead I guess.
FIRST_VALUE(t1.tot_accum_mgt) OVER(PARTITION BY t1.[line_seg] ORDER BY t1.mgt_year DESC)