Excluding null entries from multiples values with SQL - sql

From 3 different tables, I want to know if a person (table1), with multiple visit in a store (table2), have purchased toys and enjoyed them (table3). In table3, 0 stand as either negative (so not enjoyed) or not bought. 1 stands for positive. Every visit has its own identification number.
My problem is that for every ID in table1, I have multiple entries for table2 for which I have multiple entries for table3 and only one of them is null.
Person Visit Toy
ID age Number Visit ID number name value
1 12 1 1 1 1 Plane
2 10 2 1 2 1 Train 1
3 2 1 2 Plane 1
4 2 2 2 Train 0
3 Plane 0
3 Train 1
(goes on for every id) (goes on for every visit)
I want to if know how many people have enjoyed a certain toy. However, since I have some null info, I have some trouble having those for which I only have value for both of their visit. For instance, the following code works only if the null condition is placed only on one of the visits
Select p.id, max(toy.value) as value
from person p
join visit v on p.id = v.id
join toy t on v.number = t.number
where
((t.name='plane' and v.visit=1)
or (t.name='plane' and v.visit=2))
and (
(v.visit=1 and ((t.value=1 or t.value=0) is not null))
---and (v.visit=2 and ((t.value=1 or t.value=0) is not null))
)
group by p.id
order by p.id
I have tried many ways of writing this. It does work if I try with both of null condition independently, but if I remove the -- and try for the condition on both the visit 1 and 2, it doesn't work. Note that I am using max on the value because I want a positive value is possible.

If you want to know how many people have enjoyed a certain toy, Then you may simply write this:
select count(*) from toy t where t.name='TOY NAME' and t.level=1;
If you want something else. Then kindly clarify.
Edited Query,
Select p.id, max(toy.value) as value
from person p
join visit v on p.id = v.id
join toy t on v.number = t.number
where
t.name='plane'
and t.value is not null
group by p.id
order by p.id

I used count as a way to eliminate all the null entries. The sum of null and a value is always null, so by adding restriction count=2 it eliminate the null

Related

Inner join + group by - select common columns and aggregate functions

Let's say i have two tables
Customer
---
Id Name
1 Foo
2 Bar
and
CustomerPurchase
---
CustomerId, Amount, AmountVAT, Accountable(bit)
1 10 11 1
1 20 22 0
2 5 6 0
2 2 3 0
I need a single record for every joined and grouped Customer and CustomerPurchase group.
Every record would contain
columns from table Customer
some aggregation functions like SUM
a 'calculated' column. For example difference of other columns
result of subquery to CustomerPurchase table
An example of result i would like to get
CustomerPurchases
---
Name Total TotalVAT VAT TotalAccountable
Foo 30 33 3 10
Bar 7 9 2 0
I was able to get a single row only by grouping by all the common columns, which i dont think is the right way to do. Plus i have no idea how to do the 'VAT' column and 'TotalAccountable' column, which filters out only certain rows of CustomerPurchase, and then runs some kind of aggregate function on the result. Following example doesn't work ofc but i wanted to show what i would like to achieve
select C.Name,
SUM(CP.Amount) as 'Total',
SUM(CP.AmountVAT) as 'TotalVAT',
diff? as 'VAT',
subquery? as 'TotalAccountable'
from Customer C
inner join CustomerPurchase CR
on C.Id = CR.CustomerId
group by C.Id
I would suggest you just need the follow slight changes to your query. I would also consider for clarity, if you can, to use the terms net and gross which is typical for prices excluding and including VAT.
select c.[Name],
Sum(cp.Amount) as Total,
Sum(cp.AmountVAT) as TotalVAT,
Sum(cp.AmountVAT) - Sum(CP.Amount) as VAT,
Sum(case when cp.Accountable = 1 then cp.Amount end) as TotalAccountable
from Customer c
join CustomerPurchase cp on cp.CustomerId = c.Id
group by c.[Name];

How to consecutively count everything greater than or equal to itself in SQL?

