TSQL select one or many rows to join - tsql

This is question similar to: TSQL select rows by one from 2 conditions, but it is different in results that I would like to have
I have a table like so:
ORDER_ID CODE1 CODE2 CODE3 STATUS TYPE SUM GROUP
1 '001' 'BIGP' NULL 4 'company' 120 48
2 '002' 'BIGP' NULL 1 'priv' 100 20
3 '001' NULL NULL 6 'priv' 50 49
4 '002' NULL 'L' 1 'company' 1253 22
and second table like so:
ADDRESS_ID ORDER_ID ZIP TYPE ADD_DATE CATEGORY VERIFIED
1 1 '15-125' 'K1' '2010-01-01' 'CLIENT' 1
2 1 '22-022' 'D1' '2010-01-02' 'SYSTEM' 1
3 2 '16-159' 'D2' '2010-01-02' 'SYSTEM' 1
4 2 '15-125' 'D2' '2010-02-01' 'CLIENT' 0
Third and fourth table contains zip codes and city names like so:
ZIP CITY
'15-125' 'Warszawa'
'22-022' 'Koszalin'
'16-159' 'Krakow'
'15-125' 'Lublin'
For every order that has
status not in (4,6)
code1 between '002' and '005'
(code2=null and code3=null) or (code2 in ('BIGA', 'BIGP') and code3=null) or (code2=NULL and code3 = 'L')
If code1 ='002' AND group IN (48,59,60,87) I must choose a single address
(big thanks to Nikola Markovinović):
SELECT TOP 1000 o.order_Id
, a.Address_Id
, a.Zip
--, *
FROM orders o
CROSS APPLY
(
select TOP 1
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
ORDER BY case a.Type
when 'D2' then 1
when 'K1' then 2
else 3
end,
a.ADD_DATE
) a
WHERE
o.Status NOT IN (4, 6)
AND code1='002'
AND group IN (48,59,60,87)
AND ((code2 IS NULL AND code3 IS NULL) OR (code2 IN ('BIGA', 'BIGP') AND code3 IS NULL) OR (code2 IS NULL AND code3 = 'L'))
For all other orders that meet top criteria and got code1 ='002' AND group NOT IN (48,59,60,87) I must select all addresses for those orders that have verified=1
After collecting those addresses I will be able to check if a specific post company can deliver my mail to those addresses (I will check in another table containing zip codes)
I was thinking about making union all, taking first select and doing union with second that will return all addresses for code1 ='002' AND group NOT IN (48,59,60,87).
But maybe it is possible to do it without union all?
This it the final result I would like to get:
CODE1 TYPE COUNT_OF_ORDERS COUNT_OF_ADDRESSES COMPANY1 OTHER
'001' 'NORMAL' 125 150 110 40
'002' 'NORMAL' 100 122 100 22
'003' 'NORMAL' 150 110 100 10
'004' 'NORMAL' 200 220 220 0
'005' 'NORMAL' 220 240 210 30
'005' 'PRIORITY' 100 110 110 0
'SX1' 'PRIORITY' 100 100 20 80
So if my type is 'normal' I must check if that address for order exists in table having normal zip codes, if it has type 'priority' I must check in table with priority codes.
If code exists in specific table I add +1 to COMPANY1 column, if not to OTHER, so that sum of those columns must be sum of my addresses.
This is query that I've managed to do (with help of #Nikola Markovinović)
SELECT TOP 1000 o.order_Id
, a.Address_Id
, a.Zip
--, *
FROM orders o
CROSS APPLY
(
select TOP 1
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND code1='002'
AND o.[group] IN (48,59,60,87)
ORDER BY case a.Type
when 'D2' then 1
when 'K1' then 2
else 3
end,
a.ADD_DATE
UNION ALL
select
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND ((code1='002' AND o.[group] NOT IN (48,59,60,87)) OR code1 IN ('001', '003', '004', '005'))
--I'm not shure of that top line, it work's but mayby it con de written better
AND Verified = 1
) a
WHERE
o.Status NOT IN (4, 6)
AND ((code2 IS NULL AND code3 IS NULL)
OR (code2 IN ('BIGA', 'BIGP') AND code3 IS NULL)
OR (code2 IS NULL AND code3 = 'L'))

