Horizontal Rows as Vertical Columns in SQL Server - sql

I have this requirement where I need to present horizontal rows as vertical columns in SQL Server native query.
For example, my select query returns 100 records for the select query using inner join.
select XX, XX etc FROM TAB1 con inner join TAB2 reg on con.id = reg.reference_id WHERE con.is_active=1 and
con.approval_status=3 order by XXX
[![enter image description here][1]][1]
Now I want my query returning 1 records for 10 records where dynamic columns name should be created based on the business_ids for example 670_h_current, 671_h_current etc for all those 10 records. The result should be returned as a single query with 10 dynamic columns each represents a row value for business id and hcurrent.
I tried to use the Pivot table, but it does not seem working.Is there a way to manipulate the SQLs to return the required number of records. I am doing it at the moment in java but fetching all the records, then transforming. However, pagnation is an issue and my interface is slower as it picks a lot of records. I wanted the select query fetches a reduced view of records.
Any idea? Any help much appreciated
Current Select output
ID DEP LEG MAS EXE DES P1 BUS_ID ATTE
==================================================================
COI_AP002 215 216 4071 758 clients. NULL 673 1
COI_AP002 215 216 4071 758 clients. NULL 672 0
Expected Output
ID DEP LEG MAS EXE DES P1 ATTE_673 ATTE_672
==========================================================================
COI_AP002 215 216 4071 758 clients. NULL 1 0

Here's something to get you started. Then, you need to do a little work, to learn the concept, and to get it to do exactly what you want it to do.
--Drop Table dbo.Lateral
CREATE TABLE dbo.Lateral
( Id VARCHAR(100),
Dep int,
Des VARCHAR(100),
Bus_ID int,
Atte int)
--Delete from Account
INSERT INTO dbo.Lateral
(Id, Dep, Des, Bus_ID, Atte)
VALUES
('COI_AP002','215','clients',673,1)
INSERT INTO dbo.Lateral
(Id, Dep, Des, Bus_ID, Atte)
VALUES
('COI_AP002','215','clients',672,0)
Select *
From Lateral
select
x.ID,
sum(case when t.bus_id = '673' then 1 end) atte_673,
sum(case when t.bus_id =' 672' then 0 end) atte_672
from lateral t
cross apply (values
('COI_AP002' , t.bus_ID)
) as x(ID, val)
group by x.ID

Related

SQL Select to return one line multiple times based on a number within the dataset

