Is it possible in SQL Server to "group" a result from a single query based on data in a specific column as if I ran multiple select queries?
I'm trying to find a lazy way out to extract data such as the below:
StoreId | ClientId
1 | 4
1 | 5
2 | 5
2 | 6
2 | 7
3 | 8
whereby every store ID result is grouped into its own table.
Whilst I can create a select statement for every store id to have it grouped, the list is too long to do so.
I can't imagine that this is really helpful but you can use dynamic sql to do something like this. I can't say I would recommend this approach for generating excel documents but whatever.
create table #Something
(
StoreID int
, ClientID int
)
insert #Something
select 1, 4 union all
select 1, 5 union all
select 2, 5 union all
select 2, 6 union all
select 2, 7
declare #sql nvarchar(max) = ''
select #sql = #sql + 'select StoreID, ClientID from #Something where StoreID = ' + CAST(StoreID as varchar(4)) + ';'
from #Something
group by StoreID
select #sql
exec sp_executesql #sql
drop table #Something
Related
Let's say I have a table 'T1' that contains 3 records like this:
A B
101,103,115,189 NAME1
101,115 NAME2
102,116 NAME3
and now I have to find all the rows from field A that contains (101,102,115) which should be NAME1, NAME2 and NAME3.
Since there are over 100000 rows, I need to find an effective way to do this.
Very much appreciate for any kind help.
I'm using SQL Server 2014.
Solution:
I create a third table to maintain the relationship between Job and Category table, the final query should be like this:
SELECT * FROM Job WHERE Job_Id IN (
SELECT DISTINCT(Job_Id) FROM Job_RS_Category WHERE Category_Id in (100015,100054,100060,100062,100063,100068,100070,100072,100073,100081,100096,100099))
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE Testdata
(
SomeID INT,
A VARCHAR(MAX),
B VARCHAR(MAX)
)
INSERT Testdata SELECT 1, '101,103,115,189', 'NAME1'
INSERT Testdata SELECT 2, '101,115' ,'NAME2'
INSERT Testdata SELECT 3, '102,116' , 'NAME3'
Query 1:
;WITH tmp(SomeID, B, DataItem, A) AS
(
SELECT
SomeID,
B,
LEFT(A, CHARINDEX(',', A + ',') - 1),
STUFF(A, 1, CHARINDEX(',', A + ','), '')
FROM Testdata
UNION all
SELECT
SomeID,
B,
LEFT(A, CHARINDEX(',', A + ',') - 1),
STUFF(A, 1, CHARINDEX(',', A + ','), '')
FROM tmp
WHERE
A > ''
)
select distinct B from tmp where DataItem IN ('101','102','115')
Results:
| B |
|-------|
| NAME1 |
| NAME2 |
| NAME3 |
I have an old classic ASP application which needs to insert many thousand rows into a SQL Server 2008 table. Currently the application is sending an INSERT command for each row separately, which takes a long time and meanwhile locks the table.
Is there a better way to do this? For example maybe:
Insert all rows into a temp table
and then do
SELECT INTO from the temp table
?
If you're generating the list of dates in the application itself, then you could probably generate them with the necessary additions to make this work.
In SQL Server 2008, you can insert multiple rows in a single command, which is a bit better than inserting row-by-row.
Here are a couple of examples of how you could do it, using a table variable for dummy data, and using GETDATE() to generate a few different dates (which you would obviously be generating in your application):
DECLARE #TABLE AS TABLE
(
RowID INT IDENTITY
,MyDate DATETIME
)
;
INSERT INTO #TABLE (MyDate)
VALUES
(GETDATE())
,(GETDATE()+1)
,(GETDATE()+2)
,(GETDATE()+3)
,(GETDATE()+4)
,(GETDATE()+5)
,(GETDATE()+6)
SELECT * FROM #TABLE
;
Returns:
RowID | MyDate
1 | 26/11/2017 10:51:49
2 | 27/11/2017 10:51:49
3 | 28/11/2017 10:51:49
4 | 29/11/2017 10:51:49
5 | 30/11/2017 10:51:49
6 | 01/12/2017 10:51:49
7 | 02/12/2017 10:51:49
You can also use this format:
INSERT INTO #TABLE (MyDate)
SELECT GETDATE()
UNION ALL
SELECT GETDATE() + 1
UNION ALL
SELECT GETDATE() + 2
UNION ALL
SELECT GETDATE() + 3
UNION ALL
SELECT GETDATE() + 4
UNION ALL
SELECT GETDATE() + 5
UNION ALL
SELECT GETDATE() + 6
;
SELECT * FROM #TABLE
;
Returns:
RowID | MyDate
1 | 26/11/2017 10:51:49
2 | 27/11/2017 10:51:49
3 | 28/11/2017 10:51:49
4 | 29/11/2017 10:51:49
5 | 30/11/2017 10:51:49
6 | 01/12/2017 10:51:49
7 | 02/12/2017 10:51:49
Not an ASP expert, but if you're concatenating the string in your application, you should be able to concatenate the string continuously rather than recreating it as a whole new INSERT statement for each date.
I need to convert the table from (A) into (B).
I am able to get it work by joining the same table multiple times and use Max operator to assign the fields, but is there any better way to achieve this as Max operator could cause performance issue on huge table.
Can this be done by using pivot and will it cause any performance issue on huge table?
Btw, ID in below example is only 1 of the fields as example, there are other fields that need to achieve the same thing.
(A)
Class ID
1 11
1 12
1 13
2 11
2 12
2 13
(B)
Class ID2 ID3 ID4
1 11 12 13
2 11
12 13
You can you PIVOT:
SELECT *
FROM
(SELECT * FROM MY_TABLE
) pivot ( MAX(id) FOR id IN ([11],[12],[13]) );
Assuming you need to go DYNAMIC
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(concat('ID',1+Row_Number() over (Partition By Class Order By ID))) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Class],' + #SQL + '
From (
Select [Class]
,ID
,Col = concat(''ID'',1+Row_Number() over (Partition By [Class] Order By [ID]))
From YourTable
) A
Pivot (max(ID) For [Col] in (' + #SQL + ') ) P'
Exec(#SQL);
Returns
Class ID2 ID3 ID4
1 11 12 13
2 11 12 13
I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2
I queried data like this
member_no cover_version product_id product_name product_type
--------- ------------- ---------- -------------------- ------------
11421 7 4 Excellent More E
11421 7 15 Comprehensive Data D
But I want to shape this data like this:
member_no cover_version product_e_id product_e_name product_d_id product_d_name
--------- ------------- ------------ -------------------- ------------ --------------------
11421 7 4 Excellent More 15 Comprehensive Data
I am using SQL Server 2008. What should be the best approach to shape the data as I want?
Assuming you've only got product types D and E as stated, a simple self-join will get you what you're after.
If you want something more generic, please expand your question.
DROP TABLE IF EXISTS #Demo
SELECT
*
INTO
#Demo
FROM
(VALUES
(11421, 7, 4, 'Excellent More', 'E')
,(11421, 7, 15, 'Comprehensive Data', 'D')) A
(member_no, cover_version, product_id, product_name, product_type)
SELECT
D.member_no
,D.cover_version
,E.product_id product_e_id
,E.product_name product_e_name
,D.product_id product_d_id
,D.product_name product_d_name
FROM
#Demo D
JOIN #Demo E ON D.member_no = E.member_no
AND D.product_type = 'D'
AND E.product_type = 'E';
member_no cover_version product_e_id product_e_name product_d_id product_d_name
----------- ------------- ------------ ------------------ ------------ ------------------
11421 7 4 Excellent More 15 Comprehensive Data
If you want to do this dynamically, based on unknown product types, you can use this.
DECLARE #SQL NVARCHAR(MAX),
#Columns NVARCHAR(MAX),
#CaseExpressions NVARCHAR(MAX) = 'MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_id END) AS [product_<<productType>>_id],
MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_name END) AS [product_<<productType>>_name]'
-- build concatenated string with 2 columns for each product_type in your table.
-- group by product_type to get distinct results.
-- replaces <<productType>> with the actual product_type value
SELECT #Columns = COALESCE(#Columns + ',', '') + REPLACE(#CaseExpressions, '<<productType>>', product_type)
FROM myTable
GROUP BY product_type
-- build select query
SET #SQL = 'SELECT member_no, cover_version,' + #Columns + ' FROM myTable GROUP BY member_no, cover_version'
-- to see what the dynamic sql looks like
PRINT #SQL
-- to execute the dynamic sql and see result
EXEC (#SQL)