Merging two rows on to one in different columns - sql

Firstly sorry if this has already been answered somewhere else, I have been unable to find an answer though after days of searching.
Is there a way to merge two rows into one row using different columns.
You will see from the image below, the row is identical, other than the date and location what I am looking for is to have the details below on one row. Where the date column is displayed twice with different column names for example 'Date sent to X location' and 'Date sent to Y location'. The location would not need to be displayed if we put the correct dates in the correct columns, as they would see what the location was from the column name.
So far I use this query, and I am unsure on how to adjust it to do what I need?
select
l.lot_number,
trunc(l.start_tran_date) AS "Date sent to location",
l.location_id_2 AS "Location"
FROM t_tran_log l
WHERE
(l.location_id_2 = 'SENTTOMAP' OR l.location_id_2 = 'WAITINGFORCOLLECTION')
;
This is what I would like the above result to look like:

This would be my approach:
1- Create a test table
create table MESSYLOG
(
lotn varchar(20),
datesent date,
location varchar(20)
);
insert into messylog values ('abc', '06-JUN-16', 'waiting');
insert into messylog values ('abc', '07-JUN-16', 'sent');
insert into messylog values ('def', '08-JUN-16', 'waiting');
insert into messylog values ('def', '10-JUN-16', 'sent');
--select * from MESSYLOG
2- Write 2 subqueries
select t1.lotn,t2.DateWait, t1.DateSentmap,
from
(
select e.lotn, e.datesent as DateSentmap
from messylog e
where e.location = 'sent'
) t1
JOIN
(
select m.lotn, m.datesent as DateWait
from messylog m
where m.location = 'waiting'
)t2
on t1.lotn = t2.lotn
3-Resultset
LOTN DATEWAIT DATESENTMAP
abc 06-JUN-16 07-JUN-16
def 08-JUN-16 10-JUN-16

If there is only a single date to a location for a given lot, could try something like this:
SELECT lot_number
,MAX(CASE WHEN location_id = 'WAITINGFORCOLLECTION' THEN start_tran_date) ELSE NULL END) AS "Date waiting for collection"
,MAX(CASE WHEN location_id = 'SENTTOMAP' THEN start_tran_date) ELSE NULL END) AS "Date sent to map"
FROM t_tran_log
GROUP BY lot_number
The aggregate function (MAX) will skip the NULL column values leaving the single value for the location.

Related

avoiding group by for column used in datediff?

