SQL - combine two columns into a comma separated list - sql

The problem I'm facing is probably easy to fix, but I can't seem to find an answer online due to the specificity of the issue.
In my database, I have a 3 tables to denote how an educational course is planned. Suppose there is a course called Working with Excel. This means the table Courses has a row for this.
The second table denotes cycles of the same course. If the course is given on Jan 1 2013 and Feb 1 2013, in the underlying tables Cycles, you will find 2 rows, one for each date.
I currently already have an SQL script that gives me two columns: The course name, and a comma separated list with all the Cycle dates.
Please note I am using dd/MM/yyyy notation
This is how it's currently set up (small excerpt, this is the SELECT statement to explain the desired output):
SELECT course.name,
stuff((SELECT distinct ',' + CONVERT(varchar(10), cycleDate, 103) --code 101 = mm/dd/yyyy, code 103 = dd/mm/yyyy
FROM cycles t2
where t2.courseID= course.ID and t2.cycleDate > GETDATE()
FOR XML PATH('')),1,1,'') as 'datums'
The output it gives me:
NAME DATUMS
---------------------------------------------------
Working with Excel 01/01/2013,01/02/2013
Some other course 12/3/2013, 1/4/2013, 1/6/2013
The problem is that I need to add info from the third table I haven't mentioned yet. The table ExtraDays contains additional days for a cycle, in case this spans more than a day.
E.g., if the Working with Excel course takes 3 days, (Jan 1+2+3 and Feb 1+2+3), each of the course cycles will have 2 ExtraDays rows that contain the 'extra days'.
The tables would look like this:
Table COURSES
ID NAME
---------------------------------------------------
1 Working with Excel
Table CYCLES
ID DATE COURSEID
---------------------------------------------------
1 1/1/2013 1
2 1/2/2013 1
Table EXTRADAYS
ID EXTRADATE CYCLEID
---------------------------------------------------
1 2/1/2013 1
2 3/1/2013 1
3 2/2/2013 2
4 3/2/2013 2
I need to add these ExtraDates to the comma-separated list of dates in my output. Preferably sorted, but this is not necessary.
I've been stumped quite some time by this. I have some SQL experience, but apparently not enough for this issue :)
I'm hoping to get the following output:
NAME DATUMS
--------------------------------------------------------------------------------------
Working with Excel 01/01/2013,02/01/2013,03/01,2013,01/02/2013,02/02/2013,03/02/2013
I'm well aware that the database structure could be improved to simplify this, but unfortunately this is a legacy application, I cannot change the structure.
Can anyone point me in the right way to combining these two columns.
I hope I described my issue clear enough for you. Else, just ask :)

SELECT course.name,
stuff((SELECT distinct ',' + CONVERT(varchar(10), cycleDate, 103) --code 101 = mm/dd/yyyy, code 103 = dd/mm/yyyy
FROM (select id, date, courseid from cycles
union
select id, extradate, courseid from extradays) t2
where t2.courseID= course.ID and t2.cycleDate > GETDATE()
FOR XML PATH('')),1,1,'') as 'datums'

Related

SQL - delimiting + matching

I have a table with the follow columns:
reportid, reportname, startdate, consolidated
Reports which are consolidated do not have a start date.
What I need to do is to find the earliest start date within the subreports and set it as the start date
For example
report reportname startdate consolidated
1 ABC 2019/1/1 1
2 DEF 3,4
3 GHI 2019/4/1 3
4 JKF 2019/5/1 4
The report may be consolidated from any number of reports (ie. report 10 may consist of 11,12,13 while report 20 may consist of only 21 and 22)
Output required
report reportname startdate consolidated
1 ABC 2019/1/1 1
2 DEF 2019/4/1 3,4
3 GHI 2019/4/1 3
4 JKF 2019/5/1 4
I can only think of pulling each number and looping through the entire list, comparing each date that is picked up as I go. However, this list is very very long which doesn't make it very feasible.
Thanks in advance!
Unfortunately, I do not have the authority to adjust the database where these tables are concerned.
You need to fix your data model. Storing multiple values in a string is wrong. Storing numbers in a string is wrong.
Sometimes, we are stuck with other peoples really, really bad decisions. You can do what you want, but it is more complicated than necessary:
select t.*
from (select t.*, min(startdate) over (partition by s.value) as imputed_startdate,
row_number() over (partition by report order by report) as seqnum
from t outer apply
string_split(t.consolidated, ',') s
) t
where seqnum = 1;
Here is a db<>fiddle.

