Get the sales rep and where they belong - sql-server

TerritoryId, Description, ParentTerritoryId, Type
--------------------------------------------------------
1, UnitedStates, null, Territory
1, John Smith, 1, Sales Rep
2, Georgia, 1, Territory
2, Jane Doe, 2, Sales Rep
2, Ann Smith, 2, Sales Rep
How can I write the following T-SQL? Let's say I'm searching for a name like "Ann Smith".
I'd like the resultset to look like:
1, United States, null, Territory
2, Georgia, 1, Territory
2, Ann Smith, 2, Sales Rep
Basically, I want to find a sales rep or sales reps and what organization they belong to all the way up the chain.

Assuming SQL Server 2005+ so we can use a recursive CTE and assuming the TerritoryId values should really be unique:
TerritoryId Description ParentTerritoryId Type
--------------------------------------------------------
1 UnitedStates NULL Territory
2 John Smith 1 Sales Rep
3 Georgia 1 Territory
4 Jane Doe 3 Sales Rep
5 Ann Smith 3 Sales Rep
Then you can do this:
WITH cteRecursion AS (
SELECT TerritoryId, Description, ParentTerritoryId, Type, 1 AS Level
FROM YourTable
WHERE Description = 'Ann Smith'
UNION ALL
SELECT t.TerritoryId, t.Description, t.ParentTerritoryId, t.Type, c.Level + 1
FROM YourTable t
INNER JOIN cteRecursion c
ON t.TerritoryId = c.ParentTerritoryId
)
SELECT TerritoryId, Description, ParentTerritoryId, Type
FROM cteRecursion
ORDER BY Level DESC;

While it is possible to have a multilevel structure in the table ideally you would want to split your table.
one table for territory and one for sales reps.
If your sales reps can have more than one territory you would need to go to 3 tables
one for sales reps, one for territory, and a lookup table.
if you are going to do a multi level table you need each entry to have a unique id

Related

Getting customer name based on lowest order from 2 tables

I am a beginner learning SQL. I have 2 tables. Trying to get the customer first and last name with least transaction amount - only that 1 row. Been trying different code all day but getting nowhere.
Table 1: Orders
Customer_Id TxnDate Amount
-------------------------------
4001 21-Aug-18 245.99
4002 30-Jan-18 49.99
4003 15-Apr-17 204.87
4001 18-Dec-18 130.88
4004 15-May-17 198.33
4006 4-Feb-17 783.65
Table 2: Customers
Customer_Id AcctOpenDate CustomerFirstName CustomerLastName
--------------------------------------------------------------
4001 7-Jan-16 John Doe
4002 15-Apr-15 Ashley Smith
4003 14-May-14 Carter Jones
4004 17-Sep-16 Ika Gaut
4005 18-Aug-14 Gray Show
4006 25-Oct-15 Kathia Kim
Query:
SELECT
C.CustomerFirstName, C.CustomerLastName, O.Amount
FROM
Customers C
WHERE
Amount = (SELECT MIN(Amount) FROM Orders) Leasttxnamt
INNER JOIN
Customers C ON O.Customer_Id = C.Customer_Id;
Current error produced:
Msg 102, Level 15, State 1, Line 31
Incorrect syntax near 'Leasttxnamt'
Try this..
returns first and last name of customers with lowest transaction amount
select CustomerFirstName,CustomerLastName
from Customer
where Customer_Id in (
SELECT DISTINCT Customer_Id
from Orders
where Amount=(select min(Amount) from orders))
If I understand correctly, you are looking for customer wise MIN amount from the Order table. If this is correct, you can use the following script for your purpose-
This is workable in most of the databases.
SELECT C.CustomerFirstName, C.CustomerLastName, MIN(Amount) min_amount
FROM Customers C
LEFT JOIN Orders O ON O.Customer_Id = C.Customer_Id
GROUP BY C.CustomerFirstName, C.CustomerLastNam
Try the following query :
SELECT Customers.CustomerFirstName ,Customers.CustomerLastName
FROM Customers
JOIN Orders ON Customers.Customer_Id = Orders.Customer_Id
AND Orders.Amount=(
SELECT min(Amount) FROM Orders
WHERE Customers.Customer_Id = Orders.Customer_Id
)

Oracle Data Masking using random names from a temp table

