Get sum(a+b-c)and some individual fields like select name from table? - postgresql

SELECT (
SELECT SUM(qty)
FROM sk_lgr_hd
WHERE item_id = 4
AND tra_type = 'OS'
AND dept_id = 7
) + (
SELECT SUM(qty)
FROM sk_lgr_hd
WHERE item_id = 4
AND tra_type = 'IR'
AND dept_id = 7
) - (
SELECT SUM(qty)
FROM sk_lgr_hd
WHERE item_id = 4
AND tra_type = 'IS'
AND dept_id = 7
)
I can Get sum still i need to get dept_id,tra_type,id,..
i need some thing like this.
sum dept_id id
20 2 3

Related

Optimising T-SQL reporting performance

I have the table bellow, I need to delete opposite rows between two dates by pairs based on PerCode Value,
In fact, we delete rows inside the date range that have the same PerCode and have equal and opposite values.
The problem is that begin date and end date are provided by users as parameters while reporting but the query take too much time if i try to delete these at runtime.
Example:
Begin date = 01/01/2018
End date = 31/12/2018
I should delete rows 3 and 4.
Do u have any idea how to do that while optimising performance (the table have 200 Millions of rows)
+----+------------+---------+---------+-----------+
| Id | Date | PerCode | Value | IsDeleted |
+----+------------+---------+---------+-----------+
| 1 | 01/10/2017 | C1 | 10 | |
| 2 | 01/01/2018 | C1 | -10 | |
| 3 | 15/02/2018 | C2 | 20 | 1 |
| 4 | 10/03/2018 | C2 | -20 | 1 |
| 5 | 01/12/2018 | C3 | 15 | |
| 6 | 01/02/2019 | C3 | -15 | |
+----+------------+---------+---------------------+
I had a quick go at this, using a table variable to allow me to knock together a query using your test data. However, this might not perform well when used over 2 million rows?
DECLARE #table TABLE (id INT, [date] DATE, percode CHAR(2), [value] INT, isdeleted BIT);
INSERT INTO #table
SELECT 1, '20171001', 'C1', 10, NULL
UNION ALL
SELECT 2, '20180101', 'C1', -10, NULL
UNION ALL
SELECT 3, '20180215', 'C2', 20, NULL
UNION ALL
SELECT 4, '20180310', 'C2', -20, NULL
UNION ALL
SELECT 5, '20181201', 'C3', 15, NULL
UNION ALL
SELECT 6, '20190201', 'C3', -15, NULL;
DECLARE #date_from DATE = '20180101';
DECLARE #date_to DATE = '20181231';
WITH ordered AS (
SELECT
id,
percode,
[value],
ROW_NUMBER() OVER (PARTITION BY percode, [value] ORDER BY [value]) AS order_id
FROM
#table
WHERE
[date] BETWEEN #date_from AND #date_to
AND ISNULL(isdeleted, 0) != 1),
matches AS (
SELECT
m1.id AS match_1_id,
m2.id AS match_2_id
FROM
ordered m1
INNER JOIN ordered m2 ON m1.percode = m2.percode AND m1.[value] = m2.[value] * -1 AND m1.order_id = m2.order_id)
UPDATE
t
SET
isdeleted = 1
FROM
#table t
INNER JOIN matches m ON m.match_1_id = t.id OR m.match_2_id = t.id;
SELECT * FROM #table;
Results:
id date percode value isdeleted
1 2017-10-01 C1 10 NULL
2 2018-01-01 C1 -10 NULL
3 2018-02-15 C2 20 1
4 2018-03-10 C2 -20 1
5 2018-12-01 C3 15 NULL
6 2019-02-01 C3 -15 NULL
How does it work? Well I broke the task down into steps:
make a list of all rows in the date period specified, where they aren't already deleted;
for each row of data assign it a running count number, grouped by the percode and the value. So the first C1 10 would be number #1, then the second C1 10 would be number #2, etc.;
to find matches it's simply a case of finding any value that has the same percode, the equal and opposite value to another value group, and the same running count number;
where there's a match set the isdeleted flag to 1.
Here is my code but this is not performant over 200 millions rows in real time.
and in real life Percode is concatenation of 5 columns (date, varchar(13), varchar(2),varchar(1) and varchar(50)) and Value is 4 numeric columns.
I am searching for other ideas.
--DECLARE #table TABLE (id INT, [date] DATE, percode CHAR(2), [value] INT, isdeleted BIT);
Select * INTO #MasterTable FROM
(
SELECT 1 id, '20171001' [date], 'C1' percode, 10 [value], NULL isdeleted
UNION ALL
SELECT 2, '20180101', 'C1', -10, NULL
UNION ALL
SELECT 3, '20180215', 'C2', 20, NULL
UNION ALL
SELECT 4, '20180310', 'C2', -20, NULL
UNION ALL
SELECT 5, '20181201', 'C3', 15, NULL
UNION ALL
SELECT 6, '20190201', 'C3', -15, NULL
) T ;
DECLARE #date_from DATE = '20180101';
DECLARE #date_to DATE = '20181231';
select F.id
Into #TmpTable
from
(
select Id, PerCode, Value
,ROW_NUMBER() over (partition by PerCode, Value order by (select 0)) Rn2
from
#MasterTable ) F
inner join (
select
PerCode
, Rn1
from (
select
PerCode
,Value
,ROW_NUMBER() over (partition by PerCode, Value order by (select 0)) Rn1
FROM #MasterTable
where
[date] BETWEEN #date_from AND #date_to
) A
group by PerCode , Rn1
having sum(Value) = 0 and count(*)>1
) B on F.PerCode = B.PerCode
and F.Rn2 = B.Rn1
update R
set IsDeleted = 1
from #MasterTable R
inner join #TmpTable P
on R.id = P.id
select * from #MasterTable
drop table #MasterTable ;
drop table #TmpTable;

Merging several joins into a single select / case

I am currently querying a table 4 times using different criteria and then using left joins as part of a much larger query to return all the data. The larger query does not run particularly quickly and I am fairly certain that my current approach is not efficient.
What I am wondering is if it is possible to somehow use a CASE statement to increment one of the 4 columns. My 4 queries currently are:
SELECT ts.department,
Sum([hours]) AS ChargeableTimeYTD
FROM bwbfiles.sos.timesummary ts
WHERE category = 'C'
AND [year] = '2019'
GROUP BY department
SELECT ts.department,
Sum([hours]) AS ChargeableTimeMTD
FROM bwbfiles.sos.timesummary ts
WHERE category = 'C'
AND [year] = '2019'
AND [period] = 4
GROUP BY department
SELECT ts.department,
Sum([hours]) AS NonChargeableTimeProBono
FROM bwbfiles.sos.timesummary ts
WHERE category = 'NC'
AND ( [act_code] = '001N'
OR [act_code] = '00N6' )
AND [year] = '2019'
GROUP BY department
SELECT ts.department,
Sum([hours]) AS NonChargeableTimeNonProBono
FROM bwbfiles.sos.timesummary ts
WHERE category = 'NC'
AND ( [act_code] <> '001N'
AND [act_code] <> '00N6' )
AND [year] = '2019'
GROUP BY department
The aim would be to end up with a query result with 5 columns
Department, ChargeableTimeYTD, ChargeableTimeMTD, NonChargeableTimeProBono, NonChargeableTimeNonProBono
Or instead of CASE would I remove the group by department from each bit and have a query that produced 3 columns
Department, Hours, Category (where Category is ChargeableTimeYTD/ChargeableTimeMTD etc...etc...) and then pivot that into 5 columns.
Thanks in advance!
This may do what i think you're asking for
SELECT ts.department,
Sum(case when category = 'C' then [hours] else 0 end) AS ChargeableTimeYTD,
Sum(case when category = 'C' and [period] = 4 then [hours] else 0 end) AS ChargeableTimeMTD,
Sum(case when category = 'NC' and ([act_code] = '001N' or [act_code] = '00N6') then [hours] else 0 end) AS NonChargeableTimeProBono,
Sum(case when category = 'NC' and ([act_code] <> '001N' or [act_code] <> '00N6') then [hours] else 0 end) AS NonChargeableTimeNonProBono
FROM bwbfiles.sos.timesummary ts
where [year] = '2019'
and [category] in ('C','NC')
GROUP BY department

SQL adding numbers column

Let's say I have a table:
Table1
ID | Table2_ID | Title
1 1 Breaking_Bad
2 1 Breaking_Bad
3 2 Simpsons
4 1 House_Of_Cards
I want to rename the title by adding '_XX' (where XX is a number) to only to those entries that are the same title and have the same Table2_ID.
So end results would be
Table1
ID | Table2_ID | Title
1 1 Breaking_Bad_01
2 1 Breaking_Bad_02
3 2 Simpsons
4 1 House_Of_Cards
How could I do this with TSQL?
You can do this with
WITH T
AS (SELECT *,
COUNT(*) OVER (PARTITION BY Table2_ID, Title) AS Cnt,
ROW_NUMBER() OVER (PARTITION BY Table2_ID, Title ORDER BY ID) AS RN
FROM Table1)
UPDATE T
SET Title = Title + '_' + FORMAT(RN, 'D2')
WHERE Cnt > 1;
SQL Fiddle
Or if you are on a version without FORMAT
SET Title = Title + CASE WHEN RN < 10 THEN '_0' ELSE '_' END + CAST(RN AS VARCHAR(10))
Will this work in SQL Server? The padding to 2 digits would be straightforward.
UPDATE TABLE1 A SET TITLE = TITLE + '_' + (SELECT COUNT(*) FROM TABLE1 B WHERE A.TITLE=B.TITLE AND A.ID<=B.ID) WHERE A.ID IN (SELECT B.ID FROM TABLE1 B
WHERE A.Id<>B.ID and A.TITLE=B.TITLE)

Capture First Character of Last Group of 1s in a Binary Series Part II: Multiple IDs

I have data something like this:
ID 1 1 1 1 1 1 1 1 1 1 1 1
Month J F M A M J J A S O N D
Status 1 0 0 1 0 1 0 0 1 1 1 1
ID 2 2 2 2 2 2 2 2 2 2 2 2
Month J F M A M J J A S O N D
Status 1 0 1 0 1 0 1 0 1 0 1 1
ID 3 3 3 3 3 3 3 3 3 3 3 3
Month J F M A M J J A S O N D
Status 0 0 0 0 0 0 0 0 0 0 0 1
Using t-SQL, I am trying to capture the month corresponding to the first STATUS = 1 in the last group of 1s for each ID, i.e., September, November and December in this example.
Here is the code I'm using:
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL DROP TABLE #Temp1
;WITH PARTITIONED1 AS
(SELECT t0.ID
, t0.Year_Month
, LAST_VALUE(t0.Year_Month) OVER (PARTITION BY t0.Account_Number ORDER BY t0.Year_Month) AS STATUS
, ROW_NUMBER() OVER (PARTITION BY t0.Account_Number ORDER BY t0.Year_Month) AS rn1
FROM #Temp0 t0
)
SELECT *
INTO #Temp1
FROM PARTITIONED1 p1
ORDER BY t0.ID
, t0.Year_Month
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
SELECT *
INTO #Temp
FROM #Temp1 t1
WHERE t1.rn1 = (SELECT MAX(b.rn1) + 1 FROM #Temp1 b WHERE b.STATUS = 0)
GROUP BY t1.ID
, t1.Year_Month
, t1.rn1
However, this just returns the last instance where STATUS = 1 is achieved overall as the first 1 of the last group of 1s, in this case January.
I've tried using CASE statements and grouping in various combinations (hence the intermediate step reading the data into #Temp1), but have not been able to get results for all three IDs; is anyone able to assist?
Thanks in advance!
Assuming Ju for June and Jl for July:
--Sample Data
IF OBJECT_ID('tempdb..#Temp0') IS NOT NULL DROP TABLE #Temp0
CREATE TABLE #Temp0 (ID INT, Year_Month VARCHAR(1), Status INT)
INSERT INTO #Temp0
VALUES(1,'J',1),(1,'F',0),(1,'M',0),(1,'A',1),(1,'M',0),(1,'J',1),(1,'J',0),(1,'A',0),(1,'S',1),(1,'O',1),(1,'N',1),(1,'D',1),(2,'J',1),(2,'F',0),(2,'M',1),(2,'A',0),(2,'M',1),(2,'J',0),(2,'J',1),(2,'A',0),(2,'S',1),(2,'O',0),(2,'N',1),(2,'D',1),(3,'J',0),(3,'F',0),(3,'M',0),(3,'A',0),(3,'M',0),(3,'J',0),(3,'J',0),(3,'A',0),(3,'S',0),(3,'O',0),(3,'N',0),(3,'D',1);
--Query
WITH A
AS ( SELECT *,
CASE Year_Month
WHEN 'J' THEN 1
WHEN 'F' THEN 2
WHEN 'M' THEN 3
WHEN 'A' THEN 4
WHEN 'M' THEN 5
WHEN 'Ju' THEN 6
WHEN 'Jl' THEN 7
WHEN 'A' THEN 8
WHEN 'S' THEN 9
WHEN 'O' THEN 10
WHEN 'N' THEN 11
WHEN 'D' THEN 12
END
AS MonthNumber
FROM #Temp0 ),
StartingPoints
AS ( SELECT ID,
Year_Month,
MonthNumber,
Status
FROM A
WHERE NOT EXISTS
(
SELECT 1
FROM A
AS B
WHERE B.ID=A.ID
AND B.Status=A.Status-1
) ),
MonthRanking
AS ( SELECT A.*,
ROW_NUMBER( ) OVER( PARTITION BY A.ID ORDER BY A.MonthNumber )
AS rownum
FROM A
INNER JOIN
(
SELECT ID,
MAX( MonthNumber )+1
AS StartOfLastGroup
FROM StartingPoints
GROUP BY ID
)
AS B
ON A.ID=B.ID
AND A.MonthNumber>=B.StartOfLastGroup )
SELECT *
FROM MonthRanking
WHERE rownum=1;
Results:
If Month Names are recorded in Full as in July, June then this would work as well:
WITH StartingPoints
AS (SELECT ID,
Year_Month,
MonthNUmber = MONTH('01-'+Year_Month+'-2010'),
Status
FROM #Temp0
WHERE NOT EXISTS
(
SELECT 1
FROM #Temp0 AS B
WHERE B.ID = #Temp0.ID
AND B.Status = #Temp0.Status - 1
)),
MonthRanking
AS (SELECT A.*,
ROW_NUMBER() OVER(PARTITION BY A.ID ORDER BY MONTH('01-'+A.Year_Month+'-2010')) AS rownum
FROM #Temp0 AS A
INNER JOIN
(
SELECT ID,
MAX(MonthNumber) + 1 AS StartOfLastGroup
FROM StartingPoints
GROUP BY ID
) AS B ON A.ID = B.ID
AND MONTH('01-'+A.Year_Month+'-2010') >= B.StartOfLastGroup)
SELECT *
FROM MonthRanking
WHERE rownum = 1;
Results:
And if we assume that the data is as Iamdave assumes then it simply like so:
WITH StartingPoints
AS (SELECT ID,
Year_Month,
Status
FROM #Temp0
WHERE NOT EXISTS
(
SELECT 1
FROM #Temp0 AS B
WHERE B.ID = #Temp0.ID
AND B.Status = #Temp0.Status - 1
)),
MonthRanking
AS (SELECT A.*,
ROW_NUMBER() OVER(PARTITION BY A.ID ORDER BY Year_Month) AS rownum
FROM #Temp0 AS A
INNER JOIN
(
SELECT ID,
MAX(Year_Month) + 1 AS StartOfLastGroup
FROM StartingPoints
GROUP BY ID
) AS B ON A.ID = B.ID
AND A.Year_Month >= B.StartOfLastGroup)
SELECT *
FROM MonthRanking
WHERE rownum = 1;
Results:
You can do this with a couple derived tables that stack two window functions on top of one another (which can't be done in the same select). I have assumed that your data is slightly different to the table you have provided, based on the column names in your query. If they are not as I have them below, I strongly recommend having a look at how you store your data:
declare #t table(ID int, YearMonth int,StatusValue bit);
insert into #t values (1,201501,1),(1,201502,0),(1,201503,0),(1,201504,1),(1,201505,0),(1,201506,1),(1,201507,0),(1,201508,0),(1,201509,1),(1,201510,1),(1,201511,1),(1,201512,1),(2,201601,1),(2,201602,0),(2,201603,1),(2,201604,0),(2,201605,1),(2,201606,0),(2,201607,1),(2,201608,0),(2,201609,1),(2,201610,0),(2,201611,1),(2,201612,1),(3,201701,0),(3,201702,0),(3,201703,0),(3,201704,0),(3,201705,0),(3,201706,0),(3,201707,0),(3,201708,0),(3,201709,0),(3,201710,0),(3,201711,0),(3,201712,1);
with c as
(
select ID
,YearMonth
,StatusValue
,case when StatusValue = 1
and lead(StatusValue,1,1) over (partition by ID
order by YearMonth desc) = 0
then 1
else 0
end as c
from #t
), sc as
(
select ID
,YearMonth
,StatusValue
,sum(c) over (partition by ID order by YearMonth desc) as sc
from c
where c = 1
)
select ID
,YearMonth
,StatusValue
from sc
where sc = 1
order by ID;
Output:
+----+-----------+-------------+
| ID | YearMonth | StatusValue |
+----+-----------+-------------+
| 1 | 201509 | 1 |
| 2 | 201611 | 1 |
| 3 | 201712 | 1 |
+----+-----------+-------------+

Loop with inner loop and split

I have records like this in a table called "Entry":
TABLE: Entry
ID Tags
--- ------------------------------------------------------
1 Coffee, Tea, Cake, BBQ
2 Soda, Lemonade
...etc.
TABLE: Tags
ID TagName
---- -----------
1 Coffee
2 Tea
3 Soda
...
TABLE: TagEntry
ID TAGID ENTRYID
--- ----- -------
1 1 1
2 2 1
3 3 2
....
I need to loop through each record in the entire table for Entry, then for each row loop the comma delimited tags because I need to split each tag then do a Tag lookup based on tag name to grab the TagID, and then ultimately insert TagID, EntryID in a bridge table called TagEntry for each comma delimited tag
Not sure how to go about this.
Try this
;with entry as
(
select 1 id, 'Coffee, Tea, Cake, BBQ' tags
Union all
select 2, 'Soda, Lemonade'
), tags as
(
select 1 id,'Coffee' TagName union all
select 2,'Tea' union all
select 3,'Soda'
), entryxml as
(
SELECT id, ltrim(rtrim(r.value('.','VARCHAR(MAX)'))) as Item from (
select id, CONVERT(XML, N'<root><r>' + REPLACE(tags,',','</r><r>') + '</r></root>') as XmlString
from entry ) x
CROSS APPLY x.XmlString.nodes('//root/r') AS RECORDS(r)
)
select e.id EntryId, t.id TagId from entryxml e
inner join tags t on e.Item = t.TagName
This SQL will split your Entry table, for joining to the others:
with raw as (
select * from ( values
(1, 'Coffee, Tea, Cake, BBQ'),
(2, 'Soda, Lemonade')
) Entry(ID,Tags)
)
, data as (
select ID, Tag = convert(varchar(255),' '), Tags, [Length] = len(Tags) from raw
union all
select
ID = ID,
Tag = case when charindex(',',Tags) = 0 then Tags else convert(varchar(255), substring(Tags, 1, charindex(',',Tags)-1) ) end,
Tags = substring(Tags, charindex(',',Tags)+1, 255),
[Length] = [Length] - case when charindex(',',Tags) = 0 then len(Tags) else charindex(',',Tags) end
from data
where [Length] > 0
)
select ID, Tag = ltrim(Tag)
from data
where Tag <> ''
and returns this for the given input:
ID Tag
----------- ------------
2 Soda
2 Lemonade
1 Coffee
1 Tea
1 Cake
1 BBQ