SQL query to compare subsets of rows between each other - sql

There is an SQL query I'm after (in SQL Server). I need to get the count of instances where company Y is more expensive than company X. How would I start to tackle this? I've looked through various examples, but I cannot find anything similiar. I see PARTITION BY could be helpful, but not sure how to start from there - any hint will be very helpful.
ReadingId | Product | Price | Company
----------------------------------------------
1 | A | 3 | X
2 | A | 4 | Y
3 | A | 5 | Z
4 | B | 11 | X
5 | B | 12 | Y
6 | B | 13 | Z
...

One method is conditional aggregation. For each product:
select product,
max(case when company = 'Y' then price end) as Yprice
max(case when company = 'X' then price end) as Xprice
from t
group by product;
For a count, you can then do:
select count(*)
from (select product,
max(case when company = 'Y' then price end) as Yprice
max(case when company = 'X' then price end) as Xprice
from t
group by product;
) p
where Yprice > Xprice;
There are other methods as well. Pivot can be used, as well as a join with aggregation:
select count(*)
from t ty join
t tx
on ty.company = 'Y' and tx.company = 'X' and ty.product = tx.product
where ty.price > tx.price;
I should point out that all these methods sort of assume that X and Y only appear once for each product. That seems reasonable given your data.

Rather straight-forward. Get product prices for companies X and Y. Join them together on product and compare prices.
It assumes that each product is listed once for a company.
WITH
CTE_X
AS
(
SELECT Product, Price
FROM T
WHERE Company = 'X'
)
,CTE_Y
AS
(
SELECT Product, Price
FROM T
WHERE Company = 'Y'
)
SELECT COUNT(*) AS cc
FROM
CTE_X
INNER JOIN CTE_Y ON CTE_Y.Product = CTE_X.Product
WHERE
CTE_Y.Price > CTE_X.Price
;

You can do this with conditional aggregation.
with xandy as (select product,
max(case when company = 'X' then price end) as xprice,
max(case when company = 'Y' then price end) as yprice
from tablename
group by product)
select count(*)
from xandy
where yprice > xprice

This query, tho not efficient, will give you the details of what you need:
Select
CompanyY.*,
CompanyX.*
FROM
(
select * from OrderDetails
where Company = 'Y'
) CompanyY
JOIN
(
select * from OrderDetails
where Company = 'X'
) CompanyX
ON CompanyX.Product = CompanyY.Product
WHERE CompanyY.Price > CompanyX.Price
Try the SQLFiddle Here

You can use:
SELECT COUNT(*)
FROM (
SELECT Product
FROM mytable
GROUP BY Product
HAVING MAX(CASE WHEN Company = 'Y' THEN Price END)
>
MAX(CASE WHEN Company = 'X' THEN Price END) ) AS t
The sub-query returns the list of products where company Y is more expensive than company X. The outer query simply counts the number of these products.
Yet another version using a window function:
SELECT COUNT(*)
FROM (
SELECT Company,
ROW_NUMBER() OVER (PARTITION BY Product
ORDER BY Price DESC) AS rn
FROM mytable
WHERE Company IN ('X', 'Y')) AS t
WHERE t.rn = 1 AND Company = 'Y'
The sub-query filters out any rows not having either 'X' or 'Y' as their Company. The outer query counts the number of rows for which Company = 'Y' has the highest price.

Related

Compare the same id with 2 values in string in one table

I have a table like this:
id
status
grade
123
Overall
A
123
Current
B
234
Overall
B
234
Current
D
345
Overall
C
345
Current
A
May I know how can I display how many ids is fitting with the condition:
The grade is sorted like this A > B > C > D > F,
and the Overall grade must be greater than or equal to the Current grade
Is it need to use CASE() to switch the grade to a number first?
e.g. A = 4, B = 3, C = 2, D = 1, F = 0
In the table, there should be 345 is not match the condition. How can I display the tables below:
qty_pass_the_condition
qty_fail_the_condition
total_ids
2
1
3
and\
fail_id
345
Thanks.
As grade is sequential you can do order by desc to make the number. for the first result you can do something like below
select
sum(case when GradeRankO >= GradeRankC then 1 else 0 end) AS
qty_pass_the_condition,
sum(case when GradeRankO < GradeRankC then 1 else 0 end) AS
qty_fail_the_condition,
count(*) AS total_ids
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from YourTbale
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from YourTbale
) as a where Status='Current'
) as c on b.Id=c.Id
For second one you can do below
select
b.Id fail_id
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from Grade
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from Grade
) as a where Status='Current'
) as c on b.Id=c.Id
where GradeRankO < GradeRankC
You can use pretty simple conditional aggregation for this, there is no need for window functions.
A Pass is when the row of Overall has grade which is less than or equal to Current, with "less than" being in A-Z order.
Then aggregate again over the whole table, and qty_pass_the_condition is simply the number of non-nulls in Pass. And qty_fail_the_condition is the inverse of that.
SELECT
qty_pass_the_condition = COUNT(t.Pass),
qty_fail_the_condition = COUNT(*) - COUNT(t.Pass),
total_ids = COUNT(*)
FROM (
SELECT
t.id,
Pass = CASE WHEN MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) <=
MIN(CASE WHEN t.status = 'Current' THEN t.grade END)
THEN 1 END
FROM YourTable t
GROUP BY
t.id
) t;
To query the actual failed IDs, simply use a HAVING clause:
SELECT
t.id
FROM YourTable t
GROUP BY
t.id
HAVING MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) >
MIN(CASE WHEN t.status = 'Current' THEN t.grade END);
db<>fiddle

