SQL Server: how to construct a IF like system within "where" clause? - sql

I have a table like below after several joins
username trandate positionname ChannelID
-------- ---- ------------ ---------
system 01/01/2019 1
anderson 06/04/2019 chief 1
williams 07/03/2019 chief 2
julie 15/02/2019 technician 48
julie 27/05/2019 chief technician 21
I want to count total number of transactions, sum of amount grouped by month, year and channel type.
there is a condition: if channelID is equals to "1", then query should exclude (1) transactions with has null value on "positionname" or (2) transactions with "system" username.
My code is something like this below:
select DISTINCT
DATEPART(YEAR,a.TranDate) as [year],
DATEPART(MONTH,a.TranDate) as [month],
count(*) as [transaction number],
sum(a.Amount) as [Total amount],
b.Name as [branch name],
c.ChannelName as [channel]
from transactions_main as a
left join branches as b WITH (NOLOCK) ON a.TranBranch=b.BranchId
left join Channels as c WITH (NOLOCK) ON a.ChannelId=c.ChannelId
left join Staff_Info as d WITH (NOLOCK) ON a.UserName=d.UserCode
where
a.TranDate>'20181231'
and b.name not in ('HQ')
and (a.ChannelId=1
and d.positionname is not null
and a.UserName not in ('SYSTEM', 'auto') )
group by b.Name, c.ChannelName,DATEPART(YEAR,a.TranDate), DATEPART(MONTH,a.TranDate)
order by b.Name, Year,month
In the end, of course, it didnt worked. I got the sum and count of transactions of only channelID=1's.
BONUS: You want to help another topic of mine? in which i also need help about indexing:
determining the staff's title when a certain transaction is done
here it is : link

use an OR operator
where
a.TranDate>'20181231'
and b.name not in ('HQ')
and (
(a.ChannelId=1
and d.positionname is not null
and a.UserName not in ('SYSTEM', 'auto') )
)
OR
a.ChannelId<>1
)

Related

SQL: How to concatenate two cells from one column of multiple rows if all of the other row's cells are equal

Looking for DepartmentName1 + ', ' + DepartmentName2
I'm trying to merge two rows into one row when only one column has different values. Specifically I'm trying to list the name, job title, gender, pay rate, hire date and department name of the top 100 highest paid employees of the AdventureWorks2017 database. Here is the code I have so far:
SELECT TOP 100 (P.FirstName + ' ' + P.LastName) AS Name, HRE.JobTitle, HRE.Gender,
CAST(HRPH.Rate AS Decimal(10,2)) AS PayRate, HRE.HireDate, HRD.Name AS Department
FROM ((((Person.Person AS P
INNER JOIN HumanResources.Employee AS HRE
ON P.BusinessEntityID = HRE.BusinessEntityID)
INNER JOIN
(SELECT BusinessEntityID, MAX(RateChangeDate) AS RCD, MAX(Rate) AS Rate
FROM HumanResources.EmployeePayHistory
GROUP BY BusinessEntityID) AS HRPH
ON HRE.BusinessEntityID = HRPH.BusinessEntityID)
INNER JOIN HumanResources.EmployeeDepartmentHistory AS HRDH
ON HRE.BusinessEntityID = HRDH.BusinessEntityID)
INNER JOIN HumanResources.Department AS HRD
ON HRDH.DepartmentID = HRD.DepartmentID)
ORDER BY HRPH.Rate DESC;
This gives me the following result:
Two questions:
How can I get every 'Name' to be listed only once, regardless of DepartmentName? For example: Rows 5 & 6 to be only Row 5: Laura Norman | Chief Financial Officer | F | 60.10 | 2009-01-31 | Executive, Finance.
OR, David Bradley...|...Marketing, Purchasing
Does my code include an employee that may have gotten a pay cut? Meaning, the RateChangeDate (RCD) is MAX but the Rate is not?
Using Microsoft SQL Server 2019
I bet you can make use of the string_agg() to aggregate the values with a delimiter in a query field.
SELECT TOP 100 (P.FirstName + ' ' + P.LastName) AS Name, HRE.JobTitle, HRE.Gender,
CAST(HRPH.Rate AS Decimal(10,2)) AS PayRate, HRE.HireDate, STRING_AGG(HRD.Name,',') AS Department
FROM ((((Person.Person AS P
INNER JOIN HumanResources.Employee AS HRE
ON P.BusinessEntityID = HRE.BusinessEntityID)
INNER JOIN
(SELECT BusinessEntityID, MAX(RateChangeDate) AS RCD, MAX(Rate) AS Rate
FROM HumanResources.EmployeePayHistory
GROUP BY BusinessEntityID) AS HRPH
ON HRE.BusinessEntityID = HRPH.BusinessEntityID)
INNER JOIN HumanResources.EmployeeDepartmentHistory AS HRDH
ON HRE.BusinessEntityID = HRDH.BusinessEntityID)
INNER JOIN HumanResources.Department AS HRD
ON HRDH.DepartmentID = HRD.DepartmentID)
GROUP BY P.FirstName,P.LastName,HRE.JobTitle, HRE.Gender, HRPH.Rate, HRE.HireDate
ORDER BY HRPH.Rate DESC;
To answer the second part, I took the liberty of creating an example and you may be able to work into your solution. The data you are working with lacks a unique key and using FirstName, LastName, and Gender is an obviously bad candidate for a unique key. You also mention RateChangeDate but do not mention how to handle that value when the data aggregates. The query below basically ignores RateChangeDate on the output and marks the records that have a decrease in pay. Another query into the data is needed to remove those records, below I did it using a HAVING clause.
DECLARE #X TABLE (ID INT, Rate MONEY, RateChangeDate DATETIME, Department NVARCHAR(50))
INSERT #X VALUES
(1,25.00,'01/01/2021','A'),
(1,23.00,'05/01/2021','A'),
(2,25.00,'01/01/2021','A'),
(3,25.00,'01/01/2021','A'),
(3,26.00,'02/01/2021','A'),
(4,25.00,'01/01/2021','A'),
(4,25.00,'01/01/2021','B')
SELECT
ID,
SUM(LatestRate) AS LatestRate,
MAX(MaxRateChange) AS RateChanges,
Departments
FROM
(
SELECT
ID,
STRING_AGG(Department,',') AS Departments,
Rate,
MAX(RateChangeDate) AS MaxRateChange,
CASE WHEN LAG(Rate) OVER (PARTITION BY ID ORDER BY RateChangeDate) > Rate THEN 1 ELSE 0 END AS DecreaseInPay,
CASE WHEN MAX(RateChangeDate)OVER(PARTITION BY ID) = RateChangeDate THEN Rate ELSE NULL END LatestRate
FROM
#X
GROUP BY
ID,Rate,RateChangeDate
)AS X
GROUP BY
ID,Departments
HAVING
MAX(DecreaseInPay) = 0

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!

