Converting query with aggregate functions to a group by - sql

What I am trying to do is extract what and how many orders are ordered by customers.
I am able to get all the data but what I want is to group it based on a TrackingID unique to each customer, and thus get only one row per customer, regardless of how many items ordered.
The Code I currently have is
Select OT.TrackingID As FW_ID
,( Select
SUBSTRING(CT.Name, 1, CHARINDEX(' ', CT.Name) - 1)
Where LEN(CT.Name) - LEN(REPLACE(CT.Name, ' ', '')) > 0
) As Forename
,( Select
SUBSTRING(CT.Name, CHARINDEX(' ', CT.Name) + 1, 8000)
Where LEN(CT.Name) - LEN(REPLACE(CT.Name, ' ', '')) > 0
) As Surname
,( Select CAST(1 as VARCHAR) + ' p1 male'
Where OT.ArticleNr = 1
And CT.GroupNr IN (2,5)) As Amount_male_t1
,( Select CAST(1 as VARCHAR) + ' p1 female'
Where OT.ArticleNr = 2
And CT.GroupNr IN (2,5)) As Amount_female_t1
,( Select CAST(1 as VARCHAR) + ' p2 male'
Where OT.ArticleNr = 1
And CT.GroupNr IN (3,6)) As Amount_male_t2
,( Select CAST(1 as VARCHAR) + ' p2 female'
Where OT.ArticleNr = 2
And CT.GroupNr IN (3,6)) As Amount_female_t2
From OrderTable As OT
JOIN CustomerTable As CT
ON OT.CustomerNr = CT.CustomerNr
JOIN CampaignTable As CT
ON OT.TrackingID = CT.TrackingID
Where CT.GroupNr IN (2,3,5,6)
And OT.NewOrder = 1
An example of what I can get from this is
FW_ID Forename Surname Amount_male_t1 Amount_female_t1 Amount_male_t2 Amount_female_t2
101 John Doe 1 p1 male NULL NULL NULL
101 John Doe NULL 1 p1 female NULL NULL
102 Steve Boss NULL NULL 1 p2 male NULL
102 Steve Boss NULL NULL 1 p2 male NULL
And what I want is
FW_ID Forename Surname Amount_male_t1 Amount_female_t1 Amount_male_t2 Amount_female_t2
101 John Doe 1 p1 male 1 p1 female NULL NULL
102 Steve Boss NULL NULL 2 p2 male NULL
Problem is that when I use Group By on OT.TrackingID I get an error when using MAX() on the names due to them being aggregated already and errors when trying to turn the package counters into COUNT() funktions.
Help would be most appreciated.
The joined tables looks something like this
OrderTable:
TrackingID CustomerNr OrderNr ArticleNr NewOrder OrderDate
101 10054 25 1 1 2014-06-09
101 10054 24 2 1 2014-06-09
102 10036 23 1 1 2014-06-08
102 10036 22 1 1 2014-06-07
103 10044 21 2 0 2014-06-06
CustomerTable
CustomerNr Name Adress ZipCode CustomerCreatedDate
10054 John Doe Upstreet 123456 2013-05-18
10036 Steve Boss Downstreet 234567 2014-06-07
10044 Eric Cartman Sidestreet 345678 2014-02-21
CampaignTable
TrackingID GroupNr ProductDescription
101 2 Group 2 & 5 are offered package 1
102 3 Group 3 & 6 are offered package 2
103 5 Group 2 & 5 are offered package 1
NOTE: If someone could give advice as to why my question is downvoted that would be most appreciated. I don't quite know what I've done wrong.

One approach would be to use a view, as mentioned. You can do this in-line in the query, and it doesn't need to be saved in the schema.
I've made a demo to show how this can be done with the sample table data you provided. From here if you wanted to change the representation of the data to a single line, you can just pivot it as shown here.
SELECT SplitNames.Forename, SplitNames.LastName FROM CustomerTable
INNER JOIN
(
SELECT CustomerNr,
SUBSTRING(CT.Name, 1, CHARINDEX(' ', CT.Name) - 1) As Forename,
SUBSTRING(CT.Name, CHARINDEX(' ', CT.Name), LEN(CT.Name) - 1) As LastName
FROM CustomerTable CT
) SplitNames ON CustomerTable.CustomerNr = SplitNames.CustomerNr
In general you should try to use as few subqueries in the select statement as possible, as it makes it impossible to properly aggregate any of your results.

You could use your query as a view and then use that view for a new query using distinct and count clauses.
I'm sorry, I understand this is not the best solution since it has two steps and maybe could be solve in a better way, but I can't find anything better, at moment, without any example data on a my db.