Join to Calendar Table - 5 Business Days

So this is somewhat of a common question on here but I haven't found an answer that really suits my specific needs. I have 2 tables. One has a list of ProjectClosedDates. The other table is a calendar table that goes through like 2025 which has columns for if the row date is a weekend day and also another column for is the date a holiday.
My end goal is to find out based on the ProjectClosedDate, what date is 5 business days post that date. My idea was that I was going to use the Calendar table and join it to itself so I could then insert a column into the calendar table that was 5 Business days away from the row-date. Then I was going to join the Project table to that table based on ProjectClosedDate = RowDate.
If I was just going to check the actual business-date table for one record, I could use this:
SELECT actual_date from
(
SELECT actual_date, ROW_NUMBER() OVER(ORDER BY actual_date) AS Row
FROM DateTable
WHERE is_holiday= 0 and actual_date > '2013-12-01'
ORDER BY actual_date
) X
WHERE row = 65
from here:
sql working days holidays
However, this is just one date and I need a column of dates based off of each row. Any thoughts of what the best way to do this would be? I'm using SQL-Server Management Studio.
Completely untested and not thought through:
If the concept of "business days" is common and important in your system, you could add a column "Business Day Sequence" to your table. The column would be a simple unique sequence, incremented by one for every business day and null for every day not counting as a business day.
The data would look something like this:
Date BDAY_SEQ
========== ========
2014-03-03 1
2014-03-04 2
2014-03-05 3
2014-03-06 4
2014-03-07 5
2014-03-08
2014-03-09
2014-03-10 6
Now it's a simple task to find the N:th business day from any date.
You simply do a self join with the calendar table, adding the offset in the join condition.
select a.actual_date
,b.actual_date as nth_bussines_day
from DateTable a
join DateTable b on(
b.bday_seq = a.bday_seq + 5
);

SQL of a daterange without empty spaces

I need to generate a diagram out of data from a table. This table has the following date:
Timestamp | Value
01-20-2013| 5
01-21-2013| 7
01-22-2013| 3
01-25-2013| 5
As you can see not every date has a value. If I put that into a diagram it looks weird. Dates are used for the X-axis. As 01-23-2013 and 01-24-2013 is missing this values are either not printed in the diagram (looks weird) or the are printed put the line of the diagram goes from 3 directly to 5 and not to 0 as it should.
Is there a way via SQL to select the data so that it looks like this:
Timestamp | Value
01-20-2013| 5
01-21-2013| 7
01-22-2013| 3
01-23-2013| 0
01-24-2013| 0
01-25-2013| 5
Any help is appreciated!
Regards,
Alex
Edit: I had no clue that the database engine was that important. This is running on a MySQL 5 Database (not sure about the complete version string).
There are various ways to do this, depending on the database. Date functions are notoriously database independent.
Here is an approach using a "driver" table with all dates and to use this for a left outer join:
select driver.timestamp, coalesce(t.value, 0) as value
from (select distinct timestamp + n.n as timestamp
from t cross join
(select 0 as n union all select 1 union all select 2 union all select 3
)
) driver left outer join
t;
This version assumes that there are gaps of no more than three days.
In some databases, you can construct the list of dates using a recursive CTE. Such an approach would handle gaps of any size.

How to create SQL query

