Access subquery for Distinct Count and Count on same table - sql

Hello I have browsed the forum for a while and am asking my first question here. I'm in a bit of a bind and was wondering if I could get some help out. I am using Access and have not found a good answer to the question on the Net yet.
I have a table called tblTransactions for transactions on Access 2013. It looks like this:
Transaction_ID
Customer_No
Prod_ID
Lıcence_ID
1
111
1
1
2
111
1
2
3
222
1
2
4
111
2
1
5
222
2
1
6
222
2
2
7
333
1
1
tblProd looks like:
Prod_ID
Prod_Name
Prod_Price
1
Prod 1
30
2
Prod 2
50
tblLicence looks like:
Lıcence_ID
Lıcence_Name
Lıcence_Price
1
Lıcence 1
80
2
Lıcence 2
100
The customer purchases the product once and may obtain multiple licenses for this product. The product is paid once, but for all licenses owned.
I want to create a summary list for transactions. I cannot print how many different prod it has and how many licenses it has in total next to the customer number.
The output I want should look like this:
Customer_No
Count_Uniq_Prods
Count_Licences
Sum_Prods_Price
Sum_Licences_Price
111
2
3
80
260
222
2
3
80
280
333
1
1
30
80
I tried different methods for the first 3 columns.
When I try with subquery, the Customer number and product count are correct, but it also removes duplicates from licenses.
SELECT C.Customer_No, T2.Count_Uniq_Prods, T2.Count_Licences" & _
FROM" & _
(SELECT T1.Customer_No, T1.Count_Uniq_Prods, Count(Lıcence_ID) As Count_Licences"
FROM" & _
(SELECT DISTINCT Customer_No, Lıcence_ID, Count(Prod_ID) As Count_Uniq_Prods
FROM tblTransactions GROUP BY Customer_No, Lıcence_ID ) AS T1
GROUP BY T1.Customer_No, T1.Count_Uniq_Prods) AS T2
INNER JOIN tblTransactions AS C
ON T2.Customer_No = C.Customer_No" & _
GROUP BY C.Customer_No, T2.Count_Uniq_Prods, T2.Count_Licences;
When I try the left join operation, I can successfully get results for the product and license separately, but when I want to get it in a single table, the results are not what I want.
It's work for Customer_No, Count_Uniq_Prods, Sum_Prods_Price:
SELECT T.Customer_No,
Count(T.Prod_ID), SUM(tblProd.Prod_Price) AS Sum_Prods_Price
FROM ((SELECT DISTINCT Customer_No, Prod_ID FROM tblTransactions ) AS T
LEFT JOIN tblProd ON tblProd.Prod_ID= T.Prod_ID)
GROUP BY T.Customer_No;
It's work for Customer_No, Count_Licences, Sum_Licences_Price:
SELECT T.Customer_No,
Count(T.Lıcence_ID), SUM(tblLicence.[Lıcence_Price]) AS Sum_Licences_Price
FROM ((SELECT Customer_No, Lıcence_ID FROM tblTransactions ) AS T
LEFT JOIN tblLicence ON tblLicence.Lıcence_ID = T.Lıcence_ID)
GROUP BY T.Customer_No
But when I take one as a subquery inside the other, I cannot reach the desired result in both results.
I hope I was able to explain clearly. Thanks in advance for any help.

This should work for you.
My approach was to take your problem and break it down into its constituent elements.
This query retrieves the products in the format that you
requested.
select customer_no, count(t.prod_id) as Count_Uniq_Prods,
sum(p.prod_price) as Sum_Prods_Price
from (
select customer_no, prod_id
from tblTransactions t
group by customer_no, prod_id
) t
inner join tblProd p on
t.prod_id = p.prod_id
group by customer_no
This query retrieves the licenses in the format that you
requested.
select customer_no, count(t. license_id) as Count_Licenses,
sum(l.license_price) as Sum_Licenses_Price
from tblTransactions t
inner join tblLicense l on
t.license_id = l.license_id
group by customer_no
Finally, we put them together and get the following:
select distinct p.customer_no, Count_Uniq_Prods,
Count_Licenses,
Sum_Prods_Price,
Sum_Licenses_Price
from (
select customer_no, count(t.prod_id) as Count_Uniq_Prods,
sum(p.prod_price) as Sum_Prods_Price
from (
select customer_no, prod_id
from tblTransactions t
group by customer_no, prod_id
) t
inner join tblProd p on
t.prod_id = p.prod_id
group by customer_no
) p
inner join (
select customer_no, count(t. license_id) as Count_Licenses,
sum(l.license_price) as Sum_Licenses_Price
from tblTransactions t
inner join tblLicense l on
t.license_id = l.license_id
group by customer_no
) l
on p.customer_no = l.customer_no

