Removing group of results if total is 0 - sql

I am using the following table to create a stacked bar chart - its quite a bit larger than this:
ID | Name | foodEaten | total
1 | Sam | Burger | 3
1 | Sam | Pizza | 1
1 | Sam | Kebab | 0
1 | Sam | Cheesecake| 3
1 | Sam | Sandwich | 5
2 | Jeff | Burger | 0
2 | Jeff | Pizza | 0
2 | Jeff | Kebab | 0
2 | Jeff | Cheesecake| 0
2 | Jeff | Sandwich | 0
I need to find a way to remove results like Jeff. Where the entire total for what he ate is 0. I can't think of the easiest way to achieve this. I've tried grouping the entire result by Id and creating a total, but its just not happening.
If the person has eaten a total of 0 food, then he needs to be excluded. But if he hasn't, and he hasn't eaten any kebabs, as shown in my above table, this needs to be included in the result!
So the output needed is:
ID | Name | foodEaten | total
1 | Sam | Burger | 3
1 | Sam | Pizza | 1
1 | Sam | Kebab | 0
1 | Sam | Cheesecake| 3
1 | Sam | Sandwich | 5

Assuming that you want the data as it appears, and not the aggregate out and then exclude:
WITH CTE AS (
SELECT ID,
[Name],
foodEaten,
total,
SUM(total) OVER (PARTITION BY [Name]) AS nameTotal
FROM YourTable)
SELECT ID,
[Name],
foodEaten,
total
FROM CTE
WHERE nameTotal > 0;

select id, name, foodEaten, sum(total) as total from <table> group by ID having sum(total) > 0
Does this work for you?

You can try below -
select id,name
from tablename a
group by id,name
having sum(total)>0
OR
DEMO
select * from tablename a
where not exists (select 1 from tablename b where a.id=b.id group by id,name
having sum(total)=0)

Try this
;WITH CTE (ID , Name , foodEaten , total)
AS
(
SELECT 1 , 'Sam' , 'Burger' , 3 UNION ALL
SELECT 1 , 'Sam' , 'Pizza' , 1 UNION ALL
SELECT 1 , 'Sam' , 'Kebab' , 2 UNION ALL
SELECT 1 , 'Sam' , 'Cheesecake', 3 UNION ALL
SELECT 1 , 'Sam' , 'Sandwich' , 5 UNION ALL
SELECT 2 , 'Jeff' , 'Burger' , 0 UNION ALL
SELECT 2 , 'Jeff' , 'Pizza' , 0 UNION ALL
SELECT 2 , 'Jeff' , 'Kebab' , 0 UNION ALL
SELECT 2 , 'Jeff' , 'Cheesecake', 0 UNION ALL
SELECT 2 , 'Jeff' , 'Sandwich' , 0
)
SELECT ID , Name ,SUM( total) AS Grandtotal
FROM CTE
GROUP BY ID , Name
HAVING SUM( total) >0
Result
ID Name Grandtotal
----------------------
1 Sam 14

Using DELETE with HAVING SUM(total) = 0 will remove the group of result which their total is 0
DELETE FROM TableName
WHERE ID IN (SELECT Id FROM TableName GROUP BY ID HAVING SUM(total) = 0)
or if you want to remvoe and select only the records which has sum of total is zero, then
SELECT * FROM TableName
WHERE ID NOT IN (SELECT Id FROM TableName GROUP BY ID HAVING SUM(total) = 0)

Assuming total is never negative, then probably the most efficient method is to use exists:
select t.*
from t
where exists (select 1
from t t2
where t2.name = t.name and
t2.total > 0
);
In particular, this can take advantage of an index on (name, total).

Related

How to create a query with all of dependencies in hierarchical organization?

