value = 0 while other line items > 0 - sql

I'd like to know how to write a query where unique IDs with multiple line items will have 1 line item with a particular code, let's call it A4445, where charge amount is = 0, while all the other lines with other codes other than A4445 linked to that ID would be greater than 0 like the following. If the data looked like this:
ID | line item | code | Charge |
---------+-----------+--------+---------
3334400 | 1 | A4445 | 32.50 |
3334400 | 2 | B0021 | 0.00 |
3334400 | 3 | B0666 | 9.00 |
but I want IDs that have the A4445 code = 0.00 and the other lines with a charge amount greater > 0
ID | line item | code | Charge |
---------+-----------+--------+---------
3334422 | 1 | A4445 | 0.00 |
3334422 | 2 | B0021 | 12.30 |
3334422 | 3 | B0666 | 9.00 |
I'm currently using the union all function but I don't think it's working. This is my query:
Select
ID,
Line item,
Code,
Charge
from
claim
where
code = 'A4445'
and charge = 0.00
union all
Select
ID,
Line item,
Code,
Charge
from
claim
where
code <> 'A4445'
and charge > 0.00
I'm not sure how to articulate this but hopefully the above illustration will give you an idea of what I'm looking for

I believe you want:
select c.id
from claim c
group by c.id
having max(case when c.code = 'A4445' then c.charge end) = 0 and
min(case when c.code <> 'A4445' then c.charge end) > 0;
This assumes that charge is never negative (you have no such examples in your data). It is easy enough to support negative charges, but the logic is a little bit more complicated.
If you want the original rows, you can use this with join, exists, or in to get those values. However, I would probably use not exists:
select c.*
from claims c
where not exists (select 1
from claims c2
where c2.id = c.id and c2.code = 'A4445' and c2.charge <> 0
) and
not exists (select 1
from claims c2
where c2.id = c.id and c2.code <> 'A4445' and c2.charge = 0
);

Related

SUM in multi-currency

I am trying to do SUM() in a multi-currency setup. The following will demonstrate the problem that I am facing:-
Customer
-------------------------
Id | Name
1 | Mr. A
2 | Mr. B
3 | Mr. C
4 | Mr. D
-------------------------
Item
-------------------------
Id | Name | Cost | Currency
1 | Item 1 | 5 | USD
2 | Item 2 | 2 | EUR
3 | Item 3 | 10 | GBP
4 | Item 4 | 5 | GBP
5 | Item 5 | 50 | AUD
6 | Item 6 | 20 | USD
7 | Item 3 | 10 | EUR
-------------------------
Order
-------------------------
User_Id | Product_Id
1 | 1
2 | 1
1 | 2
3 | 3
1 | 5
1 | 7
1 | 5
2 | 6
3 | 4
4 | 2
-------------------------
Now, I want the output of a SELECT query that lists the Customer Name and the total amount worth of products purchased as:-
Customer Name | Amount
Mr. A | Multiple-currencies
Mr. B | 25 USD
Mr. C | 15 GBP
Mr. D | 2 EUR
So basically, I am looking for a way to add the cost of multiple products under the same customer, if all of them have the same currency, else simply show 'multiple-currencies'. Running the following query will not help:-
SELECT Customer.Name, SUM(Item.Amount) FROM Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY Customer.Name
What should my query be? I am using Sqlite
I would suggest two output columns, one for the currency and one for the amount:
SELECT c.Name,
(case when max(currency) = min(currency) then sum(amount)
end) as amount,
(case when max(currency) = min(currency) then max(currency)
else 'Multiple Currencies'
end) as currency
FROM Customer c INNER JOIN
Order o
ON o.User_Id = c.Id INNER JOIN
Item
ON i.Id = o.Product_Id
GROUP BY c.Name
If you want, you can concatenate these into a single string column. I just prefer to have the information in two different columns for something like this.
The above is standard SQL.
I think your query should looks like this
SELECT
Data.Name AS [Customer Name],
CASE WHEN Data.Count > 1 THEN "Multiple-currencies" ELSE CAST(Data.Amount AS NVARCHAR) END AS Amount
FROM
(SELECT
Customer.Name,
COUNT(Item.Currency) AS Count,
SUM(Item.Amount) AS Amount
FROM
Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY
Customer.Name) AS Data
A subquery to get the count of currencies and then ask for them in the main query to show the total or the text "Multiple-currencies".
Sorry if there is any mistake or mistype but I don't have a database server to test it
Hope this helps.
IMO I would start by standardizing variable names. Why call ID in customer table USER_ID in order table? Just a pet peeve. Anyway, you should learn how to build queries.
start with joining the customer table to the order table on then join the result to the item table. The first join is on CUSTOMER_ID and the second join is on PRODUCT_ID. Once you have that working use SUM and GROUP BY
Ok, I managed to solve the problem this way:-
SELECT innerQuery.Name AS Name, (CASE WHEN innerQuery.Currencies=1 THEN (innerQuery.Amount || innerQuery.Currency) ELSE 'Mutliple-Currencies' END) AS Amount, FROM
(SELECT Customer.Name, SUM(Item.Amount), COUNT(DISTINCT Item.Currency) AS Currencies, Item.Currency AS Currency FROM Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY Customer.Name)innerQuery