SQL request with multiple joins condition on a list of transactions (postgreSQL)

I have a transactions table with a this structure of data, showing the most important fields to keep in mind.
It's a list of transactions, where type=1 transactions are account deposit, while type=2 are account withdrawals.
login | type | value | date.
1234 | 1 | 100 | 25/09/2021
1234. | 2. | 250. | 26/09/2021
4321. | 2. | 234. | 13/09/2021
4321. | 1. | 342. | 14/08/2021
...
...
What I'm trying to get is the list of the accounts, where their balance during the period is bigger than > some amount, X, and no active deposits after 16/09/2021.
SELECT t.date,
a.login, t.account, sum(d.value) as deposits, sum(w.value) as withdrawals, sum(d.value) - sum(w.value) as balance
FROM b.transactions AS t
INNER JOIN b.accounts as a ON t.account=a.id
INNER JOIN b.transactions AS d ON t.account = d.account and d.date=t.date and d.type=t.type
INNER JOIN b.transactions AS w ON t.account = w.account and w.date=t.date and w.type=t.type
WHERE
d.value - w.value > 5000 and
d.type = '1' and
w.type = '2' and
d.date<='2021-09-29' and d.date>='2021-09-29' and
w.date<='2021-09-29' and w.date>='2021-09-29' and
t.date<='2021-09-29' and t.date>='2021-09-29' and
t.account not in
(
SELECT t.account
FROM b.transactions AS t
where t.type in (1) and
t.date>='2021-09-16' and
t.value>0
group by t.account
)
I'm getting some output, but it looks like seriously underestimated (at least 10x- fold)... Can not find the mistake, where should I look?
Thanks in advance..
You may be overthinking this — I don't think it is necessary to join the same table three times:
SELECT t.date,
a.login,
t.account,
sum(CASE WHEN t.type = '1'
THEN t.value
WHEN t.type = '2'
THEN -t.value
END) AS balance
FROM b.transactions AS t
INNER JOIN b.accounts AS a
ON t.account = a.id
WHERE t.date BETWEEN '2021-09-29' AND '2021-09-29'
GROUP BY t.account
HAVING sum(CASE WHEN t.type = '1'
THEN t.value
WHEN t.type = '2'
THEN -t.value
END) > 5000;
First, to compute the running balance you can use a window function. For example:
select
login, date, type, value,
sum(
case when type = 1 then value when type = 2 then -value end
) over(partition by login order by date) as running_balance
from t
order by login, date;
Result:
login date type value running_balance
------ ------------------------- ----- ------ ---------------
1234 2021-09-25T00:00:00.000Z 1 100 100
1234 2021-09-26T00:00:00.000Z 2 250 -150
4321 2021-09-13T00:00:00.000Z 2 234 -234
4321 2021-09-14T00:00:00.000Z 1 342 108
See running example at DB Fiddle - Simple Running Balance.
Then you can use the logic above to compute more complex values so you can use them to filter data, as in:
select distinct login
from (
select
login, date, type, value,
sum(
case when type = 1 then value when type = 2 then -value end
) over(partition by login order by date) as running_balance,
sum(
case when type = 1 and date > date '2021-09-16' then 1 else 0 end
) over(partition by login order by date) as extra_deposits
from t
) x
where running_balance >= 100 and extra_deposits = 0
Result:
login
-----
4321
See running example at DB Fiddle - Full Query.

Performing additions within multiple columns of different rows in a SQL Server table based on the text in a column

