I have created a report for our Sales department, and they have what (on the surface) appears to be a simple request: if a column name contains "date_WeeklySales2017", drop the "date_" portion of the column name.
Given the following setup, how do I create a dynamic column name for COL_A in the dynamic pivot? (I'm using SQL Server 2005 sp3)
CREATE TABLE #CombinedSales(
WeekNumber int,
WeekStart varchar(11),
StoreNumber int,
Address nvarchar(255),
City nvarchar(255),
Province varchar(2),
WeeklySales2017 decimal(13,2),
WeeklySales2018 decimal(13,2)
)
INSERT INTO #CombinedSales
SELECT 1, '19-Feb-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 1, '19-Feb-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 2, '26-Feb-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 2, '26-Feb-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 3, '05-Mar-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 3, '05-Mar-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 4, '12-Mar-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 4, '12-Mar-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 5, '19-Mar-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 5, '19-Mar-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
-- create the dynamic columns
DECLARE #COLUMNS nvarchar(MAX)
SELECT #COLUMNS = SUBSTRING(
(SELECT ', ' + QUOTENAME(WeekStart + '_WeeklySales2017'),
+ ', ' + QUOTENAME(WeekStart + '_WeeklySales2018')
FROM (SELECT DISTINCT TOP 100 PERCENT WeekNumber, WeekStart FROM #CombinedSales WHERE WeekNumber IS NOT NULL ORDER BY WeekNumber) R
ORDER BY WeekNumber DESC -- most-recent date displayed first in pivot table report
For XML PATH ('')
), 2, 10000)
PRINT #COLUMNS
-- build the dynamic pivot
DECLARE #PIVOT_SQL nvarchar(MAX)
SET #PIVOT_SQL = N'SELECT *
FROM (
SELECT StoreNumber,
Address,
City,
Province,
COL_A = CAST(WeekStart as varchar(11)) + ''_'' + COL_A,
VALUE_A
FROM #CombinedSales
CROSS APPLY( SELECT ''WeeklySales2017'', ISNULL(WeeklySales2017, 0)
UNION ALL
SELECT ''WeeklySales2018'', ISNULL(WeeklySales2018, 0)
) c (COL_A, VALUE_A)
) d
PIVOT(MAX(VALUE_A) FOR COL_A IN(' + #COLUMNS + ')
) PIV'
PRINT #PIVOT_SQL
EXEC sp_executesql #PIVOT_SQL
DROP TABLE #CombinedSales
Related
In the original table there are a lot of records. Is it better to filter within the CTE (example B) or should this be done in the JOIN condition (example A). Or is it possibly all the same, both are equally good/fast?
I guess in the CTE the whole table would have to be prefiltered first and in the JOIN only the corresponding records would be affected.
DECLARE #Orders TABLE(
orderid int NOT NULL,
orderdate datetime NOT NULL,
empid int NOT NULL,
custid varchar(5) NOT NULL,
qty int NOT NULL
);
-- in original lots of data
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(30001, '20020802', 3, 'A', 10);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(10001, '20021224', 1, 'A', 12);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(10005, '20021224', 1, 'B', 20);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(40001, '20030109', 4, 'A', 40);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(10006, '20030118', 1, 'C', 14);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(20001, '20030212', 2, 'B', 12);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(40005, '20040212', 4, 'A', 10);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(20002, '20040216', 2, 'C', 20);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(30003, '20040418', 3, 'B', 15);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(30004, '20020418', 3, 'C', 22);
INSERT INTO #Orders(orderid, orderdate, empid, custid, qty) VALUES(30007, '20020907', 3, 'D', 30);
-- example A
WITH CTE_
AS(
SELECT
orderid
,orderdate
,empid
,custid
,qty
,DENSE_RANK() OVER(PARTITION BY custid ORDER BY orderid) AS [Ranking]
FROM #Orders
)
SELECT
d.*
FROM CTE_ AS d
WHERE d.[Ranking] = 1;
-------------------------------------
-- example B
WITH CTE_
AS(
SELECT
orderid
,orderdate
,empid
,custid
,qty
FROM(
SELECT
orderid
,orderdate
,empid
,custid
,qty
,DENSE_RANK() OVER(PARTITION BY custid ORDER BY orderid) AS [Ranking]
FROM #Orders
) AS d
WHERE d.[Ranking] = 1
)
SELECT
d.*
FROM CTE_ AS d
Generally, it's better to filter within the CTE (example B), especially in case of your tables have indexes in filter conditions, these indexes could be applied to be pre-filtered data first. Since a CTE is a temporary view, it can't have indexes, so in case of example A, your main query could not use indexes (if have) for filter condition in WHERE statement.
I need to create groups for every n records in each categories. For example I have student table with StdId, Gender and Subject, now I want to divide all students in groups by their gender and subject and each group can not have more than two.
Here is code for sample data
declare #Students table (StdId int, Gender char(1), Subj varchar(10))
insert into #students select 1, 'F', 'Math'
insert into #students select 2, 'M', 'Math'
insert into #students select 3, 'M', 'Math'
insert into #students select 4, 'F', 'Math'
insert into #students select 5, 'F', 'Math'
insert into #students select 6, 'F', 'History'
insert into #students select 7, 'M', 'History'
insert into #students select 8, 'F', 'English'
insert into #students select 9, 'F', 'English'
insert into #students select 10, 'M', 'English'
insert into #students select 11, 'F', 'English'
insert into #students select 12, 'M', 'English'
And I need output like this:
GroupNo Subj Gender StdId
1 English F 8
1 English F 9
2 English F 11
3 English M 10
3 English M 12
4 History F 6
5 History M 7
6 Math F 1
6 Math F 4
7 Math F 5
8 Math M 2
8 Math M 3
Ok found my solution with the help of this
;with FirstRank as
(select DENSE_RANK() over (order by Subj, Gender) as rnk, * from #students),
SecondRank AS
(select (ROW_NUMBER() OVER (PARTITION BY rnk ORDER by gender)-1)
/ 2 as rn,*
from FirstRank
)
select DENSE_RANK() OVER (ORDER BY rnk,rn) as GrpNo, Subj, Gender, StdId from SecondRank
Using Sql Server 2008 R2.
Where there is more than 1 row of type demographic change, I need to delete all but 1 per person, but the types of demographic changes are weighted, with some more important than others. I don't know what the data will hold but if a more important one exists for a particular Contact, I want it to rise to the top.
I tried:
;WITH cte AS
(
SELECT lastname, firstname, FieldChanged,
Case 'FieldChanged'
When 'firstname' then 0
When 'lastname' then 0
When 'ssn' then 1
When 'xyz' then 5
End as "Weight"
, ROW_NUMBER() OVER (PARTITION BY D2.ContactId, D2.ContractId ORDER BY weight asc) AS demorow
FROM MyDATA d2
where d2.FieldChanged in ('firstname', 'lastname', 'ssn', 'xyz')
)
SELECT *
FROM cte
WHERE demorow > 1
This gives me an error: Invalid column name 'weight'.
I think I can't use APPLY since there's no unique key in the source table, which is not under my control.
Update:
CREATE TABLE dbo.MyTempTable
(firstname varchar(25) NOT NULL,
lastname varchar(25) NOT NULL,
FieldChanged varchar(25),
ContactId uniqueidentifier,
ContractId uniqueidentifier
)
GO
Insert into dbo.mytemptable
(firstname ,
lastname ,
FieldChanged ,
ContactId ,
ContractId)
Values
('john', 'smith', 'ssn', '688CB150-C7FD-E511-8709-00155D070201', '688CB150-C7FD-E511-8709-00155D070202')
, ('john', 'smith', 'xyz', '688CB150-C7FD-E511-8709-00155D070201', '688CB150-C7FD-E511-8709-00155D070202')
, ('mary', 'doe', 'xyz', '688CB150-C7FD-E511-8709-00155D070203', '688CB150-C7FD-E511-8709-00155D070202')
, ('mary', 'doe', 'firstname', '688CB150-C7FD-E511-8709-00155D070203', '688CB150-C7FD-E511-8709-00155D070202')
, ('mary', 'doe', 'lastname', '688CB150-C7FD-E511-8709-00155D070203', '688CB150-C7FD-E511-8709-00155D070202')
, ('mary', 'doe', 'ssn', '688CB150-C7FD-E511-8709-00155D070203', '688CB150-C7FD-E511-8709-00155D070202')
For this data I'd want John Smith's and Mary Doe's respective xyz rows to be selected, as less important than their name change rows.
Update 2:
I think this works:
;WITH cte AS
(
SELECT lastname, firstname, FieldChanged,
Case FieldChanged
When 'firstname' then 0
When 'lastname' then 0
When 'ssn' then 5
When 'xyz' then 1
else 9
End as "Weight",
ContactId, ContractID
FROM edi..MyDATA d2
where d2.FieldChanged in ('firstname', 'lastname', 'ce_ssn', 'Policy Number')
),
cte2 As
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY ContactId, ContractId ORDER BY weight asc) AS demorow
FROM cte
)
SELECT *
FROM cte2
WHERE demorow > 1
Column aliases are assigned after all of the other clauses of a SELECT expression are executed (except for ORDER BY clauses, but not ORDER BY expressions), so you cannot use them within the same SELECT expression, only outside of them (or in an ORDER BY clause).
Here's a quick fix:
;WITH cte AS
(
SELECT lastname, firstname, FieldChanged,
Case FieldChanged
When 'firstname' then 0
When 'lastname' then 0
When 'ssn' then 1
When 'xyz' then 5
End as "Weight",
ContactId, ContractID
FROM MyDATA d2
where d2.FieldChanged in ('firstname', 'lastname', 'ssn', 'xyz')
),
cte2 As
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY ContactId, ContractId ORDER BY weight asc) AS demorow
FROM cte
)
SELECT *
FROM cte2
WHERE demorow > 1
replace "weight" in the order by with the full CASE statement. Or put the main query (without order by) in a sub query and the row number in the outer query. YOu should then be able to access the "weight" column in order by.
I have a CTE that returns the below records. How should i proceed with the new query so that all records with GID = NULL will get the previous last GID?
ID GID VALUE
1 1 Some Value
2 NULL Some Value
3 2 Some Value
4 3 Some Value
5 NULL Some Value
6 NULL Some Value
Eg. Records with ID 5 and 6 will have GID = 3
with C(ID, GID, VALUE) as
(
select 1, 1, 'Some Value' union all
select 2, NULL, 'Some Value' union all
select 3, 2, 'Some Value' union all
select 4, 3, 'Some Value' union all
select 5, NULL, 'Some Value' union all
select 6, NULL, 'Some Value'
)
select C1.ID,
C3.GID,
C1.VALUE
from C as C1
cross apply
(select top 1 C2.ID, C2.GID
from C as C2
where C2.ID <= C1.ID and
C2.GID is not null
order by C2.ID desc) as C3
;WITH C(ID, GID, VALUE) as
(
select 1, 1, 'Some Value' union all
select 2, NULL, 'Some Value' union all
select 3, 2, 'Some Value' union all
select 4, 3, 'Some Value' union all
select 5, NULL, 'Some Value' union all
select 6, NULL, 'Some Value'
)
select c.id, d.GID, c.value from c
cross apply
(select max(GID) GID from c d where c.id >= id) d
CREATE TABLE #t(LocationCode varchar(10), ResourceId int, TransType char(3))
INSERT #t
SELECT 'STORE 001', 1, 'In' UNION ALL
SELECT 'STORE 002', 2, 'In' UNION ALL
SELECT 'STORE 003', 3, 'In' UNION ALL
SELECT 'STORE 001', 1, 'Out' UNION ALL
SELECT 'STORE 004', 1, 'In' UNION ALL
SELECT 'STORE 004', 4, 'In' UNION ALL
SELECT 'STORE 004', 4, 'Out' UNION ALL
SELECT 'STORE 004', 1, 'Out' UNION ALL
SELECT 'STORE 001', 1, 'In'
DROP TABLE #t
How to show only the items with the corresponding location having maximum number of "Ins" when compared with "Outs" (sorry for my bad english).
LocationCode ResourceId
STORE 001[edited] 1
STORE 002 2
STORE 003 3
Assuming you only want Ins where there isn't a matching Out.
SELECT *
FROM #t AS a
WHERE a.TransType = 'In'
AND NOT EXISTS (
SELECT *
FROM #t AS b
WHERE b.TransType = 'Out'
AND b.LocationCode = a.LocationCode
AND b.ResourceId = a.ResourceId
)
You'd need more data in your schema to be able to match an Out with an In by time.
Try something simpler like this:
SELECT LocationCode, ResourceID
FROM #t
GROUP BY LocationCode, ResourceID
HAVING COUNT(*) % 2 = 1
Here's an example where the transactions are sequenced and two ways to use that sequence:
CREATE TABLE #t(LocationCode varchar(10), ResourceId int, TransType char(3), Seq int UNIQUE NOT NULL)
INSERT #t
SELECT 'STORE 001', 1, 'In', 1 UNION ALL
SELECT 'STORE 002', 2, 'In', 2 UNION ALL
SELECT 'STORE 003', 3, 'In', 3 UNION ALL
SELECT 'STORE 001', 1, 'Out', 4 UNION ALL
SELECT 'STORE 004', 1, 'In', 5 UNION ALL
SELECT 'STORE 004', 4, 'In', 6 UNION ALL
SELECT 'STORE 004', 4, 'Out', 7 UNION ALL
SELECT 'STORE 004', 1, 'Out', 8 UNION ALL
SELECT 'STORE 001', 1, 'In', 9
;WITH Ins AS (
SELECT * FROM #t
WHERE TransType = 'In'
)
,Outs AS (
SELECT * FROM #t
WHERE TransType = 'Out'
)
,Matched AS (
SELECT *,
(SELECT MIN(Seq)
FROM Outs
WHERE Outs.LocationCode = Ins.LocationCode
AND Outs.ResourceID = Ins.ResourceID
AND Outs.Seq > Ins.Seq) AS OutSeq
FROM Ins
)
SELECT *
FROM Matched
WHERE OutSeq IS NULL
;WITH LastIn AS (
SELECT ResourceID, MAX(Seq) AS Seq
FROM #t
WHERE TransType = 'In'
GROUP BY ResourceID
)
SELECT *
FROM LastIn
WHERE NOT EXISTS (
SELECT *
FROM #t outs
WHERE outs.TransType = 'Out'
AND Outs.ResourceID = LastIn.ResourceID
AND outs.Seq > LastIn.Seq)
DROP TABLE #t