So I've tried many other attempts at answers around this topic from here and so far everything has either outright failed or not given me the result I'm after:
I have a select statement to use for a report that brings through delivery information. The result set is from a main table that only has one line per delivery number (the delivery header record) and within the dataset there is also a field called palletspaces which we use to indicate (you guessed it) how many pallets are needed for the delivery
What I now need to do is the following:
find that palletspaces number
return the single delivery record the same number of times as that palletspaces number
include a new column in the results that counts up to that palletspaces number
so for instance, my SQL will return every record from the deliveries table and would look something like this
id traderid toaddressid county postcode palletspaces
D-124597 2101 2 READING RG6 1AZ 3
D-124600 20060 12 MAGOR, GWENT NP26 3DF 1
D-124601 20060 13 RUGBY CV23 8YH 2
So now, I'd need to see that palletspaces number, then return the particular line that many times and then also have a new column that counts these instances:
id traderid toaddressid county postcode palletspaces LineCount
D-124597 2101 2 READING RG6 1AZ 3 1
D-124597 2101 2 READING RG6 1AZ 3 2
D-124597 2101 2 READING RG6 1AZ 3 3
D-124600 20060 12 MAGOR, GWENT NP26 3DF 1 1
D-124601 20060 13 RUGBY CV23 8YH 2 1
D-124601 20060 13 RUGBY CV23 8YH 2 2
The other thing to mention is that naturally I'll have hundreds of different delivery records (all returned as one line each) and all will have differing palletspaces numbers. And of course stating the obvious I need the line to only replicate and count based on it's own palletspaces number
The SQL in use is as below
select
deliveries.id,
deliveries.traderid,
customers.name,
deliveries.toaddressid,
deliveries.eutransportid,
deliveries.street,
deliveries.city,
deliveries.county,
deliveries.postcode,
delivery_custom.palletspaces,
ectransport.ectranspdesc
from deliveries
INNER JOIN customers ON
deliveries.traderid = customers.id
INNER JOIN delivery_custom ON
deliveries.id = delivery_custom.id
INNER JOIN ectransport ON
deliveries.eutransportid = ectransport.ectranspcode
Try like this:
select deliveries.id,
deliveries.traderid,
customers.name,
deliveries.toaddressid,
deliveries.eutransportid,
deliveries.street,
deliveries.city,
deliveries.county,
deliveries.postcode,
delivery_custom.palletspaces,
ectransport.ectranspdesc
INTO #MyTemp
from deliveries
INNER JOIN customers ON
deliveries.traderid = customers.id
INNER JOIN delivery_custom ON
deliveries.id = delivery_custom.id
INNER JOIN ectransport ON
deliveries.eutransportid = ectransport.ectranspcode
;WITH CTE AS(
SELECT id,traderid,toaddressid,county,postcode,palletspaces,1 AS LineCount
FROM #MyTemp
UNION ALL
SELECT id,traderid,toaddressid,county,postcode,palletspaces,LineCount+1
FROM CTE
WHERE LineCount<palletspaces
)
SELECT *
FROM CTE
ORDER BY id, LineCount;
DROP TABLE #MyTemp
Hope this time you get it.
Using Recursive CTE, we can achieve this:
DECLARE #TAB TABLE ([D Number] VARCHAR(20) ,customer INT, postcode VARCHAR(20), palletspaces INT)
INSERT INTO #TAB VALUES('D-123456' ,19114, 'DA12 1TF' , 4)
INSERT INTO #TAB VALUES('D-111111' ,19114, 'DDDD 1TF' , 3)
;WITH CTE AS(
SELECT [D Number],customer,postcode,palletspaces,1 AS A
FROM #TAB
UNION ALL
SELECT [D Number],customer,postcode,palletspaces,A+1
FROM CTE
WHERE A<palletspaces
)
SELECT *
FROM CTE
ORDER BY [D Number], LineCount;
Output:
D Number customer postcode palletspaces LineCount
D-123456 19114 DA12 1TF 4 1
D-123456 19114 DA12 1TF 4 2
D-123456 19114 DA12 1TF 4 3
D-123456 19114 DA12 1TF 4 4

SQL. how to compare values and from two table, and report per-row results

