How to UPDATE pivoted table in SQL SERVER - sql

I have flat table which I have to join using EAN attribute with my main table and update gid (id of my main table).
id attrib value gid
1 weight 10 NULL
1 ean 123123123112 NULL
1 color blue NULL
2 weight 5 NULL
2 ean 331231313123 NULL
I was trying to pivot ean rows into column, next join on ean both tables, and for this moment everything works great.
--update SideTable
--set gid = ab_id
select gid, ab_id
from SideTable
pivot (max (value) for attrib in ([EAN],[MPN])) as b
join MainTable as c
on c.ab_ean = b.EAN
where b.EAN !='' AND c.ab_archive = '0'
When I am selecting both id columns is okey, but when I am uncomment first lines and delete select whole table is set with first gid from my main table.
It have to set my main id into all attributes where ID where ean is matched from my main table.
I am sorry for my terrible english but I hope someone can help me, with that.

The reason your update does not work is that you don't have any link between your source and target for the update, although you reference sidetable in the FROM clause, this is effectively destroyed by the PIVOT function, leaving no link back to the instance of SideTable that you are updating. Since there is no link, all rows are updated with the same value, this will be the last value encountered in the FROM.
This can be demonstrated by running the following:
DECLARE #S TABLE (ID INT, Attrib VARCHAR(50), Value VARCHAR(50), gid INT);
INSERT #S
VALUES
(1, 'weight', '10', NULL), (1, 'ean', '123123123112', NULL), (1, 'color', 'blue', NULL),
(2, 'weight', '5', NULL), (2, 'ean', '331231313123', NULL);
SELECT s.*
FROM #S AS s
PIVOT (MAX(Value) FOR attrib IN ([EAN],[MPN])) AS pvt;
You clearly have a table aliased s in the FROM clause, however because you have used pivot you cannot use SELECT s*, you get the following error:
The column prefix 's' does not match with a table name or alias name used in the query.
You haven't provided sample data for your main table, but I am about 95% certain your PIVOT is not needed, I think you can get your update using just normal JOINs:
UPDATE s
SET gid = ab_id
FROM SideTable AS s
INNER JOIN SideTable AS ean
ON ean.ID = s.ID
AND ean.attrib = 'ean'
INNER JOIN MainTable AS m
ON m.ab_EAN = ean.Value
WHERE m.ab_archive = '0'
AND m.ab_EAN != '';

As per comment to the question, you need to use update + select statement.
A standard version looks like:
UPDATE
T
SET
T.col1 = OT.col1,
T.col2 = OT.col2
FROM
Some_Table T
INNER JOIN
Other_Table OT
ON
T.id = OT.id
WHERE
T.col3 = 'cool'
As to your needs:
update a
set a.gid = p.ab_id
from SideTable As a
Inner join (
select gid, ab_id
from SideTable
pivot (max (value) for attrib in ([EAN],[MPN])) as b
join MainTable as c
on c.ab_ean = b.EAN
where b.EAN !='' AND c.ab_archive = '0') p ON a.ean = p.EAN

try and break it down a bit more like this..
update SideTable
set SideTable.gid = p.ab_id
FROM
(
select gid, ab_id
from SideTable
pivot (max (value) for attrib in ([EAN],[MPN])) as b
join MainTable as c
on c.ab_ean = b.EAN
where b.EAN !='' AND c.ab_archive = '0'
) p
WHERE p.EAN = SideTable.EAN

Related

Select rows having value combination listed in another table

I have tables:
Result containing 5 columns: result_id, num_1, num_2, num_3, num_4
Ref containing 4 columns: num_1, num_2, num_3, num_4
Columns num contain random int in range of 1-9
Aim of exercise is to display all result_id from Result table which have num values combination present in Ref table and to display result_id which have not met combination criteria.
I've been trying left joining ref to result, but unfortunately no success. Could you please share some light how to deal with it?
If you want the result_id for which combination exists in the ref table then use following JOIN query:
select distinct r.result_id
from results r
join ref on r.num_1 = ref.num_1 and r.num_2 = ref.num_2
and r.num_3 = ref.num_3 and r.num_4 = ref.num_4
If you want the result_id for which combination do not exists in REF table then use the LEFT JOIN as follows:
select r.result_id
from results r
left join ref on r.num_1 = ref.num_1 and r.num_2 = ref.num_2
and r.num_3 = ref.num_3 and r.num_4 = ref.num_4
where ref.num_1 is null -- or use PK / Not nullable column of REF table here
Assuming you want the columns to "line up" and you want to add a flag to the result_id in the first table, then use exists:
select t1.*,
(case when exists (select 1
from table2 t2
where t2.n1 = t1.n1 and t2.n2 = t1.n2 and t2.n3 = t1.n3 and t2.n4
)
then 'present' else 'not present'
end) as flag
from t2;

