Using "Group By" in a Select Sub-Query - sql

Below is the query that I created, but upon validation of the results produced, the query is producing inaccurate results.
select a.acct_id, c.bill_dt
from account a inner join invoice_detail b on a.acct_id = b.acct_id
inner join
(select acct_id, max(bill_dt) as bill_dt from invoice_detail
where bill_dt < '1/1/2014'
group by acct_id)c on b.acct_id = c.acct_id
and a.acct_stat_id = '275'
and not a.acct_id in (select acct_id from contract where cntrct_stat_id in ('394','554','555','556'))
and not a.acct_id in (select acct_id from billing_adj where bill_adj_stat_id in ('4','394','553','554','555'))
group by a.acct_id, c.bill_dt
order by a.acct_id ASC
I would like my results to only show acct_ids and the max(bill_dt) after meeting all query criteria. The invoice_detail table contains multiple records for an acct_id. However, when I executed the query, I randomly selected an acct_id that had a max(bill_dt) of 12/31/2013 for validation. I looked in the invoice_detail table by acct_id and the results came back with additional records with a bill_dt greater than 1/1/2014. I want to identify acct_ids that do not have any invoices after 1/1/2014.

I want to identify acct_ids that do not have any invoices after 1/1/2014
Then your condition in your subquery needs to be:
HAVING max(bill_dt) < '1/1/2014'
You're also not using the invoice_detail table except in the subquery, so you can take it out of the main query:
select a.acct_id, c.bill_dt
from account a
inner join
( select acct_id, max(bill_dt) as bill_dt from invoice_detail
group by acct_id
HAVING max(bill_dt) < '1/1/2014'
) c on a.acct_id = c.acct_id
and a.acct_stat_id = '275'
and not a.acct_id in (select acct_id from contract where cntrct_stat_id in ('394','554','555','556'))
and not a.acct_id in (select acct_id from billing_adj where bill_adj_stat_id in ('4','394','553','554','555'))
group by a.acct_id, c.bill_dt
order by a.acct_id ASC

Related

Column "not contained in either an aggregate function or a GROUP BY clause"

