SQL query to separate a column into separate columns - sql

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.

Related

I cannot figure out how to change this into a table that has only one row and no null values. Is there a way to make this a loop?

screenshot of code and table
DELETE FROM summary;
INSERT INTO summary(g_rating_total)
SELECT COUNT(rating) FROM detailed
WHERE rating = 'G';
INSERT INTO summary(pg_rating_total)
SELECT COUNT(rating) FROM detailed
WHERE rating = 'PG';
INSERT INTO summary(pg13_rating_total)
SELECT COUNT(rating) FROM detailed
WHERE rating = 'PG-13';
INSERT INTO summary(r_rating_total)
SELECT COUNT(rating) FROM detailed
WHERE rating = 'R';
INSERT INTO summary(nc17_rating_total)
SELECT COUNT(rating) FROM detailed
WHERE rating = 'NC-17';
INSERT INTO summary(total_movies)
SELECT COUNT(rating) FROM detailed;
SELECT * FROM summary;
I am trying to get the total count of each different rating and insert them into another premade table name summary. I had to initially create a summary table and detail table put data into details from multiple other tables than transform that data into something that populates the summary table. The issue I am having is inserting the count result into the summary table without creating 6 rows where each iteration of COUNT() adds a new row with null values for all previous and new rows for each column.
Just use a case statement for each column e.g.
INSERT INTO summary(g_rating_total, pg_rating_total, …)
SELECT
SUM(CASE WHEN rating = 'G' Then 1 else 0 end)
, SUM(CASE WHEN rating = 'PG' Then 1 else 0 end),
,…
FROM detailed;

Display SUM of 2 listed/GROUP BY values using WHERE condition

I want to add the values of two columns displayed and display as 1 column name.
This is the output I'm getting,
ID Total
Apple 10
RawApple 10
Mango 10
RawMango 10
I want the output as
ID Total
Apples 20
Mangoes 20
If the issue is removing the first three characters -- if they are "Raw" -- then you can do:
select (case when id like 'Raw%' then stuff(id, 1, 3, '') else id end) as id,
sum(total)
from t
group by (case when id like 'Raw%' then stuff(id, 1, 3, '') else id end);
If you want to replace specific values with other values, I would suggest an in-query lookup table:
select coalesce(v.new_id, t.id) as id, sum(total)
from t left join
(values ('RawApple', 'Apple'),
('RawMango', 'Mango')
) v(id, new_id)
on t.id = v.id
group by coalesce(v.new_id, t.id);
If we can assume that the name of the fruit is after the prefix, and the prefix ends with a hyphen (-), then we can use STUFF to remove the prefix and then aggregate:
WITH VTE AS(
SELECT *
FROM (VALUES('Apple',10),
('Raw-Apple',10),
('Mango',10),
('Raw-Mango',10))V(ID,Total))
SELECT S.ID,
SUM(V.Total) AS Total
FROM VTE V
CROSS APPLY(VALUES(STUFF(V.ID,1,CHARINDEX('-',V.ID),'')))S(ID)
GROUP BY S.ID;
Note I don't change the names of the fruits to the plural, as depending on the fruit changes what the plural is. You'll need a dictionary table to store what the plural of the fruit is and then `JOIN to that. So a table that looks like this:
CREATE TABLE dbo.FruitPlural (Fruit varchar(20), Plural varchar(20));
INSERT INTO dbo.FruitPlural
VALUES ('Apple','Apples'),
('Mango','Mangoes'),
('Strawberry','Strawberries'),
...;
Note, this answer was invalidated due to the OP moving the goal posts due to the sample data not being representative of their actual data, however, I am leaving here as it may help future users.

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

Merging two rows on to one in different columns

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.

UNPIVOT/PIVOT to combine multiple records into one

Currently, I have a table that tracks product inventory locations using the following table:
ProductID(PK) Location(PK) BIN1 BIN2
1000 EAST XRZY CCAB
1000 WEST AAAA NULL
I'm attempting to UNPIVOT the data into the following:
ProductID EAST_BIN1 EAST_BIN2 WEST_BIN1 WEST_BIN2
1000 XRZY CCAB AAAA NULL
Note that the location column has been PIVOTed into part of the BIN value field.
However, I've found that if I pivot the data, I'm unable to combine it with the BIN fields. PIVOT simply aggregates (using MAX) the BIN values into one field, while UNPIVOT just transforms the BIN* fields into rows.
What am I missing in terms of transforming the data above?
Any help would be greatly appreciated!
You can do it "by hand" as follows:
SELECT ProductID,
MAX(CASE WHEN Location='EAST' THEN BIN1 ELSE NULL END) AS EAST_BIN1,
MAX(CASE WHEN Location='EAST' THEN BIN2 ELSE NULL END) AS EAST_BIN2,
MAX(CASE WHEN Location='WEST' THEN BIN1 ELSE NULL END) AS WEST_BIN1,
MAX(CASE WHEN Location='WEST' THEN BIN2 ELSE NULL END) AS WEST_BIN2
FROM YOURTABLE
GROUP BY ProductID
This creates multiple rows (as your source table) with the results in the correct column, then smashes them down to one row with a group by. The correct value is taken using the aggregate function MAX.
To pivot you'll need to get the data into a single BIN column to pivot on. Consider this...
declare #t table (ProductId int, Location varchar(20), BIN1 varchar(4), BIN2 varchar(4));
insert into #t values(1000, 'EAST', 'XRZY', 'CCAB'), (1000, 'WEST', 'AAAA', null);
with cte as (
select ProductId, Col = Location + '_BIN1', BINVal = Bin1 from #t
union all
select ProductId, Col = Location + '_BIN2', BINVal = Bin2 from #t
)
select
*
from
cte
pivot (
max(BINVal)
for Col in ([EAST_BIN1], [EAST_BIN2], [WEST_BIN1], [WEST_BIN2])
) p