remove Union from query

I have query to get category and subcategories of same category. I used below query:
Select Id from category where name like '%events%' and deleted = 0 and published = 1
UNION
SELECT Id from category where parentcategoryid = (Select id from category where name like '%events%' and deleted = 0 and published = 1)
I do not want to use UNION, want to use Join only. But not getting how i can achieve. Below is the table structure. Please help me. thanks in Advance
The following might solve your problem:
DECLARE #t TABLE (id int, name varchar(20), parentcatid varchar(200))
insert into #t values(23,'Christmas', 34)
,(29,'Birthday', 34)
,(31,'New year', 34)
,(34,'Events', 0)
,(35,'gfrhrt', 0)
;WITH cte AS(
Select Id from #t
where name like '%events%' --and deleted = 0 and published = 1
),
cte2 AS (
SELECT a.id AS tId, cpa.id, cId2.id AS idPa
from #t a
LEFT JOIN cte AS cId ON cId.Id = a.Id
FULL OUTER JOIN #t AS cPa ON cPa.parentcatid = cId.Id
LEFT JOIN cte cId2 ON cId2.id = cPa.id
WHERE cId.Id IS NOT NULL OR cPa.Id IS NOT NULL
)
SELECT id
FROM cte2
WHERE tId IS NOT NULL
OR id = idPa
The idea is to get all required IDs within the cte and then get all categories, where either ID oder ParentID match the IDs from the cte. However, depending on the size of your table, you might want to add further filters to the cte.

Use Data of 1 table into another one dynamically

I have one table category_code having data like
SELECT Item, Code, Prefix from category_codes
Item Code Prefix
Bangles BL BL
Chains CH CH
Ear rings ER ER
Sets Set ST
Rings RING RG
Yellow GOld YG YG........
I have another table item_categories having data like
select code,name from item_categories
code name
AQ.TM.PN AQ.TM.PN
BL.YG.CH.ME.PN BL.YG.CH.ME.PN
BS.CZ.ST.YG.PN BS.CZ.ST.YG.PN
CR.YG CR.YG.......
i want to update item_categories.name column corresponding to category_code.item column like
code name
BL.YG.CH.ME.PN Bangles.Yellow Gold.Chains.. . . .
Please suggest good solution for that. Thanks in advance.
First, split the code into several rows, join with the category code and then, concat the result to update the table.
Here an example, based on the data you gave
create table #category_code (item varchar(max), code varchar(max), prefix varchar(max));
create table #item_categories (code varchar(max), name varchar(max));
insert into #category_code (item, code, prefix) values ('Bangles','BL','BL'),('Chains','CH','CH'),('Ear rings','ER','ER'), ('Sets','Set','ST'),('Rings','RING','RG'), ('Yellow gold','YG','YG');
insert into #item_categories (code, name) values ('AQ.TM,PN','AQ.TM.PN'),('BL.YG.CH.ME.PN','BL.YG.CH.ME.PN'),('BS.CZ.ST.YG.PN','BS.CZ.ST.YG.PN')
;with splitted as ( -- split the codes into individual code
select row_number() over (partition by ic.code order by ic.code) as id, ic.code, x.value, cc.item
from #item_categories ic
outer apply string_split(ic.code, '.') x -- SQL Server 2016+, otherwise, use another method to split the data
left join #category_code cc on cc.code = x.value -- some values are missing in you example, but can use an inner join
)
, joined as ( -- then joined them to concat the name
select id, convert(varchar(max),code) as code, convert(varchar(max),coalesce(item + ',','')) as Item
from splitted
where id = 1
union all
select s.id, convert(varchar(max), s.code), convert(varchar(max), j.item + coalesce(s.item + ',',''))
from splitted s
inner join joined j on j.id = s.id - 1 and j.code = s.code
)
update #item_categories
set name = substring (j.item ,1,case when len(j.item) > 1 then len(j.item)-1 else 0 end)
output deleted.name, inserted.name
from #item_categories i
inner join joined j on j.code = i.code
inner join (select code, max(id)maxid from joined group by code) mj on mj.code = j.code and mj.maxid = j.id