I am trying to figure a query which performs some additions and subtractions of data in different rows and different columns based on the text/data in some other column in the same table.
Problem can be clearly addressed with the following example
Consider, I have table named Outright with four fields/columns with several records as follows
Product Term Bid Offer
------------------------------
A Aug14 P Q
A/B Aug14 R S
B Aug14 X Y
B Sep14 ab xy
B/C Sep14 pq rs
C Sep14 wx yz
When I run the query it should look for the Products that is separated by / in the above case there are two products of that type A/B and B/Cand then it should look for individual products based the those that are separated by / like we have a product A/B which is separated by a /, so it should look for product A and B with same term as A/B and perform some operations and return the data as follows
Product Term Bid Offer
------------------------------
A Aug14 a b
B Aug14 c d
B Sep14 ab cd
C Sep14 abc cde
in the above results
a=R+Y b=S+X
c=Q-S d=P-R
where P,Q,R,S,X,Y are Bid and Offer values from the table Outright
similar calculations are applied for all other data too like for B/C Sep14.. and many other
Example
Table Outright
A Oct14 -175 -75
B Oct14 125 215
A/B Oct14 NULL -150
Result should be
A Oct14 NULL -150+125=-25
B Oct14 -75-(-150)=75 NULL
The above values are calculated using the equations mentioned earlier
May I know a better way to solve it in SQL Server 2012?
Ok lets create some test data:
DECLARE #Outright TABLE
(
Product VARCHAR(10),
Term VARCHAR(10),
Bid VARCHAR(10),
Offer VARCHAR(10)
)
INSERT INTO #Outright
VALUES
('A', 'Aug14','P','Q'),
('A/B','Aug14','R','S'),
('B', 'Aug14','X','Y');
Making a cte to try to figure out the logic posted above and match the single product line to the multiproduct line
;WITH t AS
(
SELECT
a.*,
d.DRN,
d.Bid dBid,
d.Product dProduct,
d.Offer dOffer,
ROW_NUMBER() OVER (ORDER BY a.Product) RN
FROM #Outright a
OUTER APPLY
(
SELECT *,
ROW_NUMBER() OVER (ORDER BY d.Product) DRN
FROM #Outright d
WHERE d.Product LIKE (a.Product + '/%')
OR d.Product LIKE ('%/' + a.Product)
) d
WHERE d.Product IS NOT NULL
)
Now we try to implement the + - rules as stated above (bids to offers, offers to bids, etc)
SELECT
*,
CASE WHEN RN = 1 THEN FE1_1 + '+' + FE1_2 ELSE FE1_1 + '-' + FE1_2 END Col1,
CASE WHEN RN = 1 THEN FE2_1 + '+' + FE2_2 ELSE FE2_1 + '-' + FE2_2 END Col2
FROM
(
SELECT
MAX(CASE WHEN RN = 1 THEN Product END) Prod1,
MAX(CASE WHEN RN = 1 THEN Term END) Term1,
MAX(CASE WHEN RN = 1 THEN dBid END) FE1_1,
MAX(CASE WHEN RN = 2 THEN Offer END) FE1_2,
MAX(CASE WHEN RN = 2 THEN dOffer END) FE2_1,
MAX(CASE WHEN RN = 2 THEN Bid END) FE2_2,
1 RN
FROM t
UNION ALL
SELECT
MAX(CASE WHEN RN = 2 THEN Product END) Prod2,
MAX(CASE WHEN RN = 2 THEN Term END) Term2,
MAX(CASE WHEN RN = 1 THEN Offer END) FE3_1,
MAX(CASE WHEN RN = 2 THEN dOffer END) FE3_2,
MAX(CASE WHEN RN = 1 THEN Bid END) FE4_1,
MAX(CASE WHEN RN = 2 THEN dBid END) FE4_2,
2 RN
FROM t
) d
Here is the output, with some extra columns to show the data being pulled
Prod1 Term1 FE1_1 FE1_2 FE2_1 FE2_2 RN Col1 Col2
A Aug14 R Y S X 1 R+Y S+X
B Aug14 Q S P R 2 Q-S P-R

SQL condition sum from two joined tables