I have two Tables.
table A
id name Size
===================
1 Apple 7
2 Orange 15
3 Banana 22
4 Kiwi 2
5 Melon 28
6 Peach 9
And Table B
id size
==============
1 14
2 5
3 31
4 9
5 1
6 16
7 7
8 25
My desired result will be (add one column to Table A, which is the number of rows in Table B that have size smaller than Size in Table A)
id name Size Num.smaller.in.B
==============================
1 Apple 7 2
2 Orange 15 5
3 Banana 22 6
4 Kiwi 2 1
5 Melon 28 7
6 Peach 9 3
Both Table A and B are pretty huge. Is there a clever way of doing this. Thanks
Use this query it's helpful
SELECT id,
name,
Size,
(Select count(*) From TableB Where TableB.size<Size)
FROM TableA
The standard way to get your result involves a non-equi-join, which will be a product join in Explain. First duplicating 20,000 rows, followed by 7,000,000 * 20,000 comparisons and a huge intermediate spool before the count.
There's a solution based on OLAP-functions which is usually quite efficient:
SELECT dt.*,
-- Do a cumulative count of the rows of table #2
-- sorted by size, i.e. count number of rows with a size #2 less size #1
Sum(CASE WHEN NAME = '' THEN 1 ELSE 0 end)
Over (ORDER BY SIZE, NAME DESC ROWS Unbounded Preceding)
FROM
( -- mix the rows of both tables, an empty name indicates rows from table #2
SELECT id, name, size
FROM a
UNION ALL
SELECT id, '', size
FROM b
) AS dt
-- only return the rows of table #1
QUALIFY name <> ''
If there are multiple rows with the same size in table #2 you better count before the Union to reduce the size:
SELECT dt.*,
-- Do a cumulative sum of the counts of table #2
-- sorted by size, i.e. count number of rows with a size #2 less size #1
Sum(CASE WHEN NAME ='' THEN id ELSE 0 end)
Over (ORDER BY SIZE, NAME DESC ROWS Unbounded Preceding)
FROM
( -- mix the rows of both tables, an empty name indicates rows from table #2
SELECT id, name, size
FROM a
UNION ALL
SELECT Count(*), '', SIZE
FROM b
GROUP BY SIZE
) AS dt
-- only return the rows of table #1
QUALIFY NAME <> ''
There is no clever way of doing that, you just need to join the tables like this:
select a.*, b.size
from TableA a join TableB b on a.id = b.id
To improve performance you'll need to have indexes on the id columns.
maybe
select
id,
name,
a.Size,
sum(cnt) as sum_cnt
from
a inner join
(select size, count(*) as cnt from b group by size) s on
s.size < a.size
group by id,name,a.size
if you're working with large tables. Indexing table b's size field could help. I'm also assuming the values in table B converge, that there's many duplicates you don't care about, other than you want to count them.
sqlfiddle
#Ritesh solution is perfectly correct, another similar solution is using CROSS JOIN as shown below
use tempdb
create table dbo.A (id int identity, name varchar(30), size int );
create table dbo.B (id int identity, size int);
go
insert into dbo.A (name, size)
values ('Apple', 7)
,('Orange', 15)
,('Banana', 22)
,('Kiwi', 2 )
,('Melon', 28)
,('Peach', 6 )
insert into dbo.B (size)
values (14), (5),(31),(9),(1),(16), (7),(25)
go
-- using cross join
select a.*, t.cnt
from dbo.A
cross apply (select cnt=count(*) from dbo.B where B.size < A.size) T(cnt)
try this query
SELECT
A.id,A.name,A.size,Count(B.size)
from A,B
where A.size>B.size
group by A.size
order by A.id;