You might filter addresses easily ([group] IN (48,59,60,87) OR Verified = 1), but tweaking TOP 1 would make things ridiculous (TOP (case when [group] IN (48,59,60,87) then 1 else (select count(*) from addresses where order_Id = o.order_Id) end). So I propose that you do union all but for adresses only:
SELECT TOP 1000 o.order_Id
, a.Address_Id
, a.Zip
--, *
FROM orders o
CROSS APPLY
(
select TOP 1
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND o.[group] IN (48,59,60,87)
ORDER BY case a.Type
when 'D2' then 1
when 'K1' then 2
else 3
end,
a.ADD_DATE
UNION ALL
select
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND o.[group] NOT IN (48,59,60,87)
AND Verified = 1
) a
WHERE
o.Status NOT IN (4, 6)
AND code1='002'
AND ((code2 IS NULL AND code3 IS NULL)
OR (code2 IN ('BIGA', 'BIGP') AND code3 IS NULL)
OR (code2 IS NULL AND code3 = 'L'))
P.S. If order might not have an address replace CROSS APPLY with OUTER APPLY.

Related

Require result with of multiple customers along with city name

we have following requirement
I need customer_name which are in Active status and attribute_name=City and attribute_value in(Indore,Mumbai) and result should return count less than=2
It means from result for Indore i should get 2 results out of 3 and for Mumbai i should get 1 out of 1.
I tried below 2 ways but getting all the rows for customers which are in city Indore and Mumbai
Customer_table has below details
customer_id customer_name customer_Status
------------------------------------------
1 ABC Active
2 XYZ Active
3 PQR NA
4 ABCD Active
4 ABCDE Active
customer_details table has below details
customer_id attribute_name attribute_value
------------------------------------------
1 City Indore
1 Phone Number 9100000000
1 Country India
2 City Mumbai
2 Phone Number 9100000001
2 Country India
3 City Delhi
3 Phone Number 9100000002
3 Country India
4 City Mumbai
4 Phone Number 9100000003
4 Country India
5 City Mumbai
5 Phone Number 9100000004
5 Country India
Code:-
select attribute_value, r.customer_name from customer_details res
join lateral (
select customer_name from Customer_table
where res.customer_id=customer_id
and customer_Status= 'Active'
limit 2
) r on true
where attribute_name= 'City' and attribute_value in ('Indore','Mumbai');
Code:-
SELECT s.customer_name,attribute_value
FROM (
SELECT *, row_number() OVER (PARTITION BY customer_id ) AS rn
FROM customer_details
WHERE attribute_name= 'City' and attribute_value in ('Indore','Mumbai')
) e
JOIN Customer_table s USING (customer_id)
WHERE rn <= 2
and and customer_Status= 'Active'
ORDER BY customer_id, e.rn;
Please try this way.
select customer_name,attribute_value from (
select ct.customer_name,attribute_value,row_number() OVER (PARTITION BY attribute_value ) AS rn
from customer_table ct ,customer_details cd
where ct.customer_status = 'Active' and ct.customer_id = cd.customer_id
and attribute_name='City' and attribute_value in('Indore','Mumbai')
) as t
where rn <= 2
This design will kill any performance you hope to get around customers. The first thing I would do in drop the customer_details table and the columns phone_number, city, and country to the customers table. Lacking that I would create a view that joined customers to customer_details having all the columns in the view.
create view Customer_Standard as
select c.cust_id, c.name, c.status, ph.attribute_value phone_number, ct.attribute_value city, cn.attribute_value country
from customers c
left join customer_details ph on (ph.cust_id = c.cust_id and ph.attribute_name = 'Phone Number')
left join customer_details ct on (ct.cust_id = c.cust_id and ct.attribute_name = 'City')
left join customer_details cn on (cn.cust_id = c.cust_id and cn.attribute_name = 'Country') ;
Then the query you want becomes:
select cust_id, name, status, phone_number, city, country
from (select cs.*, row_number() over (partition by city order by cust_id) rn
from Customer_Standard cs
) cust
where city in ('Indore','Mumbai')
and rn<3;

Write all days of period for each single account from a single data table

I have a data table with daily transactions on several bank accounts. I would like to calculate the sum of the transactions on each bank account for each day within a certain time period. For days where there was no transaction during that period, I want to see a NULL value.
I am using two tables: one with the transaction data and one calendar table.
I was able to get the desired result for a single account with the code shown below (ZWID is the ID of the bank account)
WITH sum_transactions as
(
SELECT csd.ZWID, csd.ValueDate, sum_total = sum(csd.amount)
FROM myDataBase.CashData as csd
WHERE csd.ValueDate > '20190131' and csd.ValueDate <= '20190208'
AND csd.ZWID IN (1592)
GROUP BY csd.ZWID, csd.ValueDate
)
SELECT st.zwid, cal.Calendar_Date, st.sum_total
FROM treasury.dbo.calendar as cal
LEFT JOIN sum_transactions as st on st.ValueDate = cal.Calendar_Date
WHERE cal.Calendar_Date > '20190131' and cal.Calendar_Date<= '20190208'
ORDER BY 1, 2
I get the following (desired) output:
zwid Calendar_Date sum_total
1592 2019-02-01 606174,09
NULL 2019-02-02 NULL
NULL 2019-02-03 NULL
1592 2019-02-04 -600000
NULL 2019-02-05 NULL
NULL 2019-02-06 NULL
NULL 2019-02-07 NULL
NULL 2019-02-08 NULL
i.e. there were two days with transaction(s) on that specific bank account in the period.
Now, when I add a second account (ID 1593) (to the IN statement), I would hope to get a second set of 8 new rows (for 01 Feb to 08 Feb) with either a sum or a NULL value (a total of 16 rows for both accounts).
However, I now get a result table that shows no rows with NULL values for the first account anymore (apart from the two days where both accounts show no transactions).
zwid Calendar_date sum_total
NULL 2019-02-02 NULL
NULL 2019-02-03 NULL
1592 2019-02-04 -600000
1592 2019-02-01 606174,09
1593 2019-02-01 -847958,75
1593 2019-02-04 303105,26
1593 2019-02-05 -285312,64
1593 2019-02-06 502762,95
1593 2019-02-07 405372,02
1593 2019-02-08 326213,87
Obviously, I do not succeed in having the query write all Dates for each account separatly.
How do I need to change my query for it to run through one bank account, write all days of the period (value or NULL) and only then move on to the next account?
Update: I am looking at a large number of bank accounts. The number of accounts will change over time
I think this might be what you need, try it out and let me know. But basically I had to use a CROSS APPLY to the full list of IDs/Dates you were looking for and then I used the rest of your code to get your desired results:
DROP TABLE IF EXISTS #Test;
DROP TABLE IF EXISTS #FullCalendar;
CREATE TABLE #Test
(
ZWID INT ,
ValueDate DATE ,
Amount MONEY
);
INSERT INTO #Test ( ZWID ,
ValueDate ,
Amount )
VALUES ( 1, '20190101', 100.00 ) ,
( 1, '20190101', 75.00 ) ,
( 1, '20190108', 75.00 ) ,
( 1, '20190110', 50.00 ) ,
( 2, '20190101', 25.00 ) ,
( 2, '20190102', 35.00 ) ,
( 2, '20190103', 50.00 ) ,
( 2, '20190103', 125.00 ) ,
( 3, '20190102', 150.00 ) ,
( 3, '20190109', 100.00 ) ,
( 3, '20190110', 75.00 ) ,
( 3, '20190110', 75.00 );
SELECT dd.Date, t.ZWID
INTO #FullCalendar
FROM dbo.DateDimension AS dd
CROSS APPLY #Test AS t
WHERE dd.Date >= '20190101' AND dd.Date < '20190111'
GROUP BY dd.Date ,
t.ZWID
--SELECT * FROM #FullCalendar ORDER BY ZWID, Date
;WITH sum_trans AS (
SELECT
t.ZWID, t.ValueDate, sum_total = SUM(t.Amount)
FROM #Test AS t
GROUP BY t.ZWID ,
t.ValueDate )
SELECT fc.Date, fc.ZWID, st.sum_total
FROM #FullCalendar AS fc
LEFT OUTER JOIN sum_trans AS st ON st.ZWID = fc.ZWID AND fc.Date = st.ValueDate
ORDER BY fc.ZWID,fc.Date;
Leaving my old answer here as well.
I was able to get your desired result by using 2 CTEs and a UNION ALL:
WITH sum_transactions as
(
SELECT csd.ZWID, csd.ValueDate, sum_total = sum(csd.amount)
FROM myDataBase.CashData as csd
WHERE csd.ValueDate > '20190131' and csd.ValueDate <= '20190208'
AND csd.ZWID IN (1592)
GROUP BY csd.ZWID, csd.ValueDate
) ,
WITH sum_transactions2 as
(
SELECT csd.ZWID, csd.ValueDate, sum_total = sum(csd.amount)
FROM myDataBase.CashData as csd
WHERE csd.ValueDate > '20190131' and csd.ValueDate <= '20190208'
AND csd.ZWID IN (1593)
GROUP BY csd.ZWID, csd.ValueDate
)
SELECT st.zwid, cal.Calendar_Date, st.sum_total
FROM treasury.dbo.calendar as cal
LEFT JOIN sum_transactions as st on st.ValueDate = cal.Calendar_Date
WHERE cal.Calendar_Date > '20190131' and cal.Calendar_Date<= '20190208'
ORDER BY 1, 2
UNION ALL
SELECT st.zwid, cal.Calendar_Date, st.sum_total
FROM treasury.dbo.calendar as cal
LEFT JOIN sum_transactions2 as st on st.ValueDate = cal.Calendar_Date
WHERE cal.Calendar_Date > '20190131' and cal.Calendar_Date<= '20190208'
ORDER BY 1, 2

