The webguys wants unique urls based on the name of the products
If more products have the same name, add a number after the name.
our.dom/red-sock
our.dom/red-sock-1
They do not want the product id or another number on all products, i.e.
our.dom/red-sock-123481354
I store this in a field i call seourl.
I have it covered when I create new products, a trigger tries adding the seourl, if it is already there, increment the number, until an unique value is found.
But I now have to give the entire table new seourls.
If I just
update tab set seourl=dbo.createurl(title)
I am sure to have collissions, and the operation is rolled back.
Is there a way to have the statement to commit the updates that work, and leave the rest unchanged?
Or must I just do a RBAR, Row By Agonizing Row operation in a loop?
Adapt this to your needs:
select
*
from (values('aaa'), ('aaa-12'), ('aaa-'), ('bbb-3')) as src (x)
cross apply (
select isnull(nullif(patindex('%-[0-9]%', x) - 1, -1), LEN(x))
) as p(idx)
cross apply (
select
SUBSTRING(x, 1, idx)
, SUBSTRING(x, idx + 1, LEN(x) - idx)
) as t(t, xx)
Try this:
declare #tmp table (
id int not null identity
, name varchar(100) -- you need name to be indexed
, urlSuffix int -- store the number (ot you'll have to use PATINDEX, etc. as previously shown)!
, url as name + ISNULL('_' + cast(NULLIF(urlSuffix, 0) as varchar(100)), '')
, unique (name, id) -- (trick) index on name
)
insert #tmp (name, urlSuffix)
select
src.name
, ISNULL(T.urlSuffix, -1) + ROW_NUMBER() OVER (PARTITION BY src.name ORDER BY (select 1))
from (values
('x')
, ('y')
, ('y')
, ('y')
, ('z')
, ('z')
) as src (name)
left join (
select
name
, MAX(T.urlSuffix) as urlSuffix
from #tmp AS T
GROUP BY name
) as T on (
T.name = src.name
)
insert #tmp (name, urlSuffix)
select
src.name
, ISNULL(T.urlSuffix, -1) + ROW_NUMBER() OVER (PARTITION BY src.name ORDER BY (select 1))
from (values
('a')
, ('b')
, ('b')
, ('b')
, ('z')
, ('z')
) as src (name)
left join (
select
name
, MAX(T.urlSuffix) as urlSuffix
from #tmp AS T
GROUP BY name
) as T on (
T.name = src.name
)
select
name, url
from #tmp
order by url
The solution to yur problem should lies in the use of ROW_NUMBER()
Related
I want to extract and concat a field in a json column .
I have a table with two field :
id
JsonColumn
In the json Column i can have x object which contains 2 fields (Name and Type)
For Example, in my table I can have :
What i want to do is to extract and concat the field Name in a json colum.
So i will have :
Don't hesitate to share your opinion.
(I was thinking about a big loop with openJson but i fear that the query will be very long after that).
You need to parse the JSON content using OPENSJON() to extract the "Name" key and then aggregate the values. For SQL Server 2016 you need to use FOR XML PATH for aggregation.
Table:
SELECT Id, JsonColumn
INTO Data
FROM (VALUES
(1, '[{"Name": "Matthew", "Type":"Public"}, {"Name": "Rachel", "Type":"Private"}]'),
(2, '[{"Name": "Sample", "Type":"Private"}]')
) v (Id, JsonColumn)
Statement:
SELECT
Id,
Name = STUFF(
(
SELECT ',' + Name
FROM OPENJSON(JsonColumn) WITH (Name varchar(100) '$.Name')
FOR XML PATH('')
), 1, 1, ''
)
FROM Data
Result:
Id JsonColumn
------------------
1 Matthew,Rachel
2 Sample
Rough approach (currently not enough time for me to provide working SQL to code).
To do it in one step (best as view) use the CTE approach to stagger the steps. This generates more code and over time allows easier amendments. It is a trade off.
Recursive approach
First step:
Extract relational records with ID and names. Use OPENJSON WITH a defined table structure where only the Name is extracted (rest can be ignored or left as additional JSON).
Second step:
Use the output from first step and turn into recursive concatenation. Using a variable to concatenate to forces the use of a procedure. Doing it in a view requires definition of anchor and end conditions. Not quite sure on this as it is tricky.
In a CTE part this requires an anchor element union'ed with all other elements. In effect this groups by the selected key field(s).
Third step:
Output of the finished recursion by key field(s).
Quick demo code
DECLARE
#Demo TABLE
(
id_col tinyint identity(1,1),
dsc_json nvarchar(max)
)
;
INSERT INTO
#Demo
(
dsc_json
)
SELECT N'[{"Name":"Celery","Type":"Vegetable"}, {"Name":"Tomato","Type":"Fruit"}]'
UNION
SELECT N'[{"Name":"Potato","Type":"Vegetable"}]'
UNION
SELECT N'[{"Name":"Cherry","Type":"Fruit"}, {"Name":"Apple","Type":"Fruit"}]'
;
SELECT
*
FROM
#Demo
;
-- extract JSON
SELECT
demo.id_col,
jsond.dsc_name,
Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS val_row_asc,
Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS val_row_desc
FROM
#Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
;
WITH
cte_json
(
id_col,
dsc_name,
val_row_asc,
val_row_desc
)
AS
(
SELECT
demo.id_col,
jsond.dsc_name,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS int ) AS val_row_asc,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS int ) AS val_row_desc
FROM
#Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
),
cte_concat
(
id_col,
dsc_names,
val_row_asc,
val_row_desc
)
AS
( -- anchor first
-- - emtpy string per ID
SELECT
anchor.id_col,
Cast( N'' AS nvarchar(500) ) AS names,
Cast( 0 AS int) AS val_row_asc,
Cast( -1 AS int ) AS val_row_desc
FROM
cte_json AS anchor
WHERE -- anchor criteria
val_row_asc = 1
UNION ALL
SELECT
anchor.id_col,
Cast( anchor.dsc_names + N', ' + element.dsc_name AS nvarchar(500) ) AS names,
element.val_row_asc,
element.val_row_desc
FROM
cte_json AS element
INNER JOIN cte_concat AS anchor
ON anchor.id_col = element.id_col
AND anchor.val_row_asc = element.val_row_asc -1
)
SELECT
cte.id_col,
Right( cte.dsc_names, Len( cte.dsc_names ) -2 ) AS dsc_names,
cte.val_row_desc
FROM
cte_concat AS cte
WHERE -- only latest result
cte.val_row_desc = 1
ORDER BY
cte.id_col ASC
;
The additional row numbers allow:
define start and end point for the recursive connection = val_row_asc
define "definition" of latest result = val_row_desc
Stuff ... for XML Path
This approach works on all versions and is much easier to read than the recursive part thanks to Zhorov's answer. Works on the base laid by the first part of the code above (or just straight afterwards).
WITH
cte_json
(
id_col,
dsc_name,
val_row_asc,
val_row_desc
)
AS
(
SELECT
demo.id_col,
jsond.dsc_name,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS int ) AS val_row_asc,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS int ) AS val_row_desc
FROM
#Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
)
SELECT
cte_outer.id_col,
Stuff(
( SELECT
',' + cte_inner.dsc_name
FROM
cte_json AS cte_inner
WHERE
cte_inner.id_col = cte_outer.id_col
FOR XML PATH('')
), 1, 1, ''
) AS dsc_names
FROM
cte_json AS cte_outer
GROUP BY
cte_outer.id_col
;
String_agg
This approach only works with SQL Server 2017 onwards. It is a continuation to the code above.
WITH
cte_json
(
id_col,
dsc_name,
val_row_asc,
val_row_desc
)
AS
(
SELECT
demo.id_col,
jsond.dsc_name,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name ASC ) AS int ) AS val_row_asc,
Cast( Row_number() OVER ( PARTITION BY demo.id_col ORDER BY jsond.dsc_name DESC ) AS int ) AS val_row_desc
FROM
#Demo AS demo
CROSS APPLY OPENJSON( demo.dsc_json )
WITH
(
dsc_name nvarchar(100) '$.Name'
) AS jsond
)
SELECT
cte.id_col,
String_agg( cte.dsc_name, ',' ) AS dsc_names
FROM
cte_json AS cte
GROUP BY
cte.id_col
;
is this possible to make a "newtable" from "oldtable" like a picture down below?
Use PIVOT method :
Declare #table table (id varchar(10),[time] time)
insert into #table
SELECT '01','10:08:23'
UNION ALL
SELECT '02','10:10:50'
UNION ALL
SELECT '01','13:30:00'
SELECT *
FROM
(
SELECT id , time , CASE WHEN MIN(RNo) = 1 THEN 'CheckIn' WHEN MAX(RNo) >
1 THEN 'CheckOut' END Type
FROM
(
SELECT * , ROW_NUMBER() OVER (PARTITION BY id ORDER BY time) RNo
FROM #table
) A
GROUP BY id , time
) A
PIVOT
(
MAX(time) FOR Type IN ([CheckIn],[CheckOut])
)Pvt
This can be use for matching column (s)
INSERT INTO `NEWTABLE`(`id`, `check in`)
SELECT o.id, o.time FROM OLDTABLE o
I want to union two tables with the structure:
Table1: Id, Name, Reference1, Position
Table2: Id, Name, Reference2, ArtNr, Price, Position
Now to the difficult part:
All fields with different "Reference" fields must be in that new table.
The "Reference" field must be unique in the new table, so there won't be two items with the same "reference".
BUT: If there's a record with the same reference in Table 1 and Table 2, the new Record must have the "Position" of Table 1. ALL Entries that are NOT in Table 1 but in Table 2, must have a new Position with the value of [MAX(Position) increment by 1] of Table 1.
I have NO IDEA, how to solve that :)
Thanks for your help!
EDIT:
To union both:
Select Id, Name, Reference, null as ArtNr, null as Price, Position FROM Table1
Union
Select Id, Name, Reference, ArtNr, Price, Positionfrom Rechnung_Item FROM Table2
But that shows all entries of both tables...
Sample Data:
Table1:
Id:1, Name:Test, Reference: 123, Position:5
Id:2, Name:Test2, Reference: 125, Position:7
Table2:
Id:1, Name:Test, Reference1: 123, Position:1
Id:2, Name:Test3, Reference1: 127, Position:2
Desired output:
Id:1, Name:Test, Reference2: 123, Position:5
Id:2, Name:Test2, Reference2: 125, Position:7
Id:3, Name:Test3, Reference2: 127, Position:9
I tried creating sample data, it gives you result you required
declare #temp1 as table (Id int , Name varchar(50), Reference int, Position int)
insert into #temp1 (Id ,Name , Reference , Position ) values (1,'A',123,5)
insert into #temp1 (Id ,Name , Reference , Position ) values (2,'B',125,7)
--insert into #temp1 (Id ,Name , Reference , Position ) values (1,'C','Ref3',1)
declare #temp2 as table (Id int , Name varchar(50), Reference int, Position int, ArtNr int )
insert into #temp2 (Id ,Name , Reference , Position,ArtNr ) values (1,'A',123,1,1)
insert into #temp2 (Id ,Name , Reference , Position,ArtNr ) values (1,'C',127,2,2)
--insert into #temp2 (Id ,Name , Reference , Position,ArtNr ) values (1,'B',128,1,3)
--insert into #temp2 (Id ,Name , Reference , Position ,ArtNr) values (1,'C','Ref5',2,4)
select isnull(r1 ,r2) as Reference ,
ISNULL(n1,n2) as name , newPosition as Position
from (
select t1.Reference as r1 ,t2.Reference as r2 , t1.Name as n1 , t2.Name as n2 ,
case when t1.Position is not null then t1.Position else (select max(Position)+ t2.Position from #temp1)
end as newPosition
from #temp1 t1 full outer join #temp2 t2 on t1.Reference = t2.Reference
) as tr
I worked out that solution:
SELECT * FROM (
SELECT Id, Name, Reference1 as Reference, Position FROM Table1
UNION ALL
SELECT Id, Name, Reference2 as Reference, Position + (SELECT MAX(Position) FROM Table1) FROM Table2) as result GROUP BY Reference
I need that the minimal id will be 0(zero).
I tried something like this:
INSERT INTO users(id, name)
VALUES ( (SELECT MAX(id) FROM users) + 1 , 'andrey6' )
But I have a problem when the "users" table is empty, I just cant figure out how to keep the minimal value to be 0.
You could just use COALESCE like so:
INSERT INTO users(id, name)
VALUES ( (SELECT COALESCE(MAX(id), 0) FROM users) + 1 , 'andrey6' )
Does this do what you want:
INSERT INTO users(id, name)
VALUES ( (SELECT IFNULL(MAX(id) + 1,0) FROM users) , 'andrey6' )
?
Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1