Most popular pairs of shops for workers from each company - sql

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!

Related

Access subquery for Distinct Count and Count on same table

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

T SQL Adress Table with the same Company need latest Contact

i got an Address Table with Primary and Secondary Company locations, example:
ADDRESSES:
ID CompanyName AdressType MainID Location
1 ExampleCompany H 0 Germany
2 ExampleCompany N 1 Sweden
3 ExampleCompany N 1 Germany
and we got another Contacts Table including the latest Contact to each of the Company Locations
Contacts
ID SuperID Datecreate Notes
1 1 10.04.2018 XY
2 3 09.04.2018 YX
3 2 11.04.2018 XX
Now we want to select the latest Contact per Company and sort them so we got a list of all our customers that we did not contact in a long time.
i thought about something like this:
SELECT
ADDRH.ID,
ADDRH.COMPANY1,
TOPCONT.ID,
TOPCONT.DATECREATE,
TOPCONT.NOTES0
FROM dbo.ADDRESSES ADDRH
OUTER APPLY (SELECT TOP 1 ID, SUPERID, DATECREATE, CREATEDBY, NOTES0 FROM DBO.CONTACTS CONT WHERE ADDRH.ID = CONT.SUPERID ORDER BY DATECREATE DESC) TOPCONT
WHERE
TOPCONT.ID IS NOT NULL
ORDER BY TOPCONT.DATECREATE
But this is still missing the fact that we got the same company multiple times in the addresses table. how can i create a list that got each company with the latest contact?
Thanks for your help
Greetings
Well, you have to remove duplicates from address as well. Because of the structure of your data, I think the best approach is to use row_number():
SELECT ac.*
FROM (SELECT a.ID, a.COMPANY1, c.ID, c.DATECREATE, c.NOTES0,
ROW_NUMBER() OVER (PARTITION BY a.COMPANY1 ORDER BY c.DATECREATE DESC) as seqnum
FROM dbo.ADDRESSES a JOIN
DBO.CONTACTS c
ON a.ID = c.SUPERID
WHERE c.ID IS NOT NULL
) ac
WHERE seqnum = 1
ORDER BY c.DATECREATE;

sql count of multiple tables possible inner join

I have a Table called Jobs and 02 other tables (places and times) that connect to Jobs by the JobID.
So, based on the tables below, I need the following in SQL server:
Jobs (JobID,Company)
123 ABC
456 DEF
789 GHI
PLACES (Country,JobID)
BR 123
EU 123
CA 456
TIMES(time,JobID)
05 456
08 123
09 789
Needed query Result:
QRYRESULT(JobID,CountOFPLaces,CountofTimes)
123 , 2, 1
456 , 1 , 1
789 , , 1
Thanks for the help!
There keys here are knowing that using an outer join can give you all results for all jobs and that a count(distinct field) will give you distinct counts eliminating the additional counts added by joins (assuming each record is unique and that duplicates don't exist in a given table)
SELECT J.jobID,
count(Distinct P.Country) as CountOfPlaces,
Count(Distinct T.Time) as CountOfTimes
FROM Jobs J
LEFT JOIN Places P
on J.JobID=P.JobID
LEFT JOIN Times T
on T.JobID = J.JobID
GROUP BY J.JobID
select id, pc, pt
from jobs j
left join (select jobid, count(Country) pc from Places group by JobId) p
on j.id = p.JobId
left join (select jobid, count([time]) pt from Times group by JobId) t
on j.id = t.JobId
You should use "group by" statement to count rows with the same job id for the tables "Places" and "Times" and then join it to "Jobs" table.
I also wanted to focus that you should use subquery in join statement, otherwise you get wrong data.

Allow nulls / de-duplicate within multi-table join? T-SQL

I was wondering if there is a way in either SSIS or T-SQL (SQL Server 2012) to easily return non-duplicate data when doing a multi-table join (per-column, not per row)
I am trying to denormalize / flatten a bunch of data for conversion into a warehouse and I am winding up duplicating a ton of data. I'm hoping there is a sort of rollup/summary function or a design concept I am missing that can help me when merging multiple tables to a single destination.
Example
Let's say for example I have three tables: CUSTOMERS, CUSTOMER_ADDRESSES and CUSTOMER_ACCOUNTS. They and their data look like this:
CUSTOMERS
CUST_ID NAME
1 Burton Guster
CUSTOMER_ADDRESSES
CUST_ID ADDR_SEQ ADDRESS
1 1 123 Awesome St
1 2 456 Fake St
CUSTOMER_ACCOUNTS
CUST_ID ACCT_SEQ ACCT_TYPE ACCOUNT_OPEN_DT
1 1 TAP 1/1/1989
1 2 PHARMA 1/1/2010
I join them using a query like this:
SELECT a.CUST_ID, a.NAME, b.ADDRESS, c.ACCT_TYPE, c.ACCOUNT_OPEN_DT
FROM CUSTOMERS a
JOIN CUSTOMER_ADDRESSES b on a.CUST_ID = b.CUST_ID
JOIN CUSTOMER_ACCOUNTS c on a.CUST_ID = c.CUST_ID
Obviously each row joins to each row and as expected my output looks like this:
ID NAME ADDRESS ACCT_TYPE ACCT_OPEN_DT
1 Burton Guster 123 Awesome St TAP 1/1/1989
1 Burton Guster 123 Awesome St PHARMA 1/1/2010
1 Burton Guster 456 Fake St TAP 1/1/1989
1 Burton Guster 456 Fake St PHARMA 1/1/2010
Is there any way for me to get something like this instead?:
ID NAME ADDRESS ACCT_TYPE ACCT_OPEN_DT
1 Burton Guster 123 Awesome St TAP 1/1/1989
1 NULL 456 Fake St PHARMA 1/1/2010
The goal being to group each column, returning the distinct value per column only once. The larger set would be grouped by the customer ID.
Thank you
Sure, it can be done, although it's kinda awkward to do... :-)
You can use ROW_NUMBER() to get a running row number per costumer from each table independently. Then you can use these row numbers to bring the data together:
;WITH custCTE AS (
SELECT CUST_ID, NAME, 1 AS CUST_ROW_N
FROM CUSTOMERS
),
addrCTE AS (
SELECT CUST_ID, ADDRESS, ROW_NUMBER() OVER(PARTITION BY CUST_ID ORDER BY ADDR_SEQ) CUST_ROW_N
FROM CUSTOMER_ADDRESSES
),
acctCTE AS (
SELECT CUST_ID, ACCT_TYPE, ACCOUNT_OPEN_DT, ROW_NUMBER() OVER(PARTITION BY CUST_ID ORDER BY ACCT_SEQ) CUST_ROW_N
FROM CUSTOMER_ACCOUNTS
)
SELECT COALESCE(a.CUST_ID, b.CUST_ID, c.CUST_ID), a.NAME, b.ADDRESS, c.ACCT_TYPE, c.ACCOUNT_OPEN_DT
FROM custCTE a FULL JOIN addrCTE b ON
a.CUST_ID = b.CUST_ID AND a.CUST_ROW_N = b.CUST_ROW_N FULL JOIN acctCTE c ON
(b.CUST_ID = c.CUST_ID AND b.CUST_ROW_N = c.CUST_ROW_N) OR (a.CUST_ID = c.CUST_ID AND a.CUST_ROW_N = c.CUST_ROW_N)
Here's an SQLFiddle

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