As the database is currently constructed, I can only use a Date Field of a certain table in a datediff-function that is also part of a count aggregation (not the date field, but that entity where that date field is not null. The group by in the end messes up the counting, since the one entry is counted on it's own / as it's own group.
In some detail:
Our lead recruiter want's a report that shows the sum of applications, and conducted interviews per opening. So far no problem. Additionally he likes to see the total duration per opening from making it public to signing a new employee per opening and of cause only if the opening could already be filled.
I have 4 tables to join:
table 1 holds the data of the opening
table 2 has the single applications
table 3 has the interview data of the applications
table 4 has the data regarding the publication of the openings (with the date when a certain opening was made public)
The problem is the duration requirement. table 4 holds the starting point and in table 2 one (or none) applicant per opening has a date field filled with the time he returned a signed contract and therefor the opening counts as filled. When I use that field in a datediff I'm forced to also put that column in the group by clause and that results in 2 row per opening. 1 row has all the numbers as wanted and in the second row there is always that one person who has a entry in that date field...
So far I haven't come far in thinking of a way of avoiding that problem except for explanining to the colleague that he get's his time-to-fill number in another report.
SELECT
table1.col1 as NameOfProject,
table1.col2 as Company,
table1.col3 as OpeningType,
table1.col4 as ReasonForOpening,
count (table2.col2) as NumberOfApplications,
sum (case when table2.colSTATUS = 'withdrawn' then 1 else 0 end) as mberOfApplicantsWhoWithdraw,
sum (case when table3.colTypeInterview = 'PhoneInterview' then 1 else 0 end) as NumberOfPhoneInterview,
...more sum columns...,
table1.finished, // shows „1“ if opening is occupied
DATEDIFF(day, table4.colValidFrom, **table2.colContractReceived**) as DaysToCompletion
FROM
table2 left join table3 on table2.REF_NR = table3.REF_NR
join table1 on table2.PROJEKT = table1.KBEZ
left join table4 on table1.REFNR = table4.PRJ_REFNR
GROUP BY
**table2.colContractReceived**
and all other columns except the ones in aggregate (sum and count) functions go in the GROUP BY section
ORDER BY table1.NameOfProject
Here is a short rebuild of what it looks like. First a row where the opening is not filled and all aggregations come out in one row as wanted. The next project/opening shows up double, because the field used in the datediff is grouped independently...
project company; no_of_applications; no_of_phoneinterview; no_of_personalinterview; ... ; time_to_fill_in_days; filled?
2018_312 comp a 27 4 2 null 0
2018_313 comp b 54 7 4 null 0
2018_313 comp b 1 1 1 42 1
I'd be glad to get any idea how to solve this. Thanks for considering my request!
(During the 'translation' of all the specific column and table names I might have build in a syntax error here and there but the query worked well ecxept for that unwanted extra aggregation per filled opening)
If I've understood your requirement properly, I believe the issue you are having is that you need to show the date between the starting point and the time at which an applicant responded to an opening, however this must only show a single row based on whether or not the position was filled (if the position was filled, then show that row, if not then show that row).
I've achieved this result by assuming that you count a position as filled using the "ContractsRecevied" column. This may be wrong however the principle should still provide what you are looking for.
I've essentially wrapped your query in to a subquery, performed a rank ordering by the contractsfilled column descending and partitioned by the project. Then in the outer query I filter for the first instance of this ranking.
Even if my assumption about the column structure and data types is wrong, this should provide you with a model to work with.
The only issue you might have with this ranking solution is if you want to aggregate over both rows within one (so include all of the summed columns for both the position filled and position not filled row per project). If this is the case let me know and we can work around that.
Please let me know if you have any questions.
declare #table1 table (
REFNR int,
NameOfProject nvarchar(20),
Company nvarchar(20),
OpeningType nvarchar(20),
ReasonForOpening nvarchar(20),
KBEZ int
);
declare #table2 table (
NumberOfApplications int,
Status nvarchar(15),
REF_NR int,
ReturnedApplicationDate datetime,
ContractsReceived bit,
PROJEKT int
);
declare #table3 table (
TypeInterview nvarchar(25),
REF_NR int
);
declare #table4 table (
PRJ_REFNR int,
StartingPoint datetime
);
insert into #table1 (REFNR, NameOfProject, Company, OpeningType, ReasonForOpening, KBEZ)
values (1, '2018_312', 'comp a' ,'Permanent', 'Business growth', 1),
(2, '2018_313', 'comp a', 'Permanent', 'Business growth', 2),
(3, '2018_313', 'comp a', 'Permanent', 'Business growth', 3);
insert into #table2 (NumberOfApplications, Status, REF_NR, ReturnedApplicationDate, ContractsReceived, PROJEKT)
values (27, 'Processed', 4, '2018-04-01 08:00', 0, 1),
(54, 'Withdrawn', 5, '2018-04-02 10:12', 0, 2),
(1, 'Processed', 6, '2018-04-15 15:00', 1, 3);
insert into #table3 (TypeInterview, REF_NR)
values ('Phone', 4),
('Phone', 5),
('Personal', 6);
insert into #table4 (PRJ_REFNR, StartingPoint)
values (1, '2018-02-25 08:00'),
(2, '2018-03-04 15:00'),
(3, '2018-03-04 15:00');
select * from
(
SELECT
RANK()OVER(Partition by NameOfProject, Company order by ContractsReceived desc) as rowno,
table1. NameOfProject,
table1.Company,
table1.OpeningType,
table1.ReasonForOpening,
case when ContractsReceived >0 then datediff(DAY, StartingPoint, ReturnedApplicationDate) else null end as TimeToFillInDays,
ContractsReceived Filled
FROM
#table2 table2 left join #table3 table3 on table2.REF_NR = table3.REF_NR
join #table1 table1 on table2.PROJEKT = table1.KBEZ
left join #table4 table4 on table1.REFNR = table4.PRJ_REFNR
group by NameOfProject, Company, OpeningType, ReasonForOpening, ContractsReceived,
StartingPoint, ReturnedApplicationDate
) x where rowno=1

Excluding records within an aggregate function based on presence of value in another table