Group BY with multiple selects (Oracle)

Im having a problem using group by with multiple selects. I want to select the minimum bid offer price for each Auction. But I also have to get the name of the user who made that Bid. As you can see in the results, I get multiple results for each auction, and if I remove elements from the Groupby statement I get an error message. How can I groupy by ID_Auction and still show the name of the User? Thanks for the help
SELECT A.ID_AUCTION,MIN(B.PRICE) AS PRICE,U.NAME
FROM AUCTION A,BIDS B,USERS U,PRODUCT P
WHERE P.TYPE='CocaCola'
--Joins
and A.ID_AUCTION=B.ID_AUCTION
and BID.ID_USER=U.ID_USER
and A.ID_PRODUCT=P.ID_PRODUCT
GROUP BY A.ID_AUCTION,U.NAME;
ID_AUCTION PRICE NAME
---------- ---------- -------------- ------------------------------------------
27 25 Andrew
28 40 John
27 30 Michael
28 35 Peter
The Output I Desire :
ID_AUCTION PRICE NAME
---------- ---------- -------------- ------------------------------------------
27 25 Andrew
28 35 Peter
SELECT A.ID_AUCTION,MIN(B.PRICE) AS PRICE,U.NAME
FROM AUCTION A,BIDS B,USERS U,PRODUCT P
WHERE P.TYPE='CocaCola'
--Joins
and A.ID_AUCTION=B.ID_AUCTION
and BID.ID_USER=U.ID_USER
and A.ID_PRODUCT=P.ID_PRODUCT
GROUP BY A.ID_AUCTION,U.NAME;
Should probably be like:
SELECT A.ID_AUCTION, U.NAME, B.PRICE as PRICE
from AUCTION A
inner join (SELECT ID_AUCTION, NAME, MIN(Price) from Bids order by 1,2,3) B
ON a.ID_AUCTION = b.ID_AUCTION
inner join Users U
ON B.ID_USER = U.ID_USER
inner join Product P
ON A.ID_PRODUCT = P.ID_PRODUCT
WHERE P.TYPE = 'CocaCola'
GROUP BY A.ID_AUCTION, U.NAME
I've never seen joins work the way you did it, but that might be an Oracle thing...actually I think that's considered bad practice in most SQL arenas...
Give this a try, this will do the trick. It is ok with the coding style. Some old systems still work well with the ANSI-89 SQL style:
SELECT A.ID_AUCTION,
B.PRICE,
U.NAME
FROM AUCTION A,
BIDS B,
USERS U,
PRODUCT P
WHERE P.TYPE='CocaCola' --Joins
AND A.ID_AUCTION=B.ID_AUCTION
AND B.ID_USER=U.ID_USER
AND A.ID_PRODUCT=P.ID_PRODUCT
AND B.PRICE = (SELECT MIN(bids.PRICE) FROM BIDS --get only the bid with MIN price
WHERE bids.ID_AUCTION = A.ID_AUCTION);
select * from
(
SELECT A.ID_AUCTION,B.PRICE,U.NAME,
Dense_Rank() over (partition by ID_AUCTION order by Price Asc)AS Rank
FROM
AUCTION A
JOIN
BIDS B
ON A.ID_AUCTION=B.ID_AUCTION
JOIN
USERS U
ON BID.ID_USER=U.ID_USER
JOIN
PRODUCT P
ON A.ID_PRODUCT=P.ID_PRODUCT
WHERE P.TYPE='CocaCola')
where rank='1'
First should just group by A.ID_AUCTION and second on your select you could try a min(U.NAME)
SELECT A.ID_AUCTION,MIN(B.PRICE) AS PRICE,min(U.NAME)
FROM AUCTION A,BIDS B,USERS U,PRODUCT P
WHERE P.TYPE='CocaCola'
--Joins
and A.ID_AUCTION=B.ID_AUCTION
and BID.ID_USER=U.ID_USER
and A.ID_PRODUCT=P.ID_PRODUCT
GROUP BY A.ID_AUCTION;