sql select query self join or loop through to fetch records [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have this kind of scenario in sql server I have table named Room and here is the data of it and I want output something like this as shown in this picture I have tried to show my table named room and then on top of it I have placed tag input which have RoomId,ConnectingRoomID and many more other columns now what I want is a sql select query that can return me the scenario I have placed with tag name output..
These values are self created I have thousand of rooms and in room table and thousand of connecting room with it hope my question is clear enough thanks.
I think you can use this:
with x as (
select *, sum(case connectingroomid when 0 then 1 else 0 end) over(order by roomid) as grp
from rooms
)
select x.roomid, (select min(x2.roomid) as min_roomid from x x2 where x2.grp = x.grp) as connectingroomid
from x
This is a recursive query: For all rooms go to the connecting room till you find the one that has no more connecting room (i.e. connecting room id is 0).
with rooms (roomid, connectingroomid) as
(
select
roomid,
case when connectingroomid = 0 then
roomid
else
connectingroomid
end as connectingroomid
from room
where connectingroomid = 0
union all
select room.roomid, rooms.connectingroomid
from room
inner join rooms on room.connectingroomid = rooms.roomid
)
select * from rooms
order by connectingroomid, roomid;
Here is the SQL fiddle: http://www.sqlfiddle.com/#!3/46ed0/1.
EDIT: Here is the explanation. Rather than doing this in the comments I am doing it here for better readability.
The WITH clause is used to create a recursion here. You see I named it rooms and inside rooms I select from rooms itself. Here is how to read it: Start with the part before UNION ALL. Then recursively do the part after UNION ALL. So, before UNION ALL I only select the records where connectingroomid is zero. In your example you show every room with its connectingroomid except for those with connectingroomid for which you show the room with itself. I use CASE here to do the same. But now that I am explaining this, I notice that connectingroomid is always zero because of the WHERE clause. So the statement can be simplified thus:
with rooms (roomid, connectingroomid) as
(
select
roomid,
roomid as connectingroomid
from room where connectingroomid = 0
union all
select room.roomid, rooms.connectingroomid
from room
inner join rooms on room.connectingroomid = rooms.roomid
)
select * from rooms
order by connectingroomid, roomid;
The SQL fiddle: http://www.sqlfiddle.com/#!3/46ed0/2.
With the part before the UNION ALL I found the two rooms without connecting room. Now the part after UNION ALL is executed for the two rooms found. It selects the rooms which connecting room was just found. And then it selects the rooms which connecting room was just found. And so on till the join returns no more rooms.
Hope this helps understanding the query. You can look for "recursive cte" on the Internet to find more examples and explanations on the topic.
select RoomID,
(Case when RoomID<=157 then 154
else 158 end) ConnectingRoomID
from Input
First of all, your output is not correct: Room 154 should also connect to room 0 :-)
What you are after is the transitive closure of the relation defined by the table Room. It is impossible to get this with "vanilla" SQL. There are however, a few extensions to SQL to make recursive queries possible.
For example, If I have a relation "graph":
src | target
-----+--------
1 | 2
2 | 3
3 | 4
5 | 6
6 | 7
I can define a new table "closure" with the same fields:
WITH RECURSIVE closure (src, target) AS
(SELECT src, target FROM
graph
UNION
SELECT graph.src, closure.target FROM graph, closure WHERE
graph.target = closure.src)
SELECT * FROM closure
Note that "closure" occurs in its own definition (that is why this is a recursive query) It uses the original graph as a "seed" and grows by adding tuples with increasing distance (inspecting itself to do so).
The result (it clearly shows how the relation has grown):
src | target
-----+--------
1 | 2
2 | 3
3 | 4
5 | 6
6 | 7
1 | 3
2 | 4
5 | 7
1 | 4
If you are only interested in pairs that cannot be extended further, as in your original example, you could add an extra field "distance" to the closure table and use a GROUP BY clause to keep only the maximal pairs.
Disclaimer: I'm not on Windows, and used postgres for this. MS SQL should work very much the same way.
try below sql:
Assumming #input is your input table
Note: I added an ID column in the #input table
declare #input table
(
id int identity,
RoomId int,
ConnectingRoomId int
)
insert into #input
select 154,0 union all
select 155,154 union all
select 156,155 union all
select 157,156 union all
select 158, 0 union all
select 159, 158 union all
select 160, 159
**UPDATED: remove the union **
SQL:
select
d.id,
d.roomId
,max(d.connectingRoomId) as ConnectingRoomId
from
(
select
bb.id,
bb.RoomId
,b.RoomId as connectingRoomId
from #input b
right join
(
select
a.id,
a.RoomId,a.ConnectingRoomId
from #input a
) bb on (b.id < bb.Id) or b.Id = bb.Id
where b.ConnectingRoomId = 0
) d
group by d.id, d.RoomId
/*
Result (OUTPUT TABLE)
id roomId ConnectingRoomId
----------- ----------- ----------------
1 154 154
2 155 154
3 156 154
4 157 154
5 158 158
6 159 158
7 160 158
*/

SQL return multiple rows from one record