Related

Most popular pairs of shops for workers from each company

I've got 2 tables, one with sales and one with companies:
Sales Table
Transaction_Id Shop_id Sale_date Client_ID
92356 24234 11.09.2018 12356
92345 32121 11.09.2018 32121
94323 24321 11.09.2018 21231
94278 45321 11.09.2018 42123
Company table
Client_ID Company_name
12345 ABC
13322 ABC
32321 BCD
22221 BCD
What I want to achieve is distinct count of Clients from each Company for each pair of shops(Clients who had at least 1 transaction in both of shops) :
Shop_Id_1 Shop_id_2 Company_name Count(distinct Client_id)
12356 12345 ABC 31
12345 14278 ABC 23
14323 12345 BCD 32
14278 12345 BCD 43
I think that I have to use self join, but my queries even with filter for one week is killing DB, any thoughts on that? I'm using Microsoft SQL server 2012.
Thanks
I think this is a self-join and aggregation, with a twist. The twist is that you want to include the company in each sales record, so it can be used in the self-join:
with sc as (
select s.*, c.company_name
from sales s join
companies c
on s.client_id = c.client_id
)
select sc1.shop_id, sc2.shop_id, sc1.company_name, count(distinct sc1.client_id)
from sc sc1 join
sc sc2
on sc1.client_id = sc2.client_id and
sc1.company_name = sc2.company_name
group by sc1.shop_id, sc2.shop_id, sc1.company_name;
I think there are some issues with your question. I interpreted it as such that the company table contains the shop ID's, not the ClienId's.
First you can create a solution to get the shops as rows for each company. Here I chose a maximum of 5 shops per company. Don't forget the semicolon in the previous statement before the cte's.
WITH CTE_Comp AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY CompanyName ORDER BY ShopID) AS RowNumb
FROM Company AS C
)
SELECT C1.ShopID,
C2.ShopID AS ShopID_2,
C3.ShopID AS ShopID_3,
C4.ShopID AS ShopID_4,
C5.ShopID AS ShopID_5,
C1.CompanyName
INTO ShopsByCompany
FROM CTE_Comp AS C1
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C2.CompanyName AND RowNumb = 2
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C3.CompanyName AND RowNumb = 3
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C4.CompanyName AND RowNumb = 4
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C5.CompanyName AND RowNumb = 5
WHERE C1.RowNumb = 1
After that, in a few steps, I think you could get the desired result:
WITH ClientsPerShop AS
(
SELECT ShopID,
COUNT (DISTINCT ClientID) AS TotalClients
FROM Sales
GROUP BY ShopID
)
, ClienstsPerCompany AS
(
SELECT CompanyName,
SUM (TotalClients) AS ClientsPerComp
FROM Company AS C
INNER JOIN ClientsPerShop AS CPS ON C.ShopID = CPS.ShopID
GROUP BY CompanyName
)
SELECT *
FROM ClienstsPerCompany AS CPA
INNER JOIN ShopsByCompany AS SBC ON SBC.CompanyName = CPA.CompanyName
Hopefully this will bring you closer to your solution, best of luck!

Calculate value in the query by values from two tables

