How to concatenate results of multiple rows - sql

I currently have a view in SQL Server, something like this:
Table1:
Id
Desc
Mex
Table2:
Id
IdTab1
Desc
The view select everything from Table1 left joined on Table2 on Id - IdTab1
Now I have a table 3 joined with Table2 that has like these fields:
Table3:
Id
IdTab2
Code (VarChar(3))
I would like to have in the select of the view a new field Code that contains every code in table 3 concatenated with the char ' ' without changing the record displayed from the old query (so like doing a group by concat) every Code that matches the join.
I saw some other posts but neither of them used this kind of approach.
For example using this:
declare #result varchar(500)
set #result = ''
select #result = #result + ModuleValue + ', '
from TableX where ModuleId = #ModuleId
But I have faced two problems.
I could not use declare in the view (probably because of wrong syntax), and also I have to do this group by and I can't figure out how.
Example result basic view
ID | IDTAB2 | DESC1 | DESC2 | MEX
1 | 2 | aa | bb | 4
2 | 1 | ab | cc | 2
2 | 2 | bb | bc | 2
Example result joined Table3
ID | IDTAB2 | DESC1 | DESC2 | MEX | CODE
1 | 2 | aa | bb | 4 | CS
1 | 2 | aa | bb | 4 | NN
2 | 1 | ab | cc | 2 | AF
2 | 2 | bb | bc | 2 | DC
2 | 2 | bb | bc | 2 | KK
2 | 2 | bb | bc | 2 | JD
Example result needed
ID | IDTAB2 | DESC1 | DESC2 | MEX | CODENEW
1 | 2 | aa | bb | 4 | CS NN
2 | 1 | ab | cc | 2 | AF
2 | 2 | bb | bc | 2 | DC KK JD

Considering your output from "Example result joined Table3", you can try this below option based on your SQL server version-
For MSSQL-2016 and earlier- Demo
SELECT DISTINCT A.ID,A.IDTAB2,A.DESC1,A.DESC2,A.MEX,
SUBSTRING(
(
SELECT ' '+ B.CODE AS [text()]
FROM your_table B
WHERE B.ID = A.ID
AND B.IDTAB2 = A.IDTAB2
AND B.DESC1 = A.DESC1
AND B.DESC2 = A.DESC2
AND B.MEX = A.MEX
ORDER BY B.ID,B.IDTAB2,B.DESC1,B.DESC2,B.MEX
FOR XML PATH ('')
), 2, 1000) [C_Name]
FROM your_table A
For MSSQL-2017 or newer- Demo
SELECT ID,IDTAB2,DESC1,DESC2,MEX,
STRING_AGG ( CODE, ' ' )
FROM your_table
GROUP BY ID,IDTAB2,DESC1,DESC2,MEX

Related

selecting specific fields during runtime from table

How do we select fields dynamically?
I've got a table Table1:
+----------+-------+----+
| Table1Id | x | y |
+==========+=======+====+
| 52 | alex | aa |
+----------+-------+----+
| 43 | liza | aa |
+----------+-------+----+
| 21 | harry | bb |
+----------+-------+----+
| 21 | harry | bb |
+----------+-------+----+
I'd like to join on this Table2:
+----------+----------+--------+------+
| Table2Id | Table1Id | aa | bb |
+==========+==========+========+======+
| 1 | 52 | red | tall |
+----------+----------+--------+------+
| 2 | 43 | blue | thin |
+----------+----------+--------+------+
| 3 | 21 | orange | fat |
+----------+----------+--------+------+
The result I'm looking for is:
+-------+-------+----+----------+
| xyzid | x | y | NewField |
+=======+=======+====+==========+
| 52 | alex | aa | red |
+-------+-------+----+----------+
| 43 | liza | aa | blue |
+-------+-------+----+----------+
| 21 | harry | bb | fat |
+-------+-------+----+----------+
As you can see, Table1 has data in the y column the exact field name to grab from Table2.
How do select specific fields from a table, where those fields are actually stored as data in another table?
Just another option which would be a bit more dynamic
No need for the CASE.
table2 could have any number of columns.
No need to convert columns to string or a common datatype.
BUT ... I suspect a bit less performant than The Impalar's answer.
I should note that this is for 2017+. Oddly enough, 2016 requires a "string literal" for JSON_VALUE or JSON_QUERY.
Example or dbFiddle
Select Distinct
xyzid = A.Table1Id
,A.x
,A.y
,NewField = JSON_VALUE(B.JS,'$.'+A.Y)
From Table1 A
Join ( Select [Table1Id]
,JS=(Select B1.* For JSON Path,Without_Array_Wrapper)
From Table2 B1 ) B
on A.[Table1Id]=B.[Table1Id]
Results
UPDATE - 2016 Version
Select xyzid = A.Table1Id
,A.x
,A.y
,NewField = (select max([Value]) from OpenJSON(B.JS) where [key]=A.Y collate database_default)
From Table1 A
Join ( Select [Table1Id]
,JS=(Select B1.* For JSON Path,Without_Array_Wrapper )
From Table2 B1 ) B
on A.[Table1Id]=B.[Table1Id]
2nd 2016 Approach -- Perhaps slightly more performant dbFiddle
Select xyzid = A.Table1Id
,A.x
,A.y
,NewField = B.Value
From Table1 A
Join (
Select [Table1Id]
,[Key]
,Value
From Table2 B1
Cross Apply OpenJson((Select B1.* For JSON Path,Without_Array_Wrapper ) )
) B
on A.[Table1Id]=B.[Table1Id]
and A.Y = B.[Key] collate database_default
You can select columns dynamically using CASE, as in:
select
a.Table1Id as xyzid,
a.x,
a.y,
case
when a.y = 'aa' then b.aa
when a.y = 'bb' then b.bb
end as NewField
from (select distinct * from table1) a
join table2 b on b.Table1Id = a.Table1Id
Result:
xyzid x y newfield
------ ------ --- --------
52 alex aa red
43 liza aa blue
21 harry bb fat
See running example at DB Fiddle.