Let's say if I have a table that contains Equipment IDs of equipments for each Equipment Type and Equipment Age, how can I do a Count Distinct of Equipment IDs that have at least that Equipment Age.
For example, let's say this is all the data we have:
equipment_type
equipment_id
equipment_age
Screwdriver
A123
1
Screwdriver
A234
2
Screwdriver
A345
2
Screwdriver
A456
2
Screwdriver
A567
3
I would like the output to be:
equipment_type
equipment_age
count_of_equipment_at_least_this_age
Screwdriver
1
5
Screwdriver
2
4
Screwdriver
3
1
Reason is there are 5 screwdrivers that are at least 1 day old, 4 screwdrivers at least 2 days old and only 1 screwdriver at least 3 days old.
So far I was only able to do count of equipments that falls within each equipment_age (like this query shown below), but not "at least that equipment_age".
SELECT
equipment_type,
equipment_age,
COUNT(DISTINCT equipment_id) as count_of_equipments
FROM equipment_table
GROUP BY 1, 2
Consider below join-less solution
select distinct
equipment_type,
equipment_age,
count(*) over equipment_at_least_this_age as count_of_equipment_at_least_this_age
from equipment_table
window equipment_at_least_this_age as (
partition by equipment_type
order by equipment_age
range between current row and unbounded following
)
if applied to sample data in your question - output is
Use a self join approach:
SELECT
e1.equipment_type,
e1.equipment_age,
COUNT(*) AS count_of_equipments
FROM equipment_table e1
INNER JOIN equipment_table e2
ON e2.equipment_type = e1.equipment_type AND
e2.equipment_age >= e1.equipment_age
GROUP BY 1, 2
ORDER BY 1, 2;
GROUP BY restricts the scope of COUNT to the rows in the group, i.e. it will not let you reach other rows (rows with equipment_age greater than that of the current group). So you need a subquery or windowing functions to get those. One way:
SELECT
equipment_type,
equipment_age,
(Select COUNT(*)
from equipment_table cnt
where cnt.equipment_type = a.equipment_type
AND cnt.equipment_age >= a.equipment_age
) as count_of_equipments
FROM equipment_table a
GROUP BY 1, 2, 3
I am not sure if your environment supports this syntax, though. If not, let us know we will find another way.

Where statement for exact match on Many to Many SQL tables

I am trying to construct a SQL statement to search in two tables that are in a many to many relation.
Problem : SQL statement to search for products with exact stones.
For example, in the below tables, I need a statement that will search for product with Ruby and Emerald stone ONLY. In all my attempts I get both Ring and Necklace because they both have Ruby and Emerald even though Necklace has one additional stone. It should only give Ring product.
I need a way to implement the AND operator on the stone table so that the result contains products that have the exact stones. Please help.
Table stone
s_id
s_name
1
Ruby
2
Emerald
3
Onyx
Table product
p_id
p_name
1
Ring
2
Necklace
3
Pendent
Relation table - product_stone
p_s_id
p_id
s_id
1
1
1
1
1
2
1
2
1
1
2
2
1
2
3
1
3
3
This is a relational division question. We need to find the cross join of the two tables "divided" by our list, with no remainder i.e. no other stone in product.
We will assume that p_id and s_id are unique:
;WITH StonesToFind AS ( -- we could also use a table variable etc here
SELECT *
FROM stone
WHERE s_name IN ('Ruby','Emerald')
)
SELECT p.p_name
FROM product AS p -- let's get all products...
JOIN product_stone AS ps ON ps.p_id = p.p_id -- ...cross join all their stones
LEFT JOIN StonesToFind AS s ON s.s_id = ps.s_id -- they may have stones in the list
GROUP BY p.p_id, p_name
HAVING COUNT(CASE WHEN s.s_id IS NULL THEN 1 END) = 0
-- the number of non matching stones in product must be zero
AND COUNT(*) = (SELECT COUNT(*) FROM StonesToFind);
-- the total number of stones must be the same as the list

Count number of repeats in SQL

