I have a table with 32 columns whose primary key is play_id and my goal is to create a key value pair table where the output would look like this:
play_id, field_name, field_value
play_id_1, game_date, ‘2020-07-23’
play_id_1, possession, ‘home’
' ', ' ', ' '
play_id_2, game_date, ‘2020-07-24’
play_id_2, possession, ‘away’
where all the entries in field_name are columns from the original table (besides play_id). So there would be 31 appearances of play_id_1 in this new table. I want the new primary key for this table to be (play_id, field_name). There's a long way of doing it where I can query for play_id and each column and union everything together, but I want to find a more elegant solution.
You can use a lateral join for that. But you will need to cast all values to text.
select t.play_id, x.*
from the_table t
cross join lateral (
values
('game_date', game_date::text),
('possession', possession::text),
('col_3', col_3::text),
...
) as x(field_name, field_value);
Another option is to convert the row to a JSON value and then unnest that json:
select t.play_id, x.*
from the_table t
cross join lateral jsonb_each(to_jsonb(t) - 'play_id') as x(field_name, field_value);
Related
Lets say I have som data that looks like
create table test.from
(
id integer primary key,
data json
);
insert into test.from
values
(4, '{ "some_field": 11 }'),
(9, '{ "some_field": 22 }');
create table test.to
(
id integer primary key,
some_field int
);
I would like the rows in the "to" table to have the same id key as the "from" row, and expand the json into separate columns. But using json_populate_record like below, will unsurprisingly give me null as key.
Method 1:
insert into test.to
select l.*
from test.from fr
cross join lateral json_populate_record(null::test.to, fr.data) l;
I can achieve what I'm looking for by naming columns like below
Method 2:
insert into test.to (id, some_field)
select
fr.id as id,
l.some_field
from test.from fr
cross join lateral json_populate_record(null::test.to, fr.data) l;
The challenge is that I want to avoid naming any columns other than the id column, both since it gets tedious, but also since I'd like to do this in a function where the column names are not known.
What modifications do I have to do to Method 1 to update the record with the correct id?
Just append the id key to your data like this:
insert into test.to
select l.*
from
test.from fr
cross join lateral jsonb_populate_record(
null::test.to,
fr.data::jsonb || jsonb_build_object('id', fr.id)) l;
I have a field which holds a short list of ids of a fixed length.
e.g. aab:aac:ada:afg
The field is intended to hold at most 5 ids, growing gradually. I update it by adding from a similarly constructed field that may partially overlap with my existing set, e.g. ada:afg:fda:kfc.
The field expans when joined to an "update" table, as in the following example.
Here, id_list is the aforementioned list I want to "merge", and table_update is a table with new values I want to "merge" into table1.
insert overwrite table table1
select
id,
field1,
field2,
case
when (some condition) then a.id_list
else merge(a.id_list, b.id_list)
end as id_list
from table1 a
left join
table_update b
on a.id = b.id;
I'd like to produce a combined field with the following value:
aab:aac:ada:afg:fda.
The challenge is that I don't know whether or how much overlap the strings have until execution, and I cannot run any external code, or create UDFs.
Any suggestions how I could approach this?
Split to get arrays, explode them, select existing union all new, aggregate using collect_set, it will produce unique array, concatenate array into string using concat_ws(). Not tested:
select concat_ws(':',collect_set(id))
from
(
select explode(split('aab:aac:ada:afg',':')) as id --existing
union all
select explode(split('ada:afg:fda:kfc',':')) as id --new
);
You can use UNION instead UNION ALL to get distinct values before aggregating into array. Or you can join new and existing and concatenate strings into one, then do the same:
select concat_ws(':',collect_set(id))
from
(
select explode(split(concat('aab:aac:ada:afg',':','ada:afg:fda:kfc'),':')) as id --existing+new
);
Most probably you will need to use lateral view with explode in the real query. See this answer about lateral view usage
Update:
insert overwrite table table1
select concat_ws(':',collect_set(a.idl)) as id_list,
id,
field1,
field2
from
(
select
id,
field1,
field2,
split(
case
when (some condition) then a.id_list
when b.id_list is null then a.id_list
else concat(a.id_list,':',b.id_list)
end,':') as id_list_array
from table1 a
left join table_update b on a.id = b.id
)s
LATERAL VIEW OUTER explode(id_list_array ) a AS idl
group by
id,
field1,
field2
;
I have a table in SQL Server 2014 called anotes with the following data
and I want to add this data into another table named final as
ID Notes NoteDate
With text1, text2, text3, text4 going into the Notes column in the final table and Notedate1,notedate2,notedate3,notedate4 going into Notedate column.
I tried unpivoting the data with notes first as:
select createdid, temp
from (select createdid,text1,text2,text3,text4 from anotes) p
unpivot
(temp for note in(text1,text2,text3,text4)) as unpvt
order by createdid
Which gave me proper results:
and then for the dates part I used another unpivot query:
select createdid,temp2
from (select createdid,notedate1,notedate2,notedate3,notedate4 from anotes) p
unpivot (temp2 for notedate in(notedate1,notedate2,notedate3,notedate4)) as unpvt2
which also gives me proper results:
Now I want to add this data into my final table.
and I tried the following query and it results into a cross join :(
select a.createdid, a.temp, b.temp2
from (select createdid, temp
from (select createdid,text1,text2,text3,text4 from anotes) p
unpivot
(temp for note in(text1,text2,text3,text4)) as unpvt) a inner join (select createdid,temp2
from (select createdid,notedate1,notedate2,notedate3,notedate4 from anotes) p
unpivot (temp2 for notedate in(notedate1,notedate2,notedate3,notedate4)) as unpvt) b on a.createdid=b.createdid
The output is as follows:
Is there any way where I can unpivot both the columns at the same time?
Or use two select queries to add that data into my final table?
Thanks in advance!
I would say the most concise, and probably most efficient way to unpivot multiple columns is to use CROSS APPLY along with a table valued constructor:
SELECT t.CreatedID, upvt.Text, upvt.NoteDate
FROM anotes t
CROSS APPLY
(VALUES
(Text1, NoteDate1),
(Text2, NoteDate2),
(Text3, NoteDate3),
(Text4, NoteDate4),
(Text5, NoteDate5),
(Text6, NoteDate6),
(Text7, NoteDate7)
) upvt (Text, NoteDate);
Simplified Example on SQL Fiddle
ADDENDUM
I find the concept quite a hard one to explain, but I'll try. A table valued constuctor is simply a way of defining a table on the fly, so
SELECT *
FROM (VALUES (1, 1), (2, 2)) t (a, b);
Will Create a table with Alias t with data:
a b
------
1 1
2 2
So when you use it inside the APPLY you have access to all the outer columns, so it is just a matter of defining your constructed tables with the correct pairs of values (i.e. text1 with date1).
Used the link above mentioned by #AHiggins
Following is my final query!
select createdid,temp,temp2
from (select createdid,text1,text2,text3,text4,text5,text6,text7,notedate1,notedate2,notedate3,notedate4,notedate5,notedate6,notedate7 from anotes) main
unpivot
(temp for notes in(text1,text2,text3,text4,text5,text6,text7)) notes
unpivot (temp2 for notedate in(notedate1,notedate2,notedate3,notedate4,notedate5,notedate6,notedate7)) Dates
where RIGHT(notes,1)=RIGHT(notedate,1)
Treat each query as a table and join them together based on the createdid and the fieldid (the numeric part of the field name).
select x.createdid, x.textValue, y.dateValue
from
(
select createdid, substring(note, 5, len(note)) fieldId, textValue
from (select createdid,text1,text2,text3,text4 from anotes) p
unpivot
(textValue for note in(text1,text2,text3,text4)) as unpvt
)x
join
(
select createdid, substring(notedate, 9, len(notedate)) fieldId, dateValue
from (select createdid,notedate1,notedate2,notedate3,notedate4 from anotes) p
unpivot (dateValue for notedate in(notedate1,notedate2,notedate3,notedate4)) as unpvt2
) y on x.fieldId = y.fieldId and x.createdid = y.createdid
order by x.createdid, x.fieldId
The other answer given won't work if you have too many columns and the rightmost number of the field name is duplicated (e.g. text1 and text11).
I have two queries(each on a different table) with a where clause of their own.Primary keys for two tables have different names but hold same values. How do I pull out the records that are in result set1 but not in result set2.
Here is an example of two queries
Query1:
SELECT DISTINCT [EntityID],[Year],[Name],[OperationalStatus],[RefTypeID]
FROM [DB].[dbo].[Entity]
WHERE [Year]='2014' AND [RefTypeID] IN ('abc','xys')
Query2:
SELECT DISTINCT [OrganizationID],[Year],[OperationalStatusID],[Active],[ModifiedBy],[ModifiedDate]
FROM [DB2].[dbo].[Organization] WHERE [Year]='2014'
Primary key from query1 is [EntityID] and primary key from second query is [OrganizationID]. These two columns hold same values
Like this:
SELECT * FROM
(
SELECT DISTINCT [EntityID],[Year],[Name],[OperationalStatus],[RefTypeID]
FROM [DB].[dbo].[Entity] A
WHERE [Year]='2014' AND [RefTypeID] IN ('abc','xys') AND
Not Exists
(
SELECT DISTINCT [OrganizationID],[Year],[OperationalStatusID],[Active],[ModifiedBy],
[ModifiedDate]
FROM [DB2].[dbo].[Organization] D WHERE [FiscalYear]='2014' AND
A.EntityID = D.OrganizationID AND
A.Year=D.Year
A.Name = D. Name
)
) AS T
I would like to transform a pivot table into a flat table, but in the following fashion: consider the simple example of this table:
As you can see, for each item - Address or Income -, we have a column for old values, and a column for new (updated values). I would like to convert the table to a "flat" table, looking like:
Is there an easy way of doing that?
Thank you for your help!
In order to get the result, you will need to UNPIVOT the data. When you unpivot you convert the multiple columns into multiple rows, in doing so the datatypes of the data must be the same.
I would use CROSS APPLY to unpivot the columns in pairs:
select t.employee_id,
t.employee_name,
c.data,
c.old,
c.new
from yourtable t
cross apply
(
values
('Address', Address_Old, Address_new),
('Income', cast(income_old as varchar(15)), cast(income_new as varchar(15)))
) c (data, old, new);
See SQL Fiddle with demo. As you can see this uses a cast on the income columns because I am guessing it is a different datatype from the address. Since the final result will have these values in the same column the data must be of the same type.
This can also be written using CROSS APPLY with UNION ALL:
select t.employee_id,
t.employee_name,
c.data,
c.old,
c.new
from yourtable t
cross apply
(
select 'Address', Address_Old, Address_new union all
select 'Income', cast(income_old as varchar(15)), cast(income_new as varchar(15))
) c (data, old, new)
See Demo
select employee_id,employee_name,data,old,new
from (
select employee_id,employee_name,adress_old as old,adress_new as new,'ADRESS' as data
from employe
union
select employee_id,employee_name,income_old,income_new,'INCOME'
from employe
) data
order by employee_id,data
see this fiddle demo : http://sqlfiddle.com/#!2/64344/7/0