Selecting table of properties as columns

I have two tables, things and properties:
CREATE TABLE things (
id SERIAL PRIMARY KEY
);
CREATE TABLE properties (
thing_id INT,
key TEXT,
value TEXT
);
I want to select from things and join rows from properties as columns. For example, say I have the following:
INSERT INTO things DEFAULT_VALUES; -- suppose id is 1
INSERT INTO properties (thing_id, key, value) VALUES
(1, 'height', '5'),
(1, 'width', '6'),
(1, 'length', '7');
How can I select from things with height, width, and length as columns?
Also, I don't want to specifically select height, width, and length, but any rows that may be inside properties.
For just three columns:
SELECT t.thing_id
,max(CASE WHEN p.key = 'height' THEN p.value END) AS height
,max(CASE WHEN p.key = 'width' THEN p.value END) AS width
,max(CASE WHEN p.key = 'length' THEN p.value END) AS length
FROM things t
LEFT JOIN properties p ON p.thing_id = t.id
WHERE t.id = 1
GROUP BY 1;
Or use crosstab() from the additional module tablefunc which is typically faster, and shorter for long lists of attributes:
SELECT * FROM crosstab(
'SELECT t.thing_id, p.key, p.value
FROM things t
LEFT JOIN properties p ON p.thing_id = t.id
WHERE t.id = 1
ORDER BY 1'
,$$VALUES ('height'::text), ('width'), ('length')$$) -- add more
AS ct (thing_id int, height int, width int, length int); -- add more
Types have to match. Detailed explanation:
PostgreSQL Crosstab Query
A completely dynamic list of columns cannot be achieved in a single query. I have tried many times. Here is what can be done:
Dynamic alternative to pivot with CASE and GROUP BY
May be you can try table aliases here:
SELECT p1.key, p2.key, p3.key
FROM properties as t1
JOIN properties AS p1 ON p1.thing_id= t1.thing_id
JOIN properties AS p2 ON p2.thing_id= t1.thing_id
JOIN properties AS p3 ON p3.thing_id= t1.thing_id
WHERE t1.thing_id = 1;

Is there a good way to do this in SQL?