Divide Sequential Records

I have a table in MS Access like:
table
+-----+-----+-----+
| 1st | 2nd | 3rd |
+-----+-----+-----+
| A | 1 | 100 |
| A | 2 | 200 |
| A | 3 | 300 |
| B | 1 | 100 |
| B | 2 | 200 |
| B | 3 | 300 |
| C | 1 | 100 |
| C | 2 | 200 |
| C | 3 | 300 |
+-----+-----+-----+
Now I want to read the values from the 3rd column, do some sort of manipulation to it and store them in to another table like:
summary
+-----+---------+---------+
| 1st | 2nd | 3rd |
+-----+---------+---------+
| A | 100/200 | 200/300 |
| B | 100/200 | 200/300 |
| C | 100/200 | 200/300 |
+-----+---------+---------+
In another words, for summary.2nd this means:
select table.3rd FROM table where table.1st = A AND table.2nd = 1
divided by
select table.3rd FROM table where table.1st = A AND table.2nd = 3
Can someone give me a hint how this could be done?
Maybe VBA / ADO Recordset etc?
One method is conditional aggregation:
select [1st],
max(iif([2nd] = 1, [3rd], null)) / max(iif([2nd] = 2, [3rd], null)) as [2nd],
max(iif([2nd] = 2, [3rd], null)) / max(iif([2nd] = 3, [3rd], null)) as [3rd]
from t
group by [1st];
Try this SQL
INSERT INTO Summary
SELECT DISTINCT a.[1st],
a.[3rd] / b.[3rd] AS [2nd],
a.[3rd] / c.[3rd] AS [3rd]
FROM ((tbl AS a
INNER JOIN tbl AS b
ON a.[1st] = b.[1st])
INNER JOIN tbl AS c
ON a.[1st] = c.[1st] )
WHERE a.[2nd] = 1
AND b.[2nd] = 2
AND c.[2nd] = 3
Here's another alternative, using calculated join criteria:
select
t1.[1st],
t1.[3rd]/t2.[3rd] as [2nd],
t2.[3rd]/t3.[3rd] as [3rd]
from
(
[table] t1 inner join [table] t2
on t1.[1st] = t2.[1st] and t1.[2nd] = t2.[2nd]-1
)
inner join [table] t3
on t1.[1st] = t3.[1st] and t1.[2nd] = t3.[2nd]-2
Since the 2nd column values 1, 2 & 3 are not hard-coded, this is applicable to any three integers in the 2nd column whose values differ sequentially by one.
Change [table] to the name of your table.

SUM values in SQL starting from a specific point in another table