Related

SQL Crosstab with undetermined columns

I see a lot of similar questions, but almost all of them wind up grouping results as column names (Column names based on results), mine is a more simple list. I don't care if it uses dynamic SQL or not (I'd think it has to).
Please don't tell me I need to restructure the tables, I'm working from a legacy system and don't have that option.
Basically, I just need a list of all valid table "B" entries that match a given record from table "A", in a row.
I don't have any code sample yet, because I'm not seeing a way to set this up correctly.
Table: Customer c
CustID Name
1 Bill Smith
2 Jim Jones
3 Mary Adams
4 Wendy Williams
Table: Debt d
CustID Creditor Balance
1 ABC Loans 245
1 Citibank 815
2 Soprano Financial 74000
3 Citibank 24
3 Soprano Financial 93000
3 Wells Fargo 275
3 Midwestern S&L 2500
4 ABC Loans 1500
4 Fred's Payday Loan 1000
Desired Output:
Name Cred1 Bal1 Cred2 Bal2 Cred3 Bal3 Cred4 Bal4
Bill Smith ABC Loans 245 Citibank 815 (NULL) (NULL) (NULL) (NULL)
Jim Jones Soprano Financial 74000 (NULL) (NULL) (NULL) (NULL) (NULL) (NULL)
Mary Adams Citibank 24 Soprano Finanacial 93000 Wells Fargo 275 Midwestern S&L 2500
Wendy Williams ABC Loans 1500 Fred's Payday Loan 1000 (NULL) (NULL) (NULL) (NULL)
Basically, I probably have to collect some kind of count of the most number of records for any specific "CustomerID", and define the output columns based on that. If this has already been answered, feel free to link and close this out, I did not see this specific scenario when I did my search.
Here is another dynamic approach. We use Row_Number() to create the minimal number of columns.
Example
Declare #SQL varchar(max) = Stuff((Select Distinct ','+QuoteName(concat('Cred',ColNr))
+','+QuoteName(concat('Bal',ColNr))
From (Select ColNr=Row_Number() over (Partition By CustID Order By Creditor) From Debt ) A
Order By 1
For XML Path('')),1,1,'')
Select #SQL = '
Select *
From (
Select C.Name
,B.*
From (
Select *,ColNr=Row_Number() over (Partition By CustID Order By Creditor)
From Debt
) A
Cross Apply (values (concat(''Cred'',ColNr),[Creditor])
,(concat(''Bal'' ,ColNr) ,cast(Balance as varchar(25)))
) B (Item,Value)
Join Customer C on A.CustID=C.CustID
) A
Pivot (max([Value]) For [Item] in (' + #SQL + ') ) p'
--Print #SQL
Exec(#SQL);
Returns
If if Helps, the Generated SQL Looks Like This:
Select *
From (
Select C.Name
,B.*
From (
Select *,ColNr=Row_Number() over (Partition By CustID Order By Creditor)
From Debt
) A
Cross Apply (values (concat('Cred',ColNr),[Creditor])
,(concat('Bal' ,ColNr) ,cast(Balance as varchar(25)))
) B (Item,Value)
Join Customer C on A.CustID=C.CustID
) A
Pivot (max([Value]) For [Item] in ([Cred1],[Bal1],[Cred2],[Bal2],[Cred3],[Bal3],[Cred4],[Bal4]) ) p
Just for the Visualization, the query "feeding" the Pivot generates:
I will guess you are already know how to use cross tab so you only need to prepare your data to use it.
STEP 1: Join both tables:
SELECT c.Name, d.Creditor, d.Balance
FROM Customer c
JOIN Debt d
ON c.CustID = d.CustID
STEP 2: Include a row number to each element related to the customer you are going to use to cross tab
SELECT c.Name, d.Creditor, d.Balance,
ROW_NUMBER() over (PARTITION BY Name ORDER BY creditor) as rndebt_tab
FROM Customer c
JOIN Debt d
ON c.CustID = d.CustID
Now you have:
CustID Creditor Balance rn
1 ABC Loans 245 1
1 Citibank 815 2
2 Soprano Financial 74000 1
3 Citibank 24 1
3 Soprano Financial 93000 2
3 Wells Fargo 275 3
3 Midwestern S&L 2500 4
4 ABC Loans 1500 1
4 Fred's Payday Loan 1000 2
STEP 3: Create the SOURCE for the cross tab
WITH cte as (
<query from step2>
)
SELECT Name,
'CREDITOR_' + RIGHT('000' + CAST(rn AS VARCHAR(3)),3) as cross_tab,
Creditor as Value
FROM cte
UNION all
SELECT Name,
'DEBT_' + RIGHT('000' + CAST(rn AS VARCHAR(3)),3) as cross_tab,
CAST(Balance as VARCHAR(max)) as Value
FROM cte
Now you have:
CustID cross_tab Value
1 CREDITOR_001 ABC Loans
1 CREDITOR_002 Citibank
2 CREDITOR_001 Soprano Financial
3 CREDITOR_001 Citibank
3 CREDITOR_002 Soprano Financial
3 CREDITOR_003 Wells Fargo
3 CREDITOR_004 Midwestern S&L
4 CREDITOR_001 ABC Loans
4 CREDITOR_002 Fred's Payday Loan
1 DEBT_001 245
1 DEBT_002 815
2 DEBT_001 ` 74000
3 DEBT_001 24
3 DEBT_002 93000
3 DEBT_003 275
3 DEBT_004 2500
4 DEBT_001 1500
4 DEBT_002 1000
EDIT: I use CustID instead of Name on the example but too lazy to change now.

Aggregate Functions To Pull More Record Field Data

I would like to know what would be the best way to get the data from a specific row when I use a Group By query. The real query is more complex than the example I'm providing here so I'm looking for something other than a sub-select on the Sales table.
I'm using MSSQL 2008 and I would like something that allow me to get the date field from the Sales record that has the max(amount).
Query
select uid, firstName, lastName, AmountFromTagetedRow, DateFromTargetedRow,
from users u inner join
sales s on u.uid = s.custID
group by uid, firstName, lastName
order by uid
USERS
uid firstName lastName
1 Sam Smith
2 Joe Brown
3 Kim Young
SALES
sid Custid date amount ...
1 1 2016-01-02 100
2 3 2016-01-12 485
3 1 2016-01-22 152
4 2 2016-02-01 156
5 1 2016-02-02 12
6 1 2016-03-05 84
7 2 2016-03-10 68
RESULTS
uid firstName LastName amount date
1 Sam Smith 152 2016-01-22
2 Joe Brown 156 2016-02-01
3 Kim Young 485 2016-01-12
Your posted query doesn't match your amount but something like this should get you pointed in the right direction.
with SortedResults as
(
select uid
, firstName
, lastName
, AmountFromTagetedRow
, DateFromTargetedRow
, ROW_NUMBER() over (partition by u.uid order by AmountFromTagetedRow desc) as RowNum
from users u inner join
sales s on u.uid = s.custID
group by uid
, firstName
, lastName
)
select *
from SortedResults
where RowNum = 1
order by uid

Bring similar multiple rows of a column into single row using SQL

I want to
Table name: Master_Vendors
Vendor_ID Subvendor_id Subvendor_type Name City State
1 abc 1 Johnny York MN
1 xyz 2 Meera Birmingham NY
1 gef 3 Gaurav Cochin NY
2 aaa 1 Laila Lima MA
2 bbb 2 Zebo Reno SC
2 ccc 3 Gina Pitts NY
I want one row per Vendor_ID and i cannot use any aggregations
Vendor_ID Subvendor_id_1 Name_1 City_1 State_1 Subvendor_id_2 Name_2 City_2 State_2
1 abc Johnny York NY xyz Meera Birmingham NY
2 aaa Laila Lima MA bbb Zebo Reno SC
since i cannot do aggregations i cannot use PIVOT; i have never used CTE's before can we acheive this using CTE?
I didn't put in all the fields just enough so you can see the technique. Note this only works when you know in advance how many subvendors you want to show in the query:
select a.vendor_id , a.subvendor_id as Subvendor_id 1, a. Name as name_1, b.subvendor_id as Subvendor_id 2, b. Name as name_1, c.subvendor_id as Subvendor_id 3, a. Name as name_3
From Master_Vendors a
left join Master_Vendors b on a.vendor_id = b.vendor_id and b.subvendor_type = 2
left join Master_Vendors c on a.vendor_id = c.vendor_id and c.subvendor_type = 3
where a.subvendor_type = 1
If every record would have all three subvendors then you can use inner joins.
You can do that by using the PIVOT function but only if you use MS SQL Server 2008 or Higher, Or simply use the CASE WHEN Statement as below:
SELECT Vendor_ID as 'Vendor_ID ',
case when Location_ID =1 then Location_ID END as 'Location_ID_1 '
,case when City ='York' OR City ='Lima' then City END as 'City_1 '
,case when [State]='MN' OR [State]='NA' then [State] END as 'State_1'
,case when Location_ID =2 then Location_ID END as 'Location_ID_2 '
,case when City ='Birmingham' OR City ='Reno' then City END as 'City_2 '
,case when [State]='NY' OR [State]='SC' then [State] END as 'State_2 '
,case when Location_ID =3 then Location_ID END as 'Location_ID_3 '
,case when City ='Cochin' then City END as 'City_3 '
,case when [State]='NY' then [State] END as 'State_3 '
FROM Master_Vendors

get extra rows for each group where date doesn't exist

I've been playing with this for days, and can't seem to come up with something. I have this query:
select
v.emp_name as Name
,MONTH(v.YearMonth) as m
,v.SalesTotal as Amount
from SalesTotals
Which gives me these results:
Name m Amount
Smith 1 123.50
Smith 2 40.21
Smith 3 444.21
Smith 4 23.21
Jones 1 121.00
Jones 2 499.00
Jones 3 23.23
Jones 4 41.82
etc....
What I need to do is use a JOIN or something, so that I get a NULL value for each month (1-12), for each name:
Name m Amount
Smith 1 123.50
Smith 2 40.21
Smith 3 444.21
Smith 4 23.21
Smith 5 NULL
Smith 6 NULL
Smith ... NULL
Smith 12 NULL
Jones 1 121.00
Jones 2 499.00
Jones 3 23.23
Jones 4 41.82
Jones 5 NULL
Jones ... NULL
Jones 12 NULL
etc....
I have a "Numbers" table, and have tried doing:
select
v.emp_name as Name
,MONTH(v.YearMonth) as m
,v.SalesTotal as Amount
from SalesTotals
FULL JOIN Number n on n.Number = MONTH(v.YearMonth) and n in(1,2,3,4,5,6,7,8,9,10,11,12)
But that only gives me 6 additional NULL rows, where what I want is actually 6 NULL rows for each group of names. I've tried using Group By, but not sure how to use it in a JOIN statement like that, and not even sure if that's the correct route to take.
Any advice or direction is much appreciated!
Here's one way to do it:
select
s.emp_name as Name
,s.Number as m
,st.salestotal as Amount
from (
select distinct emp_name, number
from salestotals, numbers
where number between 1 and 12) s left join salestotals st on
s.emp_name = st.emp_name and s.number = month(st.yearmonth)
Condensed SQL Fiddle
You could do:
SELECT EN.emp_name Name,
N.Number M,
ST.SalesTotal Amount
FROM ( SELECT Number
FROM NumberTable
WHERE Number BETWEEN 1 AND 12) N
CROSS JOIN (SELECT DISTINCT emp_name
FROM SalesTotals) EN
LEFT JOIN SalesTotals ST
ON N.Number = MONTH(ST.YearMonth)
AND EN.emp_name = ST.emp_name

Oracle 11G R2 SQL rows to columns

I have a table of bank staff information that looks like this:
branchNumber Position firstName lastName staffNumber
------------ -------- --------- -------- -----------
25 Manager john doe 11111
25 Secretary robert paulson 11112
25 Secretary cindy lu 11113
66 Manager tim timson 22223
66 Manager jacob jacobson 22224
66 Secretary henry henryson 22225
66 Supervisor paul paulerton 22226
I am actually done with this, but I completed the assignment using SQL common table expressions, and I can't use them in this project, I need them in this format.
branchNumber numOfManagers numOfSecretaries numOfSupervisors totalEmployees
------------ ------------- ---------------- ---------------- --------------
25 1 2 0 3
66 2 1 1 4
My issue is getting multiple columns with information from a row, I have this so far,
SELECT branchNumber, COUNT(*) AS numOfManagers
FROM Staff
WHERE position = 'Manager'
GROUP BY branchNumber, Position;
This outputs the correct information for numOfManagers, but making the next three columns eludes me without using CTE's. I tried sub selects too, with no luck. Anybody have any ideas?
You can use something like this:
select branchnumber,
sum(case when Position ='Manager' then 1 else 0 end) numofManagers,
sum(case when Position ='Secretary' then 1 else 0 end) numofSecretaries,
sum(case when Position ='Supervisor' then 1 else 0 end) numofSupervisors,
count(*) totalEmployees
from yourtable
group by branchnumber
See SQL Fiddle with Demo
Or you can use the PIVOT function:
select branchnumber,
'Manager', 'Secretary', 'Supervisor',
TotalEmployees
from
(
select t1.branchnumber,
t1.position,
t2.TotalEmployees
from yourtable t1
inner join
(
select branchnumber, count(*) TotalEmployees
from yourtable
group by branchnumber
) t2
on t1.branchnumber = t2.branchnumber
) x
pivot
(
count(position)
for position in ('Manager', 'Secretary', 'Supervisor')
) p;
See SQL Fiddle with Demo