I have two tables as below:
Invoice
InvId | Amount | Name
-----------------------
1 | 50 | John
2 | 30 | Mike
3 | 20 | John
Detail
MetalType| Weight | InvId
-------------------------
Gold | 2 | 2
Silver | 4 | 3
Silver | 3 | 3
Gold | 5 | 1
I would like to have the following output, but my query will only provide the total for silver and gold for John. How can I build a query that will also include the total invoice amount for John.
Total Invoice Amount For John = 70
Total Silver Weight = 7
total Gold Weith = 5
SELECT
SUM(IFF(D.MetalType=”Gold”, D.Weight, 0)) AS TotGold,
SUM((IFF(D.MetalType=”Silver”, D.Weight, 0)) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId WHERE I.Name = “John”
Try this:
For Sql-Server:
SELECT
SUM(TotalAmount) AS TotalAmount,
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John'
GROUP BY D.InvId, I.Amount) n
Here is an SQL Fiddle - now it kills the duplicate detail and counts it only once.
EDITED for Access:
SELECT
n.Name,
MAX(TotalAmount),
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
I.Name,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I
INNER JOIN Detail D ON I.InvId = D.InvId
GROUP BY I.Name, D.InvId, I.Amount) n
INNER JOIN (
SELECT
I.Name, SUM (I.Amount) AS TotalAmount
FROM Invoice I
GROUP BY I.Name) m ON m.Name = n.Name
GROUP BY n.Name
Try with this:
With tbl3 (Amt,Gold,Silver)
as
(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I Right JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John' Group by D.InvId, I.Amount
)
Select SUM(Amt) as Total_Invoice_Amount_For_John,
SUM(Gold) as Total_Silver_Weight,
SUM(Silver) as Total_Gold_Width from tbl3
SQL Fiddle
I havent tried out the other queries already posted and they might already be suitable for what you want but here's my take on it :-
SELECT X.NAME, X.METALTYPE, X.WEIGHT, Y.TOTAL
FROM
(SELECT NAME, METALTYPE, SUM(Weight) AS WEIGHT
FROM INVOICE i
INNER JOIN DETAIL d ON i.InvId = d.InvId
GROUP BY NAME, METALTYPE) X
INNER JOIN
(SELECT SUM(AMOUNT) AS Total, NAME
FROM INVOICE
GROUP BY NAME)Y
ON X.NAME = Y.NAME
ORDER BY NAME, TOTAL, METALTYPE
select name, sum(Amount) as 'total invoice',sum(Gold) as 'Gold',sum(Silver) as Silver from(
select aa.Name,aa.Amount,
sum(case when bb.MetalType='Gold' then bb.Weight else 0 end) as 'Gold',
sum(case when bb.MetalType='Silver' then bb.Weight else 0 end) as 'Silver'
from a aa left outer join b bb on aa.InvID=bb.InvID group by aa.InvID) as c group by c.name

Trying to create fields based on a case statement

I'm having some trouble with the query below. I am trying to determine if the "category" field is A, B or C and then creating a field based on the category. That field would sum up payments field. But I'm running into error saying "incorrect syntax near keyword As". I am creating this in a SQL View. Using SQL Server 2008
SELECT r.id, r.category
CASE
WHEN r.category = 'A' then SUM(r.payment) As A_payments
WHEN r.category = 'B' then SUM(r.payment) As B_payments
WHEN r.category = 'C' then SUM(r.payment) As C_payments
END
FROM r_invoiceTable As r
GROUP BY r.id, r.category
I have data where all of the above cases should be executed because the data that I have has A,B and C
Sample Data- r_invoiceTable
Id --- Category ---- Payment
222 A ---- 50
444 A ---- 30
111 B ---- 90
777 C ---- 20
555 C ---- 40
Desired Output
A_payments = 80, B_payments = 90, C_payments = 60
Perhaps you are looking for this:
SELECT
SUM(CASE WHEN category = 'A' THEN payment END) AS A_payments,
SUM(CASE WHEN category = 'B' THEN payment END) AS B_payments,
SUM(CASE WHEN category = 'C' THEN payment END) AS C_payments
FROM r_invoiceTable
May be you want this (category-wise payment sum)?:
SELECT r.category, SUM(r.payment) As Payments
FROM r_invoiceTable As r
GROUP BY r.category
Output will be like:
category Payments
--------- ------------
A 80
B 90
C 60
If you like these numbers in a single row here is a naive approach:
SELECT
(SELECT SUM(r.payment) From r_invoiceTable As r Where r.category = 'A') as A_Payment,
(SELECT SUM(r.payment) From r_invoiceTable As r Where r.category = 'B') as B_Payment,
(SELECT SUM(r.payment) From r_invoiceTable As r Where r.category = 'C') as C_Payment
SELECT *
FROM dbo.r_invoiceTable
PIVOT
( SUM(payment)
FOR category IN ([a],[b],[c])
) AS pivoted