SQL Query on 3 Tables Get Column from One and Use it for the Other

I have a table, commission, that is being called from a function. It is selected by user_id and it will have certain columns named sale_product_id. This column is tied to another table: sale_product, which has another column, sale_id. Now there is one last table, sale that has ids.
Currently, here is what I have:
SELECT
c.amount
FROM
commission AS c,
sale_product AS sp,
sale AS s
WHERE
c.user_id = '[id]' AND
c.sale_product_id = sp.id AND
s.is_paid = 1 AND
(s.upline_user_id = c.user_id OR sp.sale_id = s.id)
I should have returning 6 rows, but there are 21. What am I doing wrong?
OUTPUT expectation (if I do SELECT c.* ...):
id | sale_product | user_id | amount |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
What I am getting are duplicates (I think because the other tables see sale product id and do something funky)
id | sale_product | user_id | amount |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
I have made some changes to the SQL and I'm almost done.
Here is what I have:
SELECT
DISTINCT c.id, c . * , s . *
FROM
commission AS c, sale_product AS sp, sale AS s
WHERE
sp.id = c.sale_product_id
AND s.id = sp.sale_id
AND s.is_paid = '1'
AND sp.cron_executed = '1'
AND s.upline_user_id = c.user_id
AND c.user_id = '3'
When I do this query, particularly the s.upline_user_id = c.user_id line, it gives me EXACTLY 1 query. This is correct. When I remove that line, I get all 6 rows. This is also correct. However, when I try to do this:
SELECT
DISTINCT c.id, c . * , s . *
FROM
commission AS c, sale_product AS sp, sale AS s
WHERE
sp.id = c.sale_product_id
AND s.id = sp.sale_id
AND s.is_paid = '1'
AND sp.cron_executed = '1'
AND s.upline_user_id <> c.user_id
AND c.user_id = '3'
I get a null result. How come? Even if I change the line to s.upline_user_id <> '3', (which btw should give me 5 rows), gives me null.
Anyone?
Your OR operator on the join is likely causing the duplicate data because both are being met. If you are using the OR as a fallback when the condition is not met, think about using a CASE statement instead, if possible. Test the existence of the most likely condition first, ELSE is your fallback.
AND CASE
WHEN (a IS NOT NULL AND b IS NOT NULL)
THEN (a = b)
ELSE (c = d)
END
If you are testing strings, test for NULLs and lengths of zero.
Hope it helps!

Search for records with same value in one column but varying values in a another