I've been trying hard to create a query to see all dependencies in a hierarchical organization. But the only I have accuaried is to retrieve the parent dependency. I have attached an image to show what I need.
Thanks for any clue you can give me.
This is the code I have tried with the production table.
WITH CTE AS
(SELECT
H1.systemuserid,
H1.pes_aprobadorid,
H1.yomifullname,
H1.internalemailaddress
FROM [dbo].[ext_systemuser] H1
WHERE H1.pes_aprobadorid is null
UNION ALL
SELECT
H2.systemuserid,
H2.pes_aprobadorid,
H2.yomifullname,
H2.internalemailaddress
FROM [dbo].[ext_systemuser] H2
INNER JOIN CTE c ON h2.pes_aprobadorid=c.systemuserid)
SELECT *
FROM CTE
OPTION (MAXRECURSION 1000)
You are almost there with your query. You just have to include all rows as a starting point. Also the join should be cte.parent_id = ext.user_id and not the other way round. I've done an example query in postgres, but you shall easily adapt it to your DBMS.
with recursive st_units as (
select 0 as id, NULL as pid, 'Director' as nm
union all select 1, 0, 'Department 1'
union all select 2, 0, 'Department 2'
union all select 3, 1, 'Unit 1'
union all select 4, 3, 'Unit 1.1'
),
cte AS
(
SELECT id, pid, cast(nm as text) as path, 1 as lvl
FROM st_units
UNION ALL
SELECT c.id, u.pid, cast(path || '->' || u.nm as text), lvl + 1
FROM st_units as u
INNER JOIN cte as c on c.pid = u.id
)
SELECT id, pid, path, lvl
FROM cte
ORDER BY lvl, id
id | pid | path | lvl
-: | ---: | :--------------------------------------- | --:
0 | null | Director | 1
1 | 0 | Department 1 | 1
2 | 0 | Department 2 | 1
3 | 1 | Unit 1 | 1
4 | 3 | Unit 1.1 | 1
1 | null | Department 1->Director | 2
2 | null | Department 2->Director | 2
3 | 0 | Unit 1->Department 1 | 2
4 | 1 | Unit 1.1->Unit 1 | 2
3 | null | Unit 1->Department 1->Director | 3
4 | 0 | Unit 1.1->Unit 1->Department 1 | 3
4 | null | Unit 1.1->Unit 1->Department 1->Director | 4
db<>fiddle here
I've reached this code that it is working but when I include a hierarchy table of more than 1800 the query is endless.
With cte AS
(select systemuserid, systemuserid as pes_aprobadorid, internalemailaddress, yomifullname
from #TestTable
union all
SELECT c.systemuserid, u.pes_aprobadorid, u.internalemailaddress, u.yomifullname
FROM #TestTable as u
INNER JOIN cte as c on c.pes_aprobadorid = u.systemuserid
)
select distinct * from cte
where pes_aprobadorid is not null
OPTION (MAXRECURSION 0)

Alternative to CASE WHEN?

I have a table in SQL where the results look something like:
Number | Name | Name 2
1 | John | Derek
1 | John | NULL
2 | Jane | Louise
2 | Jane | NULL
3 | Michael | Mark
3 | Michael | NULL
4 | Sara | Paul
4 | Sara | NULL
I want a way to say that if Number=1, return Name 2 in new column Name 3, so that the results would look like:
Number | Name | Name 2 | Name 3
1 | John | Derek | Derek
1 | John | NULL | Derek
2 | Jane | Louise | Louise
2 | Jane | NULL | Louise
3 | Michael | Mark | Mark
3 | Michael | NULL | Mark
4 | Sara | Paul | Paul
4 | Sara | NULL | Paul
The problem is that I can't say if Number=1, return Name 2 in Name 3, because my table has >100,000 records. I need it to do it automatically. More like "if Number is the same, return Name 2 in Name 3." I've tried to use a CASE statement but haven't been able to figure it out. Is there any way to do this?
Empirically, this seems to work:
SELECT
Number, Name, [Name 2],
MAX([Name 2]) OVER (PARTITION BY Number) [Name 3]
FROM yourTable;
The idea here, if I interpreted your requirements correctly, is that you want to report the non NULL value of the second name for all records as the third name value.
Solution 3, with group by
with maxi as(
SELECT Number, max(Name2) name3
FROM #sample
group by number, name
)
SELECT f1.*, f2.name3
FROM #sample f1 inner join maxi f2 on f1.number=f2.number
Solution 4, with cross apply
SELECT *
FROM #sample f1 cross apply
(
select top 1 f2.Name2 as Name3 from #sample f2
where f2.number=f1.number and f2.Name2 is not null
) f3
you can try this:
Solution 1, with row_number
declare #sample table (Number integer, Name varchar(50), Name2 varchar(50))
insert into #sample
select 1 , 'John' , 'Derek' union all
select 1 , 'John' , NULL union all
select 2 , 'Jane' , 'Louise' union all
select 2 , 'Jane' , NULL union all
select 3 , 'Michael' , 'Mark' union all
select 3 , 'Michael' , NULL union all
select 4 , 'Sara' , 'Paul' union all
select 4 , 'Sara' , NULL ;
with tmp as (
select *, row_number() over(partition by number order by number) rang
from #sample
)
select f1.Number, f1.Name, f1.Name2, f2.Name2 as Name3
from tmp f1 inner join tmp f2 on f1.Number=f2.Number and f2.rang=1
Solution 2, with lag (if your sql server version has lag function)
SELECT
Number, Name, Name2,
isnull(Name2, lag(Name2) OVER (PARTITION BY Number order by number)) Name3
FROM #sample;

