Sorting SQL table - sql-server

can anyone help me with T-SQL to sort this table
ID Comment ParentId
-- ------- --------
3 t1 NULL
4 t2 NULL
5 t1_1 3
6 t2_1 4
7 t1_1_1 5
to look like this
ID Comment ParentId
-- ------- --------
3 t1 NULL
5 t1_1 3
7 t1_1_1 5
4 t2 NULL
6 t2_1 4
Kind regards,
Lennart

try this:
DECLARE #YourTable table (id int, Comment varchar(10), parentID int)
INSERT INTO #YourTable VALUES (3, 't1' , NULL)
INSERT INTO #YourTable VALUES (4, 't2' , NULL)
INSERT INTO #YourTable VALUES (5, 't1_1' , 3)
INSERT INTO #YourTable VALUES (6, 't2_1' , 4)
INSERT INTO #YourTable VALUES (7, 't1_1_1', 5)
;with c as
(
SELECT id, comment, parentid, CONVERT(varchar(8000),RIGHT('0000000000'+CONVERT(varchar(10),id),10)) as SortBy
from #YourTable
where parentID IS NULL
UNION ALL
SELECT y.id, y.comment, y.parentid, LEFT(c.SortBy+CONVERT(varchar(8000),RIGHT('0000000000'+CONVERT(varchar(10),y.id),10)),8000) AS SortBy
FROM c
INNER JOIN #YourTable y ON c.ID=y.PArentID
)
select * from C ORDER BY SortBy
EDIT
here is output
id comment parentid SortBy
----------- ---------- ----------- ---------------------------------
3 t1 NULL 0000000003
5 t1_1 3 00000000030000000005
7 t1_1_1 5 000000000300000000050000000007
4 t2 NULL 0000000004
6 t2_1 4 00000000040000000006
(5 row(s) affected)

humm order by?
http://t-sql.pro/t-sql/ORDER-BY.aspx

SELECT ID, Comment, ParentId
FROM TestTable
ORDER BY Comment, ParentId asc

This sounds very much like a homework question, but here's some hints on where to go with this:
You'll want to do a quick google or StackOverflow search for the ORDER BY clause to be able to get a set of results ordered by the column you want to use (i.e. the 'Comment' column).
Once you've got that, you can start writing a SQL statement to order your results.
If you need to then place re-order the actual table (and not just get the results in a specific order), you'll need to look up using temporary tables (try searching for 'DECLARE TABLE'). Much like any temp swap, you can place the results you have in a temporary place, delete the old data, and then replace the table contents with the temporary data you have, but this time in the order you want.

But just ordering by Comment will give you that? Or have I missed the point?!
declare #table table
(
Comment varchar(10)
)
insert into #table (Comment) values ('t1')
insert into #table (Comment) values ('t2')
insert into #table (Comment) values ('t1_1')
insert into #table (Comment) values ('t2_1')
insert into #table (Comment) values ('t1_1_1')
select * from #table order by comment

Related

MS SQL join/insert with non unique matching rows via script?

I need to to prove the existence of the amount of values from table1 in an MS SQL DB.
The table1 for proving has the following values:
MANDT DOKNR LFDNR
1 0020999956
1 0020999958
1 0020999960 2
1 0020999960 3
1 0020999960
1 0020999962
As you can see there are single rows and then there are special cases, where values are doubled with a running number (means it exists three times in the source), so all 2nd/3rd/further entries do get a increasing number in LFDNR.
The target table2 (where I need to proove for the amount/existance) has two columns with matching data:
DataID Facet
42101976 0020999956
42100240 0020999958
65688960 0020999960
65694287 0020999960
65697507 0020999960
42113401 0020999962
I would like to insert the DataID from 2nd table to the first table to have a 'proof', so to see if anything is missing from table2 and keep the table1 as proof.
I tried to uses joins and then I thought about a do while script running all rows down, but my knowledge stops creating scripts for this.
Edit:
Output should be then:
MANDT DOKNR LFDNR DataID
1 0020999956 42101976
1 0020999958 42100240
1 0020999960 2 65688960
1 0020999960 3 65694287
1 0020999960 65697507
1 0020999962 42113401
But it could be, for example, that a row in table 2 is missing, so a DataID would be empty then (and show that one is missing).
Any help appreciated!
You can use ROW_NUMBER to calculated [LFDNR] for each row in the second table, then to update the first table. If the [DataID] is null after the update, we have a mismatch.
DECLARE #table1 TABLE
(
[MANDT] INT
,[DOKNR] VARCHAR(32)
,[LFDNR] INT
,[DataID] INT
);
DECLARE #table2 TABLE
(
[DataID] INT
,[Facet] VARCHAR(32)
);
INSERT INTO #table1 ([MANDT], [DOKNR], [LFDNR])
VALUES (1, '0020999956', NULL)
,(1, '0020999958', NULL)
,(1, '0020999960', 2)
,(1, '0020999960', 3)
,(1, '0020999960', NULL)
,(1, '0020999962',NULL)
INSERT INTO #table2 ([DataID], [Facet])
VALUES (42101976, '0020999956')
,(42100240, '0020999958')
,(65688960, '0020999960')
,(65694287, '0020999960')
,(65697507, '0020999960')
,(42113401, '0020999962');
WITH DataSource ([DataID], [DOKNR], [LFDNR]) AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY [Facet] ORDER BY [DataID])
FROM #table2
)
UPDATE #table1
SET [DataID] = DS.[DataID]
FROM #table1 T
INNER JOIN DataSource DS
ON T.[DOKNR] = DS.[DOKNR]
AND ISNULL(T.[LFDNR], 1) = DS.[LFDNR];
SELECT *
FROM #table1;