I have data(result / output) in a table like this:
Project code project name associates time efforts in days
1 Analytics amol,manisha,sayali,pooja (21+17+20+17)=57
I need to calculate the time efforts in days. I have done it for February and I have added each persons days he has worked in that month. I mean I have all days minus absentee of any day of all associates.
So, I need to do this by SQL queries.
I have one table which contains all the associates present with dates.
Like this:
UID username date
So can any one give me a suggestion how I could do this?
It will be a better design to have a separate table to store projectid, team member id and his/her efforts in days. so that you can write a simple join query to achieve what you want.
Here is what I would do. Change you tables so you have:
projects
project_code project_name
1 Analytics
users
UID username date
1 amol
2 manisha
3 sayali
projects_users
project_code uid effort
1 1 21
1 2 17
1 3 20
Now you can query the result you asked for like this:
SELECT
p.project_code,
p.project_name,
GROUP_CONCAT(DISTINCT u.username SEPARATOR ', ') AS associates,
SUM(pu.effort) effort
JOIN users AS u
JOIN projects_users AS pu
FROM projects p
GROUP BY project_code

Can I use SQL to plot actual dates based on schedule information?

If I have a table containing schedule information that implies particular dates, is there a SQL statement that can be written to convert that information into actual rows, using some sort of CROSS JOIN, perhaps?
Consider a payment schedule table with these columns:
StartDate - the date the schedule begins (1st payment is due on this date)
Term - the length in months of the schedule
Frequency - the number of months between recurrences
PaymentAmt - the payment amount :-)
SchedID StartDate Term Frequency PaymentAmt
-------------------------------------------------
1 05-Jan-2003 48 12 1000.00
2 20-Dec-2008 42 6 25.00
Is there a single SQL statement to allow me to go from the above to the following?
Running
SchedID Payment Due Expected
Num Date Total
--------------------------------------
1 1 05-Jan-2003 1000.00
1 2 05-Jan-2004 2000.00
1 3 05-Jan-2005 3000.00
1 4 05-Jan-2006 4000.00
1 5 05-Jan-2007 5000.00
2 1 20-Dec-2008 25.00
2 2 20-Jun-2009 50.00
2 3 20-Dec-2009 75.00
2 4 20-Jun-2010 100.00
2 5 20-Dec-2010 125.00
2 6 20-Jun-2011 150.00
2 7 20-Dec-2011 175.00
I'm using MS SQL Server 2005 (no hope for an upgrade soon) and I can already do this using a table variable and while loop, but it seemed like some sort of CROSS JOIN would apply but I don't know how that might work.
Your thoughts are appreciated.
EDIT: I'm actually using SQL Server 2005 though I initially said 2000. We aren't quite as backwards as I thought. Sorry.
I cannot test the code right now, so take it with a pinch of salt, but I think that something looking more or less like the following should answer the question:
with q(SchedId, PaymentNum, DueDate, RunningExpectedTotal) as
(select SchedId,
1 as PaymentNum,
StartDate as DueDate,
PaymentAmt as RunningExpectedTotal
from PaymentScheduleTable
union all
select q.SchedId,
1 + q.PaymentNum as PaymentNum,
DATEADD(month, s.Frequency, q.DueDate) as DueDate,
q.RunningExpectedTotal + s.PaymentAmt as RunningExpectedTotal
from q
inner join PaymentScheduleTable s
on s.SchedId = q.SchedId
where q.PaymentNum <= s.Term / s.Frequency)
select *
from q
order by SchedId, PaymentNum
Try using a table of integers (or better this: http://www.sql-server-helper.com/functions/integer-table.aspx) and a little date math, e..g. start + int * freq
I've used table-valued functions to achieve a similar result. Basically the same as using a table variable I know, but I remember being really pleased with the design.
The usage ends up reading very well, in my opinion:
/* assumes #startdate and #enddate schedule limits */
SELECT
p.paymentid,
ps.paymentnum,
ps.duedate,
ps.ret
FROM
payment p,
dbo.FUNC_get_payment_schedule(p.paymentid, #startdate, #enddate) ps
ORDER BY p.paymentid, ps.paymentnum
A typical solution is to use a Calendar table. You can expand it to fit your own needs, but it would look something like:
CREATE TABLE Calendar
(
calendar_date DATETIME NOT NULL,
is_holiday BIT NOT NULL DEFAULT(0),
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED calendar_date
)
In addition to the is_holiday you can add other columns that are relevant for you. You can write a script to populate the table up through the next 10 or 100 or 1000 years and you should be all set. It makes queries like that one that you're trying to do much simpler and can give you additional functionality.