I'm writing a query that generates statistics based on postcodes and I need to be able to count the number of matching records that are within a range of postcodes except when they exist in a secondary table. This is part of a larger query and I need the count of records for each postcodes in columnar format rather than as separate rows and this minimal example demonstrates what I've attempted:
CREATE TABLE #People
(
Name nvarchar(10),
Postcode int
)
INSERT INTO #People VALUES ('Adam', 2000)
INSERT INTO #People VALUES ('John', 2001)
INSERT INTO #People VALUES ('Paul', 2001)
INSERT INTO #People VALUES ('Peter', 2099)
INSERT INTO #People VALUES ('Tom', 4000)
CREATE TABLE #PostcodesToIgnore
(
Postcode int
)
INSERT INTO #PostcodesToIgnore VALUES (2099)
SELECT SUM(CASE WHEN PostCode BETWEEN 2000 AND 2099 THEN 1 ELSE 0 END) FROM #People
SELECT SUM(CASE WHEN PostCode BETWEEN 2000 AND 2099
AND PostCode NOT IN (SELECT PostCode FROM #PostcodesToIgnore) THEN 1 ELSE 0 END)
FROM #People
The first query that counts all postcodes within the range works but the second one fails with the error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
While I could refactor the query to include all the criteria from the outer select into each subselect there are quite a few criteria in the real query so I was hoping there might be a more elegant way to go about it?
You could use a left join instead.
SELECT
SUM
(
CASE WHEN PostCode BETWEEN 2000 AND 2099
AND pcti.PostCode is null
THEN 1
ELSE 0
END
)
FROM #People p
left join #PostcodesToIgnore pcti on pcti.PostCode = p.PostCode
You could remove the SUM and push the query into a derived table or CTE.
The following works
SELECT SUM(PostCodeFlag)
FROM (SELECT CASE
WHEN PostCode BETWEEN 2000 AND 2099
AND PostCode NOT IN (SELECT PostCode
FROM #PostcodesToIgnore) THEN 1
ELSE 0
END AS PostCodeFlag
FROM #People) T
Something like this:
Use a CTE to pre-prepare your data, then do a simple grouped count.
Or you could have a look on OVER (https://msdn.microsoft.com/en-us/library/ms189461.aspx)
WITH myCTE AS
(
SELECT Name,Postcode FROM #People
WHERE Postcode NOT IN (SELECT Postcode FROM #PostcodesToIgnore)
)
SELECT Postcode, Count(Name)
FROM myCTE
GROUP BY Postcode
FROM #people WHERE postcode not in (...).
In fact, it looks like you just don't need any CASE at all and you can specify all of your predicates in the FROM.
Or am I missing something ?

I am looking for a way for a trigger to insert into a second table only where the value in table 1 changes

I am looking for a way for a trigger to insert into a second table only where the value in table 1 changes. It is essentially an audit tool to trap any changes made. The field in table 1 is price and we want to write additional fields.
This is what I have so far.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
insert into zmerps_Item_costprice_history
select NEWID(), -- unique id
GETDATE(), -- CURRENT_date
'PRICE_CHANGE', -- reason code
a.ima_itemid, -- item id
a.ima_price-- item price
FROM Inserted b inner join item a
on b.ima_recordid = a.IMA_RecordID
The table only contains a unique identifier, date, reference(item) and the field changed (price). It writes any change not just a price change
Is it as simple as this? I moved some of the code around because comments after the comma between columns is just painful to maintain. You also should ALWAYS specify the columns in an insert statement. If your table changes this code will still work.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
insert into zmerps_Item_costprice_history
(
UniqueID
, CURRENT_date
, ReasonCode
, ItemID
, ItemPrice
)
select NEWID()
, GETDATE()
, 'PRICE_CHANGE'
, d.ima_itemid
, d.ima_price
FROM Inserted i
inner join deleted d on d.ima_recordid = i.IMA_RecordID
AND d.ima_price <> i.ima_price
Since you haven't provided any other column names I Have used Column2 and Column3 and the "Other" column names in the below example.
You can expand adding more columns in the below code.
overview about the query below:
Joined the deleted and inserted table (only targeting the rows that has changed) joining with the table itself will result in unnessacary processing of the rows which hasnt changed at all.
I have used NULLIF function to yeild a null value if the value of the column hasnt changed.
converted all the columns to same data type (required for unpivot) .
used unpivot to eliminate all the nulls from the result set.
unpivot will also give you the column name its has unpivoted it.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr
ON [ITEM]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON ;
WITH CTE AS (
SELECT CAST(NULLIF(i.Price , d.Price) AS NVARCHAR(100)) AS Price
,CAST(NULLIF(i.Column2 , d.Column2) AS NVARCHAR(100)) AS Column2
,CAST(NULLIF(i.Column3 , d.Column3) AS NVARCHAR(100)) AS Column3
FROM dbo.inserted i
INNER JOIN dbo.deleted d ON i.IMA_RecordID = d.IMA_RecordID
WHERE i.Price <> d.Price
OR i.Column2 <> d.Column2
OR i.Column3 <> d.Column3
)
INSERT INTO zmerps_Item_costprice_history
(unique_id, [CURRENT_date], [reason code], Item_Value)
SELECT NEWID()
,GETDATE()
,Value
,ColumnName + '_Change'
FROM CTE UNPIVOT (Value FOR ColumnName IN (Price , Column2, Column3) )up
END
As I understand your question correctly, You want to record change If and only if The column Price value is changes, you dont need any other column changes to be recorded
here is your code
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
if update(ima_price)
insert into zmerps_Item_costprice_history
select NEWID(), -- unique id
GETDATE(), -- CURRENT_date
'PRICE_CHANGE', -- reason code
a.ima_itemid, -- item id
a.ima_price-- item price
FROM Inserted b inner join item a
on b.ima_recordid = a.IMA_RecordID

Unable to use multiple select statements to insert data into a table

So I'm trying to insert data into the Main_Contract_Data table from three different tables and it is producing an error that is shown below, does anyone know why?
Error:
Msg 120, Level 15, State 1, Line 1
The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.
//SQL Server 2008 Code
INSERT INTO Main_Contract_Data
(organisation_name,
contract_start_date,
a_manager,
d_manager)
(SELECT [Client]
FROM [Internal].[dbo].[RequiredFields$])
(SELECT [Start Date]
FROM [Internal].[dbo].[RequiredFields$])
(SELECT person_id
FROM A_Manager
WHERE person_id = '5')
(SELECT person_id
FROM D_Manager
WHERE person_id = '6')
You just need to make those sub queries:
INSERT INTO Main_Contract_Data
(organisation_name,
contract_start_date,
a_manager,
d_manager)
SELECT
(SELECT [Client]
FROM [Internal].[dbo].[RequiredFields$]),
(SELECT [Start Date]
FROM [Internal].[dbo].[RequiredFields$]),
(SELECT person_id
FROM A_Manager
WHERE person_id = '5'),
(SELECT person_id
FROM D_Manager
WHERE person_id = '6')
But keep in mind that each sub query can only return one row, while the overall query needs to return an entire result set. If that's only one row too, that's fine, but the overall SELECT is to return one or more while each sub query returns one row, and one value for each row in the overall query.

SQL query to separate a column into separate columns

I would like to have separate columns for H and T's prices, with 'period' as the common index. Any suggestions as to how I should go about this?
This is what my SQL query produces at the moment:
You can use GROUP BY and a conditional, like this:
SELECT
period
, SUM(CASE NAME WHEN 'H' THEN price ELSE 0 END) as HPrice
, SUM(CASE NAME WHEN 'T' THEN price ELSE 0 END) as TPrice
FROM MyTable
GROUP BY period
You can do the following:
SELECT period,
max(CASE WHEN name = 'H' THEN price END) as h_price,
max(CASE WHEN name = 'T' THEN price END) as t_price
FROM myTable
GROUP by period
If you mean to recreate the table?
1) Create a new table with columns: period, price_h & price_t.
2) Copy all (distinct) from period into new table's period.
3) Copy all price where name = H to new table's price_h joining the period column
4) repeat 3 for price_t....
good luck!
A little late to the game on this but you could also pivot the data.
Lets create a sample table.
CREATE TABLE myData(period int, price decimal(12,4), name varchar(10))
GO
-- Inserting Data into Table
INSERT INTO myData
(period, price, name)
VALUES
(1, 53.0450, 'H'),
(1, 55.7445, 'T'),
(2, 61.2827, 'H'),
(2, 66.0544, 'T'),
(3, 61.3405, 'H'),
(3, 66.0327, 'T');
Now the select with the pivot performed.
SELECT period, H, T
FROM (
SELECT period, price, name
FROM myData) d
PIVOT (SUM(price) FOR name IN (H, T)) AS pvt
ORDER BY period
I've used this technique when I needed to build a dynamic sql script that took in the columns in which would be displayed on the header of the table. No need for case statements.
Im not sure about the performance of the case and pivot. Maybe someone with a little more experience could add some comments on which would give better performance.