I am trying to solve the following problem entirely in SQL (ANSI or TSQL, in Sybase ASE 12), without relying on cursors or loop-based row-by-row processing.
NOTE: I already created a solution that accomplishes the same goal in application layer (therefore please refrain from "answering" with "don't do this in SQL"), but as a matter of principle (and hopefully improved performance) I would like to know if there is an efficient (e.g. no cursors) pure SQL solution.
Setup:
I have a table T with the following 3 columns (all NOT NULL):
---- Table T -----------------------------
| item | tag | value |
| [int] | [varchar(10)] | [varchar(255)] |
The table has unique index on item, tag
Every tag has a form of a string "TAG##" where "##" is a number 1-99
Existing tags are not guaranteed to be contiguous, e.g. item 13 may have tags "TAG1", "TAG3", "TAG10".
TASK: I need to insert a bunch of new rows into the table from another table T_NEW, which only have items and values, and assign new tag to them so they don't violate unique index on item, tag.
Uniqueness of values is irrelevant (assume that item+value is always unique already).
---- Table T_NEW --------------------------
| item | tag | value |
| [int] | STARTS AS NULL | [varchar(255)] |
QUESTION: How can I assign new tags to all rows in table T_NEW, such that:
All item+tag combinations in a union of T and T_NEW are unique
Newly assigned tags should all be in the form "TAG##"
Newly assigned tags should ideally be the smallest available for a given item.
If it helps, you can assume that I already have a temp table #tags, with a "tag" column that contains 99 rows containing all the valid tags (TAG1..TAG99, one per row)
I started a fiddle that will get you the list of available "open" tags by item. It does this using the #tags (AllTags) and doing an outer-join-where-null. You could use that to insert new tags from T_New...
with T_openTags as (
select
items.item,
openTagName = a.tag
from
(select distinct item from T) items
cross join AllTags a
left outer join T on
items.item = T.item
and T.tag = a.tag
where
T.item is null
)
select * from T_openTags
or see this updated fiddle to do an update on T_New table. Essentially adds a row_number so we can pick the correct open tag to use in a single update statement. I padded the Tag names with a leading zero to simplify the sorting.
with T_openTags as (
select
items.item,
openTagName = a.tag,
rn = row_number() over(partition by items.item order by a.tag)
from
(select distinct item from T) items
cross join AllTags a
left outer join T on
items.item = T.item
and T.tag = a.tag
where
T.item is null
), T_New_numbered as (
select *,
rn = row_number() over(partition by item order by value)
from T_New
)
update tnn set tag = openTagName
from T_New_numbered tnn
inner join T_openTags tot on
tot.item = tnn.item
and tot.rn = tnn.rn
select * from T_New
updated fiddle with poor mans row_number replacement that only works with distinct T_New values
Try this:
DECLARE #T TABLE (ITEM INT, TAG VARCHAR(10), VALUE VARCHAR(255))
INSERT INTO #T VALUES
(1,'TAG1', '100'),
(2,'TAG2', '200')
DECLARE #T_NEW TABLE (ITEM INT, TAG VARCHAR(10), VALUE VARCHAR(255))
INSERT INTO #T_NEW VALUES
(3,NULL, '500'),
(4,NULL, '600')
INSERT INTO #T
SELECT
ITEM,
('TAG' + CONVERT(VARCHAR(20),ITEM)) AS TAG,
VALUE
FROM
#T_NEW
SELECT * FROM #T
OK, here's a correct solution, tested to work on Sybase (H/T: big thanks to #ypercube for providing a solid basis for it)
declare #c int
select #c = 1
WHILE (#c > 0)
BEGIN
UPDATE
t_new
SET
tag =
( SELECT min(tags.tag)
FROM #tags tags
LEFT JOIN t o
ON tags.tag = o.tag
AND o.item = t_new.item
LEFT JOIN t_new n3
ON tags.tag = n3.tag
AND n3.item = t_new.item
WHERE o.tag IS NULL
AND n3.tag IS NULL
)
WHERE tag IS NULL
-- and here's the main magic for only updating one item at a time
AND NOT EXISTS (SELECT 1 FROM t_new n2 WHERE t_new.value > n2.value
and n2.tag IS NULL and n2.item=t_new.item)
SELECT #c = ##rowcount
END
Inserting directly to t:
INSERT INTO t
(item, tag, value)
SELECT
item,
( SELECT MIN(tags.tag)
FROM #tags AS tags
LEFT JOIN t AS o
ON tags.tag = o.tag
AND o.item_id = n.item_id
WHERE o.tag IS NULL
) AS tag,
value
FROM
t_new AS n ;
Updating t_new:
UPDATE
t_new AS n
SET
tag =
( SELECT MIN(tags.tag)
FROM #tags AS tags
LEFT JOIN t AS o
ON tags.tag = o.tag
AND o.item_id = n.item_id
WHERE o.tag IS NULL
) ;
Correction
UPDATE
n
SET
n.tag = w.tag
FROM
( SELECT item_id,
tag,
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY value) AS rn
FROM t_new
) AS n
JOIN
( SELECT di.item_id,
tags.tag,
ROW_NUMBER() OVER (PARTITION BY di.item_id ORDER BY tags.tag) AS rn
FROM
( SELECT DISTINCT item_id
FROM t_new
) AS di
CROSS JOIN
#tags AS tags
LEFT JOIN
t AS o
ON tags.tag = o.tag
AND o.item_id = di.item_id
WHERE o.tag IS NULL
) AS w
ON w.item_id = n.item_id
AND w.rn = n.rn ;