The query:
SELECT
A.mrno, A.remarks,
B.itemcode, B.description, B.uom, B.quantity,
C.whsecode, MAX(C.quantity) AS whseqty, D.rate
FROM
Mrhdr A
INNER JOIN
Mrdtls B ON A.mrno = B.mrno
INNER JOIN
inventoryTable C ON B.itemcode = C.itemcode
INNER JOIN
Items D ON B.itemcode = D.itemcode
WHERE
(A.mrno = #MRNo AND B.quantity < C.quantity);
The error:
Column 'Mrhdr.mrno' is invalid in the select list because it is not
contained in either an aggregate function or the GROUP BY clause.
It says that the column mrno is not contained in the aggregate function of something, but when I do something about it like put it in a GROUP BY clause, the next column requests return the same error until the last column except the C.quantity column, and when they are all in a GROUP BY clause it will only return the same output not returning the highest or maximum value for the quantity. What should I do with the other columns when I use MAX or aggregate functions.
The output of the query above:
If I put all of the columns in a GROUP BY clause it returns an output with two of the itemcode FG 4751, it just removes the error of aggregate function, but I just want the highest value to be returned (just the 100, the highest quantity in the warehouse/inventory).
You want to deal with the maximum inventory quantity per product. But you are joining all inventory rows, where you should only pick the maximum quantity rows.
This can be done with a lateral join, if your DBMS supports this (you have forgotton to tell us which you are using) or simply by joining the rows in question by applying a window function as follows.
SELECT
A.mrno, A.remarks,
B.itemcode, B.description, B.uom, B.quantity,
C.whsecode, C.whseqty, D.rate
FROM
Mrhdr A
INNER JOIN
Mrdtls B ON A.mrno = B.mrno
INNER JOIN
(
SELECT
itemcode, whsecode, quantity as whseqty,
MAX(quantity) OVER (PARTITION BY itemcode) AS max_qty
FROM inventoryTable
) C ON B.itemcode = C.itemcode AND C.whseqty = C.max_qty
INNER JOIN
Items D ON B.itemcode = D.itemcode
WHERE
A.mrno = #MRNo AND B.quantity < C.whseqty;
This query should work in most DBMS. If you are working with a DBMS that supports the standard SQL FETCH WITH TIES clause, I'd change the join to:
INNER JOIN
(
SELECT itemcode, whsecode, quantity as whseqty
FROM inventoryTable
ORDER BY RANK() OVER (PARTITION BY itemcode ORDER BY quantity DESC)
FETCH FIRST ROW WITH TIES
) C ON B.itemcode = C.itemcode
so as to only select the top rows inside the subquery already and not to awkwardly filter them later. But well, a lateral join may even be considered more straight-forward here.
The alternative to putting everything in a group by clause would be to use a window function. The question then becomes what is the MAX value relative to?
For example, you could get the MAX value based on all criteria, which would return a similar result to group by without leaving only distinct values for the column.
SELECT
A.mrno,
A.remarks,
B.itemcode,
B.description,
B.uom,
B.quantity,
C.whsecode,
MAX(C.quantity) OVER(PARTITION BY A.mrno, A.remarks, B.itemcode, B.description, B.uom, B.quantity, C.whsecode, D.rate) AS whseqty,
D.rate
FROM
Mrhdr A
INNER JOIN
Mrdtls B ON A.mrno = B.mrno
INNER JOIN
inventoryTable C ON B.itemcode = C.itemcode
INNER JOIN
Items D ON B.itemcode = D.itemcode
WHERE
(A.mrno = #MRNo AND B.quantity < C.quantity);

SQL: payments and orders query

I have 2 tables...
the first one "orders" collects
memberid
order_id
authorization date
the 2nd one "payments" collects
memberid
paymentid
payment number
last_udpate
a memberid might contain more than 1 card and I need to create a query that brings me the orderid and which payment number has used on each order taking into account when was the last update and authorization date..
I've got till here..
SELECT orders.order_id, payments.payments_number
FROM orders
JOIN payments
ON orders.memberid = payments.memberid
I'm a bit lost what subquery should I use to make it work..
Thanks!
Hmmm . . . I think a correlated subquery does what you want:
select o.*,
(select p.payment_number
from payments p
where p.memberid = o.memberid and
p.last_update <= o.authorization_date
order by last_update desc
limit 1
) as payment_number
from orders o;
You can use an additional join if you need other columns from payments.
Since you specify the cardinality of 1 to many and I am not sure of which way your cardinality is most accurately represented.
If each order has many payments then maybe you want something like this.
SELECT o.memberid,
o.order_id,
o.[authorization date],
p.payments_number,
p.last_udpate
FROM orders o
LEFT JOIN payments p
ON o.memberid = p.memberid
If it is the other way round, maybe something like this
SELECT o.memberid,
o.order_id,
o.[authorization date],
p.payments_number,
p.last_udpate
FROM payments p
LEFT JOIN orders o
ON o.memberid = p.memberid
Something similar to what was proposed but using a CTE could be:
;
with cte_partial_results as
(
SELECT o.memberid,
o.order_id,
o.[authorization date] as [authorization_date],
p.paymentid,
p.[payment number] as payment_number
p.last_update,
ROW_NUMBER() OVER(PARTITION BY o.memberid, o.order_id ORDER BY p.last_udpate desc) as RowNo
FROM orders o
LEFT JOIN payments p ON o.memberid = p.memberid
WHERE
p.last_udpate <= o.[authorization date]
)
SELECT
memberid,
order_id,
payment_number,
last_update as most_recent_update
--add other cols you want.
FROM
cte_partial_results
WHERE
RowNo = 1

Sum from the joined table in sql

I am trying to sum the amount from the tbl_paid where transaction_num has same number and tbl_client.c_id = tbl_paid.c_id.
Basically means I am trying to add the amount of 3359 and 1 where there c_id = 172 and transaction_num = 2 because they have the same record. Row 1 and 2 and then the other is as is.
tbl_client
tbl_paid
Look at the first picture to see the query I used to join table and get the output.
What I am trying to do with my query is to show all the records from tbl_client joined by tbl_paid who are accounts that are paid today. If the pay twice this day the record of the amount will be added to the existing payment.
You want to show clients with their today's total payment. So, select the total payment per client and then join this to the client table.
select c.*, p.total
from tbl_client c
join
(
select c_id, sum(amount) as total
from tbl_paid
where pay_date = current_date()
group by c_id
) p on p.c_id = c.c_id
order by c.c_id;
If you want to show clients with no current payment, too, then change the inner join to an outer join.
UPDATE: In the comments to ϻᴇᴛᴀʟ's answer you say you want to see the different transactions. If this is so, then you don't want the total per client, but the total per client and transaction, which is a very small change in the query:
select c.*, p.transaction_num, p.total
from tbl_client c
join
(
select c_id, transaction_num, sum(amount) as total
from tbl_paid
where pay_date = current_date()
group by c_id, transaction_num
) p on p.c_id = c.c_id
order by c.c_id;
sum() and group by is what you need.
select t3.*, t4.*
from tbl_client t4
inner join
(select sum(t2.amount) as amt, t1.transaction_num, t1.c_id
from tbl_client t1
inner join tbl_paid t2 on t1.c_id = t2.c_id
where t1.pay_date = current_date()
group by t1.transaction_num, t1.cid
) t3 on t3.c_id = t4.c_id
You can use SUM() :
select SUM(amount), t1.transaction_num
from tbl_client t1
left join tbl_paid t2 on t1.c_id = t2.c_id
where t1.transaction_num = 2 and t1.c_id = 172`

Using SQL how can I write a query to find the top 5 per category per month?

I am trying to get the Top 5 rows with the highest number for each category for a specific time interval such as a month. What I currently have returns 5 of the exact same descriptions for a category. I am trying to get the top five. This only happens when I try to sort it based on a time period.
WITH CustomerRank
AS
(SELECT
Count(*) AS "Count",
d.Item,
d.Description,
Name,
i.Type,
d.CreatedOn
FROM [dbo].i,
d,
dbo.b,
as,
a,
c
WHERE d.Inspection_Id = i.Id AND d.Inspection_Id = i.Id AND
b.Id = i.BuildingPart_Id AND b.as= Assessments.Id
AND as.Application_Id = a.Id AND a.Customer_Id = Customers.Id
group by d.Item, d.Description, Name, i.Type, d.CreatedOn
)
select * from (
SELECT "Count",Item,Description,Type,ROW_NUMBER() Over (PARTITION BY Name order by "Count" desc) AS RowNum, Name, CreatedOn
FROM CustomerRank
where CreatedOn > '2017-1-1 00:00:00'
) s where RowNum <6
Cheers
Try something like this:
WITH CustomerRank
AS
(SELECT
Count(*) AS "Count",
d.Item, d.Description, Name, i.Type
FROM dbo.Inspection i
INNER JOIN dbo.Details d ON d.Inspection_Id = i.Id
INNER JOIN dbo.BuildingParts b ON b.Id = i.BuildingPart_Id
INNER JOIN dbo.Assessments a ON a.Id = b.AssessmentId
INNER JOIN dbo.Applications ap ON ap.Id = a.Application_Id
INNER JOIN dbo.Customers c ON c.Id = a.Customer_Id
where CreatedOn > '2017-1-1 00:00:00'
group by d.Item, d.Description, Name, i.Type
)
select * from (
SELECT "Count",Item,Description,Type,ROW_NUMBER() Over (PARTITION BY Name order by "Count" desc) AS RowNum, Name
FROM CustomerRank
) s where RowNum <6
The idea is that the CreatedOn column must be removed from the GROUP BY clause (because if you keep it there, we would get a different row for each value of the CreatedOn column).
Also, it's better to use JOIN-s and aliases for each table.

JOIN only one row from second table and if no rows exist return null

In this query I need to show all records from the left table and only the records from the right table where the result is the highest date.
Current query:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
This returns all records where the join is valid, but I need to show all users and if they didn't make a payment yet the fields from the payments table should be null.
I could use a union to show the other rows:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
union
SELECT a.*, c.*
FROM users a
--here I would need to join with payments table to get the columns from the payments table,
but where the user doesn't have a payment yet
WHERE a.package = 1
The option to use the union doesn't seem like a good solution, but that's what I tried.
So, in other words, you want a list of users and the last payment for each.
You can use OUTER APPLY instead of INNER JOIN to get the last payment for each user. The performance might be better and it will work the way you want regarding users with no payments.
SELECT a.*, b.*
FROM users a
OUTER APPLY ( SELECT * FROM payments c
WHERE c.user_id = a.user_id
ORDER BY c.date DESC
FETCH FIRST ROW ONLY ) b
WHERE a.package = 1;
Here is a generic version of the same concept that does not require your tables (for other readers). It gives a list of database users and the most recently modified object for each user. You can see it properly includes users that have no objects.
SELECT a.*, b.*
FROM all_users a
OUTER APPLY ( SELECT * FROM all_objects b
WHERE b.owner = a.username
ORDER BY b.last_ddl_time desc
FETCH FIRST ROW ONLY ) b
I like the answer from #Matthew McPeak but OUTER APPLY is 12c or higher and isn't very idiomatic Oracle, historically anyway. Here's a straight LEFT OUTER JOIN version:
SELECT *
FROM users a
LEFT OUTER JOIN
(
-- retrieve the list of payments for just those payments that are the maxdate per user
SELECT payments.*
FROM payments
JOIN (SELECT user_id, MAX(date) maxdate
FROM payments
GROUP BY user_id
) maxpayment_byuser
ON maxpayment_byuser.maxdate = payments.date
AND maxpayment_byuser.user_id = payments.user_id
) b ON a.ID = b.user_ID
If performance is an issue, you may find the following more performant but for simplicity you'll end up with an extra "maxdate" column.
SELECT *
FROM users a
LEFT OUTER JOIN
(
-- retrieve the list of payments for just those payments that are the maxdate per user
SELECT *
FROM (
SELECT payments.*,
MAX(date) OVER (PARTITION BY user_id) maxdate
FROM payments
) max_payments
WHERE date = maxdate
) b ON a.ID = b.user_ID
A generic approach using row_number() is very useful for "highest date" or "most recent" or similar conditions:
SELECT
*
FROM users a
LEFT OUTER JOIN (
-- determine the row corresponding to "most recent"
SELECT
payments.*
, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) is_recent
FROM payments
) b ON a.ID = b.user_ID
AND b.is_recent = 1
(reversing the ORDER BY within the over clause also enables "oldest")