I have product table like this
PRODUCT_ID PACK_SIZE PACK_PRIZE
3000 5 2.5
3001 5 2.5
3002 5 2.5
3003 5 2.5
Order table
order_id client_id
75001 1024
75002 1033
75003 1030
ITEMS Table
ORDER_ID PRODUCT_ID NUMBER_ORDERED
75001 3936 2
75001 3557 5
75001 3012 3
75001 3236 4
Client Table
CLIENT_ID LAST_NAME STATUS
1021 Smith private
1022 Williams corporate
1023 Browne private
1024 Tinsell corporate
These are sample data I just added these just to show sample data.
I want to select top 2 private clients who has done the orders which are having higher values.
I have problem in selecting orders with max sold amount.
Here's what I'm trying to do.
In this I'm trying to get the Client IDS
SELECT CLIENTS.CLIENT_ID
FROM ORDERS
INNER JOIN ITEMS ON ORDERS.ORDER_ID=ITEMS.ORDER_ID
INNER JOIN PRODUCTS ON ITEMS.PRODUCT_ID =PRODUCTS.PRODUCT_ID
INNER JOIN CLIENTS ON ORDERS.CLIENT_ID = CLIENTS.CLIENT_ID
WHERE ( )
In this i'm trying to select top 2 orders
SELECT TOP 2 ORDERS.ORDER_ID FROM ORDERS
INNER JOIN ITEMS ON ORDERS.ORDER_ID=ITEMS.ORDER_ID
INNER JOIN PRODUCTS ON ITEMS.PRODUCT_ID =PRODUCTS.PRODUCT_ID
WHERE ((PRODUCTS.PACK_PRIZE/PRODUCTS.PACK_SIZE)*(ITEMS.NUMBER_ORDERED));
Gives me errors
FROM Key word not found where expected.
What I want to do is select the order ids from orders which are having highest total and which are not from same client, total should be calculated by finding the unit price by dividing pack_price from pack_size and multiplying it by number_ordered from the items table which is having the matching order id. The ordered clients should be corporate clients.
I'm using oracle 11g.
pack_prize is number pack_size is number
number_ordered is number data type
Oracle doesn't support top 2. Instead use rownum and a subquery:
WITH CTE as (
SELECT ORDERS.ORDER_ID, PRODUCTS.PACK_PRIZE, PRODUCTS.PACK_SIZE, ITEMS.NUMBER_ORDERED
FROM ORDERS INNER JOIN
ITEMS
ON ORDERS.ORDER_ID = ITEMS.ORDER_ID INNER JOIN
PRODUCTS
ON ITEMS.PRODUCT_ID = PRODUCTS.PRODUCT_ID
)
SELECT ORDER_ID
FROM (SELECT CTE.*
FROM CTE
ORDER BY (PACK_PRIZE/PACK_SIZE) * NUMBER_ORDERED DESC
) t
WHERE rownum <= 2;
I'm guessing that the strange where expression is what you are using to determine the best rows.
TOP does not work in oracle. You can use the virtual column ROWNUM or the function ROW_NUMBER() OVER() to get similar functionality.

Query Returning SUM of Quantity or 0 if no Quantity for a Product at a given date