how use distinct in second join table in sql server

I have a SQL table consists of id, name, email,.... I have another SQL table that has id, email, emailstatus but these 2 id are different they are not related. The only thing that is common between these 2 tables are emails.
I would like to join these 2 tables bring all the info from table1 and if the email address from table 1 and table 2 are same and emailstatus is 'Bounced'. But the query that I am writing gives me more record than I expected because there are multiple rows in tbl_webhook(second table) for each row in Applicant(first table) .I want to know if applicant has EVER had an email bounce.
Query without join shows 23000 record but after join shows 42000 record that is because of duplicate how I can keep same 23000 record only add info from second table?
This is my query:
SELECT
A.[Id]
,A.[Application]
,A.[Loan]
,A.[Firstname]
,A.[Lastname]
,A.[Email],
,H.[Email], H.[EmailStatus] as BouncedEmail
FROM Applicant A (NOLOCK)
left outer join [tbl_Webhook] [H] (NOLOCK)
on A.Email = H.Email
and H.[event]='bounced'
this is sample of desired data:
id email name emailFromTable2 emailstatus
1 test2#yahoo.com lili test2#yahoo.com bounced
2 tesere#yahoo.com mike Null Null
3 tedfd2#yahoo.com nik tedfd2#yahoo.com bounced
4 tdfdft2#yahoo.com sam Null Null
5 tedft2#yahoo.com james tedft2#yahoo.com bounced
6 tedft2#yahoo.com San Null
Use a nested select for this type of query. I would write this as:
select id, application, load, firstname, lastname, email,
(case when BouncedEmail is not null then email end) as EmailFromTable2,
BouncedEmail
from (SELECT A.[Id], A.[Application], A.[Loan], A.[Firstname], A.[Lastname], A.[Email],
(case when exists (select 1
from tbl_WebHook h
where A.Email = H.Email and H.[event] = 'bounced'
)
then 'bounced
end) as BouncedEmail
FROM Applicant A (NOLOCK)
) a
You can also do this with cross apply, but because you only really need one column, a correlated subquery also works.
;WITH DistinctEmails
AS
(
SELECT * , rn = ROW_NUMBER() OVER (PARTITION BY [Email] ORDER BY [Email])
FROM [tbl_Webhook]
)
SELECT
A.[Id]
,A.[Application]
,A.[Loan]
,A.[Firstname]
,A.[Lastname]
,A.[Email],
,H.[Email], H.[EmailStatus] as BouncedEmail
FROM Applicant A (NOLOCK) left outer join DistinctEmails [H] (NOLOCK)
on A.Email = H.Email
WHERE H.rn = 1
and H.[event]='bounced'
i believe query below should be enough to select distinct bounced email for you, cheer :)
SELECT
A.[Id]
,A.[Application]
,A.[Loan]
,A.[Firstname]
,A.[Lastname]
,A.[Email],
,H.[Email], H.[EmailStatus] as BouncedEmail
FROM Applicant A (NOLOCK)
Inner join [tbl_Webhook] [H] (NOLOCK)
on A.Email = H.Email
and H.[EmailStatus]='bounced'
basically i just change the joining to inner join and change the 2nd table condition from event to emailstatus, if u can provide your table structure and sample data i believe i can help you up :)

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