So I have this follwing DAX code for a Measure. What I am trying to do is replace the Billdetail[SOurceWasteServiceID] with another column ,BillDetail[SourceServiceMapID]. But the problem is that for a single SourceWasteServiceID, I can have multiple records for SourceServiceMapID. And since the data has to be grouped together, I cant just directly replace the one with other. This table does have an IsCurrent flag in the table, which is "1" for the latest record. I tried to use this IsCurrent in Filter statement but still I get mismatch data.
Anybody have any suggestions on how can I change this?
Thanks in advance for the help!!
Sum of Volume:=CALCULATE(
SUMX(
Summarize(BillDetail
,BillDetail[SourceWasteServiceID]
,BillDetail[ActualBillMonth]
,WasteServiceMap[ContainerCount]
,WasteServiceMap[WasteContainerSizeQuantity]
,WasteServiceMap[WasteContainerSizeUnit]
,WasteServiceMap[WastePickupSchedule]
,WasteServiceMap[WastePickupFrequencyMultiplier]
,WasteServiceMap[PercentFull]
,WasteServiceMap[CompactionRatio]
,"ItemQuantity", CALCULATE(Sum(BillDetail[ActualItemQuantity]),BillDetail[AlternateBillDetailKey] = True)
)
,IF ( UPPER((WasteServiceMap[WastePickupSchedule])) = "FIXED"
,(WasteServiceMap[ContainerCount])
* (WasteServiceMap[WasteContainerSizeQuantity])
*(IF(WasteServiceMap[WastePickupFrequencyMultiplier] = -1,0,WasteServiceMap[WastePickupFrequencyMultiplier]))
* (WasteServiceMap[PercentFull])
* (WasteServiceMap[CompactionRatio])
*IF(UPPER((WasteServiceMap[WasteContainerSizeUnit])) = "GALLONS"
, 0.00495113169
, IF(UPPER((WasteServiceMap[WasteContainerSizeUnit])) = "LITERS"
, 0.00130795062
,IF(UPPER((WasteServiceMap[WasteContainerSizeUnit])) = "YARDS"
,1
,BLANK())
)
)
, IF ( OR(OR(OR(UPPER((WasteServiceMap[WastePickupSchedule])) = "ON CALL" ,UPPER((WasteServiceMap[WastePickupSchedule])) = "MAILBACK"),UPPER((WasteServiceMap[WastePickupSchedule])) = "HAND PICKUP"),UPPER((WasteServiceMap[WastePickupSchedule])) = "SCHEDULED ONCALL")
, (WasteServiceMap[WasteContainerSizeQuantity])
* (WasteServiceMap[CompactionRatio])
* (WasteServiceMap[PercentFull])
* ([ItemQuantity])
*IF(UPPER((WasteServiceMap[WasteContainerSizeUnit])) = "GALLONS"
, 0.00495113169
, IF(UPPER((WasteServiceMap[WasteContainerSizeUnit])) = "LITERS"
, 0.00130795062
,IF(UPPER((WasteServiceMap[WasteContainerSizeUnit])) = "YARDS"
,1
,BLANK())
)
)
, 0
)
)
)
)
You know... example you provided does not look like just a problem related to joining latest records to some "base" records, but... if it IS related despite all that, we can "play" with this problem a little bit. Just for fun.
Let's say we have two very simple tables in our database
create table parent_table
(
parent_id int identity(1, 1) primary key,
some_value nvarchar(100)
);
create table child_table
(
child_id int identity(1, 1) primary key,
parent_id int,
is_current bit,
some_value nvarchar(100)
);
with some meaningless, but related data
insert into parent_table (some_value)
values ('value 1'),('value 2'),('value 3'),('value 4');
insert into child_table (parent_id, is_current, some_value) values
(1, 1, 'value 1.1'),
(2, 0, 'value 2.1'),
(2, 0, 'value 2.2'),
(2, 1, 'value 2.3'),
(3, 0, 'value 3.1'),
(3, 1, 'value 3.2'),
(4, 0, 'value 4.1'),
(4, 1, 'value 4.2');
And... we want to find only current child data for every parent row.
If we wrote a query on T-SQL it could look as something like this
select p.parent_id
, p.some_value [parent_value]
, c.some_value [current_child_value]
from parent_table p
left join child_table c on p.parent_id = c.parent_id
and c.is_current = 1;
(4 row(s) affected)
parent_id parent_value current_child_value
-----------------------------------------------
1 value 1 value 1.1
2 value 2 value 2.3
3 value 3 value 3.2
4 value 4 value 4.2
Now we could try to build some simple tabular model on top on these tables
and write a DAX query against it
evaluate
filter (
addcolumns(
child_table,
"parent_value", related(parent_table[some_value])
),
child_table[is_current] = True
)
having received almost the same results as using T-SQL
child_table[child_id] child_table[parent_id] child_table[is_current] child_table[some_value] [parent_value]
------------------------------------------------------------------------------------------------------------------
8 4 True value 4.2 value 4
6 3 True value 3.2 value 3
4 2 True value 2.3 value 2
1 1 True value 1.1 value 1
I hope it's helpful enough for you to solve your problem or at least it can point you to the right direction
Related
There is a SQL Server database temporary table, let it be TableA. And the table structure is following:
CREATE TABLE #TableA
(
ID BIGINT IDENTITY (1, 1) PRIMARY KEY,
MapVal1 BIGINT NOT NULL,
MapVal2 BIGINT NOT NULL,
IsActual BIT NULL
)
The table is already filled with some mappings of MapVal1 to MapVal2. The issue is that not all the mappings should be flagged as Actual. For this reason should be used IsActual column. Currently IsActual is set to NULL for every row. The task is to create the query for updating IsActual column value. UPDATE query should follow next conditions:
If MapVal1 is unique and MapVal2 is unique (one-to-one mapping) - then this mapping should be flagged as Actual, so IsActual = 1;
If MapVal1 is not unique - then Actual should be the mapping of current MapVal1 to smallest MapVal2, and this MapVal2 must be not mapped to any other MapVal1 that is smaller than current MapVal1;
If MapVal2 is not unique - then Actual should be the mapping of current MapVal2 to smallest MapVal1, and this MapVal1 must be not mapped to any other MapVal2 that is smaller than current MapVal2;
All rows that are not fulfill any of 1), 2) or 3) conditions - should be flagged as inactual, so IsActual = 0.
I believe there is relation between Condition 2) and Condition 3). For every row they both are fulfilled or both are not.
To make it clear, here is an example of result I want to obtain:
Result should be that every MapVal1 is mapped to just one MapVal2 and vice varsa every MapVal2 is mapped to just one MapVal1.
I have created sql-query to resolve my task:
IF OBJECT_ID('tempdb..#TableA') IS NOT NULL
BEGIN
DROP TABLE #TableA
END
CREATE TABLE #TableA
(
ID BIGINT IDENTITY (1, 1) PRIMARY KEY,
MapVal1 BIGINT NOT NULL,
MapVal2 BIGINT NOT NULL,
IsActual BIT NULL
)
-- insert input data
INSERT INTO #TableA (MapVal1, MapVal2)
SELECT 1, 1
UNION ALL SELECT 1, 3
UNION ALL SELECT 1, 4
UNION ALL SELECT 2, 1
UNION ALL SELECT 2, 3
UNION ALL SELECT 2, 4
UNION ALL SELECT 3, 3
UNION ALL SELECT 3, 4
UNION ALL SELECT 4, 3
UNION ALL SELECT 4, 4
UNION ALL SELECT 6, 7
UNION ALL SELECT 7, 8
UNION ALL SELECT 7, 9
UNION ALL SELECT 8, 8
UNION ALL SELECT 8, 9
UNION ALL SELECT 9, 8
UNION ALL SELECT 9, 9
CREATE NONCLUSTERED INDEX IX_Mapping_MapVal1 ON #TableA (MapVal1);
CREATE NONCLUSTERED INDEX IX_Mapping_MapVal2 ON #TableA (MapVal2);
-- UPDATE of #TableA is starting here
-- every one-to-one mapping should be actual
UPDATE m1 SET
m1.IsActual = 1
FROM #TableA m1
LEFT JOIN #TableA m2
ON m1.MapVal1 = m2.MapVal1 AND m1.ID <> m2.ID
LEFT JOIN #TableA m3
ON m1.MapVal2 = m3.MapVal2 AND m1.ID <> m3.ID
WHERE m2.ID IS NULL AND m3.ID IS NULL
-- update for every one-to-many or many-to-many mapping is more complicated
-- would be great to change this part of query to make it witout any LOOP
DECLARE #MapVal1 BIGINT
DECLARE #MapVal2 BIGINT
DECLARE #i BIGINT
DECLARE #iMax BIGINT
DECLARE #LoopCount INT = 0
SELECT
#iMax = MAX (m.ID)
FROM #TableA m
SELECT
#i = MIN (m.ID)
FROM #TableA m
WHERE m.IsActual IS NULL
WHILE #i <= #iMax
BEGIN
SELECT #LoopCount = #LoopCount + 1
SELECT
#MapVal1 = m.MapVal1,
#MapVal2 = m.MapVal2
FROM #TableA m
WHERE m.ID = #i
IF EXISTS
(
SELECT *
FROM #TableA m
WHERE
m.ID < #i
AND
(m.MapVal1 = #MapVal1
OR m.MapVal2 = #MapVal2)
AND m.IsActual IS NULL
)
BEGIN
UPDATE m SET
m.IsActual = 0
FROM #TableA m
WHERE m.ID = #i
END
SELECT #i = MIN (m.ID)
FROM #TableA m
WHERE
m.ID > #i
AND m.IsActual IS NULL
END
UPDATE m SET
m.IsActual = 1
FROM #TableA m
WHERE m.IsActual IS NULL
SELECT * FROM #TableA
but as it was expected performance of the query with LOOP is very bad, specially when input table keep millions of rows. I spent a lot of time trying to produce query without LOOP to get reduce execution time of my query but unsuccessfully.
Could anybody advice me how to improve performance of my query. It would be great to get query without LOOP.
Using a loop does not imply you need to update the table one record at a time.
It may help if each individual UPDATE statement updates multiple records.
Consider all possible combinations of MapVal1 and MapVal2 as a matrix.
Every time you flag a cell as 'actual', you can flag an entire row and an entire column as 'not actual'.
The simplest way to do this, is by following these steps.
Of all mappings with IsActual = NULL, take the first one (smallest MapVal1, together with the smallest MapVal2 it is mapped to).
Flag this mapping as actual (IsActual = 1).
Flag all other mappings with the same MapVal1 as non-actual (IsActual = 0).
Flag all other mappings with the same MapVal2 as non-actual (IsActual = 0).
Repeat from step 1 until no more records with IsActual = NULL exist.
Here's an implementation:
SELECT 0 -- force ##ROWCOUNT initially 1
WHILE ##ROWCOUNT > 0
WITH MakeActual AS (
SELECT TOP 1 MapVal1, MapVal2
FROM #TableA
WHERE IsActual IS NULL
ORDER BY MapVal1, MapVal2
)
UPDATE a
SET IsActual = CASE WHEN a.MapVal1 = m.MapVal1 AND a.MapVal2 = m.MapVal2 THEN 1 ELSE 0 END
FROM #TableA a
INNER JOIN MakeActual m ON a.MapVal1 = m.MapVal1 OR a.MapVal2 = m.MapVal2
The number of loop iterations equals the number of 'actual' mappings.
The actual performance gain depends a lot on the data.
If the majority of mappings is one-to-one (i.e. hardly any non-actual mappings), then my algorithm will make little difference.
Therefore, it may be wise to keep the initial UPDATE statement from your own code sample (the one with the comment "every one-to-one mapping should be actual").
It may also help to play around with the indexes.
This one should help to further optimize the clause ORDER BY MapVal1, MapVal2:
CREATE NONCLUSTERED INDEX IX_MapVals ON #TableA (MapVal1, MapVal2)
i have in sql table values in this way:
Id GameId GameSupplierId
1 1 NULL
2 2 NULL
3 3 1
4 3 2
5 3 3
What i want is to filter in sql procedure by GameId and if there is GameSupplierId by supplier too. I will get string from my web page in format GameID ; GameSupplierId. For example:
1; NULL
2; NULL
or if there is GameSupplier too
3;1
3;1,2
Also i want to have multiple choice for example like this:
1,2,3;1,2
In my sql query i will then filter like WHERE #GameID = Table.GameID (and also to check #GameSupplierId IN (,,,))
Just add your desired columns into ORDER BY:
ORDER BY t.GameId, t.GameSuplierId
For example:
DECLARE #table TABLE
(
ID INT,
GameId INT,
GameSuplierId INT NULL
)
INSERT INTO #table
(
ID,
GameId,
GameSuplierId
)
VALUES
(1, 1, NULL)
, (2, 2, NULL)
, (3, 3, 1)
, (4, 3, 2)
, (5, 3, 3)
SELECT
*
FROM #table t
ORDER BY t.GameId, t.GameSuplierId
The Structure
I have 2 tables that link to each other. One is a set of values and a nullable foreign key that points to the Id of the other table, which contains 2 foreign keys back to the other table.
HierarchicalTable
Id LeftId RightId SomeValue
1 1 2 some value
2 3 4 top level in tree
3 5 6 incorrect hierarchy 1
4 7 8 incorrect result top level
IntermediateTable
Id SomeValue HierarchicalTableId
1 some value NULL
2 value NULL
3 NULL 1
4 value NULL
5 incorrect result 1 NULL
6 incorrect result 3 NULL
7 incorrect result 3 NULL
8 NULL 3
Each table points down the hierarchy. Here is this structure graphed out for the Hierarchical Table records 1 & 2 and their IntermediateTable values:
(H : HierarchicalTable, I : IntermediateTable)
H-2
/ \
I-3 I-4
/
H-1
/ \
I-1 I-2
The Problem
I need to be able to send in an Id for a given HierarchicalTable and get all the HierarchicalTable records below it. So, for the structure above, if I pass 1 into a query, I should just get H-1 (and from that, I can load the related IntermediateTable values). If I pass 2, I should get H-2 and H-1 (and, again, use those to load the relevant IntermediateTable values).
The Attempts
I've tried using a CTE, but there are a few main things that are different from the examples I've seen:
In my structure, the objects point down to their children, instead of up to their parent
I have the Id of the top object, not the Id of the bottom object.
My hierarchy is split across 2 tables. This shouldn't be a big issue once I understand the algorithm to find the results I need, but this could be causing additional confusion for me.
If I run this query:
declare #TargetId bigint = 2
;
with test as (
select h.*
from dbo.hierarchicaltable h
inner join dbo.intermediatetable i
on (h.leftid = i.id or h.rightid = i.id)
union all
select h.*
from dbo.hierarchicaltable h
where h.id = #TargetId
)
select distinct *
from test
I get all 4 records in the HierarchicalTable, instead of just records 1 & 2. I'm not sure if what I want is possible to do with a CTE.
Try this:
I'm build entire tree with both tables, then filter (only hierarchicaltable records).
DECLARE #HierarchicalTable TABLE(
Id INT,
LeftId INT,
RightId INT,
SomeValue VARCHAR(MAX)
)
INSERT INTO #HierarchicalTable
VALUES
(1, 1, 2, 'some value '),
(2, 3, 4, 'top level in tree '),
(3, 5, 6, 'incorrect hierarchy 1 '),
(4, 7, 8, 'incorrect result top level')
DECLARE #IntermediateTable TABLE(
Id INT,
SomeValue VARCHAR(MAX),
HierarchicalTableId INT
)
INSERT INTO #IntermediateTable
VALUES
(1, 'some value' ,NULL ),
(2, 'value ' ,NULL ),
(3, NULL ,1 ),
(4, 'value ' ,NULL ),
(5, 'incorrect result 1' ,NULL ),
(6, 'incorrect result 3' ,NULL ),
(7, 'incorrect result 3' ,NULL ),
(8, NULL ,3 )
DECLARE #TargetId INT = 2;
WITH CTE AS (
SELECT Id AS ResultId, LeftId, RightId, NULL AS HierarchicalTableId
FROM #HierarchicalTable
WHERE Id = #TargetId
UNION ALL
SELECT C.Id AS ResultId, C.LeftId, C.RightId, NULL AS HierarchicalTableId
FROM #HierarchicalTable C
INNER JOIN CTE P ON P.HierarchicalTableId = C.Id
UNION ALL
SELECT NULL AS ResultId, NULL AS LeftId, NULL AS RightId, C.HierarchicalTableId
FROM #IntermediateTable C
INNER JOIN CTE P ON P.LeftId = C.Id OR P.RightId = C.Id
)
SELECT *
FROM CTE
WHERE ResultId IS NOT NULL
I have a table MODELS to which several ITEMS can belong. The ITEMS table is a hierarchical table with a self join on the PARENT column. Root level items will have Null in PARENT. Items can go to any level deep.
create table MODELS (
MODELID int identity,
MODELNAME nvarchar(200) not null,
constraint PK_MODELS primary key (MODELID)
)
go
create table ITEMS (
ITEMID int identity,
MODELID int not null,
PARENT int null,
ITEMNUM nvarchar(20) not null,
constraint PK_ITEMS primary key (ITEMID)
)
go
alter table ITEMS
add constraint FK_ITEMS_MODEL foreign key (MODELID)
references MODELS (MODELID)
go
alter table ITEMS
add constraint FK_ITEMS_ITEMS foreign key (PARENT)
references ITEMS (ITEMID)
go
I wish to create stored procedure to copy a row in the MODELS table into a new row and also copy the entire structure in ITEMS as well.
For example, if I have the following in ITEMS:
ITEMID MODELID PARENT ITEMNUM
1 1 Null A
2 1 Null B
3 1 Null C
4 1 1 A.A
5 1 2 B.B
6 1 4 A.A.A
7 1 4 A.A.B
8 1 3 C.A
9 1 3 C.B
10 1 9 C.B.A
I'd like to create new Model row and copies of the 10 Items that should be as follows:
ITEMID MODELID PARENT ITEMNUM
11 2 Null A
12 2 Null B
13 2 Null C
14 2 11 A.A
15 2 12 B.B
16 2 14 A.A.A
17 2 14 A.A.B
18 2 13 C.A
19 2 13 C.B
20 2 19 C.B.A
I will pass the MODELID to be copied as a parameter to the Stored Procedure. The tricky part is setting the PARENT column correctly. I think this will need to be done recursively.
Any suggestions?
The solution described here will work correctly in multi-user environment. You don't need to lock the whole table. You don't need to disable self-referencing foreign key. You don't need recursion.
(ab)use MERGE with OUTPUT clause.
MERGE can INSERT, UPDATE and DELETE rows. In our case we need only to INSERT. 1=0 is always false, so the NOT MATCHED BY TARGET part is always executed. In general, there could be other branches, see docs. WHEN MATCHED is usually used to UPDATE; WHEN NOT MATCHED BY SOURCE is usually used to DELETE, but we don't need them here.
This convoluted form of MERGE is equivalent to simple INSERT, but unlike simple INSERT its OUTPUT clause allows to refer to the columns that we need. It allows to retrieve columns from both source and destination tables thus saving a mapping between old and new IDs.
sample data
DECLARE #Items TABLE (
ITEMID int identity,
MODELID int not null,
PARENT int null,
ITEMNUM nvarchar(20) not null
)
INSERT INTO #Items (MODELID, PARENT, ITEMNUM) VALUES
(1, Null, 'A'),
(1, Null, 'B'),
(1, Null, 'C'),
(1, 1 , 'A.A'),
(1, 2 , 'B.B'),
(1, 4 , 'A.A.A'),
(1, 4 , 'A.A.B'),
(1, 3 , 'C.A'),
(1, 3 , 'C.B'),
(1, 9 , 'C.B.A');
I omit the code that duplicates the Model row. Eventually you'll have ID of original Model and new Model.
DECLARE #SrcModelID int = 1;
DECLARE #DstModelID int = 2;
Declare a table variable (or temp table) to hold the mapping between old and new item IDs.
DECLARE #T TABLE(OldItemID int, NewItemID int);
Make a copy of Items remembering the mapping of IDs in the table variable and keeping old PARENT values.
MERGE INTO #Items
USING
(
SELECT ITEMID, PARENT, ITEMNUM
FROM #Items AS I
WHERE MODELID = #SrcModelID
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT (MODELID, PARENT, ITEMNUM)
VALUES
(#DstModelID
,Src.PARENT
,Src.ITEMNUM)
OUTPUT Src.ITEMID AS OldItemID, inserted.ITEMID AS NewItemID
INTO #T(OldItemID, NewItemID)
;
Update old PARENT values with new IDs
WITH
CTE
AS
(
SELECT I.ITEMID, I.PARENT, T.NewItemID
FROM
#Items AS I
INNER JOIN #T AS T ON T.OldItemID = I.PARENT
WHERE I.MODELID = #DstModelID
)
UPDATE CTE
SET PARENT = NewItemID
;
Check the results
SELECT * FROM #Items;
You can do it without recursion. But you need to lock the table first maybe to be sure that it works fine.
insert into items (Modelid, Parent, ITEMNUM)
select 2 as modelId,
MAP.currId as Parent,
MO.ITEMNUM
from (
( select * from items where MODELID = 1) MO
left join
( select IDENT_CURRENT('ITEMS') + ROW_NUMBER() OVER(ORDER BY itemid ) currID ,
i.ItemID
from ITEMS i
where modelid = 1 ) MAP
on MO.Parent= MAP.ItemID
) ORDER BY MO.ItemID
The idea behind it is that we select all rows from original model in ITEM table and we generate fake ID for them.
The fake ID is :
Row 1 = current identity + 1,
Row 2 = current identity + 2,
etc.
After that we have mapping : oldid -> newid
Then we insert original model to ITEM table like it is but we replace Parent by record from our mapping.
The issue that I can see is that some ItemID may still not exist for Parent when we insert rows (ie. we insert row that will have ItemID 20 but its Parent is 21). For that we may need to disable constraint on Parent for the time of execution of this insert. After that we supposed to enable it again. Data will be correct of course.
Not sure how to describe this so I will show example:
table PAGES
id int
parent int
name nvarchar
status tinyint
table PAGES_MODULES
id int
id_parent int
module_type nvarchar
module_id int
status int
One page can have more than one linked modules. Example records:
id parent name status
1 -1 Xyz 1
2 -1 Yqw 1
id id_parent module_type module_id status
1 1 ARTICLE 1 1
2 1 GALLERY 2 1
3 2 CATEGORY 3 1
What I need is to create select which will not return 2 results if I do select left join page_modules.
I would like to have select which returns linked modules as this:
id parent name status modules
1 -1 Xyz 1 ARTICLE GALLERY
2 -1 Yqw 1 CATEGORY
Is that possible?
Thanks.
UPDATE
I have tried COALESE, CROSS APPLY and SELECT within SELECT methods and came to these conclusions:
http://blog.feronovak.com/2011/10/multiple-values-in-one-column-aka.html
Hope I can publish these here, not meaning to spam or something.
You'd need to create a custom aggregate function that could concatenate the strings together, there is no built-in SQL Server function that does this.
You can create a custom aggregate function (assuming your using the latest version of SQL) using a .Net assembly. Here's the MS reference on how to do this (the example in the article is actually for a CONCATENATE function just like you require): http://msdn.microsoft.com/en-us/library/ms182741.aspx
Use group_concat() to smoosh multiple rows' worth of data into a single field like that. Note that it does have a length limit (1024 chars by default), so if you're going to have a zillion records being group_concatted, you'll only get the first few lines worth unless you raise the limit.
SELECT ..., GROUP_CONCAT(modules SEPARATOR ' ')
FROM ...
GROUP BY ...
Note that it IS an aggregate function, so you must have a group-by clause.
-- ==================
-- sample data
-- ==================
declare #pages table
(
id int,
parent int,
name nvarchar(max),
status tinyint
)
declare #pages_modules table
(
id int,
id_parent int,
module_type nvarchar(max),
module_id int,
status int
)
insert into #pages values (1, -1, 'Xyz', 1)
insert into #pages values (2, -1, 'Yqw', 1)
insert into #pages_modules values (1, 1, 'ARTICLE', 1, 1)
insert into #pages_modules values (2, 1, 'GALLERY', 2, 1)
insert into #pages_modules values (3, 2, 'CATEGORY', 3, 1)
-- ==================
-- solution
-- ==================
select
*,
modules = (
select module_type + ' ' from #pages_modules pm
where pm.id_parent = p.id
for xml path('')
)
from #pages p
You need to join both tables and then GROUP BY by pages.id, pages.parent, pages.status, pages.name and pages.status. Your modules field in your resultset is then a string aggregate function, i.e in Oracle LISTAGG(pages_modules.modules, ' ') as modules.