I tried to solve one problem but without success.
I have two list of number
{1,2,3,4}
{5,6,7,8,9}
And I have table
ID Number
1 1
1 2
1 7
1 2
1 6
2 8
2 7
2 3
2 9
Now I need to count how many times number from second list come after number from first list but I should count only one by one id
in example table above result should be 2
three matched pars but because we have only two different IDs result is 2 instead 3
Pars:
1 2
1 7
1 2
1 6
2 3
2 9
note. I work with MSSQL
Edit. There is one more column Date which determined order
Edit2 - Solution
i write this query
SELECT * FROM table t
left JOIN table tt ON tt.ID = t.ID
AND tt.Date > t.Date
AND t.Number IN (1,2,3,4)
AND tt.Number IN (6,7,8,9)
And after this I had a plan to group by id and use only one match for each id but execution take a lot time
Here is a query that would do it:
select a.id, min(a.number) as a, min(b.number) as b
from mytable a
inner join mytable b
on a.id = b.id
and a.date < b.date
and b.number in (5,6,7,8,9)
where a.number in (1,2,3,4)
group by a.id
Output is:
id a b
1 1 6
2 3 9
So the two pairs are output each on one line, with the value a belonging to the first group of numbers, and the value of column b to the second group.
Here is a fiddle
Comments on attempt (edit 2 to question)
Later you added a query attempt to your question. Some comments about that attempt:
You don't need a left join because you really want to have a match for both values. inner join has in general better performance, so use that.
The condition t.Number IN (1,2,3,4) does not belong in the on clause. In combination with a left join the result will include t records that violate this condition. It should be put in the where clause.
Your concern about performance may be warranted, but can be resolved by adding a useful index on your table, i.e. on (id, number, date) or (id, date, number)

Keep one instance of duplicate appearing in one of two columns

I've got a table containing one column with unique ID and one column with each unique ID's spouse ID (if they have a spouse). The problem is that each spouse ID also appears in the unique ID column, so when I pull a list, attempting to treat a couple as a single unit, I'm often doublecounting for a single couple.
What's a good, efficient way of taking a given list of unique IDs, checking to see if their spouse is also in the same list of unique IDs, and returning only one unique ID per couple?
The issue is a little more complicated in that sometimes both spouses are not included in the same list, so it's not simply a matter of keeping one person if they're married. In the event that the spouse isn't also in the same list, I want to make sure to retain the one that is. I also want to make sure I'm retaining all people who have a NULL value in the spouse ID column.
Subset of table in question:
Unique_ID Spouse_ID
1 2
2 1
3 NULL
4 NULL
5 10
6 25
7 NULL
8 9
9 8
10 5
In this excerpt, ID's 3, 4, and 7 are all single. ID's 1, 2, 5, 8, and 9 have spouses that appear in the Unique_ID column. ID 6 has a spouse whose ID does not appear in the Unique_ID column. So, I'd want to keep ID's 1 (or 2), 3, 4, 5 (or 10), 6, 7, and 8 (or 9). Hope that makes sense.
My inclination would be to combine the two lists and remove duplicates:
select distinct id
from ((select id
from t
) union all
(select spouse_id
from t
where spouse_id in (select id from t)
)
) t
But, your question asked for an efficient way. Another way to think about this is to add a new column which is the spouse id if in the id list or NULL otherwise (this uses a left outer join. Then there are three cases:
There is no spouse id, so use the id
The id is less than the original id. Use it.
The spouse id is less than the original id. Discard this record, because the original is being used.
Here is an explicit way of expressing this:
select IdToUse
from (select t.*, tspouse.id tsid,
(case when tspouse.id is null then t.id
when t.id < tspouse.id then t.id
else NULL
end) as IdToUse
from t left outer join
t tspouse
on t.spouse_id = tspouse.id
) t
where IdToUse is not null;
You can simplify this to:
select t.*, tspouse.id tsid,
(case when tspouse.id is null then t.id
when t.id < tspouse.id then t.id
else NULL
end) as IdToUse
from t left outer join
t tspouse
on t.spouse_id = tspouse.id
where tspouse.id is null or
t.id < tspouse.id
Two tables is just plain bad design
Combine the tables
select id
from table
where id < spouseID
or spouseID is null