I have the following 4 tables:
----------
tblDates:
DateID
30/04/2012
01/05/2012
02/05/2012
03/05/2012
----------
tblGroups:
GroupID
Home
Table
----------
tblProducts:
ProductID GroupID
Chair Home
Fork Table
Knife Table
Sofa Home
----------
tblInventory:
DateID ProductID Quantity
01/05/2012 Chair 2
01/05/2012 Sofa 1
01/05/2012 Fork 10
01/05/2012 Knife 10
02/05/2012 Sofa 1
02/05/2012 Chair 3
03/05/2012 Sofa 2
03/05/2012 Chair 3
I am trying to write a query that returns all Dates in tblDates, all GroupIDs in tblGroups and the total Quantity of items in each GroupID.
I manage to do this but only get Sum(Quantity) for GroupID and DateID that are not null. I would like to get 0 instead. For exemple for the data above, I would like to get a line "02/05/2012 Table 0" as there is no data for any product in "Table" group on 01/05/12.
The SQL query I have so far is:
SELECT tblDates.DateID,
tblProducts.GroupID,
Sum(tblInventory.Quantity) AS SumOfQuantity
FROM (tblGroup
INNER JOIN tblProducts ON tblGroup.GroupID = tblProducts.GroupID)
INNER JOIN (tblDates INNER JOIN tblInventory ON tblDates.DateID = tblInventory.DateID) ON tblProducts.ProductID = tblInventory.ProductID
GROUP BY tblDates.DateID, tblProducts.GroupID;
I reckon I should basically have the same Query on a table different from tblInventory that would list all Products instead of listed products with 0 instead of no line but I am hesitant to do so as given the number of Dates and Products in my database, the query might be too slow.
There is probably a better way to achieve this.
Thanks
To get all possible Groups and Dates combinations, you'll have to CROSS JOIN those tables. Then you can LEFT JOIN to the other 2 tables:
SELECT
g.GroupID
, d.DateID
, COALESCE(SUM(i.Quantity), 0) AS Quantity
FROM
tblDates AS d
CROSS JOIN
tblGroups AS g
LEFT JOIN
tblProducts AS p
JOIN
tblInventory AS i
ON i.ProductID = p.ProductID
ON p.GroupID = g.GroupID
AND i.DateID = d.DateID
GROUP BY
g.GroupID
, d.DateID
use the isnull() function. change your query to:
SELECT tblDates.DateID, tblProducts.GroupID,
**ISNULL( Sum(tblInventory.Quantity),0)** AS SumOfQuantity
FROM (tblGroup INNER JOIN tblProducts ON tblGroup.GroupID = tblProducts.GroupID)
INNER JOIN (tblDates INNER JOIN tblInventory ON tblDates.DateID = tblInventory.DateID)
ON tblProducts.ProductID = tblInventory.ProductID
GROUP BY tblDates.DateID, tblProducts.GroupID;
You don't say which database you're using but what I'm familiar with is Oracle.
Obviously, if you inner join all tbales, you will not get the rows containing nulls. If you use an outer join to the inventory table you also have a problem because the groupid column is not listed in tblinventory and you can only outer join to one table.
I'd say you have two choices. Use a function or have a duplicate of the groupid column in the inventory table.
So, a nicer but slower solution:
create or replace
function totalquantity( d date, g varchar2 ) return number as
result number;
begin
select nvl(sum(quantity), 0 )
into result
from tblinventory i, tblproducts p
where i.productid = p.productid
and i.dateid = d
and p.groupid = g;
return result;
end;
select dateid, groupid, totalquantity( dateid, groupid )
from tbldates, tblgroups
Or, if you include the groupid column in the inventory table. (You can still quarantee integrity using constraints)
select d.dateid, i.groupid, nvl(i.quantity, 0)
from tbldates d, tblinventory i
where i.dateid(+) = d.dateid
group by d.dateid, g.groupid
Hope this helps,
Gísli

SQL Syntax Issue with getting sum

Ok I have two tables.
Table IDAssoc has the columnsbill_id, year, area_id.
Table Bill has the columns bill_id, year, main_id, and amount_due.
I'm trying to get the sum of the amount_due column from the bill table for each of the associated area_ids in the IDAssoc table.
I'm doing a select statement to select the sum and joining on the bill_ids. How can I set this up so it will have a single row for each of the associated bills in each area_id from the assoc table. There may be three or four bill_ids associated with each area_id and I need those summed for each and returned so I can use this select in another statement. I have a group by set up for the area_id but it still is returning each row and not summing them up for each area_id. I have the year and main_id specified already in the where clause to return the data that I want, but I can't get the sum to work properly. Sorry I'm still learning and I'm not sure how to do this. Thanks!
Edit- Basically the query I'm trying so far is basically just like the one posted below:
select a.area_id, sum(b.amount_due)
from IDAssoc a
inner join Bill b
on a.bill_id = b.bill_id
where Bill.year = 2006 and bill.bill_id = 11111
These are just arbitrary numbers.
The data this is returning is like this:
amount_due - area_id
.05 1003
.15 1003
.11 1003
65 1004
55 1004
I need one row returned for each area_id with the amount_due summed. The area_id is only in the assoc table and not in the bill table.
select a.area_id, sum(b.amount_due)
from IDAssoc a
inner join Bill b
on a.bill_id = b.bill_id
where b.year = 2006 and b.bill_id = 11111
group by a.area_id
You might want to change inner join to left join if one IDAssoc can have many or no Bill:
select a.area_id, coalesce(sum(b.amount_due),0)
from IDAssoc a
left join Bill b
on a.bill_id = b.bill_id
where b.year = 2006 and b.bill_id = 11111
group by a.area_id
You are missing the GROUP BY clause:
SELECT a.area_id, SUM(b.amount_due) TotalAmount
FROM IDAssoc a
LEFT JOIN Bill b
ON a.bill_id = b.bill_id
GROUP BY a.area_id

In SQL how do I write a query to return 1 record from a 1 to many relationship?