SQL Server Query to find records with aggregate funct on one column but multiple columns in select clause

Here is the minimized version of the Customer table. There can be customers having same account number mapped to different Group . I am looking to find out customer numbers which are mapped to more than one group. As I was using sybase my query below was working fine. Same query does not work in SQL Server.
Can I get both custAccnt and corresponding custId in one query as below.
select DISTINCT lt.custAccnt, lt.custId from VAL_CUSTOMERS lt
where lt.eligible = 'Y' group by lt.custAccnt
having count(distinct lt.custId) > 1
+----------+-----------+---------+----------+
| custName | custAccnt | custId | eligible |
+----------+-----------+---------+----------+
| Joe | AB1VU1235 | 43553 | Y |
| Joe | AB1VU1235 | 525577 | Y |
| Lucy | CDNMY4568 | 332875 | Y |
| Lucy | CDNMY4568 | 211574 | Y |
| Lucy | CDNMY4568 | 211345 | Y |
| Manie | TZMM7S009 | 123890 | Y |
| Tom | YFDU1235 | 1928347 | Y |
| Tom | YFDU1235 | 204183 | Y |
| Chef | TNOTE6573 | 734265 | Y |
+----------+-----------+---------+----------+
Result :-
+-----------+---------+
| AB1VU1235 | 43553 |
| AB1VU1235 | 525577 |
| CDNMY4568 | 332875 |
| CDNMY4568 | 211574 |
| CDNMY4568 | 211345 |
| YFDU1235 | 1928347 |
| YFDU1235 | 204183 |
+-----------+---------+
There are many ways to tackle this. Here are a couple of them that should work.
select lt.custAccnt
, lt.custId
from VAL_CUSTOMERS lt
cross apply
(
select c.custAccnt
from VAL_CUSTOMERS c
where c.custAccnt = lt.custAccnt
group by c.custAccnt
having count(*) > 1
) x
where lt.eligible = 'Y'
select lt.custAccnt
, lt.custId
from VAL_CUSTOMERS lt
where lt.eligible = 'Y'
AND lt.custAccnt IN
(
select c.custAccnt
from VAL_CUSTOMERS c
group by c.custAccnt
having count(*) > 1
)
In case of duplicates custAccnt and custId in the table, #Sean query won't work.
WITH cte AS(SELECT *
, COUNT (custId) OVER (PARTITION BY custAccnt) AS CntcustId
, ROW_NUMBER () OVER (PARTITION BY custAccnt, custId ORDER BY custName) AS Rownum
FROM VAL_CUSTOMERS
WHERE eligible = 'Y'
)
SELECT custAccnt, custId
FROM cte
WHERE CntcustId>1
AND Rownum = 1;
Using row number to eliminate the duplicates.
I think this might work...
"...customer numbers which are mapped to more than one group..." , <-- group is custAcct?
select t.custAccnt, t.custId
from VAL_CUSTOMERS t
where (Select count(distinct custAccnt )
from VAL_CUSTOMERS
Where custId = t.custId) > 1
The statement "...customer numbers which are mapped to more than one group..." does not say anything about "eligibility", so I did not mention it. If you really meant to say:
"...eligible customer numbers which are mapped to more than one group...", then try this:
select t.custAccnt, t.custId
from VAL_CUSTOMERS t
where eligible = 'Y'
and (Select count(distinct custAccnt )
from VAL_CUSTOMERS
Where custId = t.custId) > 1
or, this might be faster... it answers a slightly different, but, (I think) equivalent question,
"find ...eligible customer numbers where there is another row for the same customer number mapped to a different custAccnt ..."
select t.custAccnt, t.custId
from VAL_CUSTOMERS t
where eligible = 'Y'
and exists
(Select * from VAL_CUSTOMERS
Where custId = t.custId
and custAccnt != t.custAccnt )
;WITH cte1
( custName , custAccnt , custId , eligible )
As
(
SELECT 'Joe' ,'AB1VU1235' , 43553 , 'Y' UNION ALL
SELECT 'Joe' ,'AB1VU1235' , 525577 , 'Y' UNION ALL
SELECT 'Lucy' ,'CDNMY4568' , 332875 , 'Y' UNION ALL
SELECT 'Lucy' ,'CDNMY4568' , 211574 , 'Y' UNION ALL
SELECT 'Lucy' , 'CDNMY4568' , 211345 , 'Y' UNION ALL
SELECT 'Manie' ,'TZMM7S009' , 123890 , 'Y' UNION ALL
SELECT 'Tom' ,'YFDU1235' , 1928347 , 'Y' UNION ALL
SELECT 'Tom' ,'YFDU1235' , 204183 , 'Y' UNION ALL
SELECT 'Chef' ,'TNOTE6573' , 734265 , 'Y'
)
,cte2 AS (
SELECT custName
,custAccnt
,count(custName) cnt
FROM cte1
GROUP BY custName,custAccnt
)
,cte3 AS (
SELECT custName
,cnt
FROM cte2 WHERE cnt <> 1
)
SELECT custAccnt
,custId
FROM cte1
WHERE custName IN (
SELECT custName
FROM cte3
)