We need to mask some Personally Identifiable Information in our Oracle 10g database. The process I'm using is based on another masking script that we are using for Sybase (which works fine), but since the information in the Oracle and Sybase databases is quite different, I've hit a bit of a roadblock.
The process is to select all data out of the PERSON table, into a PERSON_TRANSFER table. We then use a random number to select a random name from the PERSON_TRANSFER table, and then update the PERSON table with that random name. This works fine in Sybase because there is only one row per person in the PERSON table.
The issue I've encountered is that in the Oracle DB, there are multiple rows per PERSON, and the name may or may not be different for each row, e.g.
|PERSON|
:-----------------:
|PERSON_ID|SURNAME|
|1 |Purple |
|1 |Purple |
|1 |Pink | <--
|2 |Gray |
|2 |Blue | <--
|3 |Black |
|3 |Black |
The PERSON_TRANSFER is a copy of this table. The table is in the millions of rows, so I'm just giving a very basic example here :)
The logic I'm currently using would just update all rows to be the same for that PERSON_ID, e.g.
|PERSON|
:-----------------:
|PERSON_ID|SURNAME|
|1 |Brown |
|1 |Brown |
|1 |Brown | <--
|2 |White |
|2 |White | <--
|3 |Red |
|3 |Red |
But this is incorrect as the name that is different for that PERSON_ID needs to be masked differently, e.g.
|PERSON|
:-----------------:
|PERSON_ID|SURNAME|
|1 |Brown |
|1 |Brown |
|1 |Yellow | <--
|2 |White |
|2 |Green | <--
|3 |Red |
|3 |Red |
How do I get the script to update the distinct names separately, rather than just update them all based on the PERSON_ID? My script currently looks like this
DECLARE
v_SURNAME VARCHAR2(30);
BEGIN
select pt.SURNAME
into v_SURNAME
from PERSON_TRANSFER pt
where pt.PERSON_ID = (SELECT PERSON_ID FROM
( SELECT PERSON_ID FROM PERSON_TRANSFER
ORDER BY dbms_random.value )
WHERE rownum = 1);
END;
Which causes an error because too many rows are returned for that random PERSON_ID.
1) Is there a more efficient way to update the PERSON table so that names are randomly assigned?
2) How do I ensure that the PERSON table is masked correctly, in that the various surnames are kept distinct (or the same, if they are all the same) for any single PERSON_ID?
I'm hoping this is enough information. I've simplified it a fair bit (the table has a lot more columns, such as First Name, DOB, TFN, etc.) in the hope that it makes the explanation easier.
Any input/advice/help would be greatly appreciated :)
Thanks.
One of the complications is that the same surname may appear under different person_id's in the PERSON table. You may be better off using a separate, auxiliary table holding surnames that are distinct (for example you can populate it by selecting distinct surnames from PERSONS).
Setup:
create table persons (person_id, surname) as (
select 1, 'Purple' from dual union all
select 1, 'Purple' from dual union all
select 1, 'Pink' from dual union all
select 2, 'Gray' from dual union all
select 2, 'Blue' from dual union all
select 3, 'Black' from dual union all
select 3, 'Black' from dual
);
create table mask_names (person_id, surname) as (
select 1, 'Apple' from dual union all
select 2, 'Banana' from dual union all
select 3, 'Grape' from dual union all
select 4, 'Orange' from dual union all
select 5, 'Pear' from dual union all
select 6, 'Plum' from dual
);
commit;
CTAS to create PERSON_TRANSFER:
create table person_transfer (person_id, surname) as (
select ranked.person_id, rand.surname
from ( select person_id, surname,
dense_rank() over (order by surname) as rk
from persons
) ranked
inner join
( select surname, row_number() over (order by dbms_random.value()) as rnd
from mask_names
) rand
on ranked.rk = rand.rnd
);
commit;
Outcome:
SQL> select * from person_transfer order by person_id, surname;
PERSON_ID SURNAME
---------- -------
1 Pear
1 Pear
1 Plum
2 Banana
2 Grape
3 Apple
3 Apple
Added at OP's request: The scope has been extended - the requirement now is to update surname in the original table (PERSONS). This can be best done with the merge statement and the join (sub)query I demonstrated earlier. This works best when the PERSONS table has a PK, and indeed the OP said the real-life table PERSONS has such a PK, made up of the person_id column and an additional column, date_from. In the script below, I drop persons and recreate it to include this additional column. Then I show the query and the result.
Note - a mask_names table is still needed. A tempting alternative would be to just shuffle the surnames already present in persons so there would be no need for a "helper" table. Alas that won't work. For example, in a trivial example persons has only one row. To obfuscate surnames, one MUST come up with surnames not in the original table. More interestingly, assume every person_id has exactly two rows, with distinct surnames, but those surnames in every case are 'John' and 'Mary'. It doesn't help to just shuffle those two names. One does need a "helper" table like mask_names.
New setup:
drop table persons;
create table persons (person_id, date_from, surname) as (
select 1, date '2016-01-04', 'Purple' from dual union all
select 1, date '2016-01-20', 'Purple' from dual union all
select 1, date '2016-03-20', 'Pink' from dual union all
select 2, date '2016-01-24', 'Gray' from dual union all
select 2, date '2016-03-21', 'Blue' from dual union all
select 3, date '2016-04-02', 'Black' from dual union all
select 3, date '2016-02-13', 'Black' from dual
);
commit;
select * from persons;
PERSON_ID DATE_FROM SURNAME
---------- ---------- -------
1 2016-01-04 Purple
1 2016-01-20 Purple
1 2016-03-20 Pink
2 2016-01-24 Gray
2 2016-03-21 Blue
3 2016-04-02 Black
3 2016-02-13 Black
7 rows selected.
New query and result:
merge into persons p
using (
select ranked.person_id, ranked.date_from, rand.surname
from (
select person_id, date_from, surname,
dense_rank() over (order by surname) as rk
from persons
) ranked
inner join (
select surname, row_number() over (order by dbms_random.value()) as rnd
from mask_names
) rand
on ranked.rk = rand.rnd
) t
on (p.person_id = t.person_id and p.date_from = t.date_from)
when matched then update
set p.surname = t.surname;
commit;
select * from persons;
PERSON_ID DATE_FROM SURNAME
---------- ---------- -------
1 2016-01-04 Apple
1 2016-01-20 Apple
1 2016-03-20 Orange
2 2016-01-24 Plum
2 2016-03-21 Grape
3 2016-04-02 Banana
3 2016-02-13 Banana
7 rows selected.

Filter two table rows by using group by with join in SQL Server

I have two tables as Sales and SalesDocument.
Sales Table
RequestId(PrimaryKey) ReqType DateTime
--------- ------- --------
1 Buy 22/10/2015
2 Buy 03/11/2015
3 Sell 10/11/2015
4 Return 11/12/2015
6 Sell 11/12/2015
7 Buy 20/12/2015
Sales Document Table
RequestId(Ref.Key(FK)) ReqDocument ReqDocURL
--------- ----------- ---------
2 Doc1 Http://..Doc1.PDF
3 Doc2 Http://..Doc2.PDF
3 Doc3 Http://..Doc3.PDF
4 Doc1 Http://..Doc1.PDF
4 Doc2 Http://..Doc2.PDF
4 Doc3 Http://..Doc3.PDF
6 Doc2 Http://..Doc2.PDF
6 Doc3 Http://..Doc3.PDF
Now I need to select the records in both tables by using RequestId(as Ref.Key) and the condition are,
1)In 1st Table,Need to get distinct ReqType(FirstColumn:RequestType) and It's count(SecondColumn:RequestTypeCount) based between two date ranges.
2)In 2nd Table, Need to calculate total no.of requested documents(ThirdColumn:SumOfReqDocument) by using RequestType(RequestType is not in 2nd table, hence we need to map with 1st table(sales) by RequestId and get the sum of documents.
The output should be,
RequestType RequestTypeCount SumOfReqDocument
----------- ---------------- ----------------
Buy 3 1
Sell 2 4
Return 1 3
I tried some SQL query but it does not result the actual result. Please help me on this SQL query\Suggest me some other query.
My Query is,
SELECT ReqType as RequestType,count(ReqType) as RequestTypeCount,count(salesDoc.DocumentURL) as SumOfReqDocument FROM Sales sales inner join SalesDocument salesDoc on
sales.RequestId=salesDoc.RequestId where sales.EndDate >= '2015-10-22 10:34:09.000' AND sales.EndDate <= '2015-12-31 00:00:00.000'
group by sales.ReqType
You may try change the INNER JOIN to LEFT JOIN, and COUNT DISTINCT ReqestID for RequestTypeCount
SELECT ReqType as RequestType
,count(DISTINCT sales.RequestId) as RequestTypeCount
,count(salesDoc.ReqDocURL) as SumOfReqDocument
FROM Sales sales
LEFT JOIN SalesDocument salesDoc
ON sales.RequestId=salesDoc.RequestId
WHERE sales.EndDate >= '2015-10-21 10:34:09.000' AND sales.EndDate <= '2015-12-31 00:00:00.000'
group by sales.ReqType

T-SQL Dynamic Query and pivot

I have data where people have changed role mid month and want to count the activity after their new start date. Can I use the results of a table as a dynamic Query, I have a query which returns the following resultset:-
Firstname Surname StartDate
----------------------------------
Jon Smith 2015-01-01
Paul Jones 2014-07-23
...
So the query would look something like:
SELECT Firstname +' '+ surname, month, count(1) FROM dataTable
WHERE (Firstname='John' AND Surname='Smith' AND date >=2015-01-01)
OR (Firstname='Paul' AND Surname='Jones' AND date >=2014-07-23)
OR ...
but the number of 'ORs' would depend on the number of rows in the first table
Name Month Count
----------------------------------
Jon Smith 1 15
Paul Jones 1 16
Jon Smith 2 30
Paul Jones 2 25
Charlie Gu 1 52
Which I can then pivot to get
Name 1 2
--------------------------
Jon Smith 15 30
Paul Jones 16 25
Charlie Gu 52 NULL
Thanks in advance
It seems to me that Ako is right and a simple join should do the trick rather than a dynamic query.
declare #NewStartDates table
(
Firstname nvarchar(100),
Surname nvarchar(100),
StartDate date
);
insert into #NewStartDates
(Firstname, Surname, StartDate)
values (N'Jon', N'Smith', '20150101'),
(N'Paul', N'Jones', '20140723');
select d.Firstname,
d.Surname,
year(d.Date) * 100 + month(d.Date) as Period,
count(*) as ActivityCount
from dataTable as d
inner join #NewStartDates as n
on d.Firstname = n.Firstname
and d.Surname = n.Surname
and d.Date >= n.StartDate
group by d.Firstname,
d.Surname,
year(d.Date) * 100 + month(d.Date);
Please refer this - it will give you complete idea how you can get dynamic column query.Dynamic Column Generation - Pivot [SQL]

How do I perform the following multi-layered pivot with TSQL in Access 2010?

I have looked at the following relevant posts:
How to create a PivotTable in Transact/SQL?
SQL Server query - Selecting COUNT(*) with DISTINCT
SQL query to get field value distribution
Desire: The have data change from State #1 to State #2.
Data: This data is a collection of the year(s) in which a person (identified by their PersonID) has been recorded performing a certain activity, at a certain place.
My data currently looks as follows:
State #1
Row | Year | PlaceID | ActivityID | PersonID
001 2011 Park Read 201a
002 2011 Library Read 202b
003 2012 Library Read 202b
004 2013 Library Read 202b
005 2013 Museum Read 202b
006 2011 Park Read 203c
006 2010 Library Read 203c
007 2012 Library Read 204d
008 2014 Library Read 204d
Edit (4/2/2014): I decided that I want State #2 to just be distinct counts.
What I want my data to look like:
State #2
Row | PlaceID | Column1 | Column2 | Column3
001 Park 2
002 Library 1 1 1
003 Museum 1
Where:
Column1: The count of the number of people that attended the PlaceID to read on only one year.
Column2: The count of the number of people that attended the PlaceID to read on two different years.
Column3: The count of the number of people that attended the PlaceID to read on three different years.
In the State #2 schema, a person cannot be counted in more than one column for each row (place). If a person reads at a particular place for 2010, 2011, 2012, they appear in Row 001, Column3 only. However, that person can appear in other rows, but once again, in only one column of that row.
My methodology (please correct me if I am doing this wrong):
I believe that the first step is to extract distinct counts of the number of years each person attended the place to perform the activity of interest (please correct me on this methodology if incorrect).
As such, this is where I am with the T-SQL:
SELECT
PlaceID
,PersonID
,[ActivityID]
,COUNT(DISTINCT [Year]) AS UNIQUE_YEAR_COUNT
FROM (
SELECT
Year
,PlaceID
,ActivityID
,PersonID
FROM [my].[linkeddatabasetable]
WHERE ActivityID = 'Read') t1
GROUP BY
PlaceID
,PersonID
,[ActivityID]
ORDER BY 1,2
Unfortunately, I do not know where to take it from here.
I think you have two options.
A traditional pivot
select placeID,
, Column1 = [1]
, Column2 = [2]
, Column3 = [3]
from
(
SELECT
PlaceID
,COUNT(DISTINCT [Yearvalue]) AS UNIQUE_YEAR_COUNT
FROM (
SELECT
yearValue
,PlaceID
,ActivityID
,PersonID
FROM #SO
WHERE ActivityID = 'Read') t1
GROUP BY
PlaceID
,PersonID
,[ActivityID]) up
pivot (count(UNIQUE_YEAR_COUNT) for UNIQUE_YEAR_COUNT in ([1],[2],[3]) ) as pvt
or as a case/when style pivot.
select I.PlaceID
, Column1 = count(case when UNIQUE_YEAR_COUNT = 1 then PersonID else null end)
, Column2 = count(case when UNIQUE_YEAR_COUNT = 2 then PersonID else null end)
, Column3 = count(case when UNIQUE_YEAR_COUNT = 3 then PersonID else null end)
from (
SELECT
PlaceID
, PersonID
,COUNT(DISTINCT [Yearvalue]) AS UNIQUE_YEAR_COUNT
FROM (
SELECT
yearValue
,PlaceID
,ActivityID
,PersonID
FROM #SO
WHERE ActivityID = 'Read') t1
GROUP BY
PlaceID
,PersonID
,[ActivityID]) I
group by I.PlaceID
Since you are in Access I would think using an aggregate functions would do the work.
Try with DCOUNT() to begin with http://office.microsoft.com/en-us/access-help/dcount-function-HA001228817.aspx.
Replace your count() with dcount("year", "linkeddatabasetable", "placeid=" & [placeid])

Resources