Let's say I have a Person table and a Purchases table with a 1 to many relationship. I want to run a single query that returns this person and just their latest purchase. This seems easy but I just can't seem to get it.
select p.*, pp.*
from Person p
left outer join (
select PersonID, max(PurchaseDate) as MaxPurchaseDate
from Purchase
group by PersonID
) ppm
left outer join Purchase pp on ppm.PersonID = pp.PersonID
and ppm.MaxPurchaseDate = pp.PurchaseDate
where p.PersonID = 42
This query will also show the latest purchase for all users if you remove the WHERE clause.
Assuming you have something like a PurchaseDate column and want a particular person (SQL Server):
SELECT TOP 1 P.Name, P.PersonID, C.PurchaseDescription FROM Persons AS P
INNER JOIN Purchases AS C ON C.PersonID = P.PersonID
WHERE P.PersonID = #PersonID
ORDER BY C.PurchaseDate DESC
Many Databases preform the "Limit or Top" command in different ways. Here is a reference http://troels.arvin.dk/db/rdbms/#select-limit and below are a few samples
If using SQL Server
SELECT TOP 1
*
FROM Person p
INNER JOIN Purchases pc on pc.PersonID = P.PersonID
Order BY pc.PurchaseDate DESC
Should work on MySQL
SELECT
*
FROM Person p
INNER JOIN Purchases pc on pc.PersonID = P.PersonID
Order BY pc.PurchaseDate DESC
LIMIT 1
Strictly off the top of my head!...If it's only one record then...
SELECT TOP 1 *
FROM Person p
INNER JOIN Purchases pu
ON p.ID = p.PersonId
ORDER BY pu.OrderDate
WHERE p.ID = *thePersonYouWant*
otherwise...
SELECT TOP 1 *
FROM Person p
INNER JOIN
(
SELECT TOP 1 pu.ID
FROM Purchases pu
ON pu.PersonID = p.Id
ORDER BY pu.OrderDate
) sq
I think! I haven't got access to a SQL box right now to test it on.
Without knowing your structure at all, or your dbms, you would order the results descending by the purchase date/time, and return only the first joined record.
Try TOP 1 With an order by desc on date. Ex:
CREATE TABLE #One
(
id int
)
CREATE TABLE #Many
(
id int,
[date] date,
value int
)
INSERT INTO #One (id)
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3
INSERT INTO #Many (id, [date], value)
SELECT 1, GETDATE(), 1 UNION ALL
SELECT 1, DATEADD(DD, 1 ,GETDATE()), 3 UNION ALL
SELECT 1, DATEADD(DD, -1 ,GETDATE()), 0
SELECT TOP 1 *
FROM #One O
JOIN #Many M ON O.id = M.id
ORDER BY [date] DESC
If you want to select the latest purchase for each person, that would be:
SELECT PE.ID, PE.Name, MAx(PU.pucrhaseDate) FROM Persons AS PE JOIN PURCHASE as PU ON PE.ID = PU.Person_ID
If you want to have all persons also those who have no purchases, you need to use LEFT JOIN.
I think you need one more table called Items for example.
The PERSONS table would uniquely define each person and all their attributes, while the ITEMS table would uniquely define each items and their attributes.
Assume the following:
Persons |Purchases |Items
PerID PerName |PurID PurDt PerID ItemID |ItemID ItemDesc ICost
101 Joe Smith |201 101107 101 301 |301 Laptop 500
|202 101107 101 302 |302 Desktop 699
102 Jane Doe |203 101108 102 303 |303 iPod 199
103 Jason Tut |204 101109 101 304 |304 iPad 499
|205 101109 101 305 |305 Printer 99
One Person Parent may tie to none, one or many Purchase Child.
One Item Parent may tie to none, one or many Purchase Child.
One or more Purchases Children will tie to one Person Parent, and one Item Parent.
select per.PerName as Name
, pur.PurDt as Date
, itm.ItemDesc as Item
, itm.ICost as Cost
from Persons per
, Purchases pur
, Items itm
where pur.PerID = per.PerID -- For that Person
and pur.ItemID = itm.ItemID -- and that Item
and pur.PurDt = -- and the purchase date is
( Select max(lst.PurDt) -- the last date
from Purchases lst -- purchases
where lst.PerID = per.PerID ) -- for that person
This should return:
Name Date Item Cost
Joe Smith 101109 Ipad 499
Joe Smith 101109 Printer 99
Jane Doe 101108 iPod 199