Apologies for my very ambiguous title, but i've been working on this for the better part of a day and can't get anywhere so i'm probably clouded.. Let me present sample data and explain what I'm trying to do:
+------+------+
| ID | UW |
+------+------+
| 1 | I |
| 1 | I |
| 3 | I |
| 3 | I |
| 3 | C |
| 3 | C |
| 4 | C |
| 4 | C |
I'm trying to find the count of IDs where there are both "I" and "C" in the UW column, so in the example above the count would be: 1 (for ID #3). Since ID 1 has only "I" and ID 4 has only "C" values in "UW" field. Thanks in advance for helping me with this, much appreciated.
Here is one way:
SELECT COUNT(DISTINCT A.ID) N
FROM dbo.YourTable A
WHERE EXISTS(SELECT 1 FROM dbo.YourTable
WHERE ID = A.ID
AND UW IN ('I','C'));
And another:
SELECT COUNT(*)
FROM ( SELECT ID
FROM dbo.YourTable
WHERE UW IN ('I','C')
GROUP BY ID
HAVING COUNT(DISTINCT UW) = 2) A;
You can use group by and having to get the ids that meet the conditions:
select id
from table t
group by id
having sum(case when uw = 'I' then 1 else 0 end) > 0 and
sum(case when uw = 'C' then 1 else 0 end) > 0;
You can then count these with a subquery:
select count(*)
from (select id
from table t
group by id
having sum(case when uw = 'I' then 1 else 0 end) > 0 and
sum(case when uw = 'C' then 1 else 0 end) > 0
) t
I like to formulate these problems this way, because the having clause is very general on the types of conditions that it can support.

Return count(*) even if 0

I have the following query:
select bb.Name, COUNT(*) as Num from BOutcome bo
JOIN BOffers bb ON bo.ID = bb.BOutcomeID
WHERE bo.EventID = 123 AND bo.OfferTypeID = 321 AND bb.NumA > bb.NumB
GROUP BY bb.Name
The table looks like:
Name | Num A | Num B
A | 10 | 3
B | 2 | 3
C | 10 | 3
A | 9 | 3
B | 2 | 3
C | 9 | 3
The expected output should be:
Name | Count
A | 2
B | 0
C | 2
Because when name is A and C then Num A is bigger to times than Num B and when Name is B, in both records Num A is lower than Num B.
My current output is:
Name | Count
A | 2
C | 2
Because B's output is 0, i am not getting it back in my query.
What is wrong with my query? how should I get it back?
Here is my guess. I think this is a much simpler approach than all of the left/right join hoops people have been spinning their wheels on. Since the output of the query relies only on columns in the left table, there is no need for an explicit join at all:
SELECT
bb.Name,
[Count] = SUM(CASE WHEN bb.NumA > bb.NumB THEN 1 ELSE 0 END)
-- just FYI, the above could also be written as:
-- [Count] = COUNT(CASE WHEN bb.NumA > bb.NumB THEN 1 END)
FROM dbo.BOffers AS bb
WHERE EXISTS
(
SELECT 1 FROM dbo.BOutcome
WHERE ID = bb.BOutcomeID
AND EventID = 123
AND OfferTypeID = 321
)
GROUP BY bb.Name;
Of course, we're not really sure that both Name and NumA/NumB are in the left table, since the OP talks about two tables but only shows one table in the sample data. My guess is based on the query he says is "working" but missing rows because of the explicit join.
Another wild guess. Feel free to downvote:
SELECT ba.Name, COUNT(bb.BOutcomeID) as Num
FROM
( SELECT DISTINCT ba.Name
FROM
BOutcome AS b
JOIN
BOffers AS ba
ON ba.BOutcomeID = b.ID
WHERE b.EventID = 123
AND b.OfferTypeID = 321
) AS ba
LEFT JOIN
BOffers AS bb
ON AND bb.Name = ba.Name
AND bb.NumA > bb.NumB
GROUP BY ba.Name ;

Multiple sum/counts across multiple tables in PostgreSQL