Choose the duplicates and pick based on non duplicate column

I need to write a query on below table to fetch the records only when the same email and name is shared by more than 1 member. In below example, I need resultset as
100 a#a.com nameA
300 a#a.com nameA
Table
Member email name
100 a#a.com nameA
100 a#a.com nameA
300 a#a.com nameA
200 b#b.com nameB
I doubt you have typo and you mean 100 instead of 200 in your expected result. If so, then there is one way:
with your_table(Member, email , name ) as (
select 100,'a#a.com','nameA' union all
select 100,'a#a.com','nameA' union all
select 300,'a#a.com','nameA' union all
select 200,'b#b.com','nameB'
)
-- below is actual query:
select distinct your_table.*
from your_table
inner join (
select email , name from your_table
group by email , name
having count(distinct Member) > 1
) t
on your_table.email = t.email and your_table.name = t.name

Pivot table and combine records onto 1 line

I've tried many different ways to pivot a table to show all records on 1 row. I've provided my query for the closest solution I came up with. It'll probably be easier if I illustrate what I need. Since there can be an unlimited number of teacher survey questions the query has to be dynamic. I've modified the column names to make it easier to read.
teacherSurveyQuestions
TSQID CID Order OQReference Stem
1 1011 1 q1_rb blabla
2 1011 2 q2_rb blabla
3 1011 3 q2a_cb blabla
teacherSurveyUserID
TSUID firstName lastName UID
1 Bob Smith 1027
2 Tom Jones 1034
teacherSurveyAnswers
TSAID UID TSQID TSUID Response
1 1027 1 1 Bob 1
2 1027 2 1 Bob 2
3 1027 3 1 Bob 3
4 1034 1 2 Tom 1
5 1034 2 2 Tom 2
6 1034 3 2 Tom 3
Now I need this data to look like this:
firstName lastName q1_rb q2_rb q2a_cb
Bob Smith Bob 1 Bob 2 Bob 3
Tom Jones Tom 1 Tom 2 Tom 3
Here's what I have so far that kind of works except all the responses are NULL
declare #query as nvarchar(max),
#colsPivot as nvarchar(max)
select #colsPivot = stuff((select ','
+ quotename(OQReference)
from teacherSurveyQuestions tsq
where tsq.CID = 1011
order by tsq.Order
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
set #query
= 'select *
from
(
select firstName, lastName, value, col +''_''+ CAST(rn as varchar(10)) as col
from
(
select
tsu.TSUID
,tsu.firstName
,tsu.lastName
,tsq.OQReference
,tsa.Response
,ROW_NUMBER() over(partition by tsu.TSUID order by tsq.Order) rn
from teacherSurveyQuestions tsq
inner join teacherSurveyAnswers tsa on tsa.TSQID = tsq.TSQID
inner join teacherSurveyUsers tsu on tsu.TSUID = tsa.TSUID
where tsq.CID = 1011
) x
unpivot
(
value
for col in (OQReference)
) u
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
Result of query:
firstName lastName q1_rb q2_rb q2a_cb
Bob Smith NULL NULL NULL
Tom JOnes NULL NULL NULL
Try this
declare #query as nvarchar(max),
#colsPivot as nvarchar(max)
select #colsPivot = stuff((select ','
+ quotename(OQReference)
from teacherSurveyQuestions tsq
where tsq.CID = 1011
order by tsq.[Order]
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
set #query
= 'select firstName, lastName,'+ #colspivot +'
from
(
select firstName, lastName,Response, value
from
(
select
tsu.TSUID
,tsu.firstName
,tsu.lastName
,tsq.OQReference
,tsa.Response
,ROW_NUMBER() over(partition by tsu.TSUID order by tsq.[Order]) rn
from teacherSurveyQuestions tsq
inner join teacherSurveyAnswers tsa on tsa.TSQID = tsq.TSQID
inner join teacherSurveyUserID tsu on tsu.TSUID = tsa.TSUID
where tsq.CID = 1011
) x
unpivot
(
value
for col in (OQReference)
) u
) x1
pivot
(
max(Response)
for value in ('+ #colspivot +')
) p'
exec(#query)
SQL FIDDLE DEMO

SSRS group based on two columns

I have a report that displays a list of duplicate accounts based on our business rules. This works when one new account is matched with other existing accounts. Where I'm having trouble is when multiple new accounts match the same existing duplicate.
Here's an example of how it looks now, grouped by NewId:
NewID MatchedID FirstName LastName AddDate Address PhoneNumber
10 10 Holly Johnson 4/18/2013 123 1St Rd. 123 456 7890
10 2 Hollie Johnson 1/1/1990 123 1St Rd. 123 456 7890
11 11 Holley Johnson 4/17/2013 123 1St Rd. 123-456-7890
11 2 Hollie Johnson 1/1/1990 123 First Rd. 123 456 7890
50 50 William Johnson 4/17/2013 999 2nd St. 222 222 2222
50 3 Bill Jonson 1/2/1990 999 Second St. 222-222-2222
Accounts that have matches are themselves included for comparison.
So, is there a way to group these similar accounts together without duplicates? It should look like this:
GroupID AcctID FirstName LastName AddDate Address PhoneNumber
1 2 Hollie Johnson 1/1/1990 123 First Rd. 123 456 7890
1 10 Holly Johnson 4/18/2013 123 1St Rd. 123 456 7890
1 11 Holley Johnson 4/17/2013 123 1St Rd. 123-456-7890
2 50 William Johnson 4/17/2013 999 2nd St. 222 222 2222
2 3 Bill Jonson 1/2/1990 999 Second St. 222-222-2222
I don't care if the grouping is done in SQL or in SSRS. It would need to reference the two ID columns, because the name, address, and phone number may be different. I also need a new GroupID assigned so that they can be grouped in the report.
You can use ranking functions to eliminate rows:
with NoDuplicates as
(
select *
, rownum = row_number() over (partition by MatchedID order by NewID)
from Accounts
)
select NewID
, MatchedID
, Name
, AddDate
, Address
, phoneNumber
from NoDuplicates where rownum = 1
SQL Fiddle with demo.
Although there's no reason you can't just use GROUP BY assuming the address information is always duplicated too:
select NewID = min(NewID)
, MatchedID
, Name
, AddDate
, Address
, phoneNumber
from Accounts
group by MatchedID
, Name
, AddDate
, Address
, phoneNumber
SQL Fiddle with demo.
Both of these are returning your expected result.
Edit after comment:
You can group related rows with a statement like this:
with NoDuplicates as
(
select *
, rownum = row_number() over (partition by MatchedID order by NewID)
from Accounts
where NewID <> MatchedID
)
select groupID = MatchedID
, Acct = MatchedID
, FirstName
, AddDate
, Address
, phoneNumber
from NoDuplicates where rownum = 1
union all
select groupID = coalesce(am.MatchedID, a.NewID)
, Acct = a.MatchedID
, a.FirstName
, a.AddDate
, a.Address
, a.phoneNumber
from Accounts a
-- join to the corresponding matched account
left join Accounts am on a.MatchedID = am.NewID and am.NewID <> am.MatchedID
where a.NewID = a.MatchedID
order by groupID, Acct
SQL Fiddle with demo.
However, this essentially just groups by MatchedID. If you want numbered groups starting from 1, you can add a DENSE_RANK clause to the statement:
with NoDuplicates as
(
select *
, rownum = row_number() over (partition by MatchedID order by NewID)
from Accounts
where NewID <> MatchedID
)
, GroupedAcct as
(
select GroupID = MatchedID
, Acct = MatchedID
, FirstName
, AddDate
, Address
, phoneNumber
from NoDuplicates where rownum = 1
union all
select GroupID = coalesce(am.MatchedID, a.NewID)
, Acct = a.MatchedID
, a.FirstName
, a.AddDate
, a.Address
, a.phoneNumber
from Accounts a
-- join to the corresponding matched account
left join Accounts am on a.MatchedID = am.NewID and am.NewID <> am.MatchedID
where a.NewID = a.MatchedID
)
select GroupID = Dense_Rank() over (order by GroupID)
, Acct
, FirstName
, AddDate
, Address
, phoneNumber
from GroupedAcct
order by groupID, Acct
SQL Fiddle with demo.