This is the opposite of reducing repeating records.
SQL query to create physical inventory checklists
If widget-xyz has a qty of 1 item return 1 row, but if it has 5, return 5 rows etc.
For all widgets in a particular warehouse.
Previously this was handled with a macro working through a range in excel, checking the qty column. Is there a way to make a single query instead?
The tables are FoxPro dbf files generated by an application and I am outputting this into html
Instead of generating an xml string and using xml parsing functions to generate a counter as Nestor has suggested, you might consider joining on a recursive CTE as a counter, as LukLed has hinted to:
WITH Counter AS
(
SELECT 0 i
UNION ALL
SELECT i + 1
FROM Counter
WHERE i < 100
),
Data AS
(
SELECT 'A' sku, 1 qty
UNION
SELECT 'B', 2
UNION
SELECT 'C', 3
)
SELECT *
FROM Data
INNER JOIN Counter ON i < qty
According to query analyzer, this query is much faster than the xml pseudo-table. This approach also gives you a recordset with a natural key (sku, i).
There is a default recursion limit of 100 in MSSQL that will restrict your counter. If you have quantities > 100, you can either increase this limit, use nested counters, or create a physical table for counting.
For SQL 2005/2008, take a look at
CROSS APPLY
What I would do is CROSS APPLY each row with a sub table with as many rows as qty has. A secondary question is how to create that sub table (I'd suggest to create an xml string and then parse it with the xml operators)
I hope this gives you a starting pointer....
Starting with
declare #table table (sku int, qty int);
insert into #table values (1, 5), (2,4), (3,2);
select * from #table;
sku qty
----------- -----------
1 5
2 4
3 2
You can generate:
with MainT as (
select *, convert(xml,'<table>'+REPLICATE('<r></r>',qty)+'</table>') as pseudo_table
from #table
)
select p.sku, p.qty
from MainT p
CROSS APPLY
(
select p.sku from p.pseudo_table.nodes('/table/r') T(row)
) crossT
sku qty
----------- -----------
1 5
1 5
1 5
1 5
1 5
2 4
2 4
2 4
2 4
3 2
3 2
Is that what you want?
Seriously dude... next time put more effort writing your question. It's impossible to know exactly what you are looking for.
You can use table with number from 1 to max(quantity) and join your table by quantity <= number. You can do it in many ways, but it depends on sql engine.
You can do this using dynamic sql.

How to calculate the sum of values in a tree using SQL

I need to sum points on each level earned by a tree of users. Level 1 is the sum of users' points of the users 1 level below the user. Level 2 is the Level 1 points of the users 2 levels below the user, etc...
The calculation happens once a month on a non production server, no worries about performance.
What would the SQL look like to do it?
If you're confused, don't worry, I am as well!
User table:
ID ParentID Points
1 0 230
2 1 150
3 0 80
4 1 110
5 4 54
6 4 342
Tree:
0
|---\
1 3
| \
2 4---
\ \
5 6
Output should be:
ID Points Level1 Level2
1 230 150+110 150+110+54+342
2 150
3 80
4 110 54+342
5 54
6 342
SQL Server Syntax and functions preferably...
If you were using Oracle DBMS that would be pretty straightforward since Oracle supports tree queries with the CONNECT BY/STARTS WITH syntax. For SQL Server I think you might find Common Table Expressions useful
Trees don't work well with SQL. If you have very (very very) few write accesses, you could change the tree implementation to use nested sets, that would make this query incredibly easy.
Example (if I'm not mistaken):
SELECT SUM(points)
FROM users
where left > x and right < y
However, any changes on the tree require touching a massive amount of rows. It's probably better to just do the recursion in you client.
I would say: create a stored procedure, probably has the best performance.
Or if you have a maximum number of levels, you could create subqueries, but they will have a very poort performance.
(Or you could get MS SQL Server 2008 and get the new hierarchy functions... ;) )
SQL in general, like others said, does not handle well such relations. Typically, a surrogate 'relations' table is needed (id, parent_id, unique key on (id, parent_id)), where:
every time you add a record in 'table', you:
INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_id]);
INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_parent_id]);
INSERT INTO relations (id, parent_id)
SELECT [current_id], parent_id
FROM relations
WHERE id = [current_parent_id];
have logic to avoid cycles
make sure that updates, deletions on 'relations' are handled with stored procedures
Given that table, you want:
SELECT rel.parent_id, SUM(tbl.points)
FROM table tbl INNER JOIN relations rel ON tbl.id=rel.id
WHERE rel.parent_id <> 0
GROUP BY rel.parent_id;
Ok, this gives you the results you are looking for, but there are no guarantees that I didn't miss something. Consider it a starting point. I used SQL 2005 to do this, SQL 2000 does not support CTE's
WITH Parent (id, GrandParentId, parentId, Points, Level1Points, Level2Points)
AS
(
-- Find root
SELECT id,
0 AS GrandParentId,
ParentId,
Points,
0 AS Level1Points,
0 AS Level2Points
FROM tblPoints ptr
WHERE ptr.ParentId = 0
UNION ALL (
-- Level2 Points
SELECT pa.GrandParentId AS Id,
NULL AS GrandParentId,
NULL AS ParentId,
0 AS Points,
0 AS Level1Points,
pa.Points AS Level2Points
FROM tblPoints pt
JOIN Parent pa ON pa.GrandParentId = pt.Id
UNION ALL
-- Level1 Points
SELECT pt.ParentId AS Id,
NULL AS GrandParentId,
NULL AS ParentId,
0 AS Points,
pt.Points AS Level1Points,
0 AS Level2Points
FROM tblPoints pt
JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL
UNION ALL
-- Points
SELECT pt.id,
pa.ParentId AS GrandParentId,
pt.ParentId,
pt.Points,
0 AS Level1Points,
0 AS Level2Points
FROM tblPoints pt
JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL )
)
SELECT id,
SUM(Points) AS Points,
SUM(Level1Points) AS Level1Points,
CASE WHEN SUM(Level2Points) > 0 THEN SUM(Level1Points) + SUM(Level2Points) ELSE 0 END AS Level2Points
FROM Parent
GROUP BY id
ORDER by id
If you are working with trees stored in a relational database, I'd suggest looking at "nested set" or "modified preorder tree traversal". The SQL will be as simple as that:
SELECT id,
SUM(value) AS value
FROM table
WHERE left>left\_value\_of\_your\_node
AND right<$right\_value\_of\_your\_node;
... and do this for every node you are interested in.
Maybe this will help you:
http://www.dbazine.com/oracle/or-articles/tropashko4 or use google.
You have a couple of options:
Use a cursor and a recursive user-defined function call (it's quite slow)
Create a cache table, update it on INSERT using a trigger (it's the fastest solution but could be problematic if you have lots of updates to the main table)
Do a client-side recursive calculation (preferable if you don't have too many records)
You can write a simple recursive function to do the job. My MSSQL is a little bit rusty, but it would look like this:
CREATE FUNCTION CALC
(
#node integer,
)
returns
(
#total integer
)
as
begin
select #total = (select node_value from yourtable where node_id = #node);
declare #children table (value integer);
insert into #children
select calc(node_id) from yourtable where parent_id = #node;
#current = #current + select sum(value) from #children;
return
end
The following table:
Id ParentId
1 NULL
11 1
12 1
110 11
111 11
112 11
120 12
121 12
122 12
123 12
124 12
And the following Amount table:
Id Val
110 500
111 50
112 5
120 3000
121 30000
122 300000
Only the leaves (last level) Id's have a value defined.
The SQL query to get the data looks like:
;WITH Data (Id, Val) AS
(
select t.Id, SUM(v.val) as Val from dbo.TestTable t
join dbo.Amount v on t.Id = v.Id
group by t.Id
)
select cd.Id, ISNULL(SUM(cd.Val), 0) as Amount FROM
(
-- level 3
select t.Id, d.val from TestTable t
left join Data d on d.id = t.Id
UNION
-- level 2
select t.parentId as Id, sum(y.Val) from TestTable t
left join Data y on y.id = t.Id
where t.parentId is not null
group by t.parentId
UNION
-- level 1
select t.parentId as Id, sum(y.Val) from TestTable t
join TestTable c on c.parentId = t.Id
left join Data y on y.id = c.Id
where t.parentId is not null
group by t.parentId
) AS cd
group by id
this results in the output:
Id Amount
1 333555
11 555
12 333000
110 500
111 50
112 5
120 3000
121 30000
122 300000
123 0
124 0
I hope this helps.