I've searched through several suggestions on this site and haven't quite been able to get what I'm after. I suspect there's just a syntax/punctuation issue that I'm just missing.
I work on a database using phpPgAdmin that tracks lots of information related to a population of baboons being studied. I'm trying to make a query to identify, for each individual baboon, how many tissue samples of different types we have collected for them and how many DNA samples we have of different types for each of them There are three tables that are pertinent to my problem:
Table: "biograph" has basic info about all the animals in the group, though the name is all I care about here.
name | birth
-----+-----------
A21 | 1968-07-01
AAR | 2002-03-30
ABB | 1998-09-10
ABD | 2005-03-15
ABE | 1986-01-01
Table: "babtissue" tracks information, including the below three columns, about different tissues that have been collected over the years. Some lines in this table represent tissue samples that we no longer have, but are still referred to elsewhere in the database, so the "avail" column helps us screen for samples that we still have around.
name | sample_type | avail
-----+-------------+------
A21 | BLOOD | Y
A21 | BLOOD | Y
A21 | TISSUE | N
ABB | BLOOD | Y
ABB | TISSUE | Y
Table: "dna" is similar to babtissue.
name | sample_type | avail
-----+-------------+------
ABB | GDNA | N
ABB | WGA | Y
ACC | WGA | N
ALE | GDNA | Y
ALE | GDNA | Y
Altogether, I'm trying to write a query that will return every name from biograph and tells me in one column how many 'BLOOD', 'TISSUE', 'GDNA', and 'WGA' samples I have for each individual. Something like...
name | bloodsamps | tissuesamps | gdnas | wgas | avail
-----+------------+-------------+-------+------+------
A21 | 2 | 0 | 0 | 0 | ?
AAR | 0 | 0 | 0 | 0 | ?
ABB | 1 | 1 | 0 | 1 | ?
ACC | 0 | 0 | 0 | 0 | ?
ALE | 0 | 0 | 2 | 0 | ?
(Apologies for the weird formatting above, I'm not very familiar with writing this way)
The latest version of the query that I've tried:
select b.name,
sum(case when t.sample_type='BLOOD' and t.avail='Y' then 1 else 0 end) as bloodsamps,
sum(case when t.sample_type='TISSUE' and t.avail='Y' then 1 else 0 end) as tissuesamps,
sum(case when d.sample_type='GDNA' and d.avail='Y' then 1 else 0 end) as gdnas,
sum(case when d.sample_type='WGA' and d.avail='Y' then 1 else 0 end) as wgas
from biograph b
left join babtissue t on b.name=t.name
left join dna d on b.name=d.name
where b.name is not NULL
group by b.name
order by b.name
I don't receive any errors when doing it this way, but I know the numbers it gives me are wrong--too high. I figure this has something to do with my use of more than one join, and that something about my join syntax needs to change.
Any ideas?
The numbers are too high because you're joining to babtissue and then also to dna, which is going to cause duplicates.
You can try to break it up. I don't know if this syntax will work for your database, but I believe that it follows ANSI standards, so give it a shot...
SELECT
SQ.name,
SUM(CASE WHEN T.sample_type = 'BLOOD' AND T.avail = 'Y' THEN 1 ELSE 0 END) AS bloodsamps,
SUM(CASE WHEN T.sample_type = 'TISSUE' AND T.avail = 'Y' THEN 1 ELSE 0 END) AS tissuesamps,
SQ.gdnas,
SQ.wgas
FROM
(
SELECT
B.name,
SUM(CASE WHEN D.sample_type = 'GDNA' AND T.avail = 'Y' THEN 1 ELSE 0 END) AS gdnas,
SUM(CASE WHEN D.sample_type = 'WGA' AND T.avail = 'Y' THEN 1 ELSE 0 END) AS wgas
FROM
biograph B
LEFT JOIN dna D ON D.name = B.name
GROUP BY
B.name
) AS SQ
LEFT JOIN babtissue T on T.name = SQ.name
WHERE SQ.name is not NULL
GROUP BY SQ.name, SQ.gdnas, SQ.wgas
ORDER BY SQ.name
Can the name really be NULL?
I don't know about the "avail" column, but this should give you the other columns you're looking for:
SELECT b.name,
COALESCE (t.bloodsamps, 0) AS bloodsamps,
COALESCE (t.tissuesamps, 0) AS tissuesamps
COALESCE (d.gdnas, 0) AS gdnas
COALESCE (d.wgas, 0) AS wgas
FROM biograph b
LEFT JOIN (
SELECT name,
SUM(CASE WHEN sample_type = 'BLOOD' THEN 1 ELSE 0 END) AS bloodsamps,
SUM(CASE WHEN sample_type = 'TISSUE' THEN 1 ELSE 0 END) AS tissuesamps
FROM babtissue
WHERE avail = 'Y'
GROUP BY name
) t
ON (t.name = b.name)
LEFT JOIN (
SELECT name,
SUM(CASE WHEN sample_type = 'GDNA' THEN 1 ELSE 0 END) AS gdnas,
SUM(CASE WHEN sample_type = 'WGA' THEN 1 ELSE 0 END) AS wgas
FROM dna
WHERE avail = 'Y'
GROUP BY name
) d
ON (d.name = b.name)
;