I have a table that lists the index/order, the name, and the value. For example, it looks like this:
TABLE1:
ID | NAME | VALUE
1 | A | 2
2 | B | 5
3 | C | 2
4 | D | 7
5 | E | 0
Now, I have another table that has a random list of NAMEs. It'll just show either A, B, C, D, or E. Depending on what the NAME is, I wanted to calculate the SUM of all the values that it will take to get to E. Does that make sense?
So if for example, my table looks like this:
TABLE2:
NAME
D
B
A
I'd want another column next to NAME that'll show the sum. So D would have 7 because the next event is E. B would have to be the sum of 5, 2, and 7 because B is 5, and C is 2, and D is 7. And A would have the sum of 2, 5, 3, and 7 and so on.
Hopefully this is easy to understand.
I actually don't have much at all aside from joining the two tables and getting the current value of the NAME. But I wasn't sure how to increment and so on and keep adding?
SELECT T2.NAME, T1.VALUE
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.NAME = T2.NAME
Is doing this even possible? Or am I wasting my time? Should I be referring to actual code to do this? Or should I make a function?
I wasn't sure where to start and I was hoping someone could help me out.
Thank you in advance!
The query is in two parts; this is hard to see at first, so I'll walk through each step.
Step 1: Obtain the rolling sum
Join table1 to itself for any letters greater than itself:
select *
from table1 t1
inner join table1 t2 on t2.name >= t1.name
order by t1.name
This produces the following table
+ -- + ---- + ----- + -- + ---- + ----- +
| id | name | value | id | name | value |
+ -- + ---- + ----- + -- + ---- + ----- +
| 1 | A | 2 | 1 | A | 2 |
| 1 | A | 2 | 2 | B | 5 |
| 1 | A | 2 | 3 | C | 2 |
| 1 | A | 2 | 4 | D | 7 |
| 1 | A | 2 | 5 | E | 0 |
| 2 | B | 5 | 2 | B | 5 |
| 2 | B | 5 | 3 | C | 2 |
| 2 | B | 5 | 4 | D | 7 |
| 2 | B | 5 | 5 | E | 0 |
| 3 | C | 2 | 3 | C | 2 |
| 3 | C | 2 | 4 | D | 7 |
| 3 | C | 2 | 5 | E | 0 |
| 4 | D | 7 | 4 | D | 7 |
| 4 | D | 7 | 5 | E | 0 |
| 5 | E | 0 | 5 | E | 0 |
+ -- + ---- + ----- + -- + ---- + ----- +
Notice that if we group by the name from t1, we can get the rolling sum by summing the values from t2. This query
select t1.name,
SUM(t2.value) as SumToE
from table1 t1
inner join table1 t2
on t2.name >= t1.name
group by t1.name
gives us the rolling sums we want
+ ---- + ------ +
| name | sumToE |
+ ---- + ------ +
| A | 16 |
| B | 14 |
| C | 9 |
| D | 7 |
| E | 0 |
+ ---- + ------ +
Note: This is equivalent to using a windowed function that sums over a set, but it is much easier to visually see what you're doing via this joining technique.
Step 2: Join the rolling sum
Now that you have this rolling sum for each letter, you simply join it to table2 for the letters you want
select t1.*
from table2 t2
inner join (
select t1.name,
SUM(t2.value) as SumToE
from table1 t1
inner join table1 t2
on t2.name >= t1.name
group by t1.name
) t1 on t1.name = t2.name
Result:
+ ---- + ------ +
| name | sumToE |
+ ---- + ------ +
| A | 16 |
| B | 14 |
| D | 7 |
+ ---- + ------ +
As gregory suggests, you can do this with a simple windowed function, which (in this case) will sum up all the rows after and including the current one based on the ID value. Obviously there are a number of different ways in which you can slice your data, though I'll leave that up to you to explore :)
declare #t table(ID int,Name nvarchar(50),Val int);
insert into #t values(1,'A',2),(2,'B',5),(3,'C',2),(4,'D',7),(5,'E',0);
select ID -- The desc makes the preceding work the right way. This is
,Name -- essentially shorthand for "sum(Val) over (order by ID rows between current row and unbounded following)"
,Val -- which is functionally the same, but a lot more typing...
,sum(Val) over (order by ID desc rows unbounded preceding) as s
from #t
order by ID;
Which will output:
+----+------+-----+----+
| ID | Name | Val | s |
+----+------+-----+----+
| 1 | A | 2 | 16 |
| 2 | B | 5 | 14 |
| 3 | C | 2 | 9 |
| 4 | D | 7 | 7 |
| 5 | E | 0 | 0 |
+----+------+-----+----+
CREATE TABLE #tempTable2(name VARCHAR(1))
INSERT INTO #tempTable2(name)
VALUES('D')
INSERT INTO #tempTable2(name)
VALUES('B')
INSERT INTO #tempTable2(name)
VALUES('A')
CREATE TABLE #tempTable(id INT, name VARCHAR(1), value INT)
INSERT INTO #temptable(id,name,value)
VALUES(1,'A',2)
INSERT INTO #temptable(id,name,value)
VALUES(2,'B',5)
INSERT INTO #temptable(id,name,value)
VALUES(3,'C',2)
INSERT INTO #temptable(id,name,value)
VALUES(4,'D',7)
INSERT INTO #temptable(id,name,value)
VALUES(5,'E',0)
;WITH x AS
(
SELECT id, value, name, RunningTotal = value
FROM dbo.#temptable
WHERE id = (SELECT MAX(id) FROM #temptable)
UNION ALL
SELECT y.id, y.value, y.name, x.RunningTotal + y.value
FROM x
INNER JOIN dbo.#temptable AS y ON
y.id = x.id - 1
)
SELECT x.id, x.value, x.name, x.RunningTotal
FROM x
JOIN #tempTable2 t2 ON
x.name = t2.name
ORDER BY x.id
DROP TABLE #tempTable
DROP TABLE #tempTable2

How to write a Sql query to find distinct values that have never met the following "Where Not(a=x and b=x)"

I have the following table called Attributes
* AttId * CustomerId * Class * Code *
| 1 | 1 | 1 | AA |
| 2 | 1 | 1 | AB |
| 3 | 1 | 1 | AC |
| 4 | 1 | 2 | AA |
| 5 | 1 | 2 | AB |
| 6 | 1 | 3 | AB |
| 7 | 2 | 1 | AA |
| 8 | 2 | 1 | AC |
| 9 | 2 | 2 | AA |
| 10 | 3 | 1 | AB |
| 11 | 3 | 3 | AB |
| 12 | 4 | 1 | AA |
| 13 | 4 | 2 | AA |
| 14 | 4 | 2 | AB |
| 15 | 4 | 3 | AB |
Where each Class, Code pairing represents a specific Attribute.
I'm trying to write a query that returns all customers that are NOT linked to the Attribute pairing Class = 1, Code = AB.
This would return Customer Id values 2 and 4.
I started to write Select Distinct A.CustomerId From Attributes A Where (A.Class = 1 and A.Code = 'AB') but stopped when I realised I was writing a SQL query and there is not an operator available to place before the parentheses to indicate the clause within must Not be met.
What am I missing? Or which operator should I be looking at?
Edit:
I'm trying to write a query that only returns those Customers (ie distinct Customer Id's) that have NO link to the Attribute pairing Class = 1, Code = AB.
This could only be Customer Id values 2 and 4 as the table does Not contain the rows:
* AttId * CustomerId * Class * Code *
| x | 2 | 1 | AB |
| x | 4 | 1 | AB |
Changed Title from:
How to write "Where Not(a=x and b=x)"in Sql Query
To:
How to write a Sql query to find distinct values that have never met the following "Where Not(a=x and b=x)"
As the previous title was a question in it's own right however the detail of the question added an extra dimension which led to confusion.
One way would be
SELECT DISTINCT CustomerId FROM Attributes a
WHERE NOT EXISTS (
SELECT * FROM Attributes forbidden
WHERE forbidden.CustomerId = a.CustomerId AND forbidden.Class = _forbiddenClassValue_ AND forbidden.Code = _forbiddenCodeValue_
)
or with join
SELECT DISTINCT a.CustomerId FROM Attributes a
LEFT JOIN (
SELECT CustomerId FROM Attributes
WHERE Class = _forbiddenClassValue_ AND Code = _forbiddenCodeValue_
) havingForbiddenPair ON a.CustomerId = havingForbiddenPair.CustomerId
WHERE havingForbiddenPair.CustomerId IS NULL
Yet another way is to use EXCEPT, as per ypercube's answer
SELECT CustomerId
FROM Attributes
EXCEPT
SELECT CustomerId
FROM Attributes
WHERE Class = 1
AND Code = AB ;
Since no one has posted the simple logical statement, here it is:
select . . .
where A.Class <> 1 OR A.Code <> 'AB'
The negative of (X and Y) is (not X or not Y).
I see, this is a grouping thing. For this, you use aggregation and having:
select customerId
from Attributes a
group by CustomerId
having sum(case when A.Class = 1 and A.Code = 'AB' then 1 else 0 end) = 0
I always prefer to solve "is it in a set" type questions using this technique.
Select Distinct A.CustomerId From Attributes A Where not (A.Class = 1 and A.Code = 'AB')
Try this:
SELECT DISTINCT A.CustomerId From Attributes A Where
0 = CASE
WHEN A.Class = 1 and A.Code = 'AB' THEN 1
ELSE 0
END
Edit: of course this still gives you cust 1 (doh!), you should probably use pjotrs NOT EXISTS query ideally, serves me right for not looking at the data closely enough :)

sql server transpose rows to column value

I have a table that looks like this:
Project | State
----------------
1 | A
2 | A
2 | F
3 | A
3 | F
3 | P
4 | S
5 | C
What i would like to to is get a table like this :
Project | State
----------------
1 | A
2 | AF
3 | AFP
4 | S
5 | C
Is it possible to do this ?
SELECT Project,
(SELECT State + ''
FROM table t
WHERE t.project = m.project
FOR XML PATH(''))
FROM table m
GROUP BY Project