T-SQL (Azure), how to see if multiple value are in a set

I have a column MyColumn with a value such as "Value1,Value2" (a concatenated string generated using for xml). I also have a table valued parameter #MyTVP that might have rows with a value such as "Value2" (or several values). The SQL should function something like:
where MyColumn in (select col from #MyTVP)
I was thinking about writing a user defined function whose call would look something like:
where MyFunction(MyColumn, #MyTVP)
and the function would split MyColumn into separate values (like C#'s arr.Split(',')) and see if those values are in #MyTVP.
First, is this a reasonable solution or should I head down a different path?
Second, is there an efficient way to see if the multiple values in MyColumn are IN #MyTVP?
You are playing with fire :) Well, here is the matchbox:
STRING_SPLIT()
CREATE TABLE #T (ID INT , Value VARCHAR(100))
INSERT INTO #T VALUES (1 , '1,2,3') , (2 , '4,5,6') , (3 , '7,8,9')
SELECT t1.ID, t2.value
FROM #T AS t1
CROSS APPLY STRING_SPLIT(Value, ',') AS t2
ID value
---------
1 1
1 2
1 3
2 4
2 5
2 6
3 7
3 8
3 9
Sample Data
CREATE TABLE #T (ID INT , Value VARCHAR(100))
INSERT INTO #T VALUES (1 , '1,2,3') , (2 , '4,5,6') , (3 , '7,8,9')
Query
SELECT DISTINCT ID , Value
FROM (
SELECT ID
, Value
, Split.a.value('.', 'VARCHAR(100)') SplitedValues
FROM
(
SELECT ID
,Value
,Cast ('<X>' + Replace(Value, ',', '</X><X>') + '</X>' AS XML) AS Data
FROM #T
) AS t
CROSS APPLY Data.nodes ('/X') AS Split(a)
)x
WHERE x.SplitedValues IN (1 ,9) -- Replace these hardcoded values with your tvp

SQL Function to return sequential id's

Consider this simple INSERT
INSERT INTO Assignment (CustomerId,UserId)
SELECT CustomerId,123 FROM Customers
That will obviously assign UserId=123 to all customers.
What I need to do is assign them to 3 userId's sequentially, so 3 users get one third of the accounts equally.
INSERT INTO Assignment (CustomerId,UserId)
SELECT CustomerId,fnGetNextId() FROM Customers
Could I create a function to return sequentially from a list of 3 ID's?, i.e. each time the function is called it returns the next one in the list?
Thanks
Could I create a function to return sequentially from a list of 3 ID's?,
If you create a SEQUENCE, then you can assign incremental numbers with the NEXT VALUE FOR (Transact-SQL) expression.
This is a strange requirement, but the modulus operator (%) should help you out without the need for functions, sequences, or altering your database structure. This assumes that the IDs are integers. If they're not, you can use ROW_NUMBER or a number of other tactics to get a distinct number value for each customer.
Obviously, you would replace the SELECT statement with an INSERT once you're satisfied with the code, but it's good practice to always select when developing before inserting.
SETUP WITH SAMPLE DATA:
DECLARE #Users TABLE (ID int, [Name] varchar(50))
DECLARE #Customers TABLE (ID int, [Name] varchar(50))
DECLARE #Assignment TABLE (CustomerID int, UserID int)
INSERT INTO #Customers
VALUES
(1, 'Joe'),
(2, 'Jane'),
(3, 'Jon'),
(4, 'Jake'),
(5, 'Jerry'),
(6, 'Jesus')
INSERT INTO #Users
VALUES
(1, 'Ted'),
(2, 'Ned'),
(3, 'Fred')
QUERY:
SELECT C.Name AS [CustomerName], U.Name AS [UserName]
FROM #Customers C
JOIN #Users U
ON
CASE WHEN C.ID % 3 = 0 THEN 1
WHEN C.ID % 3 = 1 THEN 2
WHEN C.ID % 3 = 2 THEN 3
END = U.ID
You would change the THEN 1 to whatever your first UserID is, THEN 2 with the second UserID, and THEN 3 with the third UserID. If you end up with another user and want to split the customers 4 ways, you would do replace the CASE statement with the following:
CASE WHEN C.ID % 4 = 0 THEN 1
WHEN C.ID % 4 = 1 THEN 2
WHEN C.ID % 4 = 2 THEN 3
WHEN C.ID % 4 = 3 THEN 4
END = U.ID
OUTPUT:
CustomerName UserName
-------------------------------------------------- --------------------------------------------------
Joe Ned
Jane Fred
Jon Ted
Jake Ned
Jerry Fred
Jesus Ted
(6 row(s) affected)
Lastly, you will want to select the IDs for your actual insert, but I selected the names so the results are easier to understand. Please let me know if this needs clarification.
Here's one way to produce Assignment as an automatically rebalancing view:
CREATE VIEW dbo.Assignment WITH SCHEMABINDING AS
WITH SeqUsers AS (
SELECT UserID, ROW_NUMBER() OVER (ORDER BY UserID) - 1 AS _ord
FROM dbo.Users
), SeqCustomers AS (
SELECT CustomerID, ROW_NUMBER() OVER (ORDER BY CustomerID) - 1 AS _ord
FROM dbo.Customers
)
-- INSERT Assignment(CustomerID, UserID)
SELECT SeqCustomers.CustomerID, SeqUsers.UserID
FROM SeqUsers
JOIN SeqCustomers ON SeqUsers._ord = SeqCustomers._ord % (SELECT COUNT(*) FROM SeqUsers)
;
This shifts assignments around if you insert a new user, which could be quite undesirable, and it's also not efficient if you had to JOIN on it. You can easily repurpose the query it contains for one-time inserts (the commented-out INSERT). The key technique there is joining on ROW_NUMBER()s.

How to create a new "shared" group in a single SELECT query in MS SQL

I'm trying to group a SELECT like you'd normally do - AND at the same time make a new "shared/aggregate group" adding that to the original result-set without a secondary SELECT and UNION.
The secondary SELECT and UNION is out of the question since the real use of this is with some very big tables, with a lot of joins, so it would be waay to slow. So the UNION way is definitely out of the question.
I've tried my best to illustrate this with the following simplified example:
BEGIN TRAN
CREATE TABLE #MyTable
(
id INT,
name VARCHAR(255)
)
INSERT INTO #MyTable VALUES (1,'cola');
INSERT INTO #MyTable VALUES (2,'cola');
INSERT INTO #MyTable VALUES (3,'cola');
INSERT INTO #MyTable VALUES (4,'fanta');
INSERT INTO #MyTable VALUES (5,'fanta');
INSERT INTO #MyTable VALUES (6,'fanta');
INSERT INTO #MyTable VALUES (7,'water');
INSERT INTO #MyTable VALUES (8,'water');
INSERT INTO #MyTable VALUES (9,'water');
INSERT INTO #MyTable VALUES (10,'cola');
INSERT INTO #MyTable VALUES (11,'cola');
SELECT
CASE
WHEN name = 'cola' OR name = 'fanta'
THEN 'soda'
ELSE
name
END as name,
COUNT(distinct id) as count
FROM #MyTable
GROUP BY name
ROLLBACK TRAN
Actual output:
soda 5
soda 3
water 3
Desired output:
cola 5
fanta 3
soda 8 <- this is the "shared/aggregate group"
water 3
As Panagiotis Kanavos correctly pointed out in the comment above, this can be done using ROLLUP:
BEGIN TRAN
CREATE TABLE #BeverageType
(
name VARCHAR(255)
)
INSERT INTO #BeverageType VALUES ('Soda');
INSERT INTO #BeverageType VALUES ('Other');
CREATE TABLE #UserBeverage
(
id INT,
name VARCHAR(255)
)
INSERT INTO #UserBeverage VALUES (1,'cola');
INSERT INTO #UserBeverage VALUES (2,'cola');
INSERT INTO #UserBeverage VALUES (3,'cola');
INSERT INTO #UserBeverage VALUES (1,'fanta'); -- <- NOTE: user 1 drinks both cola and fanta so the as intended the user is only counted 1 time in the ROLLUP 'Soda' group (7)
INSERT INTO #UserBeverage VALUES (5,'fanta');
INSERT INTO #UserBeverage VALUES (6,'fanta');
INSERT INTO #UserBeverage VALUES (7,'water');
INSERT INTO #UserBeverage VALUES (8,'water');
INSERT INTO #UserBeverage VALUES (9,'water');
INSERT INTO #UserBeverage VALUES (10,'cola');
INSERT INTO #UserBeverage VALUES (11,'cola');
SELECT ub.name, bt.name AS groupName, COUNT(distinct id) as uniqueUserCount
FROM #UserBeverage as ub
JOIN #BeverageType as bt
ON CASE
WHEN (ub.name = 'water')
THEN 'Other'
ELSE
'Soda'
END = bt.name
GROUP BY ROLLUP(bt.name, ub.name)
ROLLBACK TRAN
Outputs:
cola Soda 5
fanta Soda 3
water Other 3
NULL Other 3
NULL Soda 7
NULL NULL 10
You should repeat CASE statement everywhere.
SELECT
CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END as name,
COUNT((CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END)) as count
FROM #MyTable
GROUP BY CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END
+-------+-------+
| name | count |
+-------+-------+
| soda | 8 |
+-------+-------+
| water | 3 |
+-------+-------+
Can I suggest to use a subquery:
SELECT name, count(*) AS count
FROM (SELECT CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END as name
FROM #MyTable) x
GROUP BY name;
If you need the aggregate as well as the individual products, then an alternative may be to use a UNION and select the aggregates as a second query.
SELECT name, count(distinct id) as count
FROM #MyTable
GROUP BY name
UNION
SELECT 'SODA', COUNT(distinct id) as count
FROM #MyTable
WHERE name = 'cola' or name ='fanta'
You might also use Søren Høyer Kristensen's summary table to get the aggregate names if you need more groupings.

How to insert into a table when subquery return more value?

Hi I crate have a Split function that return rows like bellow :
declare #a nvarchar(50)= '1,2,3,4,5,6'
select Item from dbo.Split(#a,',')
Result :
Item
--------
1
2
3
4
5
6
Now I want create a table and insert into two field from my split function like below :
declare #a nvarchar(50)= '1,2,3,4,5,6'
declare #b nvarchar(50)= '10,20,30,40,50,60'
declare #tblCare table
(
id int ,
count int
)
insert into #tblCare (id,count)
values
(
(select Item from dbo.Split(#a,',')),
(select Item from dbo.Split(#b,','))
)
select * from #tblCare
and I get this
Error : Msg 512, Level 16, State 1, Line 10 Subquery returned more
than 1 value. This is not permitted when the subquery follows =, !=,
<, <= , >, >= or when the subquery is used as an expression. The
statement has been terminated.
id count
----------- -----------
(0 row(s) affected)
and its my expect result :
id count
---------------
1 10
2 20
3 30
4 40
5 50
6 60
You can do it like this:
declare #t1 table (ID bigint identity(1, 1), Item nvarchar(max))
declare #t2 table (ID bigint identity(1, 1), Item nvarchar(max))
insert into #t1
select item from dbo.Split(#a,',')
insert into #t2
select item from dbo.Split(#b,',')
insert into #tblCare (id,count)
select T1.Item, T2.Item
from #t1 as T1
inner join #t2 as T2 on T1.ID = T2.ID
Here first I'm creating tables with identity column to enumerate rows of your splitted data.
And then just joining two results using these rownumbers and inserting it.
Your dbo.Split function should return a serial no on which we can join the two splits. I am using DelimitedSplit8K by Jeff Moden which is one of the fastest splitter there is, but you can update your split function to include a serial number using ROW_NUMBER().
declare #a nvarchar(50)= '1,2,3,4,5,6'
declare #b nvarchar(50)= '10,20,30,40,50,60'
insert into #tblCare (id,count)
SELECT a.item,b.item
FROM [DelimitedSplit8K](#a,',') a
INNER JOIN [DelimitedSplit8K](#b,',') b
ON a.itemnumber = b.itemnumber
Output
1 10
2 20
3 30
4 40
5 50
6 60
Don't use a subquery, the insert syntax form:
insert into table ...
select ...
Where the number and type of the select columns matches the inserted columns.
I have assumed you want a count from all calls to split of the items returned:
insert into #tblCare (id, count)
select item, count(*) from
(select item from dbo.Split(#a,',')
union all
select item from dbo.Split(#b,',')) x
group by item

Resources