How to create a condition for this case?

Sample Table:
Id |Acc_Code|Description |Balance | Acclevel| Acctype| Exttype|
--- -------- ----------------- |-------- |-------- | -------| -------|
1 |SA |Sales | 0.00 | 1 | SA | |
2 |CS |Cost of Sales | 0.00 | 1 | CS | |
3 |5000/001|Revenue | 94.34 | 2 | SA | |
4 |5000/090|Sales(Local) | 62.83 | 2 | SA | |
5 |7000/000|Manufacturing Acc |-250.80 | 2 | CS | MA |
6 |7000/200|Manufacturing Acc | 178.00 | 2 | CS | |
This is a sample data of a temporary table which would be used to be inserted into another temporary table that would calculate the data for Profit and Loss Statement (For Manufacturing related Accounts only).
In this case, the acc_code for Manufacturing accounts start from 7000/000 and separated/partitioned for each following Exttype.
Eg: We start from the exttype of MA and based on its acclevel (could be 2 or more) until the next exttype.
The idea is we get the manufacturing accounts by SELECT FROM tmp_acc_list WHERE acc_code BETWEEN #start_acc_code (7000/000 in this case) AND #end_acc_code (the data before the next exttype)
I don't know what the exttype is, I'm still learning the tables.
How do we create the #end_acc_code part out from this sample table?
So here is a all in one script.
I created Your table for test:
create table #tmp_acc_list(
Id numeric,
Acc_Code nvarchar(100),
Acclevel numeric,
Acctype nvarchar(100),
Exttype nvarchar(100));
GO
insert into #tmp_acc_list(Id, Acc_Code, Acclevel, Acctype, Exttype)
select 1 , 'SA', 1,'SA', null union all
select 2 , 'CS', 1,'CS', null union all
select 3 , '5000/001', 2,'SA', null union all
select 4 , '5000/090', 2,'SA', null union all
select 5 , '7000/000', 2,'CS', 'MA' union all
select 6 , '7000/200', 2,'CS', null
;
Then comes the query:
with OrderedTable as -- to order the table is Id is not an order
(
select
t.*, ROW_NUMBER() over (
order by id asc --use any ordering You need here
)
as RowNum
from
#tmp_acc_list as t
),
MarkedTable as -- mark with common number
(
select
t.*,
Max(case when t.Exttype is null then null else t.RowNum end)
over (order by t.RowNum) as GroupRownum
from OrderedTable as t
),
GroupedTable as -- add group Exttype
(
select
t.Id, t.Acc_Code, t.Acclevel, t.Acctype, t.Exttype,
max(t.Exttype) over (partition by t.GroupRownum) as GroupExttype
from MarkedTable as t
)
select * from GroupedTable where GroupExttype = 'MA'
Is this what You need?
select *
from
(
select Id, Acc_Code
from tmp_acc_list
where Acc_Code = '7000/000'
) s
cross join tmp_acc_list a
cross apply
(
select top 1 x.Id, x.Acc_Code
from tmp_acc_list x
where x.Id >= a.Id
and x.AccLevel = a.AccLevel
and x.Acctype = a.Acctype
and x.Exttype = ''
order by Id desc
) e
where a.Id between s.Id and e.Id

Query for missing elements

I have a table with the following structure:
timestamp | name | value
0 | john | 5
1 | NULL | 3
8 | NULL | 12
12 | john | 3
33 | NULL | 4
54 | pete | 1
180 | NULL | 4
400 | john | 3
401 | NULL | 4
592 | anna | 2
Now what I am looking for is a query that will give me the sum of the values for each name, and treats the nulls in between (orderd by the timestamp) as the first non-null name down the list, as if the table were as follows:
timestamp | name | value
0 | john | 5
1 | john | 3
8 | john | 12
12 | john | 3
33 | pete | 4
54 | pete | 1
180 | john | 4
400 | john | 3
401 | anna | 4
592 | anna | 2
and I would query SUM(value), name from this table group by name. I have thought and tried, but I can't come up with a proper solution. I have looked at recursive common table expressions, and think the answer may lie in there, but I haven't been able to properly understand those.
These tables are just examples, and I don't know the timestamp values in advance.
Could someone give me a hand? Help would be very much appreciated.
With Inputs As
(
Select 0 As [timestamp], 'john' As Name, 5 As value
Union All Select 1, NULL, 3
Union All Select 8, NULL, 12
Union All Select 12, 'john', 3
Union All Select 33, NULL, 4
Union All Select 54, 'pete', 1
Union All Select 180, NULL, 4
Union All Select 400, 'john', 3
Union All Select 401, NULL, 4
Union All Select 592, 'anna', 2
)
, NamedInputs As
(
Select I.timestamp
, Coalesce (I.Name
, (
Select I3.Name
From Inputs As I3
Where I3.timestamp = (
Select Max(I2.timestamp)
From Inputs As I2
Where I2.timestamp < I.timestamp
And I2.Name Is not Null
)
)) As name
, I.value
From Inputs As I
)
Select NI.name, Sum(NI.Value) As Total
From NamedInputs As NI
Group By NI.name
Btw, what would be orders of magnitude faster than any query would be to first correct the data. I.e., update the name column to have the proper value, make it non-nullable and then run a simple Group By to get your totals.
Additional Solution
Select Coalesce(I.Name, I2.Name), Sum(I.value) As Total
From Inputs As I
Left Join (
Select I1.timestamp, MAX(I2.Timestamp) As LastNameTimestamp
From Inputs As I1
Left Join Inputs As I2
On I2.timestamp < I1.timestamp
And I2.Name Is Not Null
Group By I1.timestamp
) As Z
On Z.timestamp = I.timestamp
Left Join Inputs As I2
On I2.timestamp = Z.LastNameTimestamp
Group By Coalesce(I.Name, I2.Name)
You don't need CTE, just a simple subquery.
select t.timestamp, ISNULL(t.name, (
select top(1) i.name
from inputs i
where i.timestamp < t.timestamp
and i.name is not null
order by i.timestamp desc
)), t.value
from inputs t
And summing from here
select name, SUM(value) as totalValue
from
(
select t.timestamp, ISNULL(t.name, (
select top(1) i.name
from inputs i
where i.timestamp < t.timestamp
and i.name is not null
order by i.timestamp desc
)) as name, t.value
from inputs t
) N
group by name
I hope I'm not going to be embarassed by offering you this little recursive CTE query of mine as a solution to your problem.
;WITH
numbered_table AS (
SELECT
timestamp, name, value,
rownum = ROW_NUMBER() OVER (ORDER BY timestamp)
FROM your_table
),
filled_table AS (
SELECT
timestamp,
name,
value
FROM numbered_table
WHERE rownum = 1
UNION ALL
SELECT
nt.timestamp,
name = ISNULL(nt.name, ft.name),
nt.value
FROM numbered_table nt
INNER JOIN filled_table ft ON nt.rownum = ft.rownum + 1
)
SELECT *
FROM filled_table
/* or go ahead aggregating instead */