Concatenating/Converting Multiple rows based on Customer # - sql-server-2008-r2

I currently have a report with a list of clients that will be having work performed by us the following week.
Currently, when the report runs, we generate a list of Clients, the dates of work to be performed, the type of work and their email address.
A lot of these clients have multiple services performed throughout that week, and as such, appear multiple times in the report.
I'd like to combine any clients that appear multiple times in the list to appear like this:
What we currently have:
Cust ID FName LName Date Description
1 Jon Smith 01/01/17 Spring Cleanup
1 Jon Smith 01/03/17 Lawn Maintenance
1 Jon Smith 01/05/17 Irrigation
2 Jane Roberts 01/02/17 Spring Cleanup
2 Jane Roberts 01/03/17 Lawn Maintenance
3 Jim Whoever 01/04/17 Turf
What we'd like:
Cust ID FName Lname Date Description
1 Jon Smith 01/01/17 Spring Cleanup, Lawn Maintenance, Irrigation
2 Jane Roberts 01/02/17 Spring Cleanup, Lawn Maintenance
3 Jim Whoever 01/04/17 Turf
Here's what we have for code so far:
Select
cust.CustID,
cust.CustName,
cust.FirstName,
cust.LastName,
cust.Email,
wo.ShortDesc,
wos.StartTime,
br.Description Branch
From
WorkOrderSchedules wos Join
WorkOrders wo On wo.SvcOrderID = wos.SvcOrderID Join
Customers cust On cust.CustID = wo.CustID Join
Branches br On br.LocationID = wo.LocationID
Where
wos.StartTime Between DateAdd(wk, 2, DateAdd(wk, DateDiff(wk, 7,
GetDate()), -1)) And DateAdd(wk, 2, DateAdd(wk, DateDiff(wk, 7, GetDate()),
5)) And cust.CustName Not Like 'Three C%' And wo.ShortDesc Not Like
'sales lead' And wo.ShortDesc Not Like '%lawn main%' And
cust.CustName Not Like 'Port' And cust.CustName Not Like '31 Mile%' And
cust.CustName Not Like '32 Mile' And cust.CustName Not Like 'Quail Ridge' And
cust.CustName Not Like 'Hayes' And cust.CustName Not Like 'Inla' And
cust.CustName Not Like 'Eaton' And cust.CustName Not Like 'Fisher' And
cust.CustName Not Like 'Pasadena' And cust.CustName Not Like 'Mallard'
Group By
cust.CustID,
cust.CustName,
cust.FirstName,
cust.LastName,
cust.Email,
wo.ShortDesc,
wos.StartTime,
br.Description
Order By
cust.CustName,
wos.StartTime
Thank you for any help in advance as I'm very new to SQL and appreciate any help.

This is emulating GROUP CONCAT.
Select
cust.CustID,
cust.CustName,
cust.FirstName,
cust.LastName,
cust.Email,
wo.ShortDesc,
Min(wos.StartTime) as StartTime,
--br.Description Branch
Branch = STUFF((
SELECT ',' + md.Description
FROM dbo.Branches md
WHERE br.LocationID = md.LocationID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
From
WorkOrderSchedules wos Join
WorkOrders wo On wo.SvcOrderID = wos.SvcOrderID Join
Customers cust On cust.CustID = wo.CustID Join
Branches br On br.LocationID = wo.LocationID
Where
wos.StartTime Between DateAdd(wk, 2, DateAdd(wk, DateDiff(wk, 7,
GetDate()), -1)) And DateAdd(wk, 2, DateAdd(wk, DateDiff(wk, 7, GetDate()),
5)) And cust.CustName Not Like 'Three C%' And wo.ShortDesc Not Like
'sales lead' And wo.ShortDesc Not Like '%lawn main%' And
cust.CustName Not Like 'Port' And cust.CustName Not Like '31 Mile%' And
cust.CustName Not Like '32 Mile' And cust.CustName Not Like 'Quail Ridge' And
cust.CustName Not Like 'Hayes' And cust.CustName Not Like 'Inla' And
cust.CustName Not Like 'Eaton' And cust.CustName Not Like 'Fisher' And
cust.CustName Not Like 'Pasadena' And cust.CustName Not Like 'Mallard'
group by
cust.CustID,
cust.CustName,
cust.FirstName,
cust.LastName,
cust.Email,
wo.ShortDesc,

Related

Oracle SQL return value from child table with minimum row number with values in specific list

I have a need to select all rows from a table (main table) and join to another table (child table). In the results set, I want to include one column from the child table, that is only the first row / line number with a column value in a specified list. If there is no match for the specified list, it should be (null)
Desired Result:
ORDER_NO
ORDER_DATE
ORDER CUST
ORDER_VALUE
ITEM
1
02/14/2022
12345
$1,000.00
APPLES
2
02/13/2022
67890
$5,000.00
(null)
3
02/12/2022
45678
$100.00
PEARS
Example:
Main Table: Order Table
Order Number (Handle)
Order Date,
Order Customer,
Order Value
ORDER_NO
ORDER_DATE
ORDER CUST
ORDER_VALUE
1
02/14/2022
12345
$1,000.00
2
02/13/2022
67890
$5,000.00
3
02/12/2022
45678
$100.00
Child Table: Order Details Tbl
Order Number (Handle)
Line Number = Order Line No
Ordered Item,
Ordered Qty
ORDER_NO
LINE_NO
ITEM
1
10
APPLES
1
20
ORANGES
1
30
LETTUCE
2
10
BROCCOLI
2
20
CAULIFLOWER
2
30
LETTUCE
3
10
KALE
3
20
RADISHES
3
30
PEARS
In this example, the returned column is essentially the first line of the order that is a fruit, not a vegetable. And if the order includes no matching fruit, null is returned.
What my code is thus far:
SELECT
MAIN.ORDER_NO,
MAIN.ORDER_DATE,
MAIN.ORDER_CUST,
MAIN.ORDER_VALUE,
B.ITEM
FROM
MAIN
LEFT JOIN
(
SELECT
CHILD.ORDER_NO,
CHILD.LINE_NO,
CHILD.ITEM
FROM
CHILD
WHERE
CHILD.ORDER_NO||'_'||LINE_NO IN
(
SELECT
CHILD.ORDER_NO||'_'||MIN(LINE_NO) AS ORDER_LINE_NO
FROM
CHILD
WHERE
CHILD.ITEM IN ('APPLES','ORANGES','PEACHES','PEARS','GRAPES')
GROUP BY
CHILD.ORDER_NO
)
) B ON MAIN.ORDER_NO = B.ORDER_NO
'''
This code is of course not working as desired, as table 'B' is including all results from CHILD.
From Oracle 12, you can use:
SELECT o.*,
d.item
FROM orders o
LEFT OUTER JOIN LATERAL(
SELECT *
FROM order_details d
WHERE o.order_no = d.order_no
AND item IN ('APPLES','ORANGES','PEACHES','PEARS','GRAPES')
ORDER BY line_no ASC
FETCH FIRST ROW ONLY
) d
ON (1 = 1)
In earlier versions you can use:
SELECT o.*,
d.item
FROM orders o
LEFT OUTER JOIN(
SELECT d.*,
ROW_NUMBER() OVER (PARTITION BY order_no ORDER BY line_no ASC)
AS rn
FROM order_details d
WHERE item IN ('APPLES','ORANGES','PEACHES','PEARS','GRAPES')
) d
ON (o.order_no = d.order_no AND rn = 1)
Which, for the sample data:
CREATE TABLE orders (ORDER_NO, ORDER_DATE, ORDER_CUST, ORDER_VALUE) AS
SELECT 1, DATE '2022-02-14', 12345, 1000.00 FROM DUAL UNION ALL
SELECT 2, DATE '2022-02-13', 67890, 5000.00 FROM DUAL UNION ALL
SELECT 3, DATE '2022-02-12', 45678, 100.00 FROM DUAL;
CREATE TABLE Order_Details (ORDER_NO, LINE_NO, ITEM) AS
SELECT 1, 10, 'APPLES' FROM DUAL UNION ALL
SELECT 1, 20, 'ORANGES' FROM DUAL UNION ALL
SELECT 1, 30, 'LETTUCE' FROM DUAL UNION ALL
SELECT 2, 10, 'BROCCOLI' FROM DUAL UNION ALL
SELECT 2, 20, 'CAULIFLOWER' FROM DUAL UNION ALL
SELECT 2, 30, 'LETTUCE' FROM DUAL UNION ALL
SELECT 3, 10, 'KALE' FROM DUAL UNION ALL
SELECT 3, 20, 'RADISHES' FROM DUAL UNION ALL
SELECT 3, 30, 'PEARS' FROM DUAL;
Both output:
ORDER_NO
ORDER_DATE
ORDER_CUST
ORDER_VALUE
ITEM
1
2022-02-14 00:00:00
12345
1000
APPLES
2
2022-02-13 00:00:00
67890
5000
null
3
2022-02-12 00:00:00
45678
100
PEARS
db<>fiddle here

Count number of unique purchase dates

I have a log of purchases made by customers. Sometimes a customer purchases multiple items during a given purchase, other times they only purchase a single item. What I want to do, on a line by line basis, is identify which purchase events have happened (i.e. not on an item by item basis, but on a checkout by checkout basis).
Each row of the source database contains the following fields
cust_id, purchase_date, sku
So a customer who purchases three items during a given transaction would look like this
1, 01/01/01, dog1
1, 01/01/01, cat1
1, 01/01/01, mouse1
1, 01/02/01, wolf1
1, 01/03/01, lion1
WHat I want out is
cust_id, purchase_date, sku, item_purchase_number_within_purchase, unique_purchase_date_across_dates
And that would look like
1, 01/01/01, dog1, 1, 1
1, 01/01/01, cat1, 2, 1
1, 01/01/01, mouse1, 3, 1
1, 01/02/01, wolf1, 1, 2
1, 01/03/01, lion1, 1, 3
In words, on the first date, three items where purchased arbitrarily identified as purchase numbers, 1, 2, and 3, on the second purchase date (Jan 2nd, 2001), only a single item was purchase, but this was the second purchasing event, and then on the third purchasing date (Jan 3, 2001) there was another single item purchased.
I'm trying to do this in oracle10g. I'm not sure how to describe what I'm accomplishing.
This is the sql I have so far
SELECT
cust_id, purchase_date, sku, ROW_NUMBER() OVER (PARTITION BY purchase_date ORDER BY sku)
FROM
[table]
Thanks
You seem to want dense_rank() rather than row_number() (or rank()) to avoid gaps. With your sample data in a CTE:
with t (cust_id, purchase_date, sku) as (
select 1, date '2001-01-01', 'dog1' from dual
union all select 1, date '2001-01-01', 'cat1' from dual
union all select 1, date '2001-01-01', 'mouse1' from dual
union all select 1, date '2001-01-02', 'wolf1' from dual
union all select 1, date '2001-01-03', 'lion1' from dual
)
select cust_id, purchase_date, sku,
dense_rank() over (partition by cust_id, purchase_date order by sku)
as item_within_purchase,
dense_rank() over (partition by cust_id order by purchase_date)
as purchase_event
from t;
CUST_ID PURCHASE_D SKU ITEM_WITHIN_PURCHASE PURCHASE_EVENT
---------- ---------- ------ -------------------- --------------
1 2001-01-01 cat1 1 1
1 2001-01-01 dog1 2 1
1 2001-01-01 mouse1 3 1
1 2001-01-02 wolf1 1 2
1 2001-01-03 lion1 1 3
The first extra column is partition by both customer and date, and ordered by SKU as you had; the second is only partitioned by customer, and ordered by date.

PostgreSQL showing people that are born the same month

So let's say I have a table of:
Name Born
John 1994-01-01
John 1994-02-08
Jack 1995-03-09
Bob 1992-03-10
Tom 1995-07-13
Ronda 1984-01-25
And I want to make it that it only shows
John 1994-01-01
Ronda 1984-01-25
Jack 1995-03-09
Bob 1992-03-10
Because they are born in the same months.
I've tried different selects with EXTRACT and such but it doesn't seem to work for me:|
You can do this with window functions:
select t.*
from (select t.*,
count(*) over (partition by extract(month from born)) as cnt
from t
) t
where cnt > 1
order by extract(month from born);

tsql PIVOT function

Need help with the following query:
Current Data format:
StudentID EnrolledStartTime EnrolledEndTime
1 7/18/2011 1.00 AM 7/18/2011 1.05 AM
2 7/18/2011 1.00 AM 7/18/2011 1.09 AM
3 7/18/2011 1.20 AM 7/18/2011 1.40 AM
4 7/18/2011 1.50 AM 7/18/2011 1.59 AM
5 7/19/2011 1.00 AM 7/19/2011 1.05 AM
6 7/19/2011 1.00 AM 7/19/2011 1.09 AM
7 7/19/2011 1.20 AM 7/19/2011 1.40 AM
8 7/19/2011 1.10 AM 7/18/2011 1.59 AM
I would like to calculate the time difference between EnrolledEndTime and EnrolledStartTime and group it with 15 minutes difference and the count of students that enrolled in the time.
Expected Result :
Count(StudentID) Date 0-15Mins 16-30Mins 31-45Mins 46-60Mins
4 7/18/2011 3 1 0 0
4 7/19/2011 2 1 0 1
Can I use a combination of the PIVOT function to acheive the required result. Any pointers would be helpful.
Create a table variable/temp table that includes all the columns from the original table, plus one column that marks the row as 0, 16, 31 or 46. Then
SELECT * FROM temp table name PIVOT (Count(StudentID) FOR new column name in (0, 16, 31, 46).
That should put you pretty close.
It's possible (just see the basic pivot instructions here: http://msdn.microsoft.com/en-us/library/ms177410.aspx), but one problem you'll have using pivot is that you need to know ahead of time which columns you want to pivot into.
E.g., you mention 0-15, 16-30, etc. but actually, you have no idea how long some students might take -- some might take 24-hours, or your full session timeout, or what have you.
So to alleviate this problem, I'd suggesting having a final column as a catch-all, labeled something like '>60'.
Other than that, just do a select on this table, selecting the student ID, the date, and a CASE statement, and you'll have everything you need to work the pivot on.
CASE WHEN date2 - date1 < 15 THEN '0-15' WHEN date2-date1 < 30 THEN '16-30'...ELSE '>60' END.
I have an old version of ms sql server that doesn't support pivot. I wrote the sql for getting the data. I cant test the pivot, so I tried my best, couldn't test the pivot part. The rest of the sql will give you the exact data for the pivot table. If you accept null instead of 0, it can be written alot more simple, you can skip the "a subselect" part defined in "with a...".
declare #t table (EnrolledStartTime datetime,EnrolledEndTime datetime)
insert #t values('2011/7/18 01:00', '2011/7/18 01:05')
insert #t values('2011/7/18 01:00', '2011/7/18 01:09')
insert #t values('2011/7/18 01:20', '2011/7/18 01:40')
insert #t values('2011/7/18 01:50', '2011/7/18 01:59')
insert #t values('2011/7/19 01:00', '2011/7/19 01:05')
insert #t values('2011/7/19 01:00', '2011/7/19 01:09')
insert #t values('2011/7/19 01:20', '2011/7/19 01:40')
insert #t values('2011/7/19 01:10', '2011/7/19 01:59')
;with a
as
(select * from
(select distinct dateadd(day, cast(EnrolledStartTime as int), 0) date from #t) dates
cross join
(select '0-15Mins' t, 0 group1 union select '16-30Mins', 1 union select '31-45Mins', 2 union select '46-60Mins', 3) i)
, b as
(select (datediff(minute, EnrolledStartTime, EnrolledEndTime )-1)/15 group1, dateadd(day, cast(EnrolledStartTime as int), 0) date
from #t)
select count(b.date) count, a.date, a.t, a.group1 from a
left join b
on a.group1 = b.group1
and a.date = b.date
group by a.date, a.t, a.group1
-- PIVOT(max(date)
-- FOR group1
-- in(['0-15Mins'], ['16-30Mins'], ['31-45Mins'], ['46-60Mins'])AS p

TSQL Select Max

Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2005
1 Dan Kramer 1/1/2007
1 Dan Kramer 1/1/2009
2 Pamella Slattery 1/1/2005
2 Pam Slattery 1/1/2006
2 Pam Slattery 1/1/2008
3 Samamantha Cohen 1/1/2008
3 Sam Cohen 1/1/2009
I need to extract the latest updated for all these users, basically here's what I'm looking for:
Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2009
2 Pam Slattery 1/1/2008
3 Sam Cohen 1/1/2009
Now when I run the following:
SELECT Userid, FirstName, LastName, Max(UserUpdate) AS MaxDate
FROM Table
GROUP BY Userid, FirstName, LastName
I still get duplicates, something like this:
Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2009
2 Pamella Slattery 1/1/2005
2 Pam Slattery 1/1/2008
3 Samamantha Cohen 1/1/2008
3 Sam Cohen 1/1/2009
try:
declare #Table table (userid int,firstname varchar(10),lastname varchar(20), userupdate datetime)
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2005')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2007')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2009')
INSERT #Table VALUES (2, 'Pamella' ,'Slattery' ,'1/1/2005')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2006')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2008')
INSERT #Table VALUES (3, 'Samamantha' ,'Cohen' ,'1/1/2008')
INSERT #Table VALUES (3, 'Sam' ,'Cohen' ,'1/1/2009')
SELECT
dt.Userid,dt.MaxDate
,MIN(a.FirstName) AS FirstName, MIN(a.LastName) AS LastName
FROM (SELECT
Userid, Max(UserUpdate) AS MaxDate
FROM #Table GROUP BY Userid
) dt
INNER JOIN #Table a ON dt.Userid=a.Userid and dt.MaxDate =a.UserUpdate
GROUP BY dt.Userid,dt.MaxDate
OUTPUT:
Userid MaxDate FirstName LastName
----------- ----------------------- ---------- --------------------
1 2009-01-01 00:00:00.000 Dan Kramer
2 2008-01-01 00:00:00.000 Pam Slattery
3 2009-01-01 00:00:00.000 Sam Cohen
You aren't getting duplicates. 'Pam' is not equal to 'Pamella' from the perspective of the database; the fact that one is a colloquial shortening of the other doesn't mean anything to the database engine. There really is no reliable, universal way to do this (since there are names that have multiple abbreviations, like "Rob" or "Bob" for "Robert", as well as abbreviations that can suit multiple names like "Kel" for "Kelly" or "Kelsie", let alone the fact that names can have alternate spellings).
For your simple example, you could simply select and group by SUBSTRING(FirstName, 1, 3) instead of FirstName, but that's just a coincidence based upon your sample data; other name abbreviations would not fit this pattern.
Or use a subquery...
SELECT
a.userID,
a.FirstName,
a.LastName,
b.MaxDate
FROM
myTable a
INNER JOIN
( SELECT
UserID,
Max(ISNULL(UserUpdate,GETDATE())) as MaxDate
FROM
myTable
GROUP BY
UserID
) b
ON
a.UserID = b.UserID
AND a.UserUpdate = b.MaxDate
The subquery (named "b") returns the following:
Userid UserUpdate
1 1/1/2009
2 1/1/2008
3 1/1/2009
The INNER JOIN between the subquery and the original table causes the original table to be filtered for matching records only -- i.e., only records with a UserID/UserUpdate pair that matches a UserID/MaxDate pair from the subquery will be returned, giving you the unduplicated result set you were looking for:
Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2009
2 Pam Slattery 1/1/2008
3 Sam Cohen 1/1/2009
Of course, this is just a work-around. If you really want to solve the problem for the long-term, you should normalize your original table by splitting it into two.
Table1:
Userid FirstName LastName
1 Dan Kramer
2 Pam Slattery
3 Sam Cohen
Table2:
Userid UserUpdate
1 1/1/2007
2 1/1/2007
3 1/1/2007
1 1/1/2008
2 1/1/2008
3 1/1/2008
1 1/1/2009
2 1/1/2009
3 1/1/2009
This would be a more standard way to store data, and would be much easier to query (without having to resort to a subquery). In that case, the query would look like this:
SELECT
T1.UserID,
T1.FirstName,
T1.LastName,
MAX(ISNULL(T2.UserUpdate,GETDATE()))
FROM
Table1 T1
LEFT JOIN
Table2 T2
ON
T1.UserID = T2.UserID
GROUP BY
T1.UserID,
T1.FirstName,
T1.LastName
Another alternative if you have SQL 2005(I think ?) or later would be to use a Common Table Expression and pull out the user id and max date from the table then join against that to get the matching firstname and lastname on the max date. NOTE - this assumes that userid + date would always be unique, the query will break if you get 2 rows with same userid and date. As others have already pointed out this is pretty awful database design - but sometimes thats life, the problem must still be solved. e.g.
declare #Table table (userid int,firstname varchar(10),lastname varchar(20), userupdate datetime)
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2005')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2007')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2009')
INSERT #Table VALUES (2, 'Pamella' ,'Slattery' ,'1/1/2005')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2006')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2008')
INSERT #Table VALUES (3, 'Samamantha' ,'Cohen' ,'1/1/2008')
INSERT #Table VALUES (3, 'Sam' ,'Cohen' ,'1/1/2009');
with cte ( userid , maxdt ) as
(select userid,
max(userupdate)
from #table
group by userid)
SELECT dt.Userid,
dt.firstname,
dt.lastname,
cte.maxdt
FROM
#Table dt
join cte on cte.userid = dt.userid and dt.userupdate = cte.maxdt
Output
Userid firstname lastname maxdt
----------- ---------- -------------------- -----------------------
3 Sam Cohen 2009-01-01 00:00:00.000
2 Pam Slattery 2008-01-01 00:00:00.000
1 Dan